Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3dba020b0 | ||
|
|
7c766bdfc4 | ||
|
|
23959099e1 | ||
|
|
1e61f1c66a | ||
|
|
22b0f1f918 | ||
|
|
379dba47aa |
@@ -1,4 +1,4 @@
|
|||||||
FROM node:24-bullseye
|
FROM node:18-bullseye
|
||||||
|
|
||||||
# Vite wants to open the browser using `open`, so we
|
# Vite wants to open the browser using `open`, so we
|
||||||
# need to install those utils.
|
# need to install those utils.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
steps:
|
steps:
|
||||||
- uses: styfle/cancel-workflow-action@ce177499ccf9fd2aded3b0426c97e5434c2e8a73 # 0.6.0
|
- uses: styfle/cancel-workflow-action@0.6.0
|
||||||
with:
|
with:
|
||||||
workflow_id: 400555, 400556, 905313, 1451724, 1710116, 3185001, 3438604
|
workflow_id: 400555, 400556, 905313, 1451724, 1710116, 3185001, 3438604
|
||||||
access_token: ${{ secrets.GITHUB_TOKEN }}
|
access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
echo ::set-output name=body::$body
|
echo ::set-output name=body::$body
|
||||||
|
|
||||||
- name: Update description with coverage
|
- name: Update description with coverage
|
||||||
uses: kt3k/update-pr-description@1b35a6dcd84d81aa0bc1889610efdcde7f37b0c0 # v1.0.1
|
uses: kt3k/update-pr-description@v1.0.1
|
||||||
with:
|
with:
|
||||||
pr_body: ${{ steps.getCommentBody.outputs.body }}
|
pr_body: ${{ steps.getCommentBody.outputs.body }}
|
||||||
pr_title: "chore: Update translations from Crowdin"
|
pr_title: "chore: Update translations from Crowdin"
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ jobs:
|
|||||||
semantic:
|
semantic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5
|
- uses: amannn/action-semantic-pull-request@v5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
working-directory: packages/excalidraw
|
working-directory: packages/excalidraw
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
- uses: andresz1/size-limit-action@e7493a72a44b113341c0cf6186ab49c17c4b65c1 # v1
|
- uses: andresz1/size-limit-action@v1
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build_script: build:esm
|
build_script: build:esm
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ jobs:
|
|||||||
run: yarn test:coverage
|
run: yarn test:coverage
|
||||||
- name: "Report Coverage"
|
- name: "Report Coverage"
|
||||||
if: always() # Also generate the report if tests are failing
|
if: always() # Also generate the report if tests are failing
|
||||||
uses: davelosert/vitest-coverage-report-action@2500dafcee7dd64f85ab689c0b83798a8359770e # v2
|
uses: davelosert/vitest-coverage-report-action@v2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=${BUILDPLATFORM} node:24 AS build
|
FROM --platform=${BUILDPLATFORM} node:18 AS build
|
||||||
|
|
||||||
WORKDIR /opt/node_app
|
WORKDIR /opt/node_app
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ ARG NODE_ENV=production
|
|||||||
|
|
||||||
RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker
|
RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker
|
||||||
|
|
||||||
FROM nginx:1.27-alpine
|
FROM --platform=${TARGETPLATFORM} nginx:1.27-alpine
|
||||||
|
|
||||||
COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
|
COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
|
<a href="https://docs.excalidraw.com/docs/introduction/contributing">
|
||||||
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
|
<img alt="PRs welcome!" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
|
||||||
<a href="https://discord.gg/UexuTaE">
|
<a href="https://discord.gg/UexuTaE">
|
||||||
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widget=false"/></a>
|
<img alt="Chat on Discord" src="https://img.shields.io/discord/723672430744174682?color=738ad6&label=Chat%20on%20Discord&logo=discord&logoColor=ffffff&widge=false"/></a>
|
||||||
<a href="https://deepwiki.com/excalidraw/excalidraw">
|
<a href="https://deepwiki.com/excalidraw/excalidraw">
|
||||||
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" /></a>
|
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" /></a>
|
||||||
<a href="https://twitter.com/excalidraw">
|
<a href="https://twitter.com/excalidraw">
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ const config = {
|
|||||||
href: "https://discord.gg/UexuTaE",
|
href: "https://discord.gg/UexuTaE",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "𝕏",
|
label: "Twitter",
|
||||||
href: "https://x.com/excalidraw",
|
href: "https://twitter.com/excalidraw",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Linkedin",
|
label: "Linkedin",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* x and y position of top left corner, x and y position of bottom right corner
|
* x and y position of top left corner, x and y position of bottom right corner
|
||||||
*/
|
*/
|
||||||
@@ -6,7 +8,31 @@ export type Bounds = readonly [
|
|||||||
minY: number,
|
minY: number,
|
||||||
maxX: number,
|
maxX: number,
|
||||||
maxY: number,
|
maxY: number,
|
||||||
];
|
] & { _brand: "excalidraw__bounds" };
|
||||||
|
|
||||||
|
export type RotatedBounds = readonly [
|
||||||
|
minX: number,
|
||||||
|
minY: number,
|
||||||
|
maxX: number,
|
||||||
|
maxY: number,
|
||||||
|
angle: Radians,
|
||||||
|
] & {
|
||||||
|
_brand_rotated: "excalidraw__rotated_bounds";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bounds = <T extends Radians | undefined = undefined>(
|
||||||
|
minX: number,
|
||||||
|
minY: number,
|
||||||
|
maxX: number,
|
||||||
|
maxY: number,
|
||||||
|
angle: T = undefined as T,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
angle
|
||||||
|
? ([minX, minY, maxX, maxY, angle] as unknown)
|
||||||
|
: ([minX, minY, maxX, maxY] as unknown)
|
||||||
|
) as T extends Radians ? RotatedBounds : Bounds;
|
||||||
|
};
|
||||||
|
|
||||||
export const isBounds = (box: unknown): box is Bounds =>
|
export const isBounds = (box: unknown): box is Bounds =>
|
||||||
Array.isArray(box) &&
|
Array.isArray(box) &&
|
||||||
@@ -15,3 +41,12 @@ export const isBounds = (box: unknown): box is Bounds =>
|
|||||||
typeof box[1] === "number" &&
|
typeof box[1] === "number" &&
|
||||||
typeof box[2] === "number" &&
|
typeof box[2] === "number" &&
|
||||||
typeof box[3] === "number";
|
typeof box[3] === "number";
|
||||||
|
|
||||||
|
export const isRotatedBounds = (box: unknown): box is RotatedBounds =>
|
||||||
|
Array.isArray(box) &&
|
||||||
|
box.length === 5 &&
|
||||||
|
typeof box[0] === "number" &&
|
||||||
|
typeof box[1] === "number" &&
|
||||||
|
typeof box[2] === "number" &&
|
||||||
|
typeof box[3] === "number" &&
|
||||||
|
typeof box[4] === "number";
|
||||||
|
|||||||
@@ -1943,9 +1943,9 @@ export const calculateFixedPointForElbowArrowBinding = (
|
|||||||
return {
|
return {
|
||||||
fixedPoint: normalizeFixedPoint([
|
fixedPoint: normalizeFixedPoint([
|
||||||
(nonRotatedSnappedGlobalPoint[0] - hoveredElement.x) /
|
(nonRotatedSnappedGlobalPoint[0] - hoveredElement.x) /
|
||||||
Math.max(hoveredElement.width, PRECISION),
|
hoveredElement.width,
|
||||||
(nonRotatedSnappedGlobalPoint[1] - hoveredElement.y) /
|
(nonRotatedSnappedGlobalPoint[1] - hoveredElement.y) /
|
||||||
Math.max(hoveredElement.height, PRECISION),
|
hoveredElement.height,
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1976,11 +1976,9 @@ export const calculateFixedPointForNonElbowArrowBinding = (
|
|||||||
|
|
||||||
// Calculate the ratio relative to the element's bounds
|
// Calculate the ratio relative to the element's bounds
|
||||||
const fixedPointX =
|
const fixedPointX =
|
||||||
(nonRotatedPoint[0] - hoveredElement.x) /
|
(nonRotatedPoint[0] - hoveredElement.x) / hoveredElement.width;
|
||||||
Math.max(hoveredElement.width, PRECISION);
|
|
||||||
const fixedPointY =
|
const fixedPointY =
|
||||||
(nonRotatedPoint[1] - hoveredElement.y) /
|
(nonRotatedPoint[1] - hoveredElement.y) / hoveredElement.height;
|
||||||
Math.max(hoveredElement.height, PRECISION);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fixedPoint: normalizeFixedPoint([fixedPointX, fixedPointY]),
|
fixedPoint: normalizeFixedPoint([fixedPointX, fixedPointY]),
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import rough from "roughjs/bin/rough";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
|
bounds,
|
||||||
type Bounds,
|
type Bounds,
|
||||||
invariant,
|
invariant,
|
||||||
rescalePoints,
|
rescalePoints,
|
||||||
|
type RotatedBounds,
|
||||||
sizeOf,
|
sizeOf,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ export class ElementBounds {
|
|||||||
private static boundsCache = new WeakMap<
|
private static boundsCache = new WeakMap<
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
{
|
{
|
||||||
bounds: Bounds;
|
bounds: RotatedBounds;
|
||||||
version: ExcalidrawElement["version"];
|
version: ExcalidrawElement["version"];
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
@@ -102,11 +104,11 @@ export class ElementBounds {
|
|||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
static getBounds(
|
static getBounds<T extends boolean = false>(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
nonRotated: boolean = false,
|
nonRotated: T = false as T,
|
||||||
) {
|
): T extends true ? Bounds : RotatedBounds {
|
||||||
const cachedBounds =
|
const cachedBounds =
|
||||||
nonRotated && element.angle !== 0
|
nonRotated && element.angle !== 0
|
||||||
? ElementBounds.nonRotatedBoundsCache.get(element)
|
? ElementBounds.nonRotatedBoundsCache.get(element)
|
||||||
@@ -119,40 +121,41 @@ export class ElementBounds {
|
|||||||
// which is causing problems down the line. Fix TBA.
|
// which is causing problems down the line. Fix TBA.
|
||||||
!isBoundToContainer(element)
|
!isBoundToContainer(element)
|
||||||
) {
|
) {
|
||||||
return cachedBounds.bounds;
|
return cachedBounds.bounds as T extends true ? Bounds : RotatedBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonRotated && element.angle !== 0) {
|
if (nonRotated && element.angle !== 0) {
|
||||||
const nonRotatedBounds = ElementBounds.calculateBounds(
|
const [minX, minY, maxX, maxY] = ElementBounds.calculateBounds(
|
||||||
{
|
{
|
||||||
...element,
|
...element,
|
||||||
angle: 0 as Radians,
|
angle: 0 as Radians,
|
||||||
},
|
},
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
const nonRotatedBounds = bounds(minX, minY, maxX, maxY);
|
||||||
ElementBounds.nonRotatedBoundsCache.set(element, {
|
ElementBounds.nonRotatedBoundsCache.set(element, {
|
||||||
version: element.version,
|
version: element.version,
|
||||||
bounds: nonRotatedBounds,
|
bounds: nonRotatedBounds,
|
||||||
});
|
});
|
||||||
|
|
||||||
return nonRotatedBounds;
|
return nonRotatedBounds as T extends true ? Bounds : RotatedBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = ElementBounds.calculateBounds(element, elementsMap);
|
const _bounds = ElementBounds.calculateBounds(element, elementsMap);
|
||||||
|
|
||||||
ElementBounds.boundsCache.set(element, {
|
ElementBounds.boundsCache.set(element, {
|
||||||
version: element.version,
|
version: element.version,
|
||||||
bounds,
|
bounds: _bounds,
|
||||||
});
|
});
|
||||||
|
|
||||||
return bounds;
|
return _bounds as T extends true ? Bounds : RotatedBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static calculateBounds(
|
private static calculateBounds(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): Bounds {
|
): RotatedBounds {
|
||||||
let bounds: Bounds;
|
let _bounds: RotatedBounds;
|
||||||
|
|
||||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
element,
|
element,
|
||||||
@@ -169,14 +172,15 @@ export class ElementBounds {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return bounds(
|
||||||
minX + element.x,
|
minX + element.x,
|
||||||
minY + element.y,
|
minY + element.y,
|
||||||
maxX + element.x,
|
maxX + element.x,
|
||||||
maxY + element.y,
|
maxY + element.y,
|
||||||
];
|
element.angle,
|
||||||
|
);
|
||||||
} else if (isLinearElement(element)) {
|
} else if (isLinearElement(element)) {
|
||||||
bounds = getLinearElementRotatedBounds(element, cx, cy, elementsMap);
|
_bounds = getLinearElementRotatedBounds(element, cx, cy, elementsMap);
|
||||||
} else if (element.type === "diamond") {
|
} else if (element.type === "diamond") {
|
||||||
const [x11, y11] = pointRotateRads(
|
const [x11, y11] = pointRotateRads(
|
||||||
pointFrom(cx, y1),
|
pointFrom(cx, y1),
|
||||||
@@ -202,7 +206,7 @@ export class ElementBounds {
|
|||||||
const minY = Math.min(y11, y12, y22, y21);
|
const minY = Math.min(y11, y12, y22, y21);
|
||||||
const maxX = Math.max(x11, x12, x22, x21);
|
const maxX = Math.max(x11, x12, x22, x21);
|
||||||
const maxY = Math.max(y11, y12, y22, y21);
|
const maxY = Math.max(y11, y12, y22, y21);
|
||||||
bounds = [minX, minY, maxX, maxY];
|
_bounds = bounds(minX, minY, maxX, maxY, element.angle);
|
||||||
} else if (element.type === "ellipse") {
|
} else if (element.type === "ellipse") {
|
||||||
const w = (x2 - x1) / 2;
|
const w = (x2 - x1) / 2;
|
||||||
const h = (y2 - y1) / 2;
|
const h = (y2 - y1) / 2;
|
||||||
@@ -210,7 +214,7 @@ export class ElementBounds {
|
|||||||
const sin = Math.sin(element.angle);
|
const sin = Math.sin(element.angle);
|
||||||
const ww = Math.hypot(w * cos, h * sin);
|
const ww = Math.hypot(w * cos, h * sin);
|
||||||
const hh = Math.hypot(h * cos, w * sin);
|
const hh = Math.hypot(h * cos, w * sin);
|
||||||
bounds = [cx - ww, cy - hh, cx + ww, cy + hh];
|
_bounds = bounds(cx - ww, cy - hh, cx + ww, cy + hh, element.angle);
|
||||||
} else {
|
} else {
|
||||||
const [x11, y11] = pointRotateRads(
|
const [x11, y11] = pointRotateRads(
|
||||||
pointFrom(x1, y1),
|
pointFrom(x1, y1),
|
||||||
@@ -236,10 +240,10 @@ export class ElementBounds {
|
|||||||
const minY = Math.min(y11, y12, y22, y21);
|
const minY = Math.min(y11, y12, y22, y21);
|
||||||
const maxX = Math.max(x11, x12, x22, x21);
|
const maxX = Math.max(x11, x12, x22, x21);
|
||||||
const maxY = Math.max(y11, y12, y22, y21);
|
const maxY = Math.max(y11, y12, y22, y21);
|
||||||
bounds = [minX, minY, maxX, maxY];
|
_bounds = bounds(minX, minY, maxX, maxY, element.angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bounds;
|
return _bounds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +628,7 @@ export const getCubicBezierCurveBound = (
|
|||||||
minY = Math.min(minY, ...ys);
|
minY = Math.min(minY, ...ys);
|
||||||
maxY = Math.max(maxY, ...ys);
|
maxY = Math.max(maxY, ...ys);
|
||||||
}
|
}
|
||||||
return [minX, minY, maxX, maxY];
|
return bounds(minX, minY, maxX, maxY);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMinMaxXYFromCurvePathOps = (
|
export const getMinMaxXYFromCurvePathOps = (
|
||||||
@@ -677,12 +681,11 @@ export const getMinMaxXYFromCurvePathOps = (
|
|||||||
},
|
},
|
||||||
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
|
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
|
||||||
);
|
);
|
||||||
return [minX, minY, maxX, maxY];
|
return bounds(minX, minY, maxX, maxY);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBoundsFromPoints = <P extends GlobalPoint | LocalPoint>(
|
export const getBoundsFromPoints = (
|
||||||
points: readonly P[],
|
points: ExcalidrawFreeDrawElement["points"],
|
||||||
padding: number = 0,
|
|
||||||
): Bounds => {
|
): Bounds => {
|
||||||
let minX = Infinity;
|
let minX = Infinity;
|
||||||
let minY = Infinity;
|
let minY = Infinity;
|
||||||
@@ -696,7 +699,7 @@ export const getBoundsFromPoints = <P extends GlobalPoint | LocalPoint>(
|
|||||||
maxY = Math.max(maxY, y);
|
maxY = Math.max(maxY, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [minX - padding, minY - padding, maxX + padding, maxY + padding];
|
return bounds(minX, minY, maxX, maxY);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFreeDrawElementAbsoluteCoords = (
|
const getFreeDrawElementAbsoluteCoords = (
|
||||||
@@ -939,7 +942,7 @@ const getLinearElementRotatedBounds = (
|
|||||||
cx: number,
|
cx: number,
|
||||||
cy: number,
|
cy: number,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
): Bounds => {
|
): RotatedBounds => {
|
||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
|
||||||
if (element.points.length < 2) {
|
if (element.points.length < 2) {
|
||||||
@@ -950,20 +953,21 @@ const getLinearElementRotatedBounds = (
|
|||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
let coords: Bounds = [x, y, x, y];
|
let coords: RotatedBounds = bounds(x, y, x, y, element.angle);
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
[x, y, x, y],
|
bounds(x, y, x, y, element.angle),
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
coords = [
|
coords = bounds(
|
||||||
coordsWithBoundText[0],
|
coordsWithBoundText[0],
|
||||||
coordsWithBoundText[1],
|
coordsWithBoundText[1],
|
||||||
coordsWithBoundText[2],
|
coordsWithBoundText[2],
|
||||||
coordsWithBoundText[3],
|
coordsWithBoundText[3],
|
||||||
];
|
element.angle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return coords;
|
return coords;
|
||||||
}
|
}
|
||||||
@@ -979,7 +983,13 @@ const getLinearElementRotatedBounds = (
|
|||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
||||||
let coords: Bounds = [res[0], res[1], res[2], res[3]];
|
let coords: RotatedBounds = bounds(
|
||||||
|
res[0],
|
||||||
|
res[1],
|
||||||
|
res[2],
|
||||||
|
res[3],
|
||||||
|
element.angle,
|
||||||
|
);
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
@@ -987,22 +997,23 @@ const getLinearElementRotatedBounds = (
|
|||||||
coords,
|
coords,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
coords = [
|
coords = bounds(
|
||||||
coordsWithBoundText[0],
|
coordsWithBoundText[0],
|
||||||
coordsWithBoundText[1],
|
coordsWithBoundText[1],
|
||||||
coordsWithBoundText[2],
|
coordsWithBoundText[2],
|
||||||
coordsWithBoundText[3],
|
coordsWithBoundText[3],
|
||||||
];
|
element.angle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return coords;
|
return coords;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementBounds = (
|
export const getElementBounds = <T extends boolean = false>(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
nonRotated: boolean = false,
|
nonRotated: T = false as T,
|
||||||
): Bounds => {
|
) => {
|
||||||
return ElementBounds.getBounds(element, elementsMap, nonRotated);
|
return ElementBounds.getBounds<T>(element, elementsMap, nonRotated);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCommonBounds = (
|
export const getCommonBounds = (
|
||||||
@@ -1010,7 +1021,7 @@ export const getCommonBounds = (
|
|||||||
elementsMap?: ElementsMap,
|
elementsMap?: ElementsMap,
|
||||||
): Bounds => {
|
): Bounds => {
|
||||||
if (!sizeOf(elements)) {
|
if (!sizeOf(elements)) {
|
||||||
return [0, 0, 0, 0];
|
return bounds(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let minX = Infinity;
|
let minX = Infinity;
|
||||||
@@ -1028,7 +1039,7 @@ export const getCommonBounds = (
|
|||||||
maxY = Math.max(maxY, y2);
|
maxY = Math.max(maxY, y2);
|
||||||
});
|
});
|
||||||
|
|
||||||
return [minX, minY, maxX, maxY];
|
return bounds(minX, minY, maxX, maxY);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDraggedElementsBounds = (
|
export const getDraggedElementsBounds = (
|
||||||
@@ -1051,12 +1062,12 @@ export const getResizedElementAbsoluteCoords = (
|
|||||||
normalizePoints: boolean,
|
normalizePoints: boolean,
|
||||||
): Bounds => {
|
): Bounds => {
|
||||||
if (!(isLinearElement(element) || isFreeDrawElement(element))) {
|
if (!(isLinearElement(element) || isFreeDrawElement(element))) {
|
||||||
return [
|
return bounds(
|
||||||
element.x,
|
element.x,
|
||||||
element.y,
|
element.y,
|
||||||
element.x + nextWidth,
|
element.x + nextWidth,
|
||||||
element.y + nextHeight,
|
element.y + nextHeight,
|
||||||
];
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const points = rescalePoints(
|
const points = rescalePoints(
|
||||||
@@ -1066,11 +1077,11 @@ export const getResizedElementAbsoluteCoords = (
|
|||||||
normalizePoints,
|
normalizePoints,
|
||||||
);
|
);
|
||||||
|
|
||||||
let bounds: Bounds;
|
let _bounds: Bounds;
|
||||||
|
|
||||||
if (isFreeDrawElement(element)) {
|
if (isFreeDrawElement(element)) {
|
||||||
// Free Draw
|
// Free Draw
|
||||||
bounds = getBoundsFromPoints(points);
|
_bounds = getBoundsFromPoints(points);
|
||||||
} else {
|
} else {
|
||||||
// Line
|
// Line
|
||||||
const gen = rough.generator();
|
const gen = rough.generator();
|
||||||
@@ -1082,16 +1093,16 @@ export const getResizedElementAbsoluteCoords = (
|
|||||||
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
||||||
|
|
||||||
const ops = getCurvePathOps(curve);
|
const ops = getCurvePathOps(curve);
|
||||||
bounds = getMinMaxXYFromCurvePathOps(ops);
|
_bounds = getMinMaxXYFromCurvePathOps(ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [minX, minY, maxX, maxY] = bounds;
|
const [minX, minY, maxX, maxY] = _bounds;
|
||||||
return [
|
return bounds(
|
||||||
minX + element.x,
|
minX + element.x,
|
||||||
minY + element.y,
|
minY + element.y,
|
||||||
maxX + element.x,
|
maxX + element.x,
|
||||||
maxY + element.y,
|
maxY + element.y,
|
||||||
];
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementPointsCoords = (
|
export const getElementPointsCoords = (
|
||||||
@@ -1109,20 +1120,20 @@ export const getElementPointsCoords = (
|
|||||||
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
: gen.curve(points as [number, number][], generateRoughOptions(element));
|
||||||
const ops = getCurvePathOps(curve);
|
const ops = getCurvePathOps(curve);
|
||||||
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
|
||||||
return [
|
return bounds(
|
||||||
minX + element.x,
|
minX + element.x,
|
||||||
minY + element.y,
|
minY + element.y,
|
||||||
maxX + element.x,
|
maxX + element.x,
|
||||||
maxY + element.y,
|
maxY + element.y,
|
||||||
];
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getClosestElementBounds = (
|
export const getClosestElementBounds = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
from: { x: number; y: number },
|
from: { x: number; y: number },
|
||||||
): Bounds => {
|
): RotatedBounds => {
|
||||||
if (!elements.length) {
|
if (!elements.length) {
|
||||||
return [0, 0, 0, 0];
|
return bounds(0, 0, 0, 0, 0 as Radians);
|
||||||
}
|
}
|
||||||
|
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
@@ -1191,7 +1202,9 @@ export const getVisibleSceneBounds = ({
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCenterForBounds = (bounds: Bounds): GlobalPoint =>
|
export const getCenterForBounds = (
|
||||||
|
bounds: Bounds | RotatedBounds,
|
||||||
|
): GlobalPoint =>
|
||||||
pointFrom(
|
pointFrom(
|
||||||
bounds[0] + (bounds[2] - bounds[0]) / 2,
|
bounds[0] + (bounds[2] - bounds[0]) / 2,
|
||||||
bounds[1] + (bounds[3] - bounds[1]) / 2,
|
bounds[1] + (bounds[3] - bounds[1]) / 2,
|
||||||
@@ -1236,24 +1249,24 @@ export const aabbForElement = (
|
|||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
const bounds = [
|
const _bounds = bounds(
|
||||||
Math.min(topLeftX, topRightX, bottomRightX, bottomLeftX),
|
Math.min(topLeftX, topRightX, bottomRightX, bottomLeftX),
|
||||||
Math.min(topLeftY, topRightY, bottomRightY, bottomLeftY),
|
Math.min(topLeftY, topRightY, bottomRightY, bottomLeftY),
|
||||||
Math.max(topLeftX, topRightX, bottomRightX, bottomLeftX),
|
Math.max(topLeftX, topRightX, bottomRightX, bottomLeftX),
|
||||||
Math.max(topLeftY, topRightY, bottomRightY, bottomLeftY),
|
Math.max(topLeftY, topRightY, bottomRightY, bottomLeftY),
|
||||||
] as Bounds;
|
);
|
||||||
|
|
||||||
if (offset) {
|
if (offset) {
|
||||||
const [topOffset, rightOffset, downOffset, leftOffset] = offset;
|
const [topOffset, rightOffset, downOffset, leftOffset] = offset;
|
||||||
return [
|
return bounds(
|
||||||
bounds[0] - leftOffset,
|
_bounds[0] - leftOffset,
|
||||||
bounds[1] - topOffset,
|
_bounds[1] - topOffset,
|
||||||
bounds[2] + rightOffset,
|
_bounds[2] + rightOffset,
|
||||||
bounds[3] + downOffset,
|
_bounds[3] + downOffset,
|
||||||
] as Bounds;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bounds;
|
return _bounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
||||||
@@ -1262,18 +1275,7 @@ export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
|||||||
): boolean =>
|
): boolean =>
|
||||||
p[0] > bounds[0] && p[0] < bounds[2] && p[1] > bounds[1] && p[1] < bounds[3];
|
p[0] > bounds[0] && p[0] < bounds[2] && p[1] > bounds[1] && p[1] < bounds[3];
|
||||||
|
|
||||||
// TODO make pointInsideBounds inclusive and remove this function once we
|
export const doNonRotatedBoundsIntersect = (
|
||||||
// test nothing is breaking
|
|
||||||
export const pointInsideBoundsInclusive = <P extends GlobalPoint | LocalPoint>(
|
|
||||||
p: P,
|
|
||||||
bounds: Bounds,
|
|
||||||
): boolean =>
|
|
||||||
p[0] >= bounds[0] &&
|
|
||||||
p[0] <= bounds[2] &&
|
|
||||||
p[1] >= bounds[1] &&
|
|
||||||
p[1] <= bounds[3];
|
|
||||||
|
|
||||||
export const doBoundsIntersect = (
|
|
||||||
bounds1: Bounds | null,
|
bounds1: Bounds | null,
|
||||||
bounds2: Bounds | null,
|
bounds2: Bounds | null,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
@@ -1287,14 +1289,6 @@ export const doBoundsIntersect = (
|
|||||||
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const boundsContainBounds = (outerBounds: Bounds, innerBounds: Bounds) =>
|
|
||||||
[
|
|
||||||
pointFrom<GlobalPoint>(innerBounds[0], innerBounds[1]),
|
|
||||||
pointFrom<GlobalPoint>(innerBounds[0], innerBounds[3]),
|
|
||||||
pointFrom<GlobalPoint>(innerBounds[2], innerBounds[1]),
|
|
||||||
pointFrom<GlobalPoint>(innerBounds[2], innerBounds[3]),
|
|
||||||
].every((point) => pointInsideBoundsInclusive(point, outerBounds));
|
|
||||||
|
|
||||||
export const elementCenterPoint = (
|
export const elementCenterPoint = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { invariant, isTransparent, type Bounds } from "@excalidraw/common";
|
import {
|
||||||
|
bounds,
|
||||||
|
invariant,
|
||||||
|
isTransparent,
|
||||||
|
type Bounds,
|
||||||
|
} from "@excalidraw/common";
|
||||||
import {
|
import {
|
||||||
curveIntersectLineSegment,
|
curveIntersectLineSegment,
|
||||||
isPointWithinBounds,
|
isPointWithinBounds,
|
||||||
@@ -29,7 +34,7 @@ import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
|||||||
|
|
||||||
import { isPathALoop } from "./utils";
|
import { isPathALoop } from "./utils";
|
||||||
import {
|
import {
|
||||||
doBoundsIntersect,
|
doNonRotatedBoundsIntersect,
|
||||||
elementCenterPoint,
|
elementCenterPoint,
|
||||||
getCenterForBounds,
|
getCenterForBounds,
|
||||||
getCubicBezierCurveBound,
|
getCubicBezierCurveBound,
|
||||||
@@ -189,6 +194,48 @@ export const hitElementItself = ({
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getBoundsCorners(
|
||||||
|
bounds: Bounds,
|
||||||
|
): readonly [GlobalPoint, GlobalPoint, GlobalPoint, GlobalPoint];
|
||||||
|
export function getBoundsCorners(
|
||||||
|
bounds: Bounds,
|
||||||
|
angle: Radians,
|
||||||
|
): readonly [GlobalPoint, GlobalPoint, GlobalPoint, GlobalPoint];
|
||||||
|
export function getBoundsCorners(
|
||||||
|
bounds: Bounds,
|
||||||
|
angle: Radians = 0 as Radians,
|
||||||
|
) {
|
||||||
|
const [x1, y1, x2, y2] = bounds;
|
||||||
|
const center = getCenterForBounds(bounds);
|
||||||
|
const corners = [
|
||||||
|
pointFrom<GlobalPoint>(x1, y1),
|
||||||
|
pointFrom<GlobalPoint>(x2, y1),
|
||||||
|
pointFrom<GlobalPoint>(x2, y2),
|
||||||
|
pointFrom<GlobalPoint>(x1, y2),
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
if (angle === 0) {
|
||||||
|
return corners;
|
||||||
|
}
|
||||||
|
|
||||||
|
return corners.map((point) => pointRotateRads(point, center, angle)) as [
|
||||||
|
GlobalPoint,
|
||||||
|
GlobalPoint,
|
||||||
|
GlobalPoint,
|
||||||
|
GlobalPoint,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBoundsEdges = (
|
||||||
|
corners: readonly [GlobalPoint, GlobalPoint, GlobalPoint, GlobalPoint],
|
||||||
|
) =>
|
||||||
|
[
|
||||||
|
lineSegment(corners[0], corners[1]),
|
||||||
|
lineSegment(corners[1], corners[2]),
|
||||||
|
lineSegment(corners[2], corners[3]),
|
||||||
|
lineSegment(corners[3], corners[0]),
|
||||||
|
] as const;
|
||||||
|
|
||||||
const isPointInRotatedBounds = (
|
const isPointInRotatedBounds = (
|
||||||
point: GlobalPoint,
|
point: GlobalPoint,
|
||||||
bounds: Bounds,
|
bounds: Bounds,
|
||||||
@@ -217,6 +264,40 @@ export const hitElementBoundingBox = (
|
|||||||
return isPointInRotatedBounds(point, bounds, element.angle, tolerance);
|
return isPointInRotatedBounds(point, bounds, element.angle, tolerance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const doBoundsIntersectElementBoundingBox = (
|
||||||
|
intersectorBounds: Bounds,
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
const [x1, y1, x2, y2] = intersectorBounds;
|
||||||
|
const intersectorCorners = [
|
||||||
|
pointFrom<GlobalPoint>(x1, y1),
|
||||||
|
pointFrom<GlobalPoint>(x2, y1),
|
||||||
|
pointFrom<GlobalPoint>(x2, y2),
|
||||||
|
pointFrom<GlobalPoint>(x1, y2),
|
||||||
|
] as const;
|
||||||
|
const intersectorEdges = getBoundsEdges(intersectorCorners);
|
||||||
|
|
||||||
|
const elementBounds = getElementBounds(element, elementsMap, true);
|
||||||
|
const elementBoundsCorners = getBoundsCorners(elementBounds, element.angle);
|
||||||
|
const elementBoundsEdges = getBoundsEdges(elementBoundsCorners);
|
||||||
|
|
||||||
|
return (
|
||||||
|
elementBoundsCorners.some((point) =>
|
||||||
|
isPointWithinBounds(intersectorCorners[0], point, intersectorCorners[2]),
|
||||||
|
) ||
|
||||||
|
intersectorCorners.some((point) =>
|
||||||
|
isPointInRotatedBounds(point, elementBounds, element.angle),
|
||||||
|
) ||
|
||||||
|
intersectorEdges.some((selectionEdge) =>
|
||||||
|
elementBoundsEdges.some(
|
||||||
|
(elementBoundsEdge) =>
|
||||||
|
!!lineSegmentIntersectionPoints(selectionEdge, elementBoundsEdge),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const hitElementBoundingBoxOnly = (
|
export const hitElementBoundingBoxOnly = (
|
||||||
hitArgs: HitTestArgs,
|
hitArgs: HitTestArgs,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
@@ -267,9 +348,13 @@ const bindingBorderTest = (
|
|||||||
// PERF: Run a cheap test to see if the binding element
|
// PERF: Run a cheap test to see if the binding element
|
||||||
// is even close to the element
|
// is even close to the element
|
||||||
const t = Math.max(1, tolerance);
|
const t = Math.max(1, tolerance);
|
||||||
const bounds = [x - t, y - t, x + t, y + t] as Bounds;
|
const elementBounds = getElementBounds(element, elementsMap, true);
|
||||||
const elementBounds = getElementBounds(element, elementsMap);
|
if (
|
||||||
if (!doBoundsIntersect(bounds, elementBounds)) {
|
!doNonRotatedBoundsIntersect(
|
||||||
|
bounds(x - t, y - t, x + t, y + t),
|
||||||
|
elementBounds,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +365,7 @@ const bindingBorderTest = (
|
|||||||
const enclosingFrameBounds = getElementBounds(
|
const enclosingFrameBounds = getElementBounds(
|
||||||
enclosingFrame,
|
enclosingFrame,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
if (!pointInsideBounds(p, enclosingFrameBounds)) {
|
if (!pointInsideBounds(p, enclosingFrameBounds)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -429,15 +515,15 @@ export const intersectElementWithLineSegment = (
|
|||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
// First check if the line intersects the element's axis-aligned bounding box
|
// First check if the line intersects the element's axis-aligned bounding box
|
||||||
// as it is much faster than checking intersection against the element's shape
|
// as it is much faster than checking intersection against the element's shape
|
||||||
const intersectorBounds = [
|
const intersectorBounds = bounds(
|
||||||
Math.min(line[0][0] - offset, line[1][0] - offset),
|
Math.min(line[0][0] - offset, line[1][0] - offset),
|
||||||
Math.min(line[0][1] - offset, line[1][1] - offset),
|
Math.min(line[0][1] - offset, line[1][1] - offset),
|
||||||
Math.max(line[0][0] + offset, line[1][0] + offset),
|
Math.max(line[0][0] + offset, line[1][0] + offset),
|
||||||
Math.max(line[0][1] + offset, line[1][1] + offset),
|
Math.max(line[0][1] + offset, line[1][1] + offset),
|
||||||
] as Bounds;
|
);
|
||||||
const elementBounds = getElementBounds(element, elementsMap);
|
const elementBounds = getElementBounds(element, elementsMap, true);
|
||||||
|
|
||||||
if (!doBoundsIntersect(intersectorBounds, elementBounds)) {
|
if (!doNonRotatedBoundsIntersect(intersectorBounds, elementBounds)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,14 +582,14 @@ const curveIntersections = (
|
|||||||
for (const c of curves) {
|
for (const c of curves) {
|
||||||
// Optimize by doing a cheap bounding box check first
|
// Optimize by doing a cheap bounding box check first
|
||||||
const b1 = getCubicBezierCurveBound(c[0], c[1], c[2], c[3]);
|
const b1 = getCubicBezierCurveBound(c[0], c[1], c[2], c[3]);
|
||||||
const b2 = [
|
const b2 = bounds(
|
||||||
Math.min(segment[0][0], segment[1][0]),
|
Math.min(segment[0][0], segment[1][0]),
|
||||||
Math.min(segment[0][1], segment[1][1]),
|
Math.min(segment[0][1], segment[1][1]),
|
||||||
Math.max(segment[0][0], segment[1][0]),
|
Math.max(segment[0][0], segment[1][0]),
|
||||||
Math.max(segment[0][1], segment[1][1]),
|
Math.max(segment[0][1], segment[1][1]),
|
||||||
] as Bounds;
|
);
|
||||||
|
|
||||||
if (!doBoundsIntersect(b1, b2)) {
|
if (!doNonRotatedBoundsIntersect(b1, b2)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,20 +659,18 @@ const intersectLinearOrFreeDrawWithLineSegment = (
|
|||||||
for (const c of curves) {
|
for (const c of curves) {
|
||||||
// Optimize by doing a cheap bounding box check first
|
// Optimize by doing a cheap bounding box check first
|
||||||
const b1 = getCubicBezierCurveBound(c[0], c[1], c[2], c[3]);
|
const b1 = getCubicBezierCurveBound(c[0], c[1], c[2], c[3]);
|
||||||
const b2 = [
|
const b2 = bounds(
|
||||||
Math.min(segment[0][0], segment[1][0]),
|
Math.min(segment[0][0], segment[1][0]),
|
||||||
Math.min(segment[0][1], segment[1][1]),
|
Math.min(segment[0][1], segment[1][1]),
|
||||||
Math.max(segment[0][0], segment[1][0]),
|
Math.max(segment[0][0], segment[1][0]),
|
||||||
Math.max(segment[0][1], segment[1][1]),
|
Math.max(segment[0][1], segment[1][1]),
|
||||||
] as Bounds;
|
);
|
||||||
|
|
||||||
if (!doBoundsIntersect(b1, b2)) {
|
if (!doNonRotatedBoundsIntersect(b1, b2)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hits = curveIntersectLineSegment(c, segment, {
|
const hits = curveIntersectLineSegment(c, segment);
|
||||||
iterLimit: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hits.length > 0) {
|
if (hits.length > 0) {
|
||||||
intersections.push(...hits);
|
intersections.push(...hits);
|
||||||
|
|||||||
@@ -2124,8 +2124,8 @@ const normalizeArrowElementUpdate = (
|
|||||||
offsetY < -MAX_POS ||
|
offsetY < -MAX_POS ||
|
||||||
offsetY > MAX_POS ||
|
offsetY > MAX_POS ||
|
||||||
offsetX + points[points.length - 1][0] < -MAX_POS ||
|
offsetX + points[points.length - 1][0] < -MAX_POS ||
|
||||||
offsetX + points[points.length - 1][0] > MAX_POS ||
|
offsetY + points[points.length - 1][0] > MAX_POS ||
|
||||||
offsetY + points[points.length - 1][1] < -MAX_POS ||
|
offsetX + points[points.length - 1][1] < -MAX_POS ||
|
||||||
offsetY + points[points.length - 1][1] > MAX_POS
|
offsetY + points[points.length - 1][1] > MAX_POS
|
||||||
) {
|
) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { arrayToMap } from "@excalidraw/common";
|
import { arrayToMap } from "@excalidraw/common";
|
||||||
import { isPointWithinBounds, pointFrom } from "@excalidraw/math";
|
import { isPointWithinBounds, pointFrom } from "@excalidraw/math";
|
||||||
import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox";
|
import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox";
|
||||||
|
import { elementsOverlappingBBox } from "@excalidraw/utils/withinBounds";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppClassProperties,
|
AppClassProperties,
|
||||||
@@ -17,8 +18,6 @@ import {
|
|||||||
getElementLineSegments,
|
getElementLineSegments,
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
doBoundsIntersect,
|
|
||||||
getElementBounds,
|
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { getBoundTextElement, getContainerElement } from "./textElement";
|
import { getBoundTextElement, getContainerElement } from "./textElement";
|
||||||
@@ -100,7 +99,7 @@ export const isElementContainingFrame = (
|
|||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
): boolean => {
|
||||||
return getElementsWithinSelection([frame], element, elementsMap).some(
|
return getElementsWithinSelection([frame], element, elementsMap).some(
|
||||||
(e) => e.id === frame.id,
|
(e) => e.id === frame.id,
|
||||||
);
|
);
|
||||||
@@ -141,7 +140,7 @@ export const elementOverlapsWithFrame = (
|
|||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
): boolean => {
|
||||||
return (
|
return (
|
||||||
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame, elementsMap) ||
|
isElementIntersectingFrame(element, frame, elementsMap) ||
|
||||||
@@ -921,17 +920,16 @@ export const getFrameLikeTitle = (element: ExcalidrawFrameLikeElement) => {
|
|||||||
export const getElementsOverlappingFrame = (
|
export const getElementsOverlappingFrame = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
elementsMap: ElementsMap,
|
|
||||||
) => {
|
) => {
|
||||||
return elements.filter(
|
return (
|
||||||
(el) =>
|
elementsOverlappingBBox({
|
||||||
// exclude elements which are overlapping, but are in a different frame,
|
elements,
|
||||||
|
bounds: frame,
|
||||||
|
type: "overlap",
|
||||||
|
})
|
||||||
|
// removes elements who are overlapping, but are in a different frame,
|
||||||
// and thus invisible in target frame
|
// and thus invisible in target frame
|
||||||
(!el.frameId || el.frameId === frame.id) &&
|
.filter((el) => !el.frameId || el.frameId === frame.id)
|
||||||
doBoundsIntersect(
|
|
||||||
getElementBounds(el, elementsMap),
|
|
||||||
getElementBounds(frame, elementsMap),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
invariant,
|
invariant,
|
||||||
isShallowEqual,
|
isShallowEqual,
|
||||||
getFeatureFlag,
|
getFeatureFlag,
|
||||||
|
bounds,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -42,7 +43,7 @@ import type {
|
|||||||
NullableGridSize,
|
NullableGridSize,
|
||||||
Zoom,
|
Zoom,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
import type { Bounds } from "@excalidraw/common";
|
import type { RotatedBounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
calculateFixedPointForNonElbowArrowBinding,
|
calculateFixedPointForNonElbowArrowBinding,
|
||||||
@@ -1879,7 +1880,7 @@ export class LinearElementEditor {
|
|||||||
static getMinMaxXYWithBoundText = (
|
static getMinMaxXYWithBoundText = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
elementBounds: Bounds,
|
elementBounds: RotatedBounds,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
): [number, number, number, number, number, number] => {
|
): [number, number, number, number, number, number] => {
|
||||||
let [x1, y1, x2, y2] = elementBounds;
|
let [x1, y1, x2, y2] = elementBounds;
|
||||||
@@ -2004,7 +2005,7 @@ export class LinearElementEditor {
|
|||||||
return LinearElementEditor.getMinMaxXYWithBoundText(
|
return LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
[x1, y1, x2, y2],
|
bounds(x1, y1, x2, y2, element.angle),
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+336
-230
@@ -2,8 +2,8 @@ import { arrayToMap, isShallowEqual, type Bounds } from "@excalidraw/common";
|
|||||||
import {
|
import {
|
||||||
lineSegment,
|
lineSegment,
|
||||||
pointFrom,
|
pointFrom,
|
||||||
pointRotateRads,
|
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
|
type LineSegment,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -13,17 +13,20 @@ import type {
|
|||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
boundsContainBounds,
|
|
||||||
doBoundsIntersect,
|
|
||||||
elementCenterPoint,
|
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getElementBounds,
|
getElementBounds,
|
||||||
pointInsideBounds,
|
getElementLineSegments,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import { intersectElementWithLineSegment } from "./collision";
|
import {
|
||||||
|
doBoundsIntersectElementBoundingBox,
|
||||||
|
getBoundsCorners,
|
||||||
|
getBoundsEdges,
|
||||||
|
intersectElementWithLineSegment,
|
||||||
|
isPointInElement,
|
||||||
|
shouldTestInside,
|
||||||
|
} from "./collision";
|
||||||
import { isElementInViewport } from "./sizeHelpers";
|
import { isElementInViewport } from "./sizeHelpers";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
isFreeDrawElement,
|
isFreeDrawElement,
|
||||||
@@ -34,38 +37,218 @@ import {
|
|||||||
elementOverlapsWithFrame,
|
elementOverlapsWithFrame,
|
||||||
getContainingFrame,
|
getContainingFrame,
|
||||||
getFrameChildren,
|
getFrameChildren,
|
||||||
isElementIntersectingFrame,
|
|
||||||
} from "./frame";
|
} from "./frame";
|
||||||
|
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { selectGroupsForSelectedElements } from "./groups";
|
import { selectGroupsForSelectedElements } from "./groups";
|
||||||
import { getBoundTextElement } from "./textElement";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ElementsMapOrArray,
|
ElementsMapOrArray,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameLikeElement,
|
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const shouldIgnoreElementFromSelection = (
|
// Broad-phase only for overlap mode. Rotated closed shapes should not select
|
||||||
|
// from the empty corners of their axis-aligned bounds. Linear elements and
|
||||||
|
// freedraw already rely on the outline-specific path below, so exclude them.
|
||||||
|
const shouldUseRotatedOverlapBroadPhase = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
) => element.locked || isBoundToContainer(element);
|
) =>
|
||||||
|
element.angle !== 0 &&
|
||||||
|
!isLinearElement(element) &&
|
||||||
|
!isFreeDrawElement(element);
|
||||||
|
|
||||||
const excludeElementsFromFrames = <T extends ExcalidrawElement>(
|
const clipLineSegmentToBounds = (
|
||||||
selectedElements: readonly T[],
|
segment: LineSegment<GlobalPoint>,
|
||||||
framesInSelection: Set<ExcalidrawFrameLikeElement["id"]>,
|
bounds: Bounds,
|
||||||
) => {
|
): LineSegment<GlobalPoint> | null => {
|
||||||
return selectedElements.filter((element) => {
|
const [minX, minY, maxX, maxY] = bounds;
|
||||||
if (element.frameId && framesInSelection.has(element.frameId)) {
|
const [[x1, y1], [x2, y2]] = segment;
|
||||||
|
const deltaX = x2 - x1;
|
||||||
|
const deltaY = y2 - y1;
|
||||||
|
let tMin = 0;
|
||||||
|
let tMax = 1;
|
||||||
|
|
||||||
|
const clip = (p: number, q: number) => {
|
||||||
|
if (p === 0) {
|
||||||
|
return q >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratio = q / p;
|
||||||
|
|
||||||
|
if (p < 0) {
|
||||||
|
if (ratio > tMax) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tMin = Math.max(tMin, ratio);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ratio < tMin) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
tMax = Math.min(tMax, ratio);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
!clip(-deltaX, x1 - minX) ||
|
||||||
|
!clip(deltaX, maxX - x1) ||
|
||||||
|
!clip(-deltaY, y1 - minY) ||
|
||||||
|
!clip(deltaY, maxY - y1)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineSegment(
|
||||||
|
pointFrom<GlobalPoint>(x1 + tMin * deltaX, y1 + tMin * deltaY),
|
||||||
|
pointFrom<GlobalPoint>(x1 + tMax * deltaX, y1 + tMax * deltaY),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPointWithinAabb = (point: GlobalPoint, bounds: Bounds) =>
|
||||||
|
point[0] >= bounds[0] &&
|
||||||
|
point[0] <= bounds[2] &&
|
||||||
|
point[1] >= bounds[1] &&
|
||||||
|
point[1] <= bounds[3];
|
||||||
|
|
||||||
|
const shouldUsePreciseFilledOverlap = (element: NonDeletedExcalidrawElement) =>
|
||||||
|
element.type === "ellipse" ||
|
||||||
|
element.type === "diamond" ||
|
||||||
|
(element.type === "rectangle" && !!element.roundness);
|
||||||
|
|
||||||
|
const shouldSkipElementFromSelection = (element: NonDeletedExcalidrawElement) =>
|
||||||
|
element.locked || element.type === "selection" || isBoundToContainer(element);
|
||||||
|
|
||||||
|
const getFrameBoundsForSelection = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): Bounds | null => {
|
||||||
|
if (!element.frameId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const containingFrame = getContainingFrame(element, elementsMap);
|
||||||
|
return containingFrame
|
||||||
|
? (getElementBounds(containingFrame, elementsMap) as Bounds)
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalizeElementsInSelection = (
|
||||||
|
elementsInSelection: NonDeletedExcalidrawElement[],
|
||||||
|
excludeElementsInFrames: boolean,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): NonDeletedExcalidrawElement[] => {
|
||||||
|
elementsInSelection = excludeElementsInFrames
|
||||||
|
? excludeElementsInFramesFromSelection(elementsInSelection)
|
||||||
|
: elementsInSelection;
|
||||||
|
|
||||||
|
return elementsInSelection.filter((element) => {
|
||||||
|
const containingFrame = getContainingFrame(element, elementsMap);
|
||||||
|
|
||||||
|
if (containingFrame) {
|
||||||
|
return elementOverlapsWithFrame(element, containingFrame, elementsMap);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getVisibleElementOutlineSegments = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
frameBounds: Bounds | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) =>
|
||||||
|
frameBounds
|
||||||
|
? getElementLineSegments(element, elementsMap).flatMap((segment) => {
|
||||||
|
const clippedSegment = clipLineSegmentToBounds(segment, frameBounds);
|
||||||
|
return clippedSegment ? [clippedSegment] : [];
|
||||||
|
})
|
||||||
|
: getElementLineSegments(element, elementsMap);
|
||||||
|
|
||||||
|
const doesSelectionIntersectElementOutline = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
frameBounds: Bounds | null,
|
||||||
|
selectionEdges: readonly LineSegment<GlobalPoint>[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) =>
|
||||||
|
selectionEdges.some((selectionEdge) =>
|
||||||
|
intersectElementWithLineSegment(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
selectionEdge,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
).some((point) => !frameBounds || isPointWithinAabb(point, frameBounds)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const doesSelectionContainElementOutline = (
|
||||||
|
outlineSegments: readonly LineSegment<GlobalPoint>[],
|
||||||
|
selectionBounds: Bounds,
|
||||||
|
) =>
|
||||||
|
outlineSegments.length > 0 &&
|
||||||
|
outlineSegments.every(
|
||||||
|
(outlineSegment) =>
|
||||||
|
isPointWithinAabb(outlineSegment[0], selectionBounds) &&
|
||||||
|
isPointWithinAabb(outlineSegment[1], selectionBounds),
|
||||||
|
);
|
||||||
|
|
||||||
|
const doesSelectionContainElementInterior = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
frameBounds: Bounds | null,
|
||||||
|
selectionCorners: readonly GlobalPoint[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) =>
|
||||||
|
selectionCorners.some(
|
||||||
|
(selectionCorner) =>
|
||||||
|
(!frameBounds || isPointWithinAabb(selectionCorner, frameBounds)) &&
|
||||||
|
isPointInElement(selectionCorner, element, elementsMap),
|
||||||
|
);
|
||||||
|
|
||||||
|
const doesSelectionOverlapFilledElement = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
frameBounds: Bounds | null,
|
||||||
|
selectionBounds: Bounds,
|
||||||
|
selectionCorners: readonly GlobalPoint[],
|
||||||
|
selectionEdges: readonly LineSegment<GlobalPoint>[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
doesSelectionContainElementInterior(
|
||||||
|
element,
|
||||||
|
frameBounds,
|
||||||
|
selectionCorners,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
doesSelectionIntersectElementOutline(
|
||||||
|
element,
|
||||||
|
frameBounds,
|
||||||
|
selectionEdges,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outlineSegments = getVisibleElementOutlineSegments(
|
||||||
|
element,
|
||||||
|
frameBounds,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
outlineSegments.length > 0 &&
|
||||||
|
doesSelectionContainElementOutline(outlineSegments, selectionBounds)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frames and their containing elements are not to be selected at the same time.
|
* Frames and their containing elements are not to be selected at the same time.
|
||||||
* Given an array of selected elements, if there are frames and their containing elements
|
* Given an array of selected elements, if there are frames and their containing elements
|
||||||
@@ -85,14 +268,18 @@ export const excludeElementsInFramesFromSelection = <
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return excludeElementsFromFrames(selectedElements, framesInSelection);
|
return selectedElements.filter((element) => {
|
||||||
|
if (element.frameId && framesInSelection.has(element.frameId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementsWithinSelection = (
|
export const getElementsWithinSelection = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selection: NonDeletedExcalidrawElement,
|
selection: NonDeletedExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
// TODO remove (this flag is effectively unused AFAIK)
|
|
||||||
excludeElementsInFrames: boolean = true,
|
excludeElementsInFrames: boolean = true,
|
||||||
boxSelectionMode: BoxSelectionMode = "contain",
|
boxSelectionMode: BoxSelectionMode = "contain",
|
||||||
): NonDeletedExcalidrawElement[] => {
|
): NonDeletedExcalidrawElement[] => {
|
||||||
@@ -108,233 +295,152 @@ export const getElementsWithinSelection = (
|
|||||||
selectionX2,
|
selectionX2,
|
||||||
selectionY2,
|
selectionY2,
|
||||||
] as Bounds;
|
] as Bounds;
|
||||||
const selectionEdges = [
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
pointFrom(selectionX1, selectionY1),
|
|
||||||
pointFrom(selectionX2, selectionY1),
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
pointFrom(selectionX2, selectionY1),
|
|
||||||
pointFrom(selectionX2, selectionY2),
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
pointFrom(selectionX2, selectionY2),
|
|
||||||
pointFrom(selectionX1, selectionY2),
|
|
||||||
),
|
|
||||||
lineSegment<GlobalPoint>(
|
|
||||||
pointFrom(selectionX1, selectionY2),
|
|
||||||
pointFrom(selectionX1, selectionY1),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
const framesInSelection = excludeElementsInFrames
|
if (boxSelectionMode !== "overlap") {
|
||||||
? new Set<NonDeletedExcalidrawElement["id"]>()
|
const elementsInSelection: NonDeletedExcalidrawElement[] = [];
|
||||||
: null;
|
|
||||||
let elementsInSelection: NonDeletedExcalidrawElement[] = [];
|
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
if (shouldIgnoreElementFromSelection(element)) {
|
if (shouldSkipElementFromSelection(element)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const strokeWidth = element.strokeWidth;
|
|
||||||
let labelAABB: Bounds | null = null;
|
|
||||||
let elementAABB = getElementBounds(element, elementsMap);
|
|
||||||
|
|
||||||
elementAABB = [
|
|
||||||
elementAABB[0] - strokeWidth / 2,
|
|
||||||
elementAABB[1] - strokeWidth / 2,
|
|
||||||
elementAABB[2] + strokeWidth / 2,
|
|
||||||
elementAABB[3] + strokeWidth / 2,
|
|
||||||
] as Bounds;
|
|
||||||
|
|
||||||
// Whether the element bounds should include the bound text element bounds
|
|
||||||
const boundTextElement =
|
|
||||||
isArrowElement(element) && getBoundTextElement(element, elementsMap);
|
|
||||||
if (boundTextElement) {
|
|
||||||
const { x, y } = LinearElementEditor.getBoundTextElementPosition(
|
|
||||||
element,
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
labelAABB = [
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
x + boundTextElement.width,
|
|
||||||
y + boundTextElement.height,
|
|
||||||
] as Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip element bounds by its containing frame (if any), since only the
|
|
||||||
// visible (frame-clipped) portion of the element is relevant for selection.
|
|
||||||
const associatedFrame = getContainingFrame(element, elementsMap);
|
|
||||||
if (
|
|
||||||
associatedFrame &&
|
|
||||||
isElementIntersectingFrame(element, associatedFrame, elementsMap)
|
|
||||||
) {
|
|
||||||
const frameAABB = getElementBounds(associatedFrame, elementsMap);
|
|
||||||
elementAABB = [
|
|
||||||
Math.max(elementAABB[0], frameAABB[0]),
|
|
||||||
Math.max(elementAABB[1], frameAABB[1]),
|
|
||||||
Math.min(elementAABB[2], frameAABB[2]),
|
|
||||||
Math.min(elementAABB[3], frameAABB[3]),
|
|
||||||
] as Bounds;
|
|
||||||
|
|
||||||
labelAABB = labelAABB
|
|
||||||
? ([
|
|
||||||
Math.max(labelAABB[0], frameAABB[0]),
|
|
||||||
Math.max(labelAABB[1], frameAABB[1]),
|
|
||||||
Math.min(labelAABB[2], frameAABB[2]),
|
|
||||||
Math.min(labelAABB[3], frameAABB[3]),
|
|
||||||
] as Bounds)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commonAABB = labelAABB
|
|
||||||
? ([
|
|
||||||
Math.min(labelAABB[0], elementAABB[0]),
|
|
||||||
Math.min(labelAABB[1], elementAABB[1]),
|
|
||||||
Math.max(labelAABB[2], elementAABB[2]),
|
|
||||||
Math.max(labelAABB[3], elementAABB[3]),
|
|
||||||
] as Bounds)
|
|
||||||
: elementAABB;
|
|
||||||
|
|
||||||
// ============== Evaluation ==============
|
|
||||||
|
|
||||||
// 1. If the selection box WRAPs the element's AABB, then add it to the
|
|
||||||
// selection and move on, regardless of the selection mode.
|
|
||||||
//
|
|
||||||
// PERF: This trick only works with axis-aligned box selection and the
|
|
||||||
// current convex element shapes!
|
|
||||||
if (boundsContainBounds(selectionBounds, commonAABB)) {
|
|
||||||
if (framesInSelection && isFrameLikeElement(element)) {
|
|
||||||
framesInSelection.add(element.id);
|
|
||||||
} else {
|
|
||||||
elementsInSelection.push(element);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elementBounds = getElementBounds(element, elementsMap) as Bounds;
|
||||||
|
const frameBounds = getFrameBoundsForSelection(element, elementsMap);
|
||||||
|
let elementX1 = elementBounds[0];
|
||||||
|
let elementY1 = elementBounds[1];
|
||||||
|
let elementX2 = elementBounds[2];
|
||||||
|
let elementY2 = elementBounds[3];
|
||||||
|
|
||||||
|
if (frameBounds) {
|
||||||
|
elementX1 = Math.max(frameBounds[0], elementX1);
|
||||||
|
elementY1 = Math.max(frameBounds[1], elementY1);
|
||||||
|
elementX2 = Math.min(frameBounds[2], elementX2);
|
||||||
|
elementY2 = Math.min(frameBounds[3], elementY2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectionX1 <= elementX1 &&
|
||||||
|
selectionY1 <= elementY1 &&
|
||||||
|
selectionX2 >= elementX2 &&
|
||||||
|
selectionY2 >= elementY2
|
||||||
|
) {
|
||||||
|
elementsInSelection.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalizeElementsInSelection(
|
||||||
|
elementsInSelection,
|
||||||
|
excludeElementsInFrames,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectionCorners = getBoundsCorners(selectionBounds);
|
||||||
|
const selectionEdges = getBoundsEdges(selectionCorners);
|
||||||
|
const elementsInSelection: NonDeletedExcalidrawElement[] = [];
|
||||||
|
|
||||||
|
for (const element of elements) {
|
||||||
|
if (shouldSkipElementFromSelection(element)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementBounds = getElementBounds(element, elementsMap) as Bounds;
|
||||||
|
const frameBounds = getFrameBoundsForSelection(element, elementsMap);
|
||||||
|
let elementX1 = elementBounds[0];
|
||||||
|
let elementY1 = elementBounds[1];
|
||||||
|
let elementX2 = elementBounds[2];
|
||||||
|
let elementY2 = elementBounds[3];
|
||||||
|
|
||||||
|
if (frameBounds) {
|
||||||
|
elementX1 = Math.max(frameBounds[0], elementX1);
|
||||||
|
elementY1 = Math.max(frameBounds[1], elementY1);
|
||||||
|
elementX2 = Math.min(frameBounds[2], elementX2);
|
||||||
|
elementY2 = Math.min(frameBounds[3], elementY2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSelectionContainingElement =
|
||||||
|
selectionX1 <= elementX1 &&
|
||||||
|
selectionY1 <= elementY1 &&
|
||||||
|
selectionX2 >= elementX2 &&
|
||||||
|
selectionY2 >= elementY2;
|
||||||
|
|
||||||
|
const isSelectionOverlappingElementAabb =
|
||||||
|
selectionX1 <= elementX2 &&
|
||||||
|
selectionY1 <= elementY2 &&
|
||||||
|
selectionX2 >= elementX1 &&
|
||||||
|
selectionY2 >= elementY1;
|
||||||
|
const isSelectionOverlappingElement = shouldUseRotatedOverlapBroadPhase(
|
||||||
|
element,
|
||||||
|
)
|
||||||
|
? isSelectionOverlappingElementAabb &&
|
||||||
|
doBoundsIntersectElementBoundingBox(
|
||||||
|
selectionBounds,
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
: isSelectionOverlappingElementAabb;
|
||||||
|
|
||||||
|
const shouldSelectFromInside = shouldTestInside(element);
|
||||||
|
|
||||||
|
if (shouldSelectFromInside) {
|
||||||
|
if (
|
||||||
|
isSelectionOverlappingElement &&
|
||||||
|
(!shouldUsePreciseFilledOverlap(element) ||
|
||||||
|
isSelectionContainingElement ||
|
||||||
|
doesSelectionOverlapFilledElement(
|
||||||
|
element,
|
||||||
|
frameBounds,
|
||||||
|
selectionBounds,
|
||||||
|
selectionCorners,
|
||||||
|
selectionEdges,
|
||||||
|
elementsMap,
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
elementsInSelection.push(element);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSelectionOverlappingElement) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelectionContainingElement) {
|
||||||
|
elementsInSelection.push(element);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Handle the case where the label is overlapped by the selection box
|
|
||||||
if (
|
if (
|
||||||
boxSelectionMode === "overlap" &&
|
doesSelectionIntersectElementOutline(
|
||||||
labelAABB &&
|
element,
|
||||||
doBoundsIntersect(selectionBounds, labelAABB)
|
frameBounds,
|
||||||
|
selectionEdges,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
elementsInSelection.push(element);
|
elementsInSelection.push(element);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Handle the case where the selection is not wrapping the element, but
|
const outlineSegments = getVisibleElementOutlineSegments(
|
||||||
// it does intersect the element's outline (non-AABB).
|
element,
|
||||||
|
frameBounds,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
boxSelectionMode === "overlap" &&
|
outlineSegments.length > 0 &&
|
||||||
doBoundsIntersect(selectionBounds, elementAABB)
|
doesSelectionContainElementOutline(outlineSegments, selectionBounds)
|
||||||
) {
|
) {
|
||||||
let hasIntersection = false;
|
elementsInSelection.push(element);
|
||||||
|
|
||||||
// Preliminary check potential intersection imprecision
|
|
||||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
|
||||||
const center = elementCenterPoint(element, elementsMap);
|
|
||||||
hasIntersection = element.points.some((point) => {
|
|
||||||
const rotatedPoint = pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x + point[0], element.y + point[1]),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
|
|
||||||
return pointInsideBounds(rotatedPoint, selectionBounds);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const nonRotatedElementBounds = getElementBounds(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
const center = elementCenterPoint(element, elementsMap);
|
|
||||||
hasIntersection = [
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
(nonRotatedElementBounds[0] + nonRotatedElementBounds[2]) / 2,
|
|
||||||
nonRotatedElementBounds[1],
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
nonRotatedElementBounds[2],
|
|
||||||
(nonRotatedElementBounds[1] + nonRotatedElementBounds[3]) / 2,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
(nonRotatedElementBounds[0] + nonRotatedElementBounds[2]) / 2,
|
|
||||||
nonRotatedElementBounds[3],
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
nonRotatedElementBounds[0],
|
|
||||||
(nonRotatedElementBounds[1] + nonRotatedElementBounds[3]) / 2,
|
|
||||||
),
|
|
||||||
center,
|
|
||||||
element.angle,
|
|
||||||
),
|
|
||||||
].some((point) => {
|
|
||||||
return pointInsideBounds(
|
|
||||||
pointRotateRads(point, center, element.angle),
|
|
||||||
selectionBounds,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasIntersection) {
|
|
||||||
hasIntersection = selectionEdges.some(
|
|
||||||
(selectionEdge) =>
|
|
||||||
intersectElementWithLineSegment(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
selectionEdge,
|
|
||||||
strokeWidth / 2,
|
|
||||||
true, // Stop at first hit for better performance
|
|
||||||
).length > 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasIntersection) {
|
|
||||||
if (framesInSelection && isFrameLikeElement(element)) {
|
|
||||||
framesInSelection.add(element.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
elementsInSelection.push(element);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. We don't need to handle when the selection is inside the element
|
|
||||||
// as it is separately handled in App.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elementsInSelection = framesInSelection
|
return finalizeElementsInSelection(
|
||||||
? excludeElementsFromFrames(elementsInSelection, framesInSelection)
|
elementsInSelection,
|
||||||
: elementsInSelection;
|
excludeElementsInFrames,
|
||||||
|
elementsMap,
|
||||||
elementsInSelection = elementsInSelection.filter((element) => {
|
);
|
||||||
const containingFrame = getContainingFrame(element, elementsMap);
|
|
||||||
|
|
||||||
if (containingFrame) {
|
|
||||||
return elementOverlapsWithFrame(element, containingFrame, elementsMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return elementsInSelection;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getVisibleAndNonSelectedElements = (
|
export const getVisibleAndNonSelectedElements = (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { arrayToMap, reseed } from "@excalidraw/common";
|
import { arrayToMap } from "@excalidraw/common";
|
||||||
import { type GlobalPoint, type LocalPoint, pointFrom } from "@excalidraw/math";
|
import { type GlobalPoint, type LocalPoint, pointFrom } from "@excalidraw/math";
|
||||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||||
@@ -12,7 +12,6 @@ import { hitElementItself } from "../src/collision";
|
|||||||
describe("check rotated elements can be hit:", () => {
|
describe("check rotated elements can be hit:", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
reseed(7);
|
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ describe("check rotated elements can be hit:", () => {
|
|||||||
] as LocalPoint[],
|
] as LocalPoint[],
|
||||||
});
|
});
|
||||||
const hit = hitElementItself({
|
const hit = hitElementItself({
|
||||||
point: pointFrom<GlobalPoint>(88, -68),
|
point: pointFrom<GlobalPoint>(87, -67),
|
||||||
element: window.h.elements[0],
|
element: window.h.elements[0],
|
||||||
threshold: 10,
|
threshold: 10,
|
||||||
elementsMap: window.h.scene.getNonDeletedElementsMap(),
|
elementsMap: window.h.scene.getNonDeletedElementsMap(),
|
||||||
@@ -57,7 +56,6 @@ describe("hitElementItself cache", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
reseed(7);
|
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ export const getDefaultAppState = (): Omit<
|
|||||||
lockedMultiSelections: {},
|
lockedMultiSelections: {},
|
||||||
activeLockedId: null,
|
activeLockedId: null,
|
||||||
bindMode: "orbit",
|
bindMode: "orbit",
|
||||||
boxSelectionMode: "contain",
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ import {
|
|||||||
bindOrUnbindBindingElement,
|
bindOrUnbindBindingElement,
|
||||||
mutateElement,
|
mutateElement,
|
||||||
getElementBounds,
|
getElementBounds,
|
||||||
doBoundsIntersect,
|
doNonRotatedBoundsIntersect,
|
||||||
isPointInElement,
|
isPointInElement,
|
||||||
maxBindingDistance_simple,
|
maxBindingDistance_simple,
|
||||||
convertToExcalidrawElements,
|
convertToExcalidrawElements,
|
||||||
@@ -603,55 +603,6 @@ const YOUTUBE_VIDEO_STATES = new Map<
|
|||||||
ValueOf<typeof YOUTUBE_STATES>
|
ValueOf<typeof YOUTUBE_STATES>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const isGoogleDrivePreviewLink = (link: string | null | undefined) => {
|
|
||||||
if (!link) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(link);
|
|
||||||
return (
|
|
||||||
url.hostname === "drive.google.com" &&
|
|
||||||
/^\/file\/d\/[^/]+\/preview\/?$/.test(url.pathname)
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isGoogleDriveEmbeddableElement = (
|
|
||||||
element: ExcalidrawElement | null | undefined,
|
|
||||||
) => {
|
|
||||||
if (!isEmbeddableElement(element)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embedLink = getEmbedLink(toValidURL(element.link || ""));
|
|
||||||
return (
|
|
||||||
embedLink?.type === "video" && isGoogleDrivePreviewLink(embedLink.link)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const EMBEDDABLE_CANVAS_GUARD_CLASS = "excalidraw__embeddable-canvas-guard";
|
|
||||||
const EMBEDDABLE_CANVAS_GUARDS = [
|
|
||||||
{
|
|
||||||
key: "top",
|
|
||||||
style: { top: 0, left: 0, right: 0, height: "33.333%" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "bottom",
|
|
||||||
style: { bottom: 0, left: 0, right: 0, height: "33.333%" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "left",
|
|
||||||
style: { top: "33.333%", bottom: "33.333%", left: 0, width: "33.333%" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "right",
|
|
||||||
style: { top: "33.333%", bottom: "33.333%", right: 0, width: "33.333%" },
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
let IS_PLAIN_PASTE = false;
|
let IS_PLAIN_PASTE = false;
|
||||||
let IS_PLAIN_PASTE_TIMER = 0;
|
let IS_PLAIN_PASTE_TIMER = 0;
|
||||||
let PLAIN_PASTE_TOAST_SHOWN = false;
|
let PLAIN_PASTE_TOAST_SHOWN = false;
|
||||||
@@ -1203,7 +1154,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
startBounds &&
|
startBounds &&
|
||||||
endBounds &&
|
endBounds &&
|
||||||
startElement.id !== endElement.id &&
|
startElement.id !== endElement.id &&
|
||||||
doBoundsIntersect(startBounds, endBounds)
|
doNonRotatedBoundsIntersect(startBounds, endBounds)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,46 +1283,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return this.iFrameRefs.get(element.id);
|
return this.iFrameRefs.get(element.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private deactivateActiveEmbeddable = () => {
|
|
||||||
if (this.state.activeEmbeddable) {
|
|
||||||
flushSync(() => {
|
|
||||||
this.setState({ activeEmbeddable: null });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private handleEmbeddableCanvasGuardPointerDown = (
|
|
||||||
event: React.PointerEvent<HTMLDivElement>,
|
|
||||||
) => {
|
|
||||||
this.deactivateActiveEmbeddable();
|
|
||||||
this.handleCanvasPointerDown(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
private handleEmbeddableCanvasGuardPointerMove = (
|
|
||||||
event: React.PointerEvent<HTMLDivElement>,
|
|
||||||
) => {
|
|
||||||
this.lastViewportPosition.x = event.clientX;
|
|
||||||
this.lastViewportPosition.y = event.clientY;
|
|
||||||
this.deactivateActiveEmbeddable();
|
|
||||||
};
|
|
||||||
|
|
||||||
private handleEmbeddableCanvasGuardTouchStart = (
|
|
||||||
event: React.TouchEvent<HTMLDivElement>,
|
|
||||||
) => {
|
|
||||||
if (event.touches.length >= 2) {
|
|
||||||
this.deactivateActiveEmbeddable();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private handleEmbeddableCanvasGuardWheel = (
|
|
||||||
event: React.WheelEvent<HTMLDivElement>,
|
|
||||||
) => {
|
|
||||||
this.lastViewportPosition.x = event.clientX;
|
|
||||||
this.lastViewportPosition.y = event.clientY;
|
|
||||||
this.deactivateActiveEmbeddable();
|
|
||||||
this.handleWheel(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
private handleIframeLikeElementHover = ({
|
private handleIframeLikeElementHover = ({
|
||||||
hitElement,
|
hitElement,
|
||||||
scenePointer,
|
scenePointer,
|
||||||
@@ -1394,25 +1305,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||||
const isDriveEmbedActivating =
|
|
||||||
(this.state.viewModeEnabled ||
|
|
||||||
this.state.activeTool.type === "selection") &&
|
|
||||||
isGoogleDriveEmbeddableElement(hitElement);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
activeEmbeddable: {
|
activeEmbeddable: { element: hitElement, state: "hover" },
|
||||||
element: hitElement,
|
|
||||||
state: isDriveEmbedActivating ? "active" : "hover",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} else if (this.state.activeEmbeddable?.state === "hover") {
|
} else if (this.state.activeEmbeddable?.state === "hover") {
|
||||||
this.setState({ activeEmbeddable: null });
|
this.setState({ activeEmbeddable: null });
|
||||||
} else if (
|
|
||||||
this.state.activeEmbeddable?.state === "active" &&
|
|
||||||
isGoogleDriveEmbeddableElement(this.state.activeEmbeddable.element) &&
|
|
||||||
this.state.activeEmbeddable.element !== hitElement
|
|
||||||
) {
|
|
||||||
this.setState({ activeEmbeddable: null });
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -1836,8 +1734,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const isHovered =
|
const isHovered =
|
||||||
this.state.activeEmbeddable?.element === el &&
|
this.state.activeEmbeddable?.element === el &&
|
||||||
this.state.activeEmbeddable?.state === "hover";
|
this.state.activeEmbeddable?.state === "hover";
|
||||||
const shouldConstrainDriveInteraction =
|
|
||||||
isActive && isGoogleDriveEmbeddableElement(el);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -1889,7 +1785,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
width: isVisible ? `${el.width}px` : 0,
|
width: isVisible ? `${el.width}px` : 0,
|
||||||
height: isVisible ? `${el.height}px` : 0,
|
height: isVisible ? `${el.height}px` : 0,
|
||||||
transform: isVisible ? `rotate(${el.angle}rad)` : "none",
|
transform: isVisible ? `rotate(${el.angle}rad)` : "none",
|
||||||
position: "relative",
|
|
||||||
pointerEvents: isActive
|
pointerEvents: isActive
|
||||||
? POINTER_EVENTS.enabled
|
? POINTER_EVENTS.enabled
|
||||||
: POINTER_EVENTS.disabled,
|
: POINTER_EVENTS.disabled,
|
||||||
@@ -1932,26 +1827,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{shouldConstrainDriveInteraction &&
|
|
||||||
EMBEDDABLE_CANVAS_GUARDS.map(({ key, style }) => (
|
|
||||||
<div
|
|
||||||
key={key}
|
|
||||||
className={EMBEDDABLE_CANVAS_GUARD_CLASS}
|
|
||||||
onPointerDown={
|
|
||||||
this.handleEmbeddableCanvasGuardPointerDown
|
|
||||||
}
|
|
||||||
onPointerMove={
|
|
||||||
this.handleEmbeddableCanvasGuardPointerMove
|
|
||||||
}
|
|
||||||
onTouchStart={this.handleEmbeddableCanvasGuardTouchStart}
|
|
||||||
onWheel={this.handleEmbeddableCanvasGuardWheel}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: 2,
|
|
||||||
...style,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -2650,7 +2525,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const magicFrameChildren = getElementsOverlappingFrame(
|
const magicFrameChildren = getElementsOverlappingFrame(
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
magicFrame,
|
magicFrame,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
).filter((el) => !isMagicFrameElement(el));
|
).filter((el) => !isMagicFrameElement(el));
|
||||||
|
|
||||||
if (!magicFrameChildren.length) {
|
if (!magicFrameChildren.length) {
|
||||||
@@ -4858,16 +4732,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
this.state.activeEmbeddable &&
|
|
||||||
(event.ctrlKey ||
|
|
||||||
event.metaKey ||
|
|
||||||
event.key === "Control" ||
|
|
||||||
event.key === "Meta")
|
|
||||||
) {
|
|
||||||
this.setState({ activeEmbeddable: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isInputLike(event.target)) {
|
if (!isInputLike(event.target)) {
|
||||||
if (
|
if (
|
||||||
(event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
(event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||||
@@ -5746,10 +5610,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
selectedElementIds: makeNextSelectedElementIds({}, this.state),
|
selectedElementIds: makeNextSelectedElementIds({}, this.state),
|
||||||
activeEmbeddable: null,
|
activeEmbeddable: null,
|
||||||
});
|
});
|
||||||
} else if (this.state.activeEmbeddable) {
|
|
||||||
this.setState({
|
|
||||||
activeEmbeddable: null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
gesture.initialScale = this.state.zoom.value;
|
gesture.initialScale = this.state.zoom.value;
|
||||||
});
|
});
|
||||||
@@ -8304,10 +8164,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
y: event.clientY,
|
y: event.clientY,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (gesture.pointers.size >= 2 && this.state.activeEmbeddable) {
|
|
||||||
this.setState({ activeEmbeddable: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gesture.pointers.size === 2) {
|
if (gesture.pointers.size === 2) {
|
||||||
gesture.lastCenter = getCenter(gesture.pointers);
|
gesture.lastCenter = getCenter(gesture.pointers);
|
||||||
gesture.initialScale = this.state.zoom.value;
|
gesture.initialScale = this.state.zoom.value;
|
||||||
@@ -12727,8 +12583,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
event.target instanceof HTMLTextAreaElement ||
|
event.target instanceof HTMLTextAreaElement ||
|
||||||
event.target instanceof HTMLIFrameElement ||
|
event.target instanceof HTMLIFrameElement ||
|
||||||
(event.target instanceof HTMLElement &&
|
(event.target instanceof HTMLElement &&
|
||||||
(event.target.classList.contains(CLASSES.FRAME_NAME) ||
|
event.target.classList.contains(CLASSES.FRAME_NAME))
|
||||||
event.target.classList.contains(EMBEDDABLE_CANVAS_GUARD_CLASS)))
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// prevent zooming the browser (but allow scrolling DOM)
|
// prevent zooming the browser (but allow scrolling DOM)
|
||||||
|
|||||||
@@ -750,7 +750,7 @@ describe("frame resizing behavior", () => {
|
|||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 103,
|
height: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a rectangle outside the frame
|
// Create a rectangle outside the frame
|
||||||
|
|||||||
@@ -437,15 +437,16 @@ const PreferencesBoxSelectionModeItem = () => {
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appState = useUIAppState();
|
const appState = useUIAppState();
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
const boxSelectionMode = appState.boxSelectionMode ?? "contain";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemContentRadio<"contain" | "overlap">
|
<DropdownMenuItemContentRadio<"contain" | "overlap">
|
||||||
name="boxSelectionMode"
|
name="boxSelectionMode"
|
||||||
icon={emptyIcon}
|
icon={emptyIcon}
|
||||||
value={appState.boxSelectionMode}
|
value={boxSelectionMode}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setAppState({
|
setAppState({
|
||||||
boxSelectionMode: value,
|
boxSelectionMode: value === "contain" ? undefined : value,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
choices={[
|
choices={[
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
cloneJSON,
|
cloneJSON,
|
||||||
SVG_DOCUMENT_PREAMBLE,
|
SVG_DOCUMENT_PREAMBLE,
|
||||||
arrayToMap,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { getNonDeletedElements } from "@excalidraw/element";
|
import { getNonDeletedElements } from "@excalidraw/element";
|
||||||
@@ -50,7 +49,6 @@ export const prepareElementsForExport = (
|
|||||||
exportSelectionOnly: boolean,
|
exportSelectionOnly: boolean,
|
||||||
) => {
|
) => {
|
||||||
elements = getNonDeletedElements(elements);
|
elements = getNonDeletedElements(elements);
|
||||||
const elementsMap = arrayToMap(elements);
|
|
||||||
|
|
||||||
const isExportingSelection =
|
const isExportingSelection =
|
||||||
exportSelectionOnly &&
|
exportSelectionOnly &&
|
||||||
@@ -73,11 +71,7 @@ export const prepareElementsForExport = (
|
|||||||
isFrameLikeElement(exportedElements[0])
|
isFrameLikeElement(exportedElements[0])
|
||||||
) {
|
) {
|
||||||
exportingFrame = exportedElements[0];
|
exportingFrame = exportedElements[0];
|
||||||
exportedElements = getElementsOverlappingFrame(
|
exportedElements = getElementsOverlappingFrame(elements, exportingFrame);
|
||||||
elements,
|
|
||||||
exportingFrame,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
} else if (exportedElements.length > 1) {
|
} else if (exportedElements.length > 1) {
|
||||||
exportedElements = getSelectedElements(
|
exportedElements = getSelectedElements(
|
||||||
elements,
|
elements,
|
||||||
|
|||||||
@@ -96,8 +96,6 @@ type RestoredAppState = Omit<
|
|||||||
"offsetTop" | "offsetLeft" | "width" | "height"
|
"offsetTop" | "offsetLeft" | "width" | "height"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const MAX_ARROW_PX = 75_000;
|
|
||||||
|
|
||||||
export const AllowedExcalidrawActiveTools: Record<
|
export const AllowedExcalidrawActiveTools: Record<
|
||||||
AppState["activeTool"]["type"],
|
AppState["activeTool"]["type"],
|
||||||
boolean
|
boolean
|
||||||
@@ -469,8 +467,8 @@ export const restoreElement = (
|
|||||||
element.endArrowhead === undefined
|
element.endArrowhead === undefined
|
||||||
? "arrow"
|
? "arrow"
|
||||||
: normalizeArrowhead(element.endArrowhead);
|
: normalizeArrowhead(element.endArrowhead);
|
||||||
const x = element.x as number | undefined;
|
const x: number | undefined = element.x;
|
||||||
const y = element.y as number | undefined;
|
const y: number | undefined = element.y;
|
||||||
const points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one
|
const points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one
|
||||||
!Array.isArray(element.points) || element.points.length < 2
|
!Array.isArray(element.points) || element.points.length < 2
|
||||||
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
||||||
@@ -495,8 +493,8 @@ export const restoreElement = (
|
|||||||
startArrowhead,
|
startArrowhead,
|
||||||
endArrowhead,
|
endArrowhead,
|
||||||
points,
|
points,
|
||||||
x: x ?? 0,
|
x,
|
||||||
y: y ?? 0,
|
y,
|
||||||
elbowed: (element as ExcalidrawArrowElement).elbowed,
|
elbowed: (element as ExcalidrawArrowElement).elbowed,
|
||||||
...getSizeFromPoints(points),
|
...getSizeFromPoints(points),
|
||||||
};
|
};
|
||||||
@@ -515,44 +513,12 @@ export const restoreElement = (
|
|||||||
})
|
})
|
||||||
: restoreElementWithProperties(element as ExcalidrawArrowElement, base);
|
: restoreElementWithProperties(element as ExcalidrawArrowElement, base);
|
||||||
|
|
||||||
const normalizedRestoredElement = {
|
return {
|
||||||
...restoredElement,
|
...restoredElement,
|
||||||
...LinearElementEditor.getNormalizeElementPointsAndCoords(
|
...LinearElementEditor.getNormalizeElementPointsAndCoords(
|
||||||
restoredElement,
|
restoredElement,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Last resort fix for extremely large arrows
|
|
||||||
if (
|
|
||||||
normalizedRestoredElement.width > MAX_ARROW_PX ||
|
|
||||||
normalizedRestoredElement.height > MAX_ARROW_PX
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
`Removing extremely large arrow ${
|
|
||||||
normalizedRestoredElement.id
|
|
||||||
} (type: ${
|
|
||||||
isElbowArrow(normalizedRestoredElement) ? "elbow" : "simple"
|
|
||||||
}, width: ${normalizedRestoredElement.width}, height: ${
|
|
||||||
normalizedRestoredElement.height
|
|
||||||
}, x: ${normalizedRestoredElement.x}, y: ${
|
|
||||||
normalizedRestoredElement.y
|
|
||||||
})`,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...normalizedRestoredElement,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
points: [
|
|
||||||
pointFrom<LocalPoint>(0, 0),
|
|
||||||
pointFrom<LocalPoint>(100, 100),
|
|
||||||
],
|
|
||||||
isDeleted: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedRestoredElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic elements
|
// generic elements
|
||||||
@@ -700,7 +666,6 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
|||||||
const existingElementsMap = existingElements
|
const existingElementsMap = existingElements
|
||||||
? arrayToMap(existingElements)
|
? arrayToMap(existingElements)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const restoredElements = syncInvalidIndices(
|
const restoredElements = syncInvalidIndices(
|
||||||
(targetElements || []).reduce((elements, element) => {
|
(targetElements || []).reduce((elements, element) => {
|
||||||
// filtering out selection, which is legacy, no longer kept in elements,
|
// filtering out selection, which is legacy, no longer kept in elements,
|
||||||
@@ -797,7 +762,7 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE (mtolmacs): Temporary fix for invalid/self-bound elbow arrows
|
// NOTE (mtolmacs): Temporary fix for extremely large arrows
|
||||||
// Need to iterate again so we have attached text nodes in elementsMap
|
// Need to iterate again so we have attached text nodes in elementsMap
|
||||||
return restoredElements.map((element) => {
|
return restoredElements.map((element) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { arrayToMap, easeOut, THEME } from "@excalidraw/common";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
computeBoundTextPosition,
|
computeBoundTextPosition,
|
||||||
doBoundsIntersect,
|
doNonRotatedBoundsIntersect,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
getElementBounds,
|
getElementBounds,
|
||||||
getElementLineSegments,
|
getElementLineSegments,
|
||||||
@@ -219,7 +219,7 @@ const eraserTest = (
|
|||||||
origElementBounds[3] + threshold,
|
origElementBounds[3] + threshold,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!doBoundsIntersect(segmentBounds, elementBounds)) {
|
if (!doNonRotatedBoundsIntersect(segmentBounds, elementBounds)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { type Bounds } from "@excalidraw/common";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
computeBoundTextPosition,
|
computeBoundTextPosition,
|
||||||
doBoundsIntersect,
|
doNonRotatedBoundsIntersect,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
getElementBounds,
|
getElementBounds,
|
||||||
intersectElementWithLineSegment,
|
intersectElementWithLineSegment,
|
||||||
@@ -66,7 +66,7 @@ export const getLassoSelectedElementIds = (input: {
|
|||||||
const elementBounds = getElementBounds(element, elementsMap);
|
const elementBounds = getElementBounds(element, elementsMap);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
doBoundsIntersect(lassoBounds, elementBounds) &&
|
doNonRotatedBoundsIntersect(lassoBounds, elementBounds) &&
|
||||||
!intersectedElements.has(element.id) &&
|
!intersectedElements.has(element.id) &&
|
||||||
!enclosedElements.has(element.id)
|
!enclosedElements.has(element.id)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -157,11 +157,7 @@ const prepareElementsForRender = ({
|
|||||||
let nextElements: readonly ExcalidrawElement[];
|
let nextElements: readonly ExcalidrawElement[];
|
||||||
|
|
||||||
if (exportingFrame) {
|
if (exportingFrame) {
|
||||||
nextElements = getElementsOverlappingFrame(
|
nextElements = getElementsOverlappingFrame(elements, exportingFrame);
|
||||||
elements,
|
|
||||||
exportingFrame,
|
|
||||||
arrayToMap(elements),
|
|
||||||
);
|
|
||||||
} else if (frameRendering.enabled && frameRendering.name) {
|
} else if (frameRendering.enabled && frameRendering.name) {
|
||||||
nextElements = addFrameLabelsAsTextElements(elements, {
|
nextElements = addFrameLabelsAsTextElements(elements, {
|
||||||
exportWithDarkMode,
|
exportWithDarkMode,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"items": [
|
"items": [
|
||||||
@@ -1087,7 +1086,6 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1302,7 +1300,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1634,7 +1631,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1966,7 +1962,6 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2181,7 +2176,6 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2423,7 +2417,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2722,7 +2715,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3095,7 +3087,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3589,7 +3580,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3913,7 +3903,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4237,7 +4226,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4649,7 +4637,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"items": [
|
"items": [
|
||||||
@@ -5867,7 +5854,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"items": [
|
"items": [
|
||||||
@@ -7136,7 +7122,6 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"items": [
|
"items": [
|
||||||
@@ -7826,7 +7811,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"items": [
|
"items": [
|
||||||
@@ -8818,7 +8802,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"items": [
|
"items": [
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -647,7 +646,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1209,7 +1207,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1570,7 +1567,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1933,7 +1929,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2197,7 +2192,6 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2651,7 +2645,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2955,7 +2948,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3275,7 +3267,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3570,7 +3561,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3857,7 +3847,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4093,7 +4082,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4351,7 +4339,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4623,7 +4610,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4853,7 +4839,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5083,7 +5068,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5331,7 +5315,6 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5588,7 +5571,6 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5847,7 +5829,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -6177,7 +6158,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -6605,7 +6585,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -6980,7 +6959,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -7293,7 +7271,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -7586,7 +7563,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -7817,7 +7793,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8170,7 +8145,6 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8523,7 +8497,6 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8930,7 +8903,6 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9210,7 +9182,6 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9475,7 +9446,6 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9741,7 +9711,6 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9974,7 +9943,6 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10272,7 +10240,6 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10590,7 +10557,6 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10827,7 +10793,6 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -11753,7 +11718,6 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -12014,7 +11978,6 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -12250,7 +12213,6 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -12488,7 +12450,6 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -12880,7 +12841,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -13091,7 +13051,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -13299,7 +13258,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -13601,7 +13559,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -13900,7 +13857,6 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14146,7 +14102,6 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14384,7 +14339,6 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14622,7 +14576,6 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14870,7 +14823,6 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -15202,7 +15154,6 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -15373,7 +15324,6 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -15658,7 +15608,6 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -15922,7 +15871,6 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -16076,7 +16024,6 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -16359,7 +16306,6 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -16522,7 +16468,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -17271,7 +17216,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -17918,7 +17862,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -18565,7 +18508,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -19315,7 +19257,6 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -20084,7 +20025,6 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -20565,7 +20505,6 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -21077,7 +21016,6 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -21537,7 +21475,6 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -440,7 +439,6 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -857,7 +855,6 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1424,7 +1421,6 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -1632,7 +1628,6 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2017,7 +2012,6 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2263,7 +2257,6 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2444,7 +2437,6 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -2770,7 +2762,6 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3026,7 +3017,6 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3268,7 +3258,6 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3505,7 +3494,6 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -3764,7 +3752,6 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4079,7 +4066,6 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4516,7 +4502,6 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -4800,7 +4785,6 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5077,7 +5061,6 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5286,7 +5269,6 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5487,7 +5469,6 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -5881,7 +5862,6 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -6179,7 +6159,6 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -6968,7 +6947,6 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -7303,7 +7281,6 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -7583,7 +7560,6 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -7819,7 +7795,6 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8060,7 +8035,6 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8241,7 +8215,6 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8422,7 +8395,6 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8603,7 +8575,6 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -8836,7 +8807,6 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9067,7 +9037,6 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9260,7 +9229,6 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9493,7 +9461,6 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9674,7 +9641,6 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -9905,7 +9871,6 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10086,7 +10051,6 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10279,7 +10243,6 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10460,7 +10423,6 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -10992,7 +10954,6 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -11273,7 +11234,6 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -11397,7 +11357,6 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -11598,7 +11557,6 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -11918,7 +11876,6 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -12348,7 +12305,6 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -12989,7 +12945,6 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -13116,7 +13071,6 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -13748,7 +13702,6 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14088,7 +14041,6 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14353,7 +14305,6 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14477,7 +14428,6 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14842,7 +14792,6 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
@@ -14966,7 +14915,6 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
|
|||||||
@@ -1,315 +0,0 @@
|
|||||||
import { Excalidraw } from "../index";
|
|
||||||
|
|
||||||
import { Pointer } from "./helpers/ui";
|
|
||||||
import { act, fireEvent, GlobalTestState, render, waitFor } from "./test-utils";
|
|
||||||
|
|
||||||
import type { ExcalidrawProps } from "../types";
|
|
||||||
|
|
||||||
describe("embeddable interactions", () => {
|
|
||||||
const h = window.h;
|
|
||||||
const mouse = new Pointer("mouse");
|
|
||||||
|
|
||||||
const renderGoogleDriveEmbeddable = async (
|
|
||||||
excalidrawProps: Partial<ExcalidrawProps> = {},
|
|
||||||
) => {
|
|
||||||
const renderResult = await render(<Excalidraw {...excalidrawProps} />);
|
|
||||||
const fileId = "1AbCdEfGhIjKlMnOpQrStUvWxYz123456";
|
|
||||||
const src = `https://drive.google.com/file/d/${fileId}/preview`;
|
|
||||||
let embeddable!: NonNullable<
|
|
||||||
ReturnType<typeof h.app.insertEmbeddableElement>
|
|
||||||
>;
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
const insertedEmbeddable = h.app.insertEmbeddableElement({
|
|
||||||
sceneX: 40,
|
|
||||||
sceneY: 40,
|
|
||||||
link: `https://drive.google.com/file/d/${fileId}/view?usp=sharing`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!insertedEmbeddable) {
|
|
||||||
throw new Error("Google Drive embeddable not inserted");
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddable = insertedEmbeddable;
|
|
||||||
});
|
|
||||||
|
|
||||||
(
|
|
||||||
h.app as unknown as {
|
|
||||||
embedsValidationStatus: Map<string, boolean>;
|
|
||||||
}
|
|
||||||
).embedsValidationStatus.set(embeddable.id, true);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.setState({ width: 1000, height: 1000 });
|
|
||||||
h.app.scene.triggerUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
const getIframe = () => {
|
|
||||||
const iframe = renderResult.container.querySelector<HTMLIFrameElement>(
|
|
||||||
"iframe.excalidraw__embeddable",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!iframe) {
|
|
||||||
throw new Error("Google Drive iframe not rendered");
|
|
||||||
}
|
|
||||||
|
|
||||||
return iframe;
|
|
||||||
};
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(getIframe().src).toBe(src);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...renderResult,
|
|
||||||
embeddable,
|
|
||||||
getIframe,
|
|
||||||
src,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderYouTubeEmbeddable = async (
|
|
||||||
excalidrawProps: Partial<ExcalidrawProps> = {},
|
|
||||||
) => {
|
|
||||||
const renderResult = await render(<Excalidraw {...excalidrawProps} />);
|
|
||||||
let embeddable!: NonNullable<
|
|
||||||
ReturnType<typeof h.app.insertEmbeddableElement>
|
|
||||||
>;
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
const insertedEmbeddable = h.app.insertEmbeddableElement({
|
|
||||||
sceneX: 40,
|
|
||||||
sceneY: 40,
|
|
||||||
link: "https://www.youtube.com/watch?v=gkGMXY0wekg",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!insertedEmbeddable) {
|
|
||||||
throw new Error("YouTube embeddable not inserted");
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddable = insertedEmbeddable;
|
|
||||||
});
|
|
||||||
|
|
||||||
(
|
|
||||||
h.app as unknown as {
|
|
||||||
embedsValidationStatus: Map<string, boolean>;
|
|
||||||
}
|
|
||||||
).embedsValidationStatus.set(embeddable.id, true);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.setState({ width: 1000, height: 1000 });
|
|
||||||
h.app.scene.triggerUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
renderResult.container.querySelector("iframe.excalidraw__embeddable"),
|
|
||||||
).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...renderResult,
|
|
||||||
embeddable,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const setActiveEmbeddable = (
|
|
||||||
embeddable: NonNullable<ReturnType<typeof h.app.insertEmbeddableElement>>,
|
|
||||||
) => {
|
|
||||||
act(() => {
|
|
||||||
h.setState({
|
|
||||||
activeEmbeddable: { element: embeddable, state: "active" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
it("lets the initial Google Drive video click land in the iframe center", async () => {
|
|
||||||
const { container, embeddable, getIframe, src } =
|
|
||||||
await renderGoogleDriveEmbeddable();
|
|
||||||
|
|
||||||
mouse.moveTo(
|
|
||||||
embeddable.x + embeddable.width / 2,
|
|
||||||
embeddable.y + embeddable.height / 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
container.querySelector<HTMLElement>(
|
|
||||||
".excalidraw__embeddable-container__inner",
|
|
||||||
)?.style.pointerEvents,
|
|
||||||
).toBe("all");
|
|
||||||
expect(h.state.activeEmbeddable?.element.id).toBe(embeddable.id);
|
|
||||||
expect(h.state.activeEmbeddable?.state).toBe("active");
|
|
||||||
expect(
|
|
||||||
container.querySelectorAll(".excalidraw__embeddable-canvas-guard"),
|
|
||||||
).toHaveLength(4);
|
|
||||||
expect(
|
|
||||||
container.querySelector(".excalidraw__embeddable-hint"),
|
|
||||||
).toBeNull();
|
|
||||||
expect(getIframe().src).toBe(src);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns Drive embeddable edge interactions to the canvas", async () => {
|
|
||||||
const { container, embeddable } = await renderGoogleDriveEmbeddable();
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.setState({
|
|
||||||
activeEmbeddable: { element: embeddable, state: "active" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const guard = container.querySelector<HTMLElement>(
|
|
||||||
".excalidraw__embeddable-canvas-guard",
|
|
||||||
);
|
|
||||||
expect(guard).not.toBeNull();
|
|
||||||
|
|
||||||
fireEvent.pointerMove(guard!, {
|
|
||||||
clientX: embeddable.x + 1,
|
|
||||||
clientY: embeddable.y + 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable).toBeNull();
|
|
||||||
expect(
|
|
||||||
container.querySelector<HTMLElement>(
|
|
||||||
".excalidraw__embeddable-container__inner",
|
|
||||||
)?.style.pointerEvents,
|
|
||||||
).toBe("none");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deactivates an interactive embeddable on Ctrl/Cmd keydown", async () => {
|
|
||||||
const { embeddable } = await renderGoogleDriveEmbeddable({
|
|
||||||
handleKeyboardGlobally: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
mouse.moveTo(
|
|
||||||
embeddable.x + embeddable.width / 2,
|
|
||||||
embeddable.y + embeddable.height / 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable?.state).toBe("active");
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.keyDown(document, {
|
|
||||||
key: "Control",
|
|
||||||
ctrlKey: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles Ctrl/Cmd wheel on Drive embeddable guards", async () => {
|
|
||||||
const { container, embeddable } = await renderGoogleDriveEmbeddable();
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.setState({
|
|
||||||
activeEmbeddable: { element: embeddable, state: "active" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const guard = container.querySelector<HTMLElement>(
|
|
||||||
".excalidraw__embeddable-canvas-guard",
|
|
||||||
);
|
|
||||||
expect(guard).not.toBeNull();
|
|
||||||
|
|
||||||
const prevZoom = h.state.zoom.value;
|
|
||||||
|
|
||||||
fireEvent.wheel(guard!, {
|
|
||||||
clientX: embeddable.x + 1,
|
|
||||||
clientY: embeddable.y + 1,
|
|
||||||
ctrlKey: true,
|
|
||||||
deltaY: -100,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable).toBeNull();
|
|
||||||
expect(h.state.zoom.value).toBeGreaterThan(prevZoom);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deactivates a non-Drive interactive embeddable on Ctrl/Cmd keydown", async () => {
|
|
||||||
const { container, embeddable } = await renderYouTubeEmbeddable({
|
|
||||||
handleKeyboardGlobally: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
setActiveEmbeddable(embeddable);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
container.querySelector<HTMLElement>(
|
|
||||||
".excalidraw__embeddable-container__inner",
|
|
||||||
)?.style.pointerEvents,
|
|
||||||
).toBe("all");
|
|
||||||
expect(
|
|
||||||
container.querySelectorAll(".excalidraw__embeddable-canvas-guard"),
|
|
||||||
).toHaveLength(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.keyDown(document, {
|
|
||||||
key: "Control",
|
|
||||||
ctrlKey: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable).toBeNull();
|
|
||||||
expect(
|
|
||||||
container.querySelector<HTMLElement>(
|
|
||||||
".excalidraw__embeddable-container__inner",
|
|
||||||
)?.style.pointerEvents,
|
|
||||||
).toBe("none");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deactivates a non-Drive interactive embeddable on parent-observed pinch", async () => {
|
|
||||||
const { embeddable } = await renderYouTubeEmbeddable();
|
|
||||||
|
|
||||||
setActiveEmbeddable(embeddable);
|
|
||||||
|
|
||||||
fireEvent.pointerDown(GlobalTestState.interactiveCanvas, {
|
|
||||||
clientX: embeddable.x + 10,
|
|
||||||
clientY: embeddable.y + 10,
|
|
||||||
pointerId: 1,
|
|
||||||
pointerType: "touch",
|
|
||||||
});
|
|
||||||
fireEvent.pointerDown(GlobalTestState.interactiveCanvas, {
|
|
||||||
clientX: embeddable.x + 30,
|
|
||||||
clientY: embeddable.y + 30,
|
|
||||||
pointerId: 2,
|
|
||||||
pointerType: "touch",
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.pointerUp(GlobalTestState.interactiveCanvas, {
|
|
||||||
pointerId: 1,
|
|
||||||
pointerType: "touch",
|
|
||||||
});
|
|
||||||
fireEvent.pointerUp(GlobalTestState.interactiveCanvas, {
|
|
||||||
pointerId: 2,
|
|
||||||
pointerType: "touch",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deactivates a non-Drive interactive embeddable on Safari gesturestart", async () => {
|
|
||||||
const { embeddable } = await renderYouTubeEmbeddable();
|
|
||||||
|
|
||||||
setActiveEmbeddable(embeddable);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
document.dispatchEvent(
|
|
||||||
new Event("gesturestart", { bubbles: true, cancelable: true }),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(h.state.activeEmbeddable).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -314,7 +314,7 @@ describe("history", () => {
|
|||||||
expect.objectContaining({ id: rect2.id, isDeleted: true }),
|
expect.objectContaining({ id: rect2.id, isDeleted: true }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mouse.downAt(-10, -10);
|
mouse.downAt(0, 0);
|
||||||
mouse.moveTo(25, 25);
|
mouse.moveTo(25, 25);
|
||||||
mouse.moveTo(50, 50);
|
mouse.moveTo(50, 50);
|
||||||
mouse.upAt(50, 50);
|
mouse.upAt(50, 50);
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ describe("regression tests", () => {
|
|||||||
mouse.reset();
|
mouse.reset();
|
||||||
mouse.down();
|
mouse.down();
|
||||||
mouse.move(-1000, -1000);
|
mouse.move(-1000, -1000);
|
||||||
mouse.restorePosition(end[0] + 3, end[1] + 3);
|
mouse.restorePosition(...end);
|
||||||
mouse.up();
|
mouse.up();
|
||||||
|
|
||||||
expect(h.elements.length).toBe(3);
|
expect(h.elements.length).toBe(3);
|
||||||
@@ -519,7 +519,7 @@ describe("regression tests", () => {
|
|||||||
mouse.reset();
|
mouse.reset();
|
||||||
mouse.down();
|
mouse.down();
|
||||||
mouse.move(-1000, -1000);
|
mouse.move(-1000, -1000);
|
||||||
mouse.restorePosition(end[0] + 3, end[1] + 3);
|
mouse.restorePosition(...end);
|
||||||
mouse.up();
|
mouse.up();
|
||||||
|
|
||||||
for (const element of h.elements) {
|
for (const element of h.elements) {
|
||||||
@@ -537,7 +537,7 @@ describe("regression tests", () => {
|
|||||||
mouse.moveTo(-10, -10); // the NW resizing handle is at [0, 0], so moving further
|
mouse.moveTo(-10, -10); // the NW resizing handle is at [0, 0], so moving further
|
||||||
mouse.down();
|
mouse.down();
|
||||||
mouse.move(-1000, -1000);
|
mouse.move(-1000, -1000);
|
||||||
mouse.restorePosition(end[0] + 3, end[1] + 3);
|
mouse.restorePosition(...end);
|
||||||
mouse.up();
|
mouse.up();
|
||||||
|
|
||||||
Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
|
Keyboard.withModifierKeys({ ctrl: true, shift: true }, () => {
|
||||||
|
|||||||
@@ -348,10 +348,10 @@ describe("box-selection overlap mode", () => {
|
|||||||
);
|
);
|
||||||
const [outlineX1, outlineY1, outlineX2, outlineY2] = getOutlineBounds(rect);
|
const [outlineX1, outlineY1, outlineX2, outlineY2] = getOutlineBounds(rect);
|
||||||
|
|
||||||
expect(outlineX1).toBeGreaterThan(boundsX1 - 1);
|
expect(outlineX1).toBeGreaterThan(boundsX1);
|
||||||
expect(outlineY1).toBeGreaterThan(boundsY1 - 1);
|
expect(outlineY1).toBeGreaterThan(boundsY1);
|
||||||
expect(outlineX2).toBeLessThan(boundsX2 + 1);
|
expect(outlineX2).toBeLessThan(boundsX2);
|
||||||
expect(outlineY2).toBeLessThan(boundsY2 + 1);
|
expect(outlineY2).toBeLessThan(boundsY2);
|
||||||
|
|
||||||
boxSelect(
|
boxSelect(
|
||||||
outlineX1 - (outlineX1 - boundsX1) / 2,
|
outlineX1 - (outlineX1 - boundsX1) / 2,
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ export interface AppState {
|
|||||||
*/
|
*/
|
||||||
isBindingEnabled: boolean;
|
isBindingEnabled: boolean;
|
||||||
/** user box selection preference; defaults to "contain" when unset */
|
/** user box selection preference; defaults to "contain" when unset */
|
||||||
boxSelectionMode: BoxSelectionMode;
|
boxSelectionMode?: BoxSelectionMode;
|
||||||
/** user arrow binding preference */
|
/** user arrow binding preference */
|
||||||
bindingPreference: "enabled" | "disabled";
|
bindingPreference: "enabled" | "disabled";
|
||||||
/** user preference whether arrow snap to midpoints while binding */
|
/** user preference whether arrow snap to midpoints while binding */
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export const PRECISION = 10e-5;
|
||||||
|
|
||||||
// Legendre-Gauss abscissae (x values) and weights for n=24
|
// Legendre-Gauss abscissae (x values) and weights for n=24
|
||||||
// Refeerence: https://pomax.github.io/bezierinfo/legendre-gauss.html
|
// Refeerence: https://pomax.github.io/bezierinfo/legendre-gauss.html
|
||||||
export const LegendreGaussN24TValues = [
|
export const LegendreGaussN24TValues = [
|
||||||
|
|||||||
@@ -137,17 +137,8 @@ const calculate = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
[t0, s0]: [number, number],
|
[t0, s0]: [number, number],
|
||||||
l: LineSegment<Point>,
|
l: LineSegment<Point>,
|
||||||
c: Curve<Point>,
|
c: Curve<Point>,
|
||||||
tolerance: number = 1e-2,
|
|
||||||
iterLimit: number = 4,
|
|
||||||
) => {
|
) => {
|
||||||
const solution = solveWithAnalyticalJacobian(
|
const solution = solveWithAnalyticalJacobian(c, l, t0, s0, 1e-2, 4);
|
||||||
c,
|
|
||||||
l,
|
|
||||||
t0,
|
|
||||||
s0,
|
|
||||||
tolerance,
|
|
||||||
iterLimit,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!solution) {
|
if (!solution) {
|
||||||
return null;
|
return null;
|
||||||
@@ -167,43 +158,18 @@ const calculate = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
*/
|
*/
|
||||||
export function curveIntersectLineSegment<
|
export function curveIntersectLineSegment<
|
||||||
Point extends GlobalPoint | LocalPoint,
|
Point extends GlobalPoint | LocalPoint,
|
||||||
>(
|
>(c: Curve<Point>, l: LineSegment<Point>): Point[] {
|
||||||
c: Curve<Point>,
|
let solution = calculate(initial_guesses[0], l, c);
|
||||||
l: LineSegment<Point>,
|
|
||||||
opts?: {
|
|
||||||
tolerance?: number;
|
|
||||||
iterLimit?: number;
|
|
||||||
},
|
|
||||||
): Point[] {
|
|
||||||
let solution = calculate(
|
|
||||||
initial_guesses[0],
|
|
||||||
l,
|
|
||||||
c,
|
|
||||||
opts?.tolerance,
|
|
||||||
opts?.iterLimit,
|
|
||||||
);
|
|
||||||
if (solution) {
|
if (solution) {
|
||||||
return [solution];
|
return [solution];
|
||||||
}
|
}
|
||||||
|
|
||||||
solution = calculate(
|
solution = calculate(initial_guesses[1], l, c);
|
||||||
initial_guesses[1],
|
|
||||||
l,
|
|
||||||
c,
|
|
||||||
opts?.tolerance,
|
|
||||||
opts?.iterLimit,
|
|
||||||
);
|
|
||||||
if (solution) {
|
if (solution) {
|
||||||
return [solution];
|
return [solution];
|
||||||
}
|
}
|
||||||
|
|
||||||
solution = calculate(
|
solution = calculate(initial_guesses[2], l, c);
|
||||||
initial_guesses[2],
|
|
||||||
l,
|
|
||||||
c,
|
|
||||||
opts?.tolerance,
|
|
||||||
opts?.iterLimit,
|
|
||||||
);
|
|
||||||
if (solution) {
|
if (solution) {
|
||||||
return [solution];
|
return [solution];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Generic markers
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used for any type of point-likes to mark them as rotated to enlist
|
||||||
|
* the type checker to weed out subtle bugs due to rotated and non-rotated
|
||||||
|
* versions of the same data point.
|
||||||
|
*/
|
||||||
|
export type Rotated<T> = T & { _brand_rotated: "excalimath_rotated" };
|
||||||
|
|
||||||
//
|
//
|
||||||
// Measurements
|
// Measurements
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { arrayToMap, type Bounds } from "@excalidraw/common";
|
import {
|
||||||
|
arrayToMap,
|
||||||
|
bounds,
|
||||||
|
type RotatedBounds,
|
||||||
|
type Bounds,
|
||||||
|
} from "@excalidraw/common";
|
||||||
import { getElementBounds } from "@excalidraw/element";
|
import { getElementBounds } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
@@ -90,7 +95,7 @@ const getMinMaxPoints = (points: Points) => {
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRotatedBBox = (element: Element): Bounds => {
|
const getRotatedBBox = (element: Element): RotatedBounds => {
|
||||||
const points = getElementRelativePoints(element);
|
const points = getElementRelativePoints(element);
|
||||||
|
|
||||||
const { cx, cy } = getMinMaxPoints(points);
|
const { cx, cy } = getMinMaxPoints(points);
|
||||||
@@ -101,12 +106,13 @@ const getRotatedBBox = (element: Element): Bounds => {
|
|||||||
);
|
);
|
||||||
const { minX, minY, maxX, maxY } = getMinMaxPoints(rotatedPoints);
|
const { minX, minY, maxX, maxY } = getMinMaxPoints(rotatedPoints);
|
||||||
|
|
||||||
return [
|
return bounds(
|
||||||
minX + element.x,
|
minX + element.x,
|
||||||
minY + element.y,
|
minY + element.y,
|
||||||
maxX + element.x,
|
maxX + element.x,
|
||||||
maxY + element.y,
|
maxY + element.y,
|
||||||
];
|
element.angle,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isElementInsideBBox = (
|
export const isElementInsideBBox = (
|
||||||
@@ -160,12 +166,12 @@ export const elementPartiallyOverlapsWithOrContainsBBox = (
|
|||||||
|
|
||||||
export const elementsOverlappingBBox = ({
|
export const elementsOverlappingBBox = ({
|
||||||
elements,
|
elements,
|
||||||
bounds,
|
bounds: _bounds,
|
||||||
type,
|
type,
|
||||||
errorMargin = 0,
|
errorMargin = 0,
|
||||||
}: {
|
}: {
|
||||||
elements: Elements;
|
elements: Elements;
|
||||||
bounds: Bounds | ExcalidrawElement;
|
bounds: RotatedBounds | ExcalidrawElement;
|
||||||
/** safety offset. Defaults to 0. */
|
/** safety offset. Defaults to 0. */
|
||||||
errorMargin?: number;
|
errorMargin?: number;
|
||||||
/**
|
/**
|
||||||
@@ -175,15 +181,16 @@ export const elementsOverlappingBBox = ({
|
|||||||
**/
|
**/
|
||||||
type: "overlap" | "contain" | "inside";
|
type: "overlap" | "contain" | "inside";
|
||||||
}) => {
|
}) => {
|
||||||
if (isExcalidrawElement(bounds)) {
|
if (isExcalidrawElement(_bounds)) {
|
||||||
bounds = getElementBounds(bounds, arrayToMap(elements));
|
_bounds = getElementBounds(_bounds, arrayToMap(elements));
|
||||||
}
|
}
|
||||||
const adjustedBBox: Bounds = [
|
const adjustedBBox = bounds(
|
||||||
bounds[0] - errorMargin,
|
_bounds[0] - errorMargin,
|
||||||
bounds[1] - errorMargin,
|
_bounds[1] - errorMargin,
|
||||||
bounds[2] + errorMargin,
|
_bounds[2] + errorMargin,
|
||||||
bounds[3] + errorMargin,
|
_bounds[3] + errorMargin,
|
||||||
];
|
_bounds[4],
|
||||||
|
);
|
||||||
|
|
||||||
const includedElementSet = new Set<string>();
|
const includedElementSet = new Set<string>();
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
},
|
},
|
||||||
"bindMode": "orbit",
|
"bindMode": "orbit",
|
||||||
"bindingPreference": "enabled",
|
"bindingPreference": "enabled",
|
||||||
"boxSelectionMode": "contain",
|
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
"croppingElementId": null,
|
"croppingElementId": null,
|
||||||
|
|||||||
@@ -90,13 +90,6 @@ module.exports.woff2BrowserPlugin = () => {
|
|||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="${OSS_FONTS_CDN}fonts/Assistant/Assistant-SemiBold.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="${OSS_FONTS_CDN}fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2"
|
href="${OSS_FONTS_CDN}fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2"
|
||||||
|
|||||||
Reference in New Issue
Block a user