Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9954e5f3c2 | |||
| 0cc70d07c7 | |||
| 359a2f23ae | |||
| 467a4a2a6a | |||
| a7b55da49e | |||
| 4f82c32861 | |||
| 8701a5f843 | |||
| c498247e0f | |||
| 81e1d61d42 | |||
| 741c8dec31 | |||
| da0481683a | |||
| 58307a96dc | |||
| 1a0755cd56 | |||
| c742b4847f | |||
| bcb50188b8 | |||
| bdb1ec387d | |||
| b0375fe5db | |||
| 940099f85d | |||
| e30fd9960d | |||
| 80ff1562b8 | |||
| 3f00762a77 | |||
| 7b012b1cad | |||
| a02c4cb140 | |||
| 3a01122093 | |||
| 47ee8a0094 | |||
| bccd2bf30d | |||
| a977dd1bf5 | |||
| 50e4a0b37d | |||
| 9e80c9e3dd | |||
| afdf38e47c | |||
| 06a7a51baa | |||
| 3fe1883f3f | |||
| a80cb5896a | |||
| 6dfa18414a | |||
| 064bede0c5 | |||
| 997fec6c75 | |||
| 71ed96eabb | |||
| f4bebaaa50 | |||
| 3d92a3bf1f |
@@ -130,15 +130,6 @@
|
|||||||
</script>
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<!-- For Nunito only preload the latin range, which should be good enough for now -->
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2"
|
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Register Assistant as the UI font, before the scene inits -->
|
<!-- Register Assistant as the UI font, before the scene inits -->
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
// don't auto-inline small assets (i.e. fonts hosted on CDN)
|
||||||
|
assetsInlineLimit: 0,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
woff2BrowserPlugin(),
|
woff2BrowserPlugin(),
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- Prefer user defined coordinates and dimensions when creating a frame using [`convertToExcalidrawElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements) [#8517](https://github.com/excalidraw/excalidraw/pull/8517)
|
||||||
|
|
||||||
- `props.initialData` can now be a function that returns `ExcalidrawInitialDataState` or `Promise<ExcalidrawInitialDataState>`. [#8107](https://github.com/excalidraw/excalidraw/pull/8135)
|
- `props.initialData` can now be a function that returns `ExcalidrawInitialDataState` or `Promise<ExcalidrawInitialDataState>`. [#8107](https://github.com/excalidraw/excalidraw/pull/8135)
|
||||||
|
|
||||||
- Added support for multiplayer undo/redo, by calculating invertible increments and storing them inside the local-only undo/redo stacks. [#7348](https://github.com/excalidraw/excalidraw/pull/7348)
|
- Added support for multiplayer undo/redo, by calculating invertible increments and storing them inside the local-only undo/redo stacks. [#7348](https://github.com/excalidraw/excalidraw/pull/7348)
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { register } from "./register";
|
||||||
|
import { cropIcon } from "../components/icons";
|
||||||
|
import { StoreAction } from "../store";
|
||||||
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { isImageElement } from "../element/typeChecks";
|
||||||
|
import type { ExcalidrawImageElement } from "../element/types";
|
||||||
|
|
||||||
|
export const actionToggleCropEditor = register({
|
||||||
|
name: "cropEditor",
|
||||||
|
label: "helpDialog.cropStart",
|
||||||
|
icon: cropIcon,
|
||||||
|
viewMode: true,
|
||||||
|
trackEvent: { category: "menu" },
|
||||||
|
keywords: ["image", "crop"],
|
||||||
|
perform(elements, appState, _, app) {
|
||||||
|
const selectedElement = app.scene.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
includeBoundTextElement: true,
|
||||||
|
})[0] as ExcalidrawImageElement;
|
||||||
|
|
||||||
|
return {
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
isCropping: false,
|
||||||
|
croppingElementId: selectedElement.id,
|
||||||
|
},
|
||||||
|
storeAction: StoreAction.CAPTURE,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
predicate: (elements, appState, _, app) => {
|
||||||
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
|
if (
|
||||||
|
!appState.croppingElementId &&
|
||||||
|
selectedElements.length === 1 &&
|
||||||
|
isImageElement(selectedElements[0])
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData, app }) => {
|
||||||
|
const label = t("helpDialog.cropStart");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={cropIcon}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
|
onClick={() => updateData(null)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -15,7 +15,7 @@ import { isBindingElement, isLinearElement } from "../element/typeChecks";
|
|||||||
import type { AppState } from "../types";
|
import type { AppState } from "../types";
|
||||||
import { resetCursor } from "../cursor";
|
import { resetCursor } from "../cursor";
|
||||||
import { StoreAction } from "../store";
|
import { StoreAction } from "../store";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
import { isPathALoop } from "../shapes";
|
import { isPathALoop } from "../shapes";
|
||||||
|
|
||||||
export const actionFinalize = register({
|
export const actionFinalize = register({
|
||||||
@@ -115,7 +115,7 @@ export const actionFinalize = register({
|
|||||||
mutateElement(multiPointElement, {
|
mutateElement(multiPointElement, {
|
||||||
points: linePoints.map((p, index) =>
|
points: linePoints.map((p, index) =>
|
||||||
index === linePoints.length - 1
|
index === linePoints.length - 1
|
||||||
? point(firstPoint[0], firstPoint[1])
|
? pointFrom(firstPoint[0], firstPoint[1])
|
||||||
: p,
|
: p,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { Excalidraw } from "../index";
|
import { Excalidraw } from "../index";
|
||||||
import { render } from "../tests/test-utils";
|
import { render } from "../tests/test-utils";
|
||||||
import { API } from "../tests/helpers/api";
|
import { API } from "../tests/helpers/api";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
import { actionFlipHorizontal, actionFlipVertical } from "./actionFlip";
|
import { actionFlipHorizontal, actionFlipVertical } from "./actionFlip";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
@@ -50,11 +50,11 @@ describe("flipping re-centers selection", () => {
|
|||||||
startArrowhead: null,
|
startArrowhead: null,
|
||||||
endArrowhead: "arrow",
|
endArrowhead: "arrow",
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(0, -35),
|
pointFrom(0, -35),
|
||||||
point(-90.9, -35),
|
pointFrom(-90.9, -35),
|
||||||
point(-90.9, 204.9),
|
pointFrom(-90.9, 204.9),
|
||||||
point(65.1, 204.9),
|
pointFrom(65.1, 204.9),
|
||||||
],
|
],
|
||||||
elbowed: true,
|
elbowed: true,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ import {
|
|||||||
import { mutateElbowArrow } from "../element/routing";
|
import { mutateElbowArrow } from "../element/routing";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import type { LocalPoint } from "../../math";
|
import type { LocalPoint } from "../../math";
|
||||||
import { point, vector } from "../../math";
|
import { pointFrom, vector } from "../../math";
|
||||||
|
|
||||||
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
||||||
|
|
||||||
@@ -1651,7 +1651,7 @@ export const actionChangeArrowType = register({
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
[finalStartPoint, finalEndPoint].map(
|
[finalStartPoint, finalEndPoint].map(
|
||||||
(p): LocalPoint =>
|
(p): LocalPoint =>
|
||||||
point(p[0] - newElement.x, p[1] - newElement.y),
|
pointFrom(p[0] - newElement.x, p[1] - newElement.y),
|
||||||
),
|
),
|
||||||
vector(0, 0),
|
vector(0, 0),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -88,3 +88,5 @@ export { actionToggleElementLock } from "./actionElementLock";
|
|||||||
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
||||||
|
|
||||||
export { actionToggleSearchMenu } from "./actionToggleSearchMenu";
|
export { actionToggleSearchMenu } from "./actionToggleSearchMenu";
|
||||||
|
|
||||||
|
export { actionToggleCropEditor } from "./actionCropEditor";
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ export type ActionName =
|
|||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "autoResize"
|
| "autoResize"
|
||||||
| "elementStats"
|
| "elementStats"
|
||||||
| "searchMenu";
|
| "searchMenu"
|
||||||
|
| "cropEditor";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ export const getDefaultAppState = (): Omit<
|
|||||||
objectsSnapModeEnabled: false,
|
objectsSnapModeEnabled: false,
|
||||||
userToFollow: null,
|
userToFollow: null,
|
||||||
followedBy: new Set(),
|
followedBy: new Set(),
|
||||||
|
isCropping: false,
|
||||||
|
croppingElementId: null,
|
||||||
searchMatches: [],
|
searchMatches: [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -237,6 +239,8 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
objectsSnapModeEnabled: { browser: true, export: false, server: false },
|
objectsSnapModeEnabled: { browser: true, export: false, server: false },
|
||||||
userToFollow: { browser: false, export: false, server: false },
|
userToFollow: { browser: false, export: false, server: false },
|
||||||
followedBy: { browser: false, export: false, server: false },
|
followedBy: { browser: false, export: false, server: false },
|
||||||
|
isCropping: { browser: false, export: false, server: false },
|
||||||
|
croppingElementId: { browser: false, export: false, server: false },
|
||||||
searchMatches: { browser: false, export: false, server: false },
|
searchMatches: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,16 @@ import {
|
|||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isBindableElement,
|
isBindableElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
|
isImageElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./element/typeChecks";
|
} from "./element/typeChecks";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawImageElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
|
Ordered,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
SceneElementsMap,
|
SceneElementsMap,
|
||||||
} from "./element/types";
|
} from "./element/types";
|
||||||
@@ -626,6 +629,18 @@ export class AppStateChange implements Change<AppState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case "croppingElementId": {
|
||||||
|
const croppingElementId = nextAppState[key];
|
||||||
|
const element =
|
||||||
|
croppingElementId && nextElements.get(croppingElementId);
|
||||||
|
|
||||||
|
if (element && !element.isDeleted) {
|
||||||
|
visibleDifferenceFlag.value = true;
|
||||||
|
} else {
|
||||||
|
nextAppState[key] = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "editingGroupId":
|
case "editingGroupId":
|
||||||
const editingGroupId = nextAppState[key];
|
const editingGroupId = nextAppState[key];
|
||||||
|
|
||||||
@@ -756,6 +771,7 @@ export class AppStateChange implements Change<AppState> {
|
|||||||
selectedElementIds,
|
selectedElementIds,
|
||||||
editingLinearElementId,
|
editingLinearElementId,
|
||||||
selectedLinearElementId,
|
selectedLinearElementId,
|
||||||
|
croppingElementId,
|
||||||
...standaloneProps
|
...standaloneProps
|
||||||
} = delta as ObservedAppState;
|
} = delta as ObservedAppState;
|
||||||
|
|
||||||
@@ -779,7 +795,10 @@ export class AppStateChange implements Change<AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ElementPartial = Omit<ElementUpdate<OrderedExcalidrawElement>, "seed">;
|
type ElementPartial<T extends ExcalidrawElement = ExcalidrawElement> = Omit<
|
||||||
|
ElementUpdate<Ordered<T>>,
|
||||||
|
"seed"
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elements change is a low level primitive to capture a change between two sets of elements.
|
* Elements change is a low level primitive to capture a change between two sets of elements.
|
||||||
@@ -1216,6 +1235,18 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isImageElement(element)) {
|
||||||
|
const _delta = delta as Delta<ElementPartial<ExcalidrawImageElement>>;
|
||||||
|
// we want to override `crop` only if modified so that we don't reset
|
||||||
|
// when undoing/redoing unrelated change
|
||||||
|
if (_delta.deleted.crop || _delta.inserted.crop) {
|
||||||
|
Object.assign(directlyApplicablePartial, {
|
||||||
|
// apply change verbatim
|
||||||
|
crop: _delta.inserted.crop ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!flags.containsVisibleDifference) {
|
if (!flags.containsVisibleDifference) {
|
||||||
// strip away fractional as even if it would be different, it doesn't have to result in visible change
|
// strip away fractional as even if it would be different, it doesn't have to result in visible change
|
||||||
const { index, ...rest } = directlyApplicablePartial;
|
const { index, ...rest } = directlyApplicablePartial;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Radians } from "../math";
|
import type { Radians } from "../math";
|
||||||
import { point } from "../math";
|
import { pointFrom } from "../math";
|
||||||
import {
|
import {
|
||||||
COLOR_PALETTE,
|
COLOR_PALETTE,
|
||||||
DEFAULT_CHART_COLOR_INDEX,
|
DEFAULT_CHART_COLOR_INDEX,
|
||||||
@@ -260,7 +260,7 @@ const chartLines = (
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: chartWidth,
|
width: chartWidth,
|
||||||
points: [point(0, 0), point(chartWidth, 0)],
|
points: [pointFrom(0, 0), pointFrom(chartWidth, 0)],
|
||||||
});
|
});
|
||||||
|
|
||||||
const yLine = newLinearElement({
|
const yLine = newLinearElement({
|
||||||
@@ -271,7 +271,7 @@ const chartLines = (
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
points: [point(0, 0), point(0, -chartHeight)],
|
points: [pointFrom(0, 0), pointFrom(0, -chartHeight)],
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxLine = newLinearElement({
|
const maxLine = newLinearElement({
|
||||||
@@ -284,7 +284,7 @@ const chartLines = (
|
|||||||
strokeStyle: "dotted",
|
strokeStyle: "dotted",
|
||||||
width: chartWidth,
|
width: chartWidth,
|
||||||
opacity: GRID_OPACITY,
|
opacity: GRID_OPACITY,
|
||||||
points: [point(0, 0), point(chartWidth, 0)],
|
points: [pointFrom(0, 0), pointFrom(chartWidth, 0)],
|
||||||
});
|
});
|
||||||
|
|
||||||
return [xLine, yLine, maxLine];
|
return [xLine, yLine, maxLine];
|
||||||
@@ -441,7 +441,7 @@ const chartTypeLine = (
|
|||||||
height: cy,
|
height: cy,
|
||||||
strokeStyle: "dotted",
|
strokeStyle: "dotted",
|
||||||
opacity: GRID_OPACITY,
|
opacity: GRID_OPACITY,
|
||||||
points: [point(0, 0), point(0, cy)],
|
points: [pointFrom(0, 0), pointFrom(0, cy)],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { trackEvent } from "../analytics";
|
|||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
@@ -127,6 +128,11 @@ export const SelectedShapeActions = ({
|
|||||||
isLinearElement(targetElements[0]) &&
|
isLinearElement(targetElements[0]) &&
|
||||||
!isElbowArrow(targetElements[0]);
|
!isElbowArrow(targetElements[0]);
|
||||||
|
|
||||||
|
const showCropEditorAction =
|
||||||
|
!appState.croppingElementId &&
|
||||||
|
targetElements.length === 1 &&
|
||||||
|
isImageElement(targetElements[0]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panelColumn">
|
<div className="panelColumn">
|
||||||
<div>
|
<div>
|
||||||
@@ -245,6 +251,7 @@ export const SelectedShapeActions = ({
|
|||||||
{renderAction("group")}
|
{renderAction("group")}
|
||||||
{renderAction("ungroup")}
|
{renderAction("ungroup")}
|
||||||
{showLinkIcon && renderAction("hyperlink")}
|
{showLinkIcon && renderAction("hyperlink")}
|
||||||
|
{showCropEditorAction && renderAction("cropEditor")}
|
||||||
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
actionToggleElementLock,
|
actionToggleElementLock,
|
||||||
actionToggleLinearEditor,
|
actionToggleLinearEditor,
|
||||||
actionToggleObjectsSnapMode,
|
actionToggleObjectsSnapMode,
|
||||||
|
actionToggleCropEditor,
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
@@ -445,7 +446,19 @@ import {
|
|||||||
} from "../element/flowchart";
|
} from "../element/flowchart";
|
||||||
import { searchItemInFocusAtom } from "./SearchMenu";
|
import { searchItemInFocusAtom } from "./SearchMenu";
|
||||||
import type { LocalPoint, Radians } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import { point, pointDistance, vector } from "../../math";
|
import {
|
||||||
|
clamp,
|
||||||
|
pointFrom,
|
||||||
|
pointDistance,
|
||||||
|
vector,
|
||||||
|
pointRotateRads,
|
||||||
|
vectorScale,
|
||||||
|
vectorFromPoint,
|
||||||
|
vectorSubtract,
|
||||||
|
vectorDot,
|
||||||
|
vectorNormalize,
|
||||||
|
} from "../../math";
|
||||||
|
import { cropElement } from "../element/cropElement";
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
@@ -589,6 +602,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
lastPointerUpEvent: React.PointerEvent<HTMLElement> | PointerEvent | null =
|
lastPointerUpEvent: React.PointerEvent<HTMLElement> | PointerEvent | null =
|
||||||
null;
|
null;
|
||||||
lastPointerMoveEvent: PointerEvent | null = null;
|
lastPointerMoveEvent: PointerEvent | null = null;
|
||||||
|
lastPointerMoveCoords: { x: number; y: number } | null = null;
|
||||||
lastViewportPosition = { x: 0, y: 0 };
|
lastViewportPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
animationFrameHandler = new AnimationFrameHandler();
|
animationFrameHandler = new AnimationFrameHandler();
|
||||||
@@ -3923,6 +3937,28 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isInputLike(event.target)) {
|
if (!isInputLike(event.target)) {
|
||||||
|
if (
|
||||||
|
(event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||||
|
this.state.croppingElementId
|
||||||
|
) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedElements.length === 1 &&
|
||||||
|
isImageElement(selectedElements[0]) &&
|
||||||
|
event.key === KEYS.ENTER
|
||||||
|
) {
|
||||||
|
this.startImageCropping(selectedElements[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.key === KEYS.ESCAPE &&
|
event.key === KEYS.ESCAPE &&
|
||||||
this.flowChartCreator.isCreatingChart
|
this.flowChartCreator.isCreatingChart
|
||||||
@@ -4907,10 +4943,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const selectionShape = getSelectionBoxShape(
|
const selectionShape = getSelectionBoxShape(
|
||||||
element,
|
element,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.getElementHitThreshold(),
|
isImageElement(element) ? 0 : this.getElementHitThreshold(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return isPointInShape(point(x, y), selectionShape);
|
return isPointInShape(pointFrom(x, y), selectionShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
// take bound text element into consideration for hit collision as well
|
// take bound text element into consideration for hit collision as well
|
||||||
@@ -5136,6 +5172,22 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private startImageCropping = (image: ExcalidrawImageElement) => {
|
||||||
|
this.store.shouldCaptureIncrement();
|
||||||
|
this.setState({
|
||||||
|
croppingElementId: image.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private finishImageCropping = () => {
|
||||||
|
if (this.state.croppingElementId) {
|
||||||
|
this.store.shouldCaptureIncrement();
|
||||||
|
this.setState({
|
||||||
|
croppingElementId: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private handleCanvasDoubleClick = (
|
private handleCanvasDoubleClick = (
|
||||||
event: React.MouseEvent<HTMLCanvasElement>,
|
event: React.MouseEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
@@ -5167,6 +5219,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
||||||
|
this.startImageCropping(selectedElements[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
resetCursor(this.interactiveCanvas);
|
resetCursor(this.interactiveCanvas);
|
||||||
|
|
||||||
let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||||
@@ -5269,7 +5326,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
element,
|
element,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.state,
|
this.state,
|
||||||
point(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointer.x, scenePointer.y),
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -5281,11 +5338,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isTouchScreen: boolean,
|
isTouchScreen: boolean,
|
||||||
) => {
|
) => {
|
||||||
const draggedDistance = pointDistance(
|
const draggedDistance = pointDistance(
|
||||||
point(
|
pointFrom(
|
||||||
this.lastPointerDownEvent!.clientX,
|
this.lastPointerDownEvent!.clientX,
|
||||||
this.lastPointerDownEvent!.clientY,
|
this.lastPointerDownEvent!.clientY,
|
||||||
),
|
),
|
||||||
point(this.lastPointerUpEvent!.clientX, this.lastPointerUpEvent!.clientY),
|
pointFrom(
|
||||||
|
this.lastPointerUpEvent!.clientX,
|
||||||
|
this.lastPointerUpEvent!.clientY,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
!this.hitLinkElement ||
|
!this.hitLinkElement ||
|
||||||
@@ -5304,7 +5364,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
point(lastPointerDownCoords.x, lastPointerDownCoords.y),
|
pointFrom(lastPointerDownCoords.x, lastPointerDownCoords.y),
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
);
|
);
|
||||||
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
const lastPointerUpCoords = viewportCoordsToSceneCoords(
|
||||||
@@ -5315,7 +5375,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
point(lastPointerUpCoords.x, lastPointerUpCoords.y),
|
pointFrom(lastPointerUpCoords.x, lastPointerUpCoords.y),
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
);
|
);
|
||||||
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
|
||||||
@@ -5565,7 +5625,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// threshold, add a point
|
// threshold, add a point
|
||||||
if (
|
if (
|
||||||
pointDistance(
|
pointDistance(
|
||||||
point(scenePointerX - rx, scenePointerY - ry),
|
pointFrom(scenePointerX - rx, scenePointerY - ry),
|
||||||
lastPoint,
|
lastPoint,
|
||||||
) >= LINE_CONFIRM_THRESHOLD
|
) >= LINE_CONFIRM_THRESHOLD
|
||||||
) {
|
) {
|
||||||
@@ -5574,7 +5634,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
{
|
{
|
||||||
points: [
|
points: [
|
||||||
...points,
|
...points,
|
||||||
point<LocalPoint>(scenePointerX - rx, scenePointerY - ry),
|
pointFrom<LocalPoint>(scenePointerX - rx, scenePointerY - ry),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -5588,7 +5648,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
points.length > 2 &&
|
points.length > 2 &&
|
||||||
lastCommittedPoint &&
|
lastCommittedPoint &&
|
||||||
pointDistance(
|
pointDistance(
|
||||||
point(scenePointerX - rx, scenePointerY - ry),
|
pointFrom(scenePointerX - rx, scenePointerY - ry),
|
||||||
lastCommittedPoint,
|
lastCommittedPoint,
|
||||||
) < LINE_CONFIRM_THRESHOLD
|
) < LINE_CONFIRM_THRESHOLD
|
||||||
) {
|
) {
|
||||||
@@ -5636,7 +5696,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
[
|
[
|
||||||
...points.slice(0, -1),
|
...points.slice(0, -1),
|
||||||
point<LocalPoint>(
|
pointFrom<LocalPoint>(
|
||||||
lastCommittedX + dxFromLastCommitted,
|
lastCommittedX + dxFromLastCommitted,
|
||||||
lastCommittedY + dyFromLastCommitted,
|
lastCommittedY + dyFromLastCommitted,
|
||||||
),
|
),
|
||||||
@@ -5655,7 +5715,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
{
|
{
|
||||||
points: [
|
points: [
|
||||||
...points.slice(0, -1),
|
...points.slice(0, -1),
|
||||||
point<LocalPoint>(
|
pointFrom<LocalPoint>(
|
||||||
lastCommittedX + dxFromLastCommitted,
|
lastCommittedX + dxFromLastCommitted,
|
||||||
lastCommittedY + dyFromLastCommitted,
|
lastCommittedY + dyFromLastCommitted,
|
||||||
),
|
),
|
||||||
@@ -5884,8 +5944,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const distance = pointDistance(
|
const distance = pointDistance(
|
||||||
point(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y),
|
pointFrom(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y),
|
||||||
point(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointer.x, scenePointer.y),
|
||||||
);
|
);
|
||||||
const threshold = this.getElementHitThreshold();
|
const threshold = this.getElementHitThreshold();
|
||||||
const p = { ...pointerDownState.lastCoords };
|
const p = { ...pointerDownState.lastCoords };
|
||||||
@@ -6397,7 +6457,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.state,
|
this.state,
|
||||||
point(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointer.x, scenePointer.y),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
||||||
@@ -6733,11 +6793,24 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.device,
|
this.device,
|
||||||
);
|
);
|
||||||
if (elementWithTransformHandleType != null) {
|
if (elementWithTransformHandleType != null) {
|
||||||
this.setState({
|
if (
|
||||||
resizingElement: elementWithTransformHandleType.element,
|
elementWithTransformHandleType.transformHandleType === "rotation"
|
||||||
});
|
) {
|
||||||
pointerDownState.resize.handleType =
|
this.setState({
|
||||||
elementWithTransformHandleType.transformHandleType;
|
resizingElement: elementWithTransformHandleType.element,
|
||||||
|
});
|
||||||
|
pointerDownState.resize.handleType =
|
||||||
|
elementWithTransformHandleType.transformHandleType;
|
||||||
|
} else if (this.state.croppingElementId) {
|
||||||
|
pointerDownState.resize.handleType =
|
||||||
|
elementWithTransformHandleType.transformHandleType;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
resizingElement: elementWithTransformHandleType.element,
|
||||||
|
});
|
||||||
|
pointerDownState.resize.handleType =
|
||||||
|
elementWithTransformHandleType.transformHandleType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (selectedElements.length > 1) {
|
} else if (selectedElements.length > 1) {
|
||||||
pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(
|
pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(
|
||||||
@@ -6804,6 +6877,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.state.croppingElementId &&
|
||||||
|
pointerDownState.hit.element?.id !== this.state.croppingElementId
|
||||||
|
) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
}
|
||||||
|
|
||||||
if (pointerDownState.hit.element) {
|
if (pointerDownState.hit.element) {
|
||||||
// Early return if pointer is hitting link icon
|
// Early return if pointer is hitting link icon
|
||||||
const hitLinkElement = this.getElementLinkAtPosition(
|
const hitLinkElement = this.getElementLinkAtPosition(
|
||||||
@@ -7088,7 +7168,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
simulatePressure,
|
simulatePressure,
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
points: [point<LocalPoint>(0, 0)],
|
points: [pointFrom<LocalPoint>(0, 0)],
|
||||||
pressures: simulatePressure ? [] : [event.pressure],
|
pressures: simulatePressure ? [] : [event.pressure],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -7297,7 +7377,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
multiElement.points.length > 1 &&
|
multiElement.points.length > 1 &&
|
||||||
lastCommittedPoint &&
|
lastCommittedPoint &&
|
||||||
pointDistance(
|
pointDistance(
|
||||||
point(pointerDownState.origin.x - rx, pointerDownState.origin.y - ry),
|
pointFrom(
|
||||||
|
pointerDownState.origin.x - rx,
|
||||||
|
pointerDownState.origin.y - ry,
|
||||||
|
),
|
||||||
lastCommittedPoint,
|
lastCommittedPoint,
|
||||||
) < LINE_CONFIRM_THRESHOLD
|
) < LINE_CONFIRM_THRESHOLD
|
||||||
) {
|
) {
|
||||||
@@ -7399,7 +7482,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
points: [...element.points, point<LocalPoint>(0, 0)],
|
points: [...element.points, pointFrom<LocalPoint>(0, 0)],
|
||||||
});
|
});
|
||||||
const boundElement = getHoveredElementForBinding(
|
const boundElement = getHoveredElementForBinding(
|
||||||
pointerDownState.origin,
|
pointerDownState.origin,
|
||||||
@@ -7602,6 +7685,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
) {
|
) {
|
||||||
return withBatchedUpdatesThrottled((event: PointerEvent) => {
|
return withBatchedUpdatesThrottled((event: PointerEvent) => {
|
||||||
|
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
||||||
|
const lastPointerCoords =
|
||||||
|
this.lastPointerMoveCoords ?? pointerDownState.origin;
|
||||||
|
this.lastPointerMoveCoords = pointerCoords;
|
||||||
|
|
||||||
// We need to initialize dragOffsetXY only after we've updated
|
// We need to initialize dragOffsetXY only after we've updated
|
||||||
// `state.selectedElementIds` on pointerDown. Doing it here in pointerMove
|
// `state.selectedElementIds` on pointerDown. Doing it here in pointerMove
|
||||||
// event handler should hopefully ensure we're already working with
|
// event handler should hopefully ensure we're already working with
|
||||||
@@ -7624,8 +7712,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
|
||||||
|
|
||||||
if (isEraserActive(this.state)) {
|
if (isEraserActive(this.state)) {
|
||||||
this.handleEraser(event, pointerDownState, pointerCoords);
|
this.handleEraser(event, pointerDownState, pointerCoords);
|
||||||
return;
|
return;
|
||||||
@@ -7652,8 +7738,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
pointDistance(
|
pointDistance(
|
||||||
point(pointerCoords.x, pointerCoords.y),
|
pointFrom(pointerCoords.x, pointerCoords.y),
|
||||||
point(pointerDownState.origin.x, pointerDownState.origin.y),
|
pointFrom(pointerDownState.origin.x, pointerDownState.origin.y),
|
||||||
) < DRAGGING_THRESHOLD
|
) < DRAGGING_THRESHOLD
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -7662,6 +7748,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (pointerDownState.resize.isResizing) {
|
if (pointerDownState.resize.isResizing) {
|
||||||
pointerDownState.lastCoords.x = pointerCoords.x;
|
pointerDownState.lastCoords.x = pointerCoords.x;
|
||||||
pointerDownState.lastCoords.y = pointerCoords.y;
|
pointerDownState.lastCoords.y = pointerCoords.y;
|
||||||
|
if (this.maybeHandleCrop(pointerDownState, event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (this.maybeHandleResize(pointerDownState, event)) {
|
if (this.maybeHandleResize(pointerDownState, event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -7835,6 +7924,96 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region move crop region
|
||||||
|
if (this.state.croppingElementId) {
|
||||||
|
const croppingElement = this.scene
|
||||||
|
.getNonDeletedElementsMap()
|
||||||
|
.get(this.state.croppingElementId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
croppingElement &&
|
||||||
|
isImageElement(croppingElement) &&
|
||||||
|
croppingElement.crop !== null &&
|
||||||
|
pointerDownState.hit.element === croppingElement
|
||||||
|
) {
|
||||||
|
const crop = croppingElement.crop;
|
||||||
|
const image =
|
||||||
|
isInitializedImageElement(croppingElement) &&
|
||||||
|
this.imageCache.get(croppingElement.fileId)?.image;
|
||||||
|
|
||||||
|
if (image && !(image instanceof Promise)) {
|
||||||
|
const instantDragOffset = vectorScale(
|
||||||
|
vector(
|
||||||
|
pointerCoords.x - lastPointerCoords.x,
|
||||||
|
pointerCoords.y - lastPointerCoords.y,
|
||||||
|
),
|
||||||
|
Math.max(this.state.zoom.value, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topLeft = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x1, y1),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const topRight = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x2, y1),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const bottomLeft = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x1, y2),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const topEdge = vectorNormalize(
|
||||||
|
vectorSubtract(topRight, topLeft),
|
||||||
|
);
|
||||||
|
const leftEdge = vectorNormalize(
|
||||||
|
vectorSubtract(bottomLeft, topLeft),
|
||||||
|
);
|
||||||
|
|
||||||
|
// project instantDrafOffset onto leftEdge and topEdge to decompose
|
||||||
|
const offsetVector = vector(
|
||||||
|
vectorDot(instantDragOffset, topEdge),
|
||||||
|
vectorDot(instantDragOffset, leftEdge),
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(
|
||||||
|
crop.x -
|
||||||
|
offsetVector[0] * Math.sign(croppingElement.scale[0]),
|
||||||
|
0,
|
||||||
|
image.naturalWidth - crop.width,
|
||||||
|
),
|
||||||
|
y: clamp(
|
||||||
|
crop.y -
|
||||||
|
offsetVector[1] * Math.sign(croppingElement.scale[1]),
|
||||||
|
0,
|
||||||
|
image.naturalHeight - crop.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
mutateElement(croppingElement, {
|
||||||
|
crop: nextCrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Snap cache *must* be synchronously popuplated before initial drag,
|
// Snap cache *must* be synchronously popuplated before initial drag,
|
||||||
// otherwise the first drag even will not snap, causing a jump before
|
// otherwise the first drag even will not snap, causing a jump before
|
||||||
// it snaps to its position if previously snapped already.
|
// it snaps to its position if previously snapped already.
|
||||||
@@ -7968,6 +8147,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.maybeCacheVisibleGaps(event, selectedElements, true);
|
this.maybeCacheVisibleGaps(event, selectedElements, true);
|
||||||
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
|
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8002,7 +8182,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
points: [...points, point<LocalPoint>(dx, dy)],
|
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||||
pressures,
|
pressures,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -8031,7 +8211,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
points: [...points, point<LocalPoint>(dx, dy)],
|
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -8039,7 +8219,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
mutateElbowArrow(
|
mutateElbowArrow(
|
||||||
newElement,
|
newElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
[...points.slice(0, -1), point<LocalPoint>(dx, dy)],
|
[...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
|
||||||
vector(0, 0),
|
vector(0, 0),
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
@@ -8051,7 +8231,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
points: [...points.slice(0, -1), point<LocalPoint>(dx, dy)],
|
points: [...points.slice(0, -1), pointFrom<LocalPoint>(dx, dy)],
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -8216,15 +8396,18 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const {
|
const {
|
||||||
newElement,
|
newElement,
|
||||||
resizingElement,
|
resizingElement,
|
||||||
|
croppingElementId,
|
||||||
multiElement,
|
multiElement,
|
||||||
activeTool,
|
activeTool,
|
||||||
isResizing,
|
isResizing,
|
||||||
isRotating,
|
isRotating,
|
||||||
|
isCropping,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
isRotating: false,
|
isRotating: false,
|
||||||
|
isCropping: false,
|
||||||
resizingElement: null,
|
resizingElement: null,
|
||||||
selectionElement: null,
|
selectionElement: null,
|
||||||
frameToHighlight: null,
|
frameToHighlight: null,
|
||||||
@@ -8234,6 +8417,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
originSnapOffset: null,
|
originSnapOffset: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.lastPointerMoveCoords = null;
|
||||||
|
|
||||||
SnapCache.setReferenceSnapPoints(null);
|
SnapCache.setReferenceSnapPoints(null);
|
||||||
SnapCache.setVisibleGaps(null);
|
SnapCache.setVisibleGaps(null);
|
||||||
|
|
||||||
@@ -8360,9 +8545,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
: [...newElement.pressures, childEvent.pressure];
|
: [...newElement.pressures, childEvent.pressure];
|
||||||
|
|
||||||
mutateElement(newElement, {
|
mutateElement(newElement, {
|
||||||
points: [...points, point<LocalPoint>(dx, dy)],
|
points: [...points, pointFrom<LocalPoint>(dx, dy)],
|
||||||
pressures,
|
pressures,
|
||||||
lastCommittedPoint: point<LocalPoint>(dx, dy),
|
lastCommittedPoint: pointFrom<LocalPoint>(dx, dy),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.actionManager.executeAction(actionFinalize);
|
this.actionManager.executeAction(actionFinalize);
|
||||||
@@ -8409,7 +8594,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
mutateElement(newElement, {
|
mutateElement(newElement, {
|
||||||
points: [
|
points: [
|
||||||
...newElement.points,
|
...newElement.points,
|
||||||
point<LocalPoint>(
|
pointFrom<LocalPoint>(
|
||||||
pointerCoords.x - newElement.x,
|
pointerCoords.x - newElement.x,
|
||||||
pointerCoords.y - newElement.y,
|
pointerCoords.y - newElement.y,
|
||||||
),
|
),
|
||||||
@@ -8716,6 +8901,20 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// click outside the cropping region to exit
|
||||||
|
if (
|
||||||
|
// not in the cropping mode at all
|
||||||
|
!croppingElementId ||
|
||||||
|
// in the cropping mode
|
||||||
|
(croppingElementId &&
|
||||||
|
// not cropping and no hit element
|
||||||
|
((!hitElement && !isCropping) ||
|
||||||
|
// hitting something else
|
||||||
|
(hitElement && hitElement.id !== croppingElementId)))
|
||||||
|
) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
}
|
||||||
|
|
||||||
const pointerStart = this.lastPointerDownEvent;
|
const pointerStart = this.lastPointerDownEvent;
|
||||||
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
||||||
|
|
||||||
@@ -8723,8 +8922,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.eraserTrail.endPath();
|
this.eraserTrail.endPath();
|
||||||
|
|
||||||
const draggedDistance = pointDistance(
|
const draggedDistance = pointDistance(
|
||||||
point(pointerStart.clientX, pointerStart.clientY),
|
pointFrom(pointerStart.clientX, pointerStart.clientY),
|
||||||
point(pointerEnd.clientX, pointerEnd.clientY),
|
pointFrom(pointerEnd.clientX, pointerEnd.clientY),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (draggedDistance === 0) {
|
if (draggedDistance === 0) {
|
||||||
@@ -8971,7 +9170,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.store.shouldCaptureIncrement();
|
this.store.shouldCaptureIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
if (
|
||||||
|
pointerDownState.drag.hasOccurred ||
|
||||||
|
isResizing ||
|
||||||
|
isRotating ||
|
||||||
|
isCropping
|
||||||
|
) {
|
||||||
// We only allow binding via linear elements, specifically via dragging
|
// We only allow binding via linear elements, specifically via dragging
|
||||||
// the endpoints ("start" or "end").
|
// the endpoints ("start" or "end").
|
||||||
const linearElements = this.scene
|
const linearElements = this.scene
|
||||||
@@ -9185,7 +9389,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/**
|
/**
|
||||||
* inserts image into elements array and rerenders
|
* inserts image into elements array and rerenders
|
||||||
*/
|
*/
|
||||||
private insertImageElement = async (
|
insertImageElement = async (
|
||||||
imageElement: ExcalidrawImageElement,
|
imageElement: ExcalidrawImageElement,
|
||||||
imageFile: File,
|
imageFile: File,
|
||||||
showCursorImagePreview?: boolean,
|
showCursorImagePreview?: boolean,
|
||||||
@@ -9338,7 +9542,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private initializeImageDimensions = (
|
initializeImageDimensions = (
|
||||||
imageElement: ExcalidrawImageElement,
|
imageElement: ExcalidrawImageElement,
|
||||||
forceNaturalSize = false,
|
forceNaturalSize = false,
|
||||||
) => {
|
) => {
|
||||||
@@ -9386,7 +9590,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const x = imageElement.x + imageElement.width / 2 - width / 2;
|
const x = imageElement.x + imageElement.width / 2 - width / 2;
|
||||||
const y = imageElement.y + imageElement.height / 2 - height / 2;
|
const y = imageElement.y + imageElement.height / 2 - height / 2;
|
||||||
|
|
||||||
mutateElement(imageElement, { x, y, width, height });
|
mutateElement(imageElement, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
crop: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -9925,6 +10135,83 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private maybeHandleCrop = (
|
||||||
|
pointerDownState: PointerDownState,
|
||||||
|
event: MouseEvent | KeyboardEvent,
|
||||||
|
): boolean => {
|
||||||
|
// to crop, we must already be in the cropping mode, where croppingElement has been set
|
||||||
|
if (!this.state.croppingElementId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformHandleType = pointerDownState.resize.handleType;
|
||||||
|
const pointerCoords = pointerDownState.lastCoords;
|
||||||
|
const [x, y] = getGridPoint(
|
||||||
|
pointerCoords.x - pointerDownState.resize.offset.x,
|
||||||
|
pointerCoords.y - pointerDownState.resize.offset.y,
|
||||||
|
this.getEffectiveGridSize(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const croppingElement = this.scene
|
||||||
|
.getNonDeletedElementsMap()
|
||||||
|
.get(this.state.croppingElementId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
transformHandleType &&
|
||||||
|
croppingElement &&
|
||||||
|
isImageElement(croppingElement)
|
||||||
|
) {
|
||||||
|
const croppingAtStateStart = pointerDownState.originalElements.get(
|
||||||
|
croppingElement.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const image =
|
||||||
|
isInitializedImageElement(croppingElement) &&
|
||||||
|
this.imageCache.get(croppingElement.fileId)?.image;
|
||||||
|
|
||||||
|
if (
|
||||||
|
croppingAtStateStart &&
|
||||||
|
isImageElement(croppingAtStateStart) &&
|
||||||
|
image &&
|
||||||
|
!(image instanceof Promise)
|
||||||
|
) {
|
||||||
|
mutateElement(
|
||||||
|
croppingElement,
|
||||||
|
cropElement(
|
||||||
|
croppingElement,
|
||||||
|
transformHandleType,
|
||||||
|
image.naturalWidth,
|
||||||
|
image.naturalHeight,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
event.shiftKey
|
||||||
|
? croppingAtStateStart.width / croppingAtStateStart.height
|
||||||
|
: undefined,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
updateBoundElements(
|
||||||
|
croppingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
{
|
||||||
|
oldSize: {
|
||||||
|
width: croppingElement.width,
|
||||||
|
height: croppingElement.height,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isCropping: transformHandleType && transformHandleType !== "rotation",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
private maybeHandleResize = (
|
private maybeHandleResize = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
event: MouseEvent | KeyboardEvent,
|
event: MouseEvent | KeyboardEvent,
|
||||||
@@ -9941,7 +10228,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// Frames cannot be rotated.
|
// Frames cannot be rotated.
|
||||||
(selectedFrames.length > 0 && transformHandleType === "rotation") ||
|
(selectedFrames.length > 0 && transformHandleType === "rotation") ||
|
||||||
// Elbow arrows cannot be transformed (resized or rotated).
|
// Elbow arrows cannot be transformed (resized or rotated).
|
||||||
(selectedElements.length === 1 && isElbowArrow(selectedElements[0]))
|
(selectedElements.length === 1 && isElbowArrow(selectedElements[0])) ||
|
||||||
|
// Do not resize when in crop mode
|
||||||
|
this.state.croppingElementId
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -10116,6 +10405,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
actionSelectAllElementsInFrame,
|
actionSelectAllElementsInFrame,
|
||||||
actionRemoveAllElementsFromFrame,
|
actionRemoveAllElementsFromFrame,
|
||||||
CONTEXT_MENU_SEPARATOR,
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionToggleCropEditor,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
...options,
|
...options,
|
||||||
CONTEXT_MENU_SEPARATOR,
|
CONTEXT_MENU_SEPARATOR,
|
||||||
actionCopyStyles,
|
actionCopyStyles,
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ function CommandPaletteInner({
|
|||||||
actionManager.actions.increaseFontSize,
|
actionManager.actions.increaseFontSize,
|
||||||
actionManager.actions.decreaseFontSize,
|
actionManager.actions.decreaseFontSize,
|
||||||
actionManager.actions.toggleLinearEditor,
|
actionManager.actions.toggleLinearEditor,
|
||||||
|
actionManager.actions.cropEditor,
|
||||||
actionLink,
|
actionLink,
|
||||||
].map((action: Action) =>
|
].map((action: Action) =>
|
||||||
actionToCommand(
|
actionToCommand(
|
||||||
|
|||||||
@@ -222,6 +222,16 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
]}
|
]}
|
||||||
isOr={false}
|
isOr={false}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.cropStart")}
|
||||||
|
shortcuts={[t("helpDialog.doubleClick"), getShortcutKey("Enter")]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.cropFinish")}
|
||||||
|
shortcuts={[getShortcutKey("Enter"), getShortcutKey("Escape")]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("helpDialog.preventBinding")}
|
label={t("helpDialog.preventBinding")}
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ const getHints = ({
|
|||||||
return t("hints.text_editing");
|
return t("hints.text_editing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appState.croppingElementId) {
|
||||||
|
return t("hints.leaveCropEditor");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
||||||
|
return t("hints.enterCropEditor");
|
||||||
|
}
|
||||||
|
|
||||||
if (activeTool.type === "selection") {
|
if (activeTool.type === "selection") {
|
||||||
if (
|
if (
|
||||||
appState.selectionElement &&
|
appState.selectionElement &&
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { getAtomicUnits, getStepSizedValue, isPropertyEditable } from "./utils";
|
|||||||
import { getElementsInAtomicUnit, resizeElement } from "./utils";
|
import { getElementsInAtomicUnit, resizeElement } from "./utils";
|
||||||
import type { AtomicUnit } from "./utils";
|
import type { AtomicUnit } from "./utils";
|
||||||
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
||||||
import { point, type GlobalPoint } from "../../../math";
|
import { pointFrom, type GlobalPoint } from "../../../math";
|
||||||
|
|
||||||
interface MultiDimensionProps {
|
interface MultiDimensionProps {
|
||||||
property: "width" | "height";
|
property: "width" | "height";
|
||||||
@@ -182,7 +182,7 @@ const handleDimensionChange: DragInputCallbackType<
|
|||||||
nextHeight,
|
nextHeight,
|
||||||
initialHeight,
|
initialHeight,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
property,
|
property,
|
||||||
latestElements,
|
latestElements,
|
||||||
originalElements,
|
originalElements,
|
||||||
@@ -287,7 +287,7 @@ const handleDimensionChange: DragInputCallbackType<
|
|||||||
nextHeight,
|
nextHeight,
|
||||||
initialHeight,
|
initialHeight,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
property,
|
property,
|
||||||
latestElements,
|
latestElements,
|
||||||
originalElements,
|
originalElements,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useMemo } from "react";
|
|||||||
import { getElementsInAtomicUnit, moveElement } from "./utils";
|
import { getElementsInAtomicUnit, moveElement } from "./utils";
|
||||||
import type { AtomicUnit } from "./utils";
|
import type { AtomicUnit } from "./utils";
|
||||||
import type { AppState } from "../../types";
|
import type { AppState } from "../../types";
|
||||||
import { point, pointRotateRads } from "../../../math";
|
import { pointFrom, pointRotateRads } from "../../../math";
|
||||||
|
|
||||||
interface MultiPositionProps {
|
interface MultiPositionProps {
|
||||||
property: "x" | "y";
|
property: "x" | "y";
|
||||||
@@ -44,8 +44,8 @@ const moveElements = (
|
|||||||
origElement.y + origElement.height / 2,
|
origElement.y + origElement.height / 2,
|
||||||
];
|
];
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(origElement.x, origElement.y),
|
pointFrom(origElement.x, origElement.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
origElement.angle,
|
origElement.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -97,8 +97,8 @@ const moveGroupTo = (
|
|||||||
];
|
];
|
||||||
|
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(latestElement.x, latestElement.y),
|
pointFrom(latestElement.x, latestElement.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
latestElement.angle,
|
latestElement.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,8 +171,8 @@ const handlePositionChange: DragInputCallbackType<
|
|||||||
origElement.y + origElement.height / 2,
|
origElement.y + origElement.height / 2,
|
||||||
];
|
];
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(origElement.x, origElement.y),
|
pointFrom(origElement.x, origElement.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
origElement.angle,
|
origElement.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -241,8 +241,8 @@ const MultiPosition = ({
|
|||||||
const [cx, cy] = [el.x + el.width / 2, el.y + el.height / 2];
|
const [cx, cy] = [el.x + el.width / 2, el.y + el.height / 2];
|
||||||
|
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(el.x, el.y),
|
pointFrom(el.x, el.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
el.angle,
|
el.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { DragInputCallbackType } from "./DragInput";
|
|||||||
import { getStepSizedValue, moveElement } from "./utils";
|
import { getStepSizedValue, moveElement } from "./utils";
|
||||||
import type Scene from "../../scene/Scene";
|
import type Scene from "../../scene/Scene";
|
||||||
import type { AppState } from "../../types";
|
import type { AppState } from "../../types";
|
||||||
import { point, pointRotateRads } from "../../../math";
|
import { pointFrom, pointRotateRads } from "../../../math";
|
||||||
|
|
||||||
interface PositionProps {
|
interface PositionProps {
|
||||||
property: "x" | "y";
|
property: "x" | "y";
|
||||||
@@ -33,8 +33,8 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
|
|||||||
origElement.y + origElement.height / 2,
|
origElement.y + origElement.height / 2,
|
||||||
];
|
];
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(origElement.x, origElement.y),
|
pointFrom(origElement.x, origElement.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
origElement.angle,
|
origElement.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -93,8 +93,8 @@ const Position = ({
|
|||||||
appState,
|
appState,
|
||||||
}: PositionProps) => {
|
}: PositionProps) => {
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(element.x, element.y),
|
pointFrom(element.x, element.y),
|
||||||
point(element.x + element.width / 2, element.y + element.height / 2),
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const value =
|
const value =
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { API } from "../../tests/helpers/api";
|
|||||||
import { actionGroup } from "../../actions";
|
import { actionGroup } from "../../actions";
|
||||||
import { isInGroup } from "../../groups";
|
import { isInGroup } from "../../groups";
|
||||||
import type { Degrees } from "../../../math";
|
import type { Degrees } from "../../../math";
|
||||||
import { degreesToRadians, point, pointRotateRads } from "../../../math";
|
import { degreesToRadians, pointFrom, pointRotateRads } from "../../../math";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
@@ -264,8 +264,8 @@ describe("stats for a generic element", () => {
|
|||||||
rectangle.y + rectangle.height / 2,
|
rectangle.y + rectangle.height / 2,
|
||||||
];
|
];
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(rectangle.x, rectangle.y),
|
pointFrom(rectangle.x, rectangle.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -283,8 +283,8 @@ describe("stats for a generic element", () => {
|
|||||||
testInputProperty(rectangle, "angle", "A", 0, 45);
|
testInputProperty(rectangle, "angle", "A", 0, 45);
|
||||||
|
|
||||||
let [newTopLeftX, newTopLeftY] = pointRotateRads(
|
let [newTopLeftX, newTopLeftY] = pointRotateRads(
|
||||||
point(rectangle.x, rectangle.y),
|
pointFrom(rectangle.x, rectangle.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -294,8 +294,8 @@ describe("stats for a generic element", () => {
|
|||||||
testInputProperty(rectangle, "angle", "A", 45, 66);
|
testInputProperty(rectangle, "angle", "A", 45, 66);
|
||||||
|
|
||||||
[newTopLeftX, newTopLeftY] = pointRotateRads(
|
[newTopLeftX, newTopLeftY] = pointRotateRads(
|
||||||
point(rectangle.x, rectangle.y),
|
pointFrom(rectangle.x, rectangle.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
expect(newTopLeftX.toString()).not.toEqual(xInput.value);
|
expect(newTopLeftX.toString()).not.toEqual(xInput.value);
|
||||||
@@ -311,8 +311,8 @@ describe("stats for a generic element", () => {
|
|||||||
rectangle.y + rectangle.height / 2,
|
rectangle.y + rectangle.height / 2,
|
||||||
];
|
];
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(rectangle.x, rectangle.y),
|
pointFrom(rectangle.x, rectangle.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
testInputProperty(rectangle, "width", "W", rectangle.width, 400);
|
testInputProperty(rectangle, "width", "W", rectangle.width, 400);
|
||||||
@@ -321,8 +321,8 @@ describe("stats for a generic element", () => {
|
|||||||
rectangle.y + rectangle.height / 2,
|
rectangle.y + rectangle.height / 2,
|
||||||
];
|
];
|
||||||
let [currentTopLeftX, currentTopLeftY] = pointRotateRads(
|
let [currentTopLeftX, currentTopLeftY] = pointRotateRads(
|
||||||
point(rectangle.x, rectangle.y),
|
pointFrom(rectangle.x, rectangle.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
expect(currentTopLeftX).toBeCloseTo(topLeftX, 4);
|
expect(currentTopLeftX).toBeCloseTo(topLeftX, 4);
|
||||||
@@ -334,8 +334,8 @@ describe("stats for a generic element", () => {
|
|||||||
rectangle.y + rectangle.height / 2,
|
rectangle.y + rectangle.height / 2,
|
||||||
];
|
];
|
||||||
[currentTopLeftX, currentTopLeftY] = pointRotateRads(
|
[currentTopLeftX, currentTopLeftY] = pointRotateRads(
|
||||||
point(rectangle.x, rectangle.y),
|
pointFrom(rectangle.x, rectangle.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Radians } from "../../../math";
|
import type { Radians } from "../../../math";
|
||||||
import { point, pointRotateRads } from "../../../math";
|
import { pointFrom, pointRotateRads } from "../../../math";
|
||||||
import {
|
import {
|
||||||
bindOrUnbindLinearElements,
|
bindOrUnbindLinearElements,
|
||||||
updateBoundElements,
|
updateBoundElements,
|
||||||
@@ -231,8 +231,8 @@ export const moveElement = (
|
|||||||
originalElement.y + originalElement.height / 2,
|
originalElement.y + originalElement.height / 2,
|
||||||
];
|
];
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(originalElement.x, originalElement.y),
|
pointFrom(originalElement.x, originalElement.y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
originalElement.angle,
|
originalElement.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -240,8 +240,8 @@ export const moveElement = (
|
|||||||
const changeInY = newTopLeftY - topLeftY;
|
const changeInY = newTopLeftY - topLeftY;
|
||||||
|
|
||||||
const [x, y] = pointRotateRads(
|
const [x, y] = pointRotateRads(
|
||||||
point(newTopLeftX, newTopLeftY),
|
pointFrom(newTopLeftX, newTopLeftY),
|
||||||
point(cx + changeInX, cy + changeInY),
|
pointFrom(cx + changeInX, cy + changeInY),
|
||||||
-originalElement.angle as Radians,
|
-originalElement.angle as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,8 @@ const getRelevantAppStateProps = (
|
|||||||
snapLines: appState.snapLines,
|
snapLines: appState.snapLines,
|
||||||
zenModeEnabled: appState.zenModeEnabled,
|
zenModeEnabled: appState.zenModeEnabled,
|
||||||
editingTextElement: appState.editingTextElement,
|
editingTextElement: appState.editingTextElement,
|
||||||
|
isCropping: appState.isCropping,
|
||||||
|
croppingElementId: appState.croppingElementId,
|
||||||
searchMatches: appState.searchMatches,
|
searchMatches: appState.searchMatches,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ const getRelevantAppStateProps = (
|
|||||||
frameToHighlight: appState.frameToHighlight,
|
frameToHighlight: appState.frameToHighlight,
|
||||||
editingGroupId: appState.editingGroupId,
|
editingGroupId: appState.editingGroupId,
|
||||||
currentHoveredFontFamily: appState.currentHoveredFontFamily,
|
currentHoveredFontFamily: appState.currentHoveredFontFamily,
|
||||||
|
croppingElementId: appState.croppingElementId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const areEqual = (
|
const areEqual = (
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { trackEvent } from "../../analytics";
|
|||||||
import { useAppProps, useExcalidrawAppState } from "../App";
|
import { useAppProps, useExcalidrawAppState } from "../App";
|
||||||
import { isEmbeddableElement } from "../../element/typeChecks";
|
import { isEmbeddableElement } from "../../element/typeChecks";
|
||||||
import { getLinkHandleFromCoords } from "./helpers";
|
import { getLinkHandleFromCoords } from "./helpers";
|
||||||
import { point, type GlobalPoint } from "../../../math";
|
import { pointFrom, type GlobalPoint } from "../../../math";
|
||||||
|
|
||||||
const CONTAINER_WIDTH = 320;
|
const CONTAINER_WIDTH = 320;
|
||||||
const SPACE_BOTTOM = 85;
|
const SPACE_BOTTOM = 85;
|
||||||
@@ -181,7 +181,7 @@ export const Hyperlink = ({
|
|||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
appState,
|
appState,
|
||||||
point(event.clientX, event.clientY),
|
pointFrom(event.clientX, event.clientY),
|
||||||
) as boolean;
|
) as boolean;
|
||||||
if (shouldHide) {
|
if (shouldHide) {
|
||||||
timeoutId = window.setTimeout(() => {
|
timeoutId = window.setTimeout(() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { GlobalPoint, Radians } from "../../../math";
|
import type { GlobalPoint, Radians } from "../../../math";
|
||||||
import { point, pointRotateRads } from "../../../math";
|
import { pointFrom, pointRotateRads } from "../../../math";
|
||||||
import { MIME_TYPES } from "../../constants";
|
import { MIME_TYPES } from "../../constants";
|
||||||
import type { Bounds } from "../../element/bounds";
|
import type { Bounds } from "../../element/bounds";
|
||||||
import { getElementAbsoluteCoords } from "../../element/bounds";
|
import { getElementAbsoluteCoords } from "../../element/bounds";
|
||||||
@@ -35,8 +35,8 @@ export const getLinkHandleFromCoords = (
|
|||||||
const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
|
const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
|
||||||
|
|
||||||
const [rotatedX, rotatedY] = pointRotateRads(
|
const [rotatedX, rotatedY] = pointRotateRads(
|
||||||
point(x + linkWidth / 2, y + linkHeight / 2),
|
pointFrom(x + linkWidth / 2, y + linkHeight / 2),
|
||||||
point(centerX, centerY),
|
pointFrom(centerX, centerY),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
return [
|
return [
|
||||||
@@ -85,5 +85,10 @@ export const isPointHittingLink = (
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return isPointHittingLinkIcon(element, elementsMap, appState, point(x, y));
|
return isPointHittingLinkIcon(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
appState,
|
||||||
|
pointFrom(x, y),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2147,3 +2147,12 @@ export const upIcon = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const cropIcon = createIcon(
|
||||||
|
<g strokeWidth="1.25">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M8 5v10a1 1 0 0 0 1 1h10" />
|
||||||
|
<path d="M5 8h10a1 1 0 0 1 1 1v10" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"backgroundColor": "#d8f5a2",
|
"backgroundColor": "#d8f5a2",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id45",
|
"id": "id47",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "id46",
|
"id": "id48",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -47,7 +47,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id46",
|
"id": "id48",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -118,7 +118,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id47",
|
"elementId": "id49",
|
||||||
"fixedPoint": null,
|
"fixedPoint": null,
|
||||||
"focus": -0.08139534883720931,
|
"focus": -0.08139534883720931,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
@@ -200,7 +200,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id45",
|
"id": "id47",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -238,7 +238,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id48",
|
"id": "id50",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -284,7 +284,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id48",
|
"id": "id50",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -329,7 +329,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id49",
|
"id": "id51",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -392,7 +392,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"autoResize": true,
|
"autoResize": true,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id48",
|
"containerId": "id50",
|
||||||
"customData": undefined,
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 5,
|
"fontFamily": 5,
|
||||||
@@ -433,7 +433,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id38",
|
"id": "id40",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -441,7 +441,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"elbowed": false,
|
"elbowed": false,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id40",
|
"elementId": "id42",
|
||||||
"fixedPoint": null,
|
"fixedPoint": null,
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
@@ -472,7 +472,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id39",
|
"elementId": "id41",
|
||||||
"fixedPoint": null,
|
"fixedPoint": null,
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
@@ -496,7 +496,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"autoResize": true,
|
"autoResize": true,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id37",
|
"containerId": "id39",
|
||||||
"customData": undefined,
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 5,
|
"fontFamily": 5,
|
||||||
@@ -537,7 +537,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id37",
|
"id": "id39",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -574,7 +574,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id37",
|
"id": "id39",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -611,7 +611,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id42",
|
"id": "id44",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -619,7 +619,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"elbowed": false,
|
"elbowed": false,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id44",
|
"elementId": "id46",
|
||||||
"fixedPoint": null,
|
"fixedPoint": null,
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
@@ -650,7 +650,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id43",
|
"elementId": "id45",
|
||||||
"fixedPoint": null,
|
"fixedPoint": null,
|
||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
@@ -674,7 +674,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"autoResize": true,
|
"autoResize": true,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id41",
|
"containerId": "id43",
|
||||||
"customData": undefined,
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 5,
|
"fontFamily": 5,
|
||||||
@@ -716,7 +716,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id41",
|
"id": "id43",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -762,7 +762,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id41",
|
"id": "id43",
|
||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1303,7 +1303,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id54",
|
"id": "id56",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1346,7 +1346,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id55",
|
"id": "id57",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1385,7 +1385,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id56",
|
"id": "id58",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1428,7 +1428,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id57",
|
"id": "id59",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1475,7 +1475,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id58",
|
"id": "id60",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1540,7 +1540,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [
|
"boundElements": [
|
||||||
{
|
{
|
||||||
"id": "id59",
|
"id": "id61",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import {
|
|||||||
getNormalizedZoom,
|
getNormalizedZoom,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
import type { LocalPoint, Radians } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import { isFiniteNumber, point } from "../../math";
|
import { isFiniteNumber, pointFrom } from "../../math";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
@@ -258,6 +258,7 @@ const restoreElement = (
|
|||||||
status: element.status || "pending",
|
status: element.status || "pending",
|
||||||
fileId: element.fileId,
|
fileId: element.fileId,
|
||||||
scale: element.scale || [1, 1],
|
scale: element.scale || [1, 1],
|
||||||
|
crop: element.crop ?? null,
|
||||||
});
|
});
|
||||||
case "line":
|
case "line":
|
||||||
// @ts-ignore LEGACY type
|
// @ts-ignore LEGACY type
|
||||||
@@ -268,7 +269,7 @@ const restoreElement = (
|
|||||||
let y = element.y;
|
let y = element.y;
|
||||||
let points = // migrate old arrow model to new one
|
let points = // migrate old arrow model to new one
|
||||||
!Array.isArray(element.points) || element.points.length < 2
|
!Array.isArray(element.points) || element.points.length < 2
|
||||||
? [point(0, 0), point(element.width, element.height)]
|
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
||||||
: element.points;
|
: element.points;
|
||||||
|
|
||||||
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
||||||
@@ -296,7 +297,7 @@ const restoreElement = (
|
|||||||
let y: number | undefined = element.y;
|
let y: number | undefined = element.y;
|
||||||
let points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one
|
let 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
|
||||||
? [point(0, 0), point(element.width, element.height)]
|
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
||||||
: element.points;
|
: element.points;
|
||||||
|
|
||||||
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { vi } from "vitest";
|
|||||||
import type { ExcalidrawElementSkeleton } from "./transform";
|
import type { ExcalidrawElementSkeleton } from "./transform";
|
||||||
import { convertToExcalidrawElements } from "./transform";
|
import { convertToExcalidrawElements } from "./transform";
|
||||||
import type { ExcalidrawArrowElement } from "../element/types";
|
import type { ExcalidrawArrowElement } from "../element/types";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
const opts = { regenerateIds: false };
|
const opts = { regenerateIds: false };
|
||||||
|
|
||||||
@@ -309,28 +309,32 @@ describe("Test Transform", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Test Frames", () => {
|
describe("Test Frames", () => {
|
||||||
|
const elements: ExcalidrawElementSkeleton[] = [
|
||||||
|
{
|
||||||
|
type: "rectangle",
|
||||||
|
x: 10,
|
||||||
|
y: 10,
|
||||||
|
strokeWidth: 2,
|
||||||
|
id: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "diamond",
|
||||||
|
x: 120,
|
||||||
|
y: 20,
|
||||||
|
backgroundColor: "#fff3bf",
|
||||||
|
strokeWidth: 2,
|
||||||
|
label: {
|
||||||
|
text: "HELLO EXCALIDRAW",
|
||||||
|
strokeColor: "#099268",
|
||||||
|
fontSize: 30,
|
||||||
|
},
|
||||||
|
id: "2",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
it("should transform frames and update frame ids when regenerated", () => {
|
it("should transform frames and update frame ids when regenerated", () => {
|
||||||
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
||||||
{
|
...elements,
|
||||||
type: "rectangle",
|
|
||||||
x: 10,
|
|
||||||
y: 10,
|
|
||||||
strokeWidth: 2,
|
|
||||||
id: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "diamond",
|
|
||||||
x: 120,
|
|
||||||
y: 20,
|
|
||||||
backgroundColor: "#fff3bf",
|
|
||||||
strokeWidth: 2,
|
|
||||||
label: {
|
|
||||||
text: "HELLO EXCALIDRAW",
|
|
||||||
strokeColor: "#099268",
|
|
||||||
fontSize: 30,
|
|
||||||
},
|
|
||||||
id: "2",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "frame",
|
type: "frame",
|
||||||
children: ["1", "2"],
|
children: ["1", "2"],
|
||||||
@@ -352,28 +356,9 @@ describe("Test Transform", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should consider max of calculated and frame dimensions when provided", () => {
|
it("should consider user defined frame dimensions over calculated when provided", () => {
|
||||||
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
||||||
{
|
...elements,
|
||||||
type: "rectangle",
|
|
||||||
x: 10,
|
|
||||||
y: 10,
|
|
||||||
strokeWidth: 2,
|
|
||||||
id: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "diamond",
|
|
||||||
x: 120,
|
|
||||||
y: 20,
|
|
||||||
backgroundColor: "#fff3bf",
|
|
||||||
strokeWidth: 2,
|
|
||||||
label: {
|
|
||||||
text: "HELLO EXCALIDRAW",
|
|
||||||
strokeColor: "#099268",
|
|
||||||
fontSize: 30,
|
|
||||||
},
|
|
||||||
id: "2",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "frame",
|
type: "frame",
|
||||||
children: ["1", "2"],
|
children: ["1", "2"],
|
||||||
@@ -388,7 +373,27 @@ describe("Test Transform", () => {
|
|||||||
);
|
);
|
||||||
const frame = excalidrawElements.find((ele) => ele.type === "frame")!;
|
const frame = excalidrawElements.find((ele) => ele.type === "frame")!;
|
||||||
expect(frame.width).toBe(800);
|
expect(frame.width).toBe(800);
|
||||||
expect(frame.height).toBe(126);
|
expect(frame.height).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should consider user defined frame coordinates calculated when provided", () => {
|
||||||
|
const elementsSkeleton: ExcalidrawElementSkeleton[] = [
|
||||||
|
...elements,
|
||||||
|
{
|
||||||
|
type: "frame",
|
||||||
|
children: ["1", "2"],
|
||||||
|
name: "My frame",
|
||||||
|
x: 100,
|
||||||
|
y: 300,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const excalidrawElements = convertToExcalidrawElements(
|
||||||
|
elementsSkeleton,
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
const frame = excalidrawElements.find((ele) => ele.type === "frame")!;
|
||||||
|
expect(frame.x).toBe(100);
|
||||||
|
expect(frame.y).toBe(300);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -912,7 +917,7 @@ describe("Test Transform", () => {
|
|||||||
x: 111.262,
|
x: 111.262,
|
||||||
y: 57,
|
y: 57,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
points: [point(0, 0), point(272.985, 0)],
|
points: [pointFrom(0, 0), pointFrom(272.985, 0)],
|
||||||
label: {
|
label: {
|
||||||
text: "How are you?",
|
text: "How are you?",
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
@@ -935,7 +940,7 @@ describe("Test Transform", () => {
|
|||||||
x: 77.017,
|
x: 77.017,
|
||||||
y: 79,
|
y: 79,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
points: [point(0, 0)],
|
points: [pointFrom(0, 0)],
|
||||||
label: {
|
label: {
|
||||||
text: "Friendship",
|
text: "Friendship",
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
assertNever,
|
assertNever,
|
||||||
cloneJSON,
|
cloneJSON,
|
||||||
getFontString,
|
getFontString,
|
||||||
|
isDevEnv,
|
||||||
toBrandedType,
|
toBrandedType,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import { getSizeFromPoints } from "../points";
|
import { getSizeFromPoints } from "../points";
|
||||||
@@ -53,7 +54,7 @@ import { randomId } from "../random";
|
|||||||
import { syncInvalidIndices } from "../fractionalIndex";
|
import { syncInvalidIndices } from "../fractionalIndex";
|
||||||
import { getLineHeight } from "../fonts";
|
import { getLineHeight } from "../fonts";
|
||||||
import { isArrowElement } from "../element/typeChecks";
|
import { isArrowElement } from "../element/typeChecks";
|
||||||
import { point, type LocalPoint } from "../../math";
|
import { pointFrom, type LocalPoint } from "../../math";
|
||||||
|
|
||||||
export type ValidLinearElement = {
|
export type ValidLinearElement = {
|
||||||
type: "arrow" | "line";
|
type: "arrow" | "line";
|
||||||
@@ -536,7 +537,7 @@ export const convertToExcalidrawElements = (
|
|||||||
excalidrawElement = newLinearElement({
|
excalidrawElement = newLinearElement({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
points: [point(0, 0), point(width, height)],
|
points: [pointFrom(0, 0), pointFrom(width, height)],
|
||||||
...element,
|
...element,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -549,7 +550,7 @@ export const convertToExcalidrawElements = (
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
endArrowhead: "arrow",
|
endArrowhead: "arrow",
|
||||||
points: [point(0, 0), point(width, height)],
|
points: [pointFrom(0, 0), pointFrom(width, height)],
|
||||||
...element,
|
...element,
|
||||||
type: "arrow",
|
type: "arrow",
|
||||||
});
|
});
|
||||||
@@ -717,7 +718,7 @@ export const convertToExcalidrawElements = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Once all the excalidraw elements are created, we can add frames since we
|
// Once all the excalidraw elements are created, we can add frames since we
|
||||||
// need to calculate coordinates and dimensions of frame which is possibe after all
|
// need to calculate coordinates and dimensions of frame which is possible after all
|
||||||
// frame children are processed.
|
// frame children are processed.
|
||||||
for (const [id, element] of elementsWithIds) {
|
for (const [id, element] of elementsWithIds) {
|
||||||
if (element.type !== "frame" && element.type !== "magicframe") {
|
if (element.type !== "frame" && element.type !== "magicframe") {
|
||||||
@@ -764,10 +765,26 @@ export const convertToExcalidrawElements = (
|
|||||||
maxX = maxX + PADDING;
|
maxX = maxX + PADDING;
|
||||||
maxY = maxY + PADDING;
|
maxY = maxY + PADDING;
|
||||||
|
|
||||||
// Take the max of calculated and provided frame dimensions, whichever is higher
|
const frameX = frame?.x || minX;
|
||||||
const width = Math.max(frame?.width, maxX - minX);
|
const frameY = frame?.y || minY;
|
||||||
const height = Math.max(frame?.height, maxY - minY);
|
const frameWidth = frame?.width || maxX - minX;
|
||||||
Object.assign(frame, { x: minX, y: minY, width, height });
|
const frameHeight = frame?.height || maxY - minY;
|
||||||
|
|
||||||
|
Object.assign(frame, {
|
||||||
|
x: frameX,
|
||||||
|
y: frameY,
|
||||||
|
width: frameWidth,
|
||||||
|
height: frameHeight,
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
isDevEnv() &&
|
||||||
|
element.children.length &&
|
||||||
|
(frame?.x || frame?.y || frame?.width || frame?.height)
|
||||||
|
) {
|
||||||
|
console.info(
|
||||||
|
"User provided frame attributes are being considered, if you find this inaccurate, please remove any of the attributes - x, y, width and height so frame coordinates and dimensions are calculated automatically",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return elementStore.getElements();
|
return elementStore.getElements();
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ import {
|
|||||||
import type { LocalPoint, Radians } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
lineSegment,
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
@@ -720,7 +720,7 @@ export const getHeadingForElbowArrowSnap = (
|
|||||||
return vectorToHeading(
|
return vectorToHeading(
|
||||||
vectorFromPoint(
|
vectorFromPoint(
|
||||||
p,
|
p,
|
||||||
point<GlobalPoint>(
|
pointFrom<GlobalPoint>(
|
||||||
bindableElement.x + bindableElement.width / 2,
|
bindableElement.x + bindableElement.width / 2,
|
||||||
bindableElement.y + bindableElement.height / 2,
|
bindableElement.y + bindableElement.height / 2,
|
||||||
),
|
),
|
||||||
@@ -766,15 +766,15 @@ export const bindPointToSnapToElementOutline = (
|
|||||||
const intersections = [
|
const intersections = [
|
||||||
...(intersectElementWithLine(
|
...(intersectElementWithLine(
|
||||||
bindableElement,
|
bindableElement,
|
||||||
point(p[0], p[1] - 2 * bindableElement.height),
|
pointFrom(p[0], p[1] - 2 * bindableElement.height),
|
||||||
point(p[0], p[1] + 2 * bindableElement.height),
|
pointFrom(p[0], p[1] + 2 * bindableElement.height),
|
||||||
FIXED_BINDING_DISTANCE,
|
FIXED_BINDING_DISTANCE,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
) ?? []),
|
) ?? []),
|
||||||
...(intersectElementWithLine(
|
...(intersectElementWithLine(
|
||||||
bindableElement,
|
bindableElement,
|
||||||
point(p[0] - 2 * bindableElement.width, p[1]),
|
pointFrom(p[0] - 2 * bindableElement.width, p[1]),
|
||||||
point(p[0] + 2 * bindableElement.width, p[1]),
|
pointFrom(p[0] + 2 * bindableElement.width, p[1]),
|
||||||
FIXED_BINDING_DISTANCE,
|
FIXED_BINDING_DISTANCE,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
) ?? []),
|
) ?? []),
|
||||||
@@ -815,25 +815,25 @@ const headingToMidBindPoint = (
|
|||||||
switch (true) {
|
switch (true) {
|
||||||
case compareHeading(heading, HEADING_UP):
|
case compareHeading(heading, HEADING_UP):
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point((aabb[0] + aabb[2]) / 2 + 0.1, aabb[1]),
|
pointFrom((aabb[0] + aabb[2]) / 2 + 0.1, aabb[1]),
|
||||||
center,
|
center,
|
||||||
bindableElement.angle,
|
bindableElement.angle,
|
||||||
);
|
);
|
||||||
case compareHeading(heading, HEADING_RIGHT):
|
case compareHeading(heading, HEADING_RIGHT):
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(aabb[2], (aabb[1] + aabb[3]) / 2 + 0.1),
|
pointFrom(aabb[2], (aabb[1] + aabb[3]) / 2 + 0.1),
|
||||||
center,
|
center,
|
||||||
bindableElement.angle,
|
bindableElement.angle,
|
||||||
);
|
);
|
||||||
case compareHeading(heading, HEADING_DOWN):
|
case compareHeading(heading, HEADING_DOWN):
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point((aabb[0] + aabb[2]) / 2 - 0.1, aabb[3]),
|
pointFrom((aabb[0] + aabb[2]) / 2 - 0.1, aabb[3]),
|
||||||
center,
|
center,
|
||||||
bindableElement.angle,
|
bindableElement.angle,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(aabb[0], (aabb[1] + aabb[3]) / 2 - 0.1),
|
pointFrom(aabb[0], (aabb[1] + aabb[3]) / 2 - 0.1),
|
||||||
center,
|
center,
|
||||||
bindableElement.angle,
|
bindableElement.angle,
|
||||||
);
|
);
|
||||||
@@ -844,7 +844,7 @@ export const avoidRectangularCorner = (
|
|||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
p: GlobalPoint,
|
p: GlobalPoint,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
const center = point<GlobalPoint>(
|
const center = pointFrom<GlobalPoint>(
|
||||||
element.x + element.width / 2,
|
element.x + element.width / 2,
|
||||||
element.y + element.height / 2,
|
element.y + element.height / 2,
|
||||||
);
|
);
|
||||||
@@ -854,13 +854,13 @@ export const avoidRectangularCorner = (
|
|||||||
// Top left
|
// Top left
|
||||||
if (nonRotatedPoint[1] - element.y > -FIXED_BINDING_DISTANCE) {
|
if (nonRotatedPoint[1] - element.y > -FIXED_BINDING_DISTANCE) {
|
||||||
return pointRotateRads<GlobalPoint>(
|
return pointRotateRads<GlobalPoint>(
|
||||||
point(element.x - FIXED_BINDING_DISTANCE, element.y),
|
pointFrom(element.x - FIXED_BINDING_DISTANCE, element.y),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(element.x, element.y - FIXED_BINDING_DISTANCE),
|
pointFrom(element.x, element.y - FIXED_BINDING_DISTANCE),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
@@ -871,13 +871,16 @@ export const avoidRectangularCorner = (
|
|||||||
// Bottom left
|
// Bottom left
|
||||||
if (nonRotatedPoint[0] - element.x > -FIXED_BINDING_DISTANCE) {
|
if (nonRotatedPoint[0] - element.x > -FIXED_BINDING_DISTANCE) {
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(element.x, element.y + element.height + FIXED_BINDING_DISTANCE),
|
pointFrom(
|
||||||
|
element.x,
|
||||||
|
element.y + element.height + FIXED_BINDING_DISTANCE,
|
||||||
|
),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(element.x - FIXED_BINDING_DISTANCE, element.y + element.height),
|
pointFrom(element.x - FIXED_BINDING_DISTANCE, element.y + element.height),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
@@ -891,7 +894,7 @@ export const avoidRectangularCorner = (
|
|||||||
element.width + FIXED_BINDING_DISTANCE
|
element.width + FIXED_BINDING_DISTANCE
|
||||||
) {
|
) {
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(
|
pointFrom(
|
||||||
element.x + element.width,
|
element.x + element.width,
|
||||||
element.y + element.height + FIXED_BINDING_DISTANCE,
|
element.y + element.height + FIXED_BINDING_DISTANCE,
|
||||||
),
|
),
|
||||||
@@ -900,7 +903,7 @@ export const avoidRectangularCorner = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(
|
pointFrom(
|
||||||
element.x + element.width + FIXED_BINDING_DISTANCE,
|
element.x + element.width + FIXED_BINDING_DISTANCE,
|
||||||
element.y + element.height,
|
element.y + element.height,
|
||||||
),
|
),
|
||||||
@@ -917,13 +920,16 @@ export const avoidRectangularCorner = (
|
|||||||
element.width + FIXED_BINDING_DISTANCE
|
element.width + FIXED_BINDING_DISTANCE
|
||||||
) {
|
) {
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(element.x + element.width, element.y - FIXED_BINDING_DISTANCE),
|
pointFrom(
|
||||||
|
element.x + element.width,
|
||||||
|
element.y - FIXED_BINDING_DISTANCE,
|
||||||
|
),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(element.x + element.width + FIXED_BINDING_DISTANCE, element.y),
|
pointFrom(element.x + element.width + FIXED_BINDING_DISTANCE, element.y),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
@@ -938,7 +944,10 @@ export const snapToMid = (
|
|||||||
tolerance: number = 0.05,
|
tolerance: number = 0.05,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
const { x, y, width, height, angle } = element;
|
const { x, y, width, height, angle } = element;
|
||||||
const center = point<GlobalPoint>(x + width / 2 - 0.1, y + height / 2 - 0.1);
|
const center = pointFrom<GlobalPoint>(
|
||||||
|
x + width / 2 - 0.1,
|
||||||
|
y + height / 2 - 0.1,
|
||||||
|
);
|
||||||
const nonRotated = pointRotateRads(p, center, -angle as Radians);
|
const nonRotated = pointRotateRads(p, center, -angle as Radians);
|
||||||
|
|
||||||
// snap-to-center point is adaptive to element size, but we don't want to go
|
// snap-to-center point is adaptive to element size, but we don't want to go
|
||||||
@@ -953,7 +962,7 @@ export const snapToMid = (
|
|||||||
) {
|
) {
|
||||||
// LEFT
|
// LEFT
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x - FIXED_BINDING_DISTANCE, center[1]),
|
pointFrom(x - FIXED_BINDING_DISTANCE, center[1]),
|
||||||
center,
|
center,
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
@@ -964,7 +973,7 @@ export const snapToMid = (
|
|||||||
) {
|
) {
|
||||||
// TOP
|
// TOP
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(center[0], y - FIXED_BINDING_DISTANCE),
|
pointFrom(center[0], y - FIXED_BINDING_DISTANCE),
|
||||||
center,
|
center,
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
@@ -975,7 +984,7 @@ export const snapToMid = (
|
|||||||
) {
|
) {
|
||||||
// RIGHT
|
// RIGHT
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x + width + FIXED_BINDING_DISTANCE, center[1]),
|
pointFrom(x + width + FIXED_BINDING_DISTANCE, center[1]),
|
||||||
center,
|
center,
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
@@ -986,7 +995,7 @@ export const snapToMid = (
|
|||||||
) {
|
) {
|
||||||
// DOWN
|
// DOWN
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(center[0], y + height + FIXED_BINDING_DISTANCE),
|
pointFrom(center[0], y + height + FIXED_BINDING_DISTANCE),
|
||||||
center,
|
center,
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
@@ -1023,11 +1032,11 @@ const updateBoundPoint = (
|
|||||||
startOrEnd === "startBinding" ? "start" : "end",
|
startOrEnd === "startBinding" ? "start" : "end",
|
||||||
elementsMap,
|
elementsMap,
|
||||||
).fixedPoint;
|
).fixedPoint;
|
||||||
const globalMidPoint = point<GlobalPoint>(
|
const globalMidPoint = pointFrom<GlobalPoint>(
|
||||||
bindableElement.x + bindableElement.width / 2,
|
bindableElement.x + bindableElement.width / 2,
|
||||||
bindableElement.y + bindableElement.height / 2,
|
bindableElement.y + bindableElement.height / 2,
|
||||||
);
|
);
|
||||||
const global = point<GlobalPoint>(
|
const global = pointFrom<GlobalPoint>(
|
||||||
bindableElement.x + fixedPoint[0] * bindableElement.width,
|
bindableElement.x + fixedPoint[0] * bindableElement.width,
|
||||||
bindableElement.y + fixedPoint[1] * bindableElement.height,
|
bindableElement.y + fixedPoint[1] * bindableElement.height,
|
||||||
);
|
);
|
||||||
@@ -1118,7 +1127,7 @@ export const calculateFixedPointForElbowArrowBinding = (
|
|||||||
hoveredElement,
|
hoveredElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
const globalMidPoint = point(
|
const globalMidPoint = 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,
|
||||||
);
|
);
|
||||||
@@ -1337,9 +1346,9 @@ export const bindingBorderTest = (
|
|||||||
const threshold = maxBindingGap(element, element.width, element.height);
|
const threshold = maxBindingGap(element, element.width, element.height);
|
||||||
const shape = getElementShape(element, elementsMap);
|
const shape = getElementShape(element, elementsMap);
|
||||||
return (
|
return (
|
||||||
isPointOnShape(point(x, y), shape, threshold) ||
|
isPointOnShape(pointFrom(x, y), shape, threshold) ||
|
||||||
(fullShape === true &&
|
(fullShape === true &&
|
||||||
pointInsideBounds(point(x, y), aabbForElement(element)))
|
pointInsideBounds(pointFrom(x, y), aabbForElement(element)))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2197,11 +2206,11 @@ export const getGlobalFixedPointForBindableElement = (
|
|||||||
const [fixedX, fixedY] = normalizeFixedPoint(fixedPointRatio);
|
const [fixedX, fixedY] = normalizeFixedPoint(fixedPointRatio);
|
||||||
|
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(
|
pointFrom(
|
||||||
element.x + element.width * fixedX,
|
element.x + element.width * fixedX,
|
||||||
element.y + element.height * fixedY,
|
element.y + element.height * fixedY,
|
||||||
),
|
),
|
||||||
point<GlobalPoint>(
|
pointFrom<GlobalPoint>(
|
||||||
element.x + element.width / 2,
|
element.x + element.width / 2,
|
||||||
element.y + element.height / 2,
|
element.y + element.height / 2,
|
||||||
),
|
),
|
||||||
@@ -2229,7 +2238,7 @@ const getGlobalFixedPoints = (
|
|||||||
arrow.startBinding.fixedPoint,
|
arrow.startBinding.fixedPoint,
|
||||||
startElement as ExcalidrawBindableElement,
|
startElement as ExcalidrawBindableElement,
|
||||||
)
|
)
|
||||||
: point<GlobalPoint>(
|
: pointFrom<GlobalPoint>(
|
||||||
arrow.x + arrow.points[0][0],
|
arrow.x + arrow.points[0][0],
|
||||||
arrow.y + arrow.points[0][1],
|
arrow.y + arrow.points[0][1],
|
||||||
);
|
);
|
||||||
@@ -2239,7 +2248,7 @@ const getGlobalFixedPoints = (
|
|||||||
arrow.endBinding.fixedPoint,
|
arrow.endBinding.fixedPoint,
|
||||||
endElement as ExcalidrawBindableElement,
|
endElement as ExcalidrawBindableElement,
|
||||||
)
|
)
|
||||||
: point<GlobalPoint>(
|
: pointFrom<GlobalPoint>(
|
||||||
arrow.x + arrow.points[arrow.points.length - 1][0],
|
arrow.x + arrow.points[arrow.points.length - 1][0],
|
||||||
arrow.y + arrow.points[arrow.points.length - 1][1],
|
arrow.y + arrow.points[arrow.points.length - 1][1],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { LocalPoint } from "../../math";
|
import type { LocalPoint } from "../../math";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
import { ROUNDNESS } from "../constants";
|
import { ROUNDNESS } from "../constants";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
||||||
@@ -125,9 +125,9 @@ describe("getElementBounds", () => {
|
|||||||
a: 0.6447741904932416,
|
a: 0.6447741904932416,
|
||||||
}),
|
}),
|
||||||
points: [
|
points: [
|
||||||
point<LocalPoint>(0, 0),
|
pointFrom<LocalPoint>(0, 0),
|
||||||
point<LocalPoint>(67.33984375, 92.48828125),
|
pointFrom<LocalPoint>(67.33984375, 92.48828125),
|
||||||
point<LocalPoint>(-102.7890625, 52.15625),
|
pointFrom<LocalPoint>(-102.7890625, 52.15625),
|
||||||
],
|
],
|
||||||
} as ExcalidrawLinearElement;
|
} as ExcalidrawLinearElement;
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import type {
|
|||||||
import {
|
import {
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
point,
|
pointFrom,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
@@ -113,8 +113,8 @@ export class ElementBounds {
|
|||||||
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
||||||
element.points.map(([x, y]) =>
|
element.points.map(([x, y]) =>
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(x, y),
|
pointFrom(x, y),
|
||||||
point(cx - element.x, cy - element.y),
|
pointFrom(cx - element.x, cy - element.y),
|
||||||
element.angle,
|
element.angle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -130,23 +130,23 @@ export class ElementBounds {
|
|||||||
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(
|
||||||
point(cx, y1),
|
pointFrom(cx, y1),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [x12, y12] = pointRotateRads(
|
const [x12, y12] = pointRotateRads(
|
||||||
point(cx, y2),
|
pointFrom(cx, y2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [x22, y22] = pointRotateRads(
|
const [x22, y22] = pointRotateRads(
|
||||||
point(x1, cy),
|
pointFrom(x1, cy),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [x21, y21] = pointRotateRads(
|
const [x21, y21] = pointRotateRads(
|
||||||
point(x2, cy),
|
pointFrom(x2, cy),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const minX = Math.min(x11, x12, x22, x21);
|
const minX = Math.min(x11, x12, x22, x21);
|
||||||
@@ -164,23 +164,23 @@ export class ElementBounds {
|
|||||||
bounds = [cx - ww, cy - hh, cx + ww, cy + hh];
|
bounds = [cx - ww, cy - hh, cx + ww, cy + hh];
|
||||||
} else {
|
} else {
|
||||||
const [x11, y11] = pointRotateRads(
|
const [x11, y11] = pointRotateRads(
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [x12, y12] = pointRotateRads(
|
const [x12, y12] = pointRotateRads(
|
||||||
point(x1, y2),
|
pointFrom(x1, y2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [x22, y22] = pointRotateRads(
|
const [x22, y22] = pointRotateRads(
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [x21, y21] = pointRotateRads(
|
const [x21, y21] = pointRotateRads(
|
||||||
point(x2, y1),
|
pointFrom(x2, y1),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const minX = Math.min(x11, x12, x22, x21);
|
const minX = Math.min(x11, x12, x22, x21);
|
||||||
@@ -255,7 +255,7 @@ export const getElementLineSegments = (
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const center: GlobalPoint = point(cx, cy);
|
const center: GlobalPoint = pointFrom(cx, cy);
|
||||||
|
|
||||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||||
const segments: LineSegment<GlobalPoint>[] = [];
|
const segments: LineSegment<GlobalPoint>[] = [];
|
||||||
@@ -266,7 +266,7 @@ export const getElementLineSegments = (
|
|||||||
segments.push(
|
segments.push(
|
||||||
lineSegment(
|
lineSegment(
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(
|
pointFrom(
|
||||||
element.points[i][0] + element.x,
|
element.points[i][0] + element.x,
|
||||||
element.points[i][1] + element.y,
|
element.points[i][1] + element.y,
|
||||||
),
|
),
|
||||||
@@ -274,7 +274,7 @@ export const getElementLineSegments = (
|
|||||||
element.angle,
|
element.angle,
|
||||||
),
|
),
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(
|
pointFrom(
|
||||||
element.points[i + 1][0] + element.x,
|
element.points[i + 1][0] + element.x,
|
||||||
element.points[i + 1][1] + element.y,
|
element.points[i + 1][1] + element.y,
|
||||||
),
|
),
|
||||||
@@ -470,7 +470,7 @@ export const getMinMaxXYFromCurvePathOps = (
|
|||||||
ops: Op[],
|
ops: Op[],
|
||||||
transformXY?: (p: GlobalPoint) => GlobalPoint,
|
transformXY?: (p: GlobalPoint) => GlobalPoint,
|
||||||
): Bounds => {
|
): Bounds => {
|
||||||
let currentP: GlobalPoint = point(0, 0);
|
let currentP: GlobalPoint = pointFrom(0, 0);
|
||||||
|
|
||||||
const { minX, minY, maxX, maxY } = ops.reduce(
|
const { minX, minY, maxX, maxY } = ops.reduce(
|
||||||
(limits, { op, data }) => {
|
(limits, { op, data }) => {
|
||||||
@@ -484,9 +484,9 @@ export const getMinMaxXYFromCurvePathOps = (
|
|||||||
// move operation does not draw anything; so, it always
|
// move operation does not draw anything; so, it always
|
||||||
// returns false
|
// returns false
|
||||||
} else if (op === "bcurveTo") {
|
} else if (op === "bcurveTo") {
|
||||||
const _p1 = point<GlobalPoint>(data[0], data[1]);
|
const _p1 = pointFrom<GlobalPoint>(data[0], data[1]);
|
||||||
const _p2 = point<GlobalPoint>(data[2], data[3]);
|
const _p2 = pointFrom<GlobalPoint>(data[2], data[3]);
|
||||||
const _p3 = point<GlobalPoint>(data[4], data[5]);
|
const _p3 = pointFrom<GlobalPoint>(data[4], data[5]);
|
||||||
|
|
||||||
const p1 = transformXY ? transformXY(_p1) : _p1;
|
const p1 = transformXY ? transformXY(_p1) : _p1;
|
||||||
const p2 = transformXY ? transformXY(_p2) : _p2;
|
const p2 = transformXY ? transformXY(_p2) : _p2;
|
||||||
@@ -591,21 +591,21 @@ export const getArrowheadPoints = (
|
|||||||
|
|
||||||
invariant(data.length === 6, "Op data length is not 6");
|
invariant(data.length === 6, "Op data length is not 6");
|
||||||
|
|
||||||
const p3 = point(data[4], data[5]);
|
const p3 = pointFrom(data[4], data[5]);
|
||||||
const p2 = point(data[2], data[3]);
|
const p2 = pointFrom(data[2], data[3]);
|
||||||
const p1 = point(data[0], data[1]);
|
const p1 = pointFrom(data[0], data[1]);
|
||||||
|
|
||||||
// We need to find p0 of the bezier curve.
|
// We need to find p0 of the bezier curve.
|
||||||
// It is typically the last point of the previous
|
// It is typically the last point of the previous
|
||||||
// curve; it can also be the position of moveTo operation.
|
// curve; it can also be the position of moveTo operation.
|
||||||
const prevOp = ops[index - 1];
|
const prevOp = ops[index - 1];
|
||||||
let p0 = point(0, 0);
|
let p0 = pointFrom(0, 0);
|
||||||
if (prevOp.op === "move") {
|
if (prevOp.op === "move") {
|
||||||
const p = pointFromArray(prevOp.data);
|
const p = pointFromArray(prevOp.data);
|
||||||
invariant(p != null, "Op data is not a point");
|
invariant(p != null, "Op data is not a point");
|
||||||
p0 = p;
|
p0 = p;
|
||||||
} else if (prevOp.op === "bcurveTo") {
|
} else if (prevOp.op === "bcurveTo") {
|
||||||
p0 = point(prevOp.data[4], prevOp.data[5]);
|
p0 = pointFrom(prevOp.data[4], prevOp.data[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
|
// B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
|
||||||
@@ -671,13 +671,13 @@ export const getArrowheadPoints = (
|
|||||||
|
|
||||||
// Return points
|
// Return points
|
||||||
const [x3, y3] = pointRotateRads(
|
const [x3, y3] = pointRotateRads(
|
||||||
point(xs, ys),
|
pointFrom(xs, ys),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
((-angle * Math.PI) / 180) as Radians,
|
((-angle * Math.PI) / 180) as Radians,
|
||||||
);
|
);
|
||||||
const [x4, y4] = pointRotateRads(
|
const [x4, y4] = pointRotateRads(
|
||||||
point(xs, ys),
|
pointFrom(xs, ys),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
degreesToRadians(angle),
|
degreesToRadians(angle),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -690,8 +690,8 @@ export const getArrowheadPoints = (
|
|||||||
const [px, py] = element.points.length > 1 ? element.points[1] : [0, 0];
|
const [px, py] = element.points.length > 1 ? element.points[1] : [0, 0];
|
||||||
|
|
||||||
[ox, oy] = pointRotateRads(
|
[ox, oy] = pointRotateRads(
|
||||||
point(x2 + minSize * 2, y2),
|
pointFrom(x2 + minSize * 2, y2),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
Math.atan2(py - y2, px - x2) as Radians,
|
Math.atan2(py - y2, px - x2) as Radians,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -701,8 +701,8 @@ export const getArrowheadPoints = (
|
|||||||
: [0, 0];
|
: [0, 0];
|
||||||
|
|
||||||
[ox, oy] = pointRotateRads(
|
[ox, oy] = pointRotateRads(
|
||||||
point(x2 - minSize * 2, y2),
|
pointFrom(x2 - minSize * 2, y2),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
Math.atan2(y2 - py, x2 - px) as Radians,
|
Math.atan2(y2 - py, x2 - px) as Radians,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -746,8 +746,8 @@ const getLinearElementRotatedBounds = (
|
|||||||
if (element.points.length < 2) {
|
if (element.points.length < 2) {
|
||||||
const [pointX, pointY] = element.points[0];
|
const [pointX, pointY] = element.points[0];
|
||||||
const [x, y] = pointRotateRads(
|
const [x, y] = pointRotateRads(
|
||||||
point(element.x + pointX, element.y + pointY),
|
pointFrom(element.x + pointX, element.y + pointY),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -775,8 +775,8 @@ const getLinearElementRotatedBounds = (
|
|||||||
const ops = getCurvePathOps(shape);
|
const ops = getCurvePathOps(shape);
|
||||||
const transformXY = ([x, y]: GlobalPoint) =>
|
const transformXY = ([x, y]: GlobalPoint) =>
|
||||||
pointRotateRads<GlobalPoint>(
|
pointRotateRads<GlobalPoint>(
|
||||||
point(element.x + x, element.y + y),
|
pointFrom(element.x + x, element.y + y),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
|
||||||
@@ -931,8 +931,8 @@ export const getClosestElementBounds = (
|
|||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
|
||||||
const distance = pointDistance(
|
const distance = pointDistance(
|
||||||
point((x1 + x2) / 2, (y1 + y2) / 2),
|
pointFrom((x1 + x2) / 2, (y1 + y2) / 2),
|
||||||
point(from.x, from.y),
|
pointFrom(from.x, from.y),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
@@ -990,7 +990,7 @@ export const getVisibleSceneBounds = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getCenterForBounds = (bounds: Bounds): GlobalPoint =>
|
export const getCenterForBounds = (bounds: Bounds): GlobalPoint =>
|
||||||
point(
|
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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { getBoundTextShape, isPathALoop } from "../shapes";
|
import { getBoundTextShape, isPathALoop } from "../shapes";
|
||||||
import type { GlobalPoint, LocalPoint, Polygon } from "../../math";
|
import type { GlobalPoint, LocalPoint, Polygon } from "../../math";
|
||||||
import { isPointWithinBounds, point } from "../../math";
|
import { isPointWithinBounds, pointFrom } from "../../math";
|
||||||
|
|
||||||
export const shouldTestInside = (element: ExcalidrawElement) => {
|
export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||||
if (element.type === "arrow") {
|
if (element.type === "arrow") {
|
||||||
@@ -61,13 +61,13 @@ export const hitElementItself = <Point extends GlobalPoint | LocalPoint>({
|
|||||||
let hit = shouldTestInside(element)
|
let hit = shouldTestInside(element)
|
||||||
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
? // Since `inShape` tests STRICTLY againt the insides of a shape
|
||||||
// we would need `onShape` as well to include the "borders"
|
// we would need `onShape` as well to include the "borders"
|
||||||
isPointInShape(point(x, y), shape) ||
|
isPointInShape(pointFrom(x, y), shape) ||
|
||||||
isPointOnShape(point(x, y), shape, threshold)
|
isPointOnShape(pointFrom(x, y), shape, threshold)
|
||||||
: isPointOnShape(point(x, y), shape, threshold);
|
: isPointOnShape(pointFrom(x, y), shape, threshold);
|
||||||
|
|
||||||
// hit test against a frame's name
|
// hit test against a frame's name
|
||||||
if (!hit && frameNameBound) {
|
if (!hit && frameNameBound) {
|
||||||
hit = isPointInShape(point(x, y), {
|
hit = isPointInShape(pointFrom(x, y), {
|
||||||
type: "polygon",
|
type: "polygon",
|
||||||
data: getPolygonShape(frameNameBound as ExcalidrawRectangleElement)
|
data: getPolygonShape(frameNameBound as ExcalidrawRectangleElement)
|
||||||
.data as Polygon<Point>,
|
.data as Polygon<Point>,
|
||||||
@@ -89,7 +89,11 @@ export const hitElementBoundingBox = (
|
|||||||
y1 -= tolerance;
|
y1 -= tolerance;
|
||||||
x2 += tolerance;
|
x2 += tolerance;
|
||||||
y2 += tolerance;
|
y2 += tolerance;
|
||||||
return isPointWithinBounds(point(x1, y1), point(x, y), point(x2, y2));
|
return isPointWithinBounds(
|
||||||
|
pointFrom(x1, y1),
|
||||||
|
pointFrom(x, y),
|
||||||
|
pointFrom(x2, y2),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hitElementBoundingBoxOnly = <
|
export const hitElementBoundingBoxOnly = <
|
||||||
@@ -115,5 +119,5 @@ export const hitElementBoundText = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
y: number,
|
y: number,
|
||||||
textShape: GeometricShape<Point> | null,
|
textShape: GeometricShape<Point> | null,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
return !!textShape && isPointInShape(point(x, y), textShape);
|
return !!textShape && isPointInShape(pointFrom(x, y), textShape);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,587 @@
|
|||||||
|
import { type Point } from "points-on-curve";
|
||||||
|
import {
|
||||||
|
type Radians,
|
||||||
|
pointFrom,
|
||||||
|
pointCenter,
|
||||||
|
pointRotateRads,
|
||||||
|
vectorFromPoint,
|
||||||
|
vectorNormalize,
|
||||||
|
vectorSubtract,
|
||||||
|
vectorAdd,
|
||||||
|
vectorScale,
|
||||||
|
pointFromVector,
|
||||||
|
clamp,
|
||||||
|
isCloseTo,
|
||||||
|
} from "../../math";
|
||||||
|
import type { TransformHandleType } from "./transformHandles";
|
||||||
|
import type {
|
||||||
|
ElementsMap,
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawImageElement,
|
||||||
|
ImageCrop,
|
||||||
|
NonDeleted,
|
||||||
|
} from "./types";
|
||||||
|
import {
|
||||||
|
getElementAbsoluteCoords,
|
||||||
|
getResizedElementAbsoluteCoords,
|
||||||
|
} from "./bounds";
|
||||||
|
|
||||||
|
const MINIMAL_CROP_SIZE = 10;
|
||||||
|
|
||||||
|
export const cropElement = (
|
||||||
|
element: ExcalidrawImageElement,
|
||||||
|
transformHandle: TransformHandleType,
|
||||||
|
naturalWidth: number,
|
||||||
|
naturalHeight: number,
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
widthAspectRatio?: number,
|
||||||
|
) => {
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
const naturalWidthToUncropped = naturalWidth / uncroppedWidth;
|
||||||
|
const naturalHeightToUncropped = naturalHeight / uncroppedHeight;
|
||||||
|
|
||||||
|
const croppedLeft = (element.crop?.x ?? 0) / naturalWidthToUncropped;
|
||||||
|
const croppedTop = (element.crop?.y ?? 0) / naturalHeightToUncropped;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uncropped width
|
||||||
|
* *––––––––––––––––––––––––*
|
||||||
|
* | (x,y) (natural) |
|
||||||
|
* | *–––––––* |
|
||||||
|
* | |///////| height | uncropped height
|
||||||
|
* | *–––––––* |
|
||||||
|
* | width (natural) |
|
||||||
|
* *––––––––––––––––––––––––*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const rotatedPointer = pointRotateRads(
|
||||||
|
pointFrom(pointerX, pointerY),
|
||||||
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
|
-element.angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
pointerX = rotatedPointer[0];
|
||||||
|
pointerY = rotatedPointer[1];
|
||||||
|
|
||||||
|
let nextWidth = element.width;
|
||||||
|
let nextHeight = element.height;
|
||||||
|
|
||||||
|
let crop: ImageCrop | null = element.crop ?? {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: naturalWidth,
|
||||||
|
height: naturalHeight,
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
const previousCropHeight = crop.height;
|
||||||
|
const previousCropWidth = crop.width;
|
||||||
|
|
||||||
|
const isFlippedByX = element.scale[0] === -1;
|
||||||
|
const isFlippedByY = element.scale[1] === -1;
|
||||||
|
|
||||||
|
let changeInHeight = pointerY - element.y;
|
||||||
|
let changeInWidth = pointerX - element.x;
|
||||||
|
|
||||||
|
if (transformHandle.includes("n")) {
|
||||||
|
nextHeight = clamp(
|
||||||
|
element.height - changeInHeight,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByY ? uncroppedHeight - croppedTop : element.height + croppedTop,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transformHandle.includes("s")) {
|
||||||
|
changeInHeight = pointerY - element.y - element.height;
|
||||||
|
nextHeight = clamp(
|
||||||
|
element.height + changeInHeight,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByY ? element.height + croppedTop : uncroppedHeight - croppedTop,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transformHandle.includes("e")) {
|
||||||
|
changeInWidth = pointerX - element.x - element.width;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
element.width + changeInWidth,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByX ? element.width + croppedLeft : uncroppedWidth - croppedLeft,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transformHandle.includes("w")) {
|
||||||
|
nextWidth = clamp(
|
||||||
|
element.width - changeInWidth,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByX ? uncroppedWidth - croppedLeft : element.width + croppedLeft,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCropWidthAndHeight = (crop: ImageCrop) => {
|
||||||
|
crop.height = nextHeight * naturalHeightToUncropped;
|
||||||
|
crop.width = nextWidth * naturalWidthToUncropped;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCropWidthAndHeight(crop);
|
||||||
|
|
||||||
|
const adjustFlipForHandle = (
|
||||||
|
handle: TransformHandleType,
|
||||||
|
crop: ImageCrop,
|
||||||
|
) => {
|
||||||
|
updateCropWidthAndHeight(crop);
|
||||||
|
if (handle.includes("n")) {
|
||||||
|
if (!isFlippedByY) {
|
||||||
|
crop.y += previousCropHeight - crop.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle.includes("s")) {
|
||||||
|
if (isFlippedByY) {
|
||||||
|
crop.y += previousCropHeight - crop.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle.includes("e")) {
|
||||||
|
if (isFlippedByX) {
|
||||||
|
crop.x += previousCropWidth - crop.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle.includes("w")) {
|
||||||
|
if (!isFlippedByX) {
|
||||||
|
crop.x += previousCropWidth - crop.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (transformHandle) {
|
||||||
|
case "n": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToLeft = croppedLeft + element.width / 2;
|
||||||
|
const distanceToRight =
|
||||||
|
uncroppedWidth - croppedLeft - element.width / 2;
|
||||||
|
|
||||||
|
const MAX_WIDTH = Math.min(distanceToLeft, distanceToRight) * 2;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.x += (previousCropWidth - crop.width) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "s": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToLeft = croppedLeft + element.width / 2;
|
||||||
|
const distanceToRight =
|
||||||
|
uncroppedWidth - croppedLeft - element.width / 2;
|
||||||
|
|
||||||
|
const MAX_WIDTH = Math.min(distanceToLeft, distanceToRight) * 2;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.x += (previousCropWidth - crop.width) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "w": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToTop = croppedTop + element.height / 2;
|
||||||
|
const distanceToBottom =
|
||||||
|
uncroppedHeight - croppedTop - element.height / 2;
|
||||||
|
|
||||||
|
const MAX_HEIGHT = Math.min(distanceToTop, distanceToBottom) * 2;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.y += (previousCropHeight - crop.height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "e": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToTop = croppedTop + element.height / 2;
|
||||||
|
const distanceToBottom =
|
||||||
|
uncroppedHeight - croppedTop - element.height / 2;
|
||||||
|
|
||||||
|
const MAX_HEIGHT = Math.min(distanceToTop, distanceToBottom) * 2;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.y += (previousCropHeight - crop.height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ne": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (changeInWidth > -changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? uncroppedHeight - croppedTop
|
||||||
|
: croppedTop + element.height;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? croppedLeft + element.width
|
||||||
|
: uncroppedWidth - croppedLeft;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "nw": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (changeInWidth < changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? uncroppedHeight - croppedTop
|
||||||
|
: croppedTop + element.height;
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? uncroppedWidth - croppedLeft
|
||||||
|
: croppedLeft + element.width;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "se": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (changeInWidth > changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? croppedTop + element.height
|
||||||
|
: uncroppedHeight - croppedTop;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? croppedLeft + element.width
|
||||||
|
: uncroppedWidth - croppedLeft;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sw": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (-changeInWidth > changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? croppedTop + element.height
|
||||||
|
: uncroppedHeight - croppedTop;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? uncroppedWidth - croppedLeft
|
||||||
|
: croppedLeft + element.width;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newOrigin = recomputeOrigin(
|
||||||
|
element,
|
||||||
|
transformHandle,
|
||||||
|
nextWidth,
|
||||||
|
nextHeight,
|
||||||
|
!!widthAspectRatio,
|
||||||
|
);
|
||||||
|
|
||||||
|
// reset crop to null if we're back to orig size
|
||||||
|
if (
|
||||||
|
isCloseTo(crop.width, crop.naturalWidth) &&
|
||||||
|
isCloseTo(crop.height, crop.naturalHeight)
|
||||||
|
) {
|
||||||
|
crop = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: newOrigin[0],
|
||||||
|
y: newOrigin[1],
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
crop,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const recomputeOrigin = (
|
||||||
|
stateAtCropStart: NonDeleted<ExcalidrawElement>,
|
||||||
|
transformHandle: TransformHandleType,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
shouldMaintainAspectRatio?: boolean,
|
||||||
|
) => {
|
||||||
|
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
|
||||||
|
stateAtCropStart,
|
||||||
|
stateAtCropStart.width,
|
||||||
|
stateAtCropStart.height,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
const startTopLeft = pointFrom(x1, y1);
|
||||||
|
const startBottomRight = pointFrom(x2, y2);
|
||||||
|
const startCenter: any = pointCenter(startTopLeft, startBottomRight);
|
||||||
|
|
||||||
|
const [newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2] =
|
||||||
|
getResizedElementAbsoluteCoords(stateAtCropStart, width, height, true);
|
||||||
|
const newBoundsWidth = newBoundsX2 - newBoundsX1;
|
||||||
|
const newBoundsHeight = newBoundsY2 - newBoundsY1;
|
||||||
|
|
||||||
|
// Calculate new topLeft based on fixed corner during resize
|
||||||
|
let newTopLeft = [...startTopLeft] as [number, number];
|
||||||
|
|
||||||
|
if (["n", "w", "nw"].includes(transformHandle)) {
|
||||||
|
newTopLeft = [
|
||||||
|
startBottomRight[0] - Math.abs(newBoundsWidth),
|
||||||
|
startBottomRight[1] - Math.abs(newBoundsHeight),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (transformHandle === "ne") {
|
||||||
|
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
||||||
|
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newBoundsHeight)];
|
||||||
|
}
|
||||||
|
if (transformHandle === "sw") {
|
||||||
|
const topRight = [startBottomRight[0], startTopLeft[1]];
|
||||||
|
newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldMaintainAspectRatio) {
|
||||||
|
if (["s", "n"].includes(transformHandle)) {
|
||||||
|
newTopLeft[0] = startCenter[0] - newBoundsWidth / 2;
|
||||||
|
}
|
||||||
|
if (["e", "w"].includes(transformHandle)) {
|
||||||
|
newTopLeft[1] = startCenter[1] - newBoundsHeight / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust topLeft to new rotation point
|
||||||
|
const angle = stateAtCropStart.angle;
|
||||||
|
const rotatedTopLeft = pointRotateRads(newTopLeft, startCenter, angle);
|
||||||
|
const newCenter: Point = [
|
||||||
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||||
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||||
|
];
|
||||||
|
const rotatedNewCenter = pointRotateRads(newCenter, startCenter, angle);
|
||||||
|
newTopLeft = pointRotateRads(
|
||||||
|
rotatedTopLeft,
|
||||||
|
rotatedNewCenter,
|
||||||
|
-angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newOrigin = [...newTopLeft];
|
||||||
|
newOrigin[0] += stateAtCropStart.x - newBoundsX1;
|
||||||
|
newOrigin[1] += stateAtCropStart.y - newBoundsY1;
|
||||||
|
|
||||||
|
return newOrigin;
|
||||||
|
};
|
||||||
|
|
||||||
|
// refer to https://link.excalidraw.com/l/6rfy1007QOo/6stx5PmRn0k
|
||||||
|
export const getUncroppedImageElement = (
|
||||||
|
element: ExcalidrawImageElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
if (element.crop) {
|
||||||
|
const { width, height } = getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topLeftVector = vectorFromPoint(
|
||||||
|
pointRotateRads(pointFrom(x1, y1), pointFrom(cx, cy), element.angle),
|
||||||
|
);
|
||||||
|
const topRightVector = vectorFromPoint(
|
||||||
|
pointRotateRads(pointFrom(x2, y1), pointFrom(cx, cy), element.angle),
|
||||||
|
);
|
||||||
|
const topEdgeNormalized = vectorNormalize(
|
||||||
|
vectorSubtract(topRightVector, topLeftVector),
|
||||||
|
);
|
||||||
|
const bottomLeftVector = vectorFromPoint(
|
||||||
|
pointRotateRads(pointFrom(x1, y2), pointFrom(cx, cy), element.angle),
|
||||||
|
);
|
||||||
|
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
||||||
|
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
||||||
|
|
||||||
|
const { cropX, cropY } = adjustCropPosition(element.crop, element.scale);
|
||||||
|
|
||||||
|
const rotatedTopLeft = vectorAdd(
|
||||||
|
vectorAdd(
|
||||||
|
topLeftVector,
|
||||||
|
vectorScale(
|
||||||
|
topEdgeNormalized,
|
||||||
|
(-cropX * width) / element.crop.naturalWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vectorScale(
|
||||||
|
leftEdgeNormalized,
|
||||||
|
(-cropY * height) / element.crop.naturalHeight,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const center = pointFromVector(
|
||||||
|
vectorAdd(
|
||||||
|
vectorAdd(rotatedTopLeft, vectorScale(topEdgeNormalized, width / 2)),
|
||||||
|
vectorScale(leftEdgeNormalized, height / 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const unrotatedTopLeft = pointRotateRads(
|
||||||
|
pointFromVector(rotatedTopLeft),
|
||||||
|
center,
|
||||||
|
-element.angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
const uncroppedElement: ExcalidrawImageElement = {
|
||||||
|
...element,
|
||||||
|
x: unrotatedTopLeft[0],
|
||||||
|
y: unrotatedTopLeft[1],
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
crop: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return uncroppedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUncroppedWidthAndHeight = (element: ExcalidrawImageElement) => {
|
||||||
|
if (element.crop) {
|
||||||
|
const width =
|
||||||
|
element.width / (element.crop.width / element.crop.naturalWidth);
|
||||||
|
const height =
|
||||||
|
element.height / (element.crop.height / element.crop.naturalHeight);
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: element.width,
|
||||||
|
height: element.height,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const adjustCropPosition = (
|
||||||
|
crop: ImageCrop,
|
||||||
|
scale: ExcalidrawImageElement["scale"],
|
||||||
|
) => {
|
||||||
|
let cropX = crop.x;
|
||||||
|
let cropY = crop.y;
|
||||||
|
|
||||||
|
const flipX = scale[0] === -1;
|
||||||
|
const flipY = scale[1] === -1;
|
||||||
|
|
||||||
|
if (flipX) {
|
||||||
|
cropX = crop.naturalWidth - Math.abs(cropX) - crop.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flipY) {
|
||||||
|
cropY = crop.naturalHeight - Math.abs(cropY) - crop.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cropX,
|
||||||
|
cropY,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
isArrowElement,
|
isArrowElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
|
isImageElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { getFontString } from "../utils";
|
import { getFontString } from "../utils";
|
||||||
@@ -251,6 +252,14 @@ export const dragNewElement = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (width !== 0 && height !== 0) {
|
if (width !== 0 && height !== 0) {
|
||||||
|
let imageInitialDimension = null;
|
||||||
|
if (isImageElement(newElement)) {
|
||||||
|
imageInitialDimension = {
|
||||||
|
initialWidth: width,
|
||||||
|
initialHeight: height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
@@ -259,6 +268,7 @@ export const dragNewElement = ({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
...textAutoResize,
|
...textAutoResize,
|
||||||
|
...imageInitialDimension,
|
||||||
},
|
},
|
||||||
informMutation,
|
informMutation,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ const RE_GENERIC_EMBED =
|
|||||||
const RE_GIPHY =
|
const RE_GIPHY =
|
||||||
/giphy.com\/(?:clips|embed|gifs)\/[a-zA-Z0-9]*?-?([a-zA-Z0-9]+)(?:[^a-zA-Z0-9]|$)/;
|
/giphy.com\/(?:clips|embed|gifs)\/[a-zA-Z0-9]*?-?([a-zA-Z0-9]+)(?:[^a-zA-Z0-9]|$)/;
|
||||||
|
|
||||||
|
const RE_REDDIT =
|
||||||
|
/^(?:http(?:s)?:\/\/)?(?:www\.)?reddit\.com\/r\/([a-zA-Z0-9_]+)\/comments\/([a-zA-Z0-9_]+)\/([a-zA-Z0-9_]+)\/?(?:\?[^#\s]*)?(?:#[^\s]*)?$/;
|
||||||
|
|
||||||
|
const RE_REDDIT_EMBED =
|
||||||
|
/^<blockquote[\s\S]*?\shref=["'](https?:\/\/(?:www\.)?reddit\.com\/[^"']*)/i;
|
||||||
|
|
||||||
const ALLOWED_DOMAINS = new Set([
|
const ALLOWED_DOMAINS = new Set([
|
||||||
"youtube.com",
|
"youtube.com",
|
||||||
"youtu.be",
|
"youtu.be",
|
||||||
@@ -59,6 +65,7 @@ const ALLOWED_DOMAINS = new Set([
|
|||||||
"stackblitz.com",
|
"stackblitz.com",
|
||||||
"val.town",
|
"val.town",
|
||||||
"giphy.com",
|
"giphy.com",
|
||||||
|
"reddit.com",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ALLOW_SAME_ORIGIN = new Set([
|
const ALLOW_SAME_ORIGIN = new Set([
|
||||||
@@ -71,6 +78,7 @@ const ALLOW_SAME_ORIGIN = new Set([
|
|||||||
"x.com",
|
"x.com",
|
||||||
"*.simplepdf.eu",
|
"*.simplepdf.eu",
|
||||||
"stackblitz.com",
|
"stackblitz.com",
|
||||||
|
"reddit.com",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const createSrcDoc = (body: string) => {
|
export const createSrcDoc = (body: string) => {
|
||||||
@@ -218,6 +226,24 @@ export const getEmbedLink = (
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RE_REDDIT.test(link)) {
|
||||||
|
const [, page, postId, title] = link.match(RE_REDDIT)!;
|
||||||
|
const safeURL = sanitizeHTMLAttribute(
|
||||||
|
`https://reddit.com/r/${page}/comments/${postId}/${title}`,
|
||||||
|
);
|
||||||
|
const ret: IframeDataWithSandbox = {
|
||||||
|
type: "document",
|
||||||
|
srcdoc: (theme: string) =>
|
||||||
|
createSrcDoc(
|
||||||
|
`<blockquote class="reddit-embed-bq" data-embed-theme="${theme}"><a href="${safeURL}"></a><br></blockquote><script async="" src="https://embed.reddit.com/widgets.js" charset="UTF-8"></script>`,
|
||||||
|
),
|
||||||
|
intrinsicSize: { w: 480, h: 480 },
|
||||||
|
sandbox: { allowSameOrigin },
|
||||||
|
};
|
||||||
|
embeddedLinkCache.set(originalLink, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
if (RE_GH_GIST.test(link)) {
|
if (RE_GH_GIST.test(link)) {
|
||||||
const [, user, gistId] = link.match(RE_GH_GIST)!;
|
const [, user, gistId] = link.match(RE_GH_GIST)!;
|
||||||
const safeURL = sanitizeHTMLAttribute(
|
const safeURL = sanitizeHTMLAttribute(
|
||||||
@@ -361,6 +387,11 @@ export const maybeParseEmbedSrc = (str: string): string => {
|
|||||||
return twitterMatch[1];
|
return twitterMatch[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const redditMatch = str.match(RE_REDDIT_EMBED);
|
||||||
|
if (redditMatch && redditMatch.length === 2) {
|
||||||
|
return redditMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
const gistMatch = str.match(RE_GH_GIST_EMBED);
|
const gistMatch = str.match(RE_GH_GIST_EMBED);
|
||||||
if (gistMatch && gistMatch.length === 2) {
|
if (gistMatch && gistMatch.length === 2) {
|
||||||
return gistMatch[1];
|
return gistMatch[1];
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
isFlowchartNodeElement,
|
isFlowchartNodeElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { invariant } from "../utils";
|
import { invariant } from "../utils";
|
||||||
import { point, type LocalPoint } from "../../math";
|
import { pointFrom, type LocalPoint } from "../../math";
|
||||||
import { aabbForElement } from "../shapes";
|
import { aabbForElement } from "../shapes";
|
||||||
|
|
||||||
type LinkDirection = "up" | "right" | "down" | "left";
|
type LinkDirection = "up" | "right" | "down" | "left";
|
||||||
@@ -421,7 +421,7 @@ const createBindingArrow = (
|
|||||||
strokeColor: appState.currentItemStrokeColor,
|
strokeColor: appState.currentItemStrokeColor,
|
||||||
strokeStyle: appState.currentItemStrokeStyle,
|
strokeStyle: appState.currentItemStrokeStyle,
|
||||||
strokeWidth: appState.currentItemStrokeWidth,
|
strokeWidth: appState.currentItemStrokeWidth,
|
||||||
points: [point(0, 0), point(endX, endY)],
|
points: [pointFrom(0, 0), pointFrom(endX, endY)],
|
||||||
elbowed: true,
|
elbowed: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type {
|
|||||||
Radians,
|
Radians,
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
import {
|
import {
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
pointScaleFromOrigin,
|
pointScaleFromOrigin,
|
||||||
radiansToDegrees,
|
radiansToDegrees,
|
||||||
@@ -82,7 +82,7 @@ export const headingForPointFromElement = <
|
|||||||
|
|
||||||
const top = pointRotateRads(
|
const top = pointRotateRads(
|
||||||
pointScaleFromOrigin(
|
pointScaleFromOrigin(
|
||||||
point(element.x + element.width / 2, element.y),
|
pointFrom(element.x + element.width / 2, element.y),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
),
|
),
|
||||||
@@ -91,7 +91,7 @@ export const headingForPointFromElement = <
|
|||||||
);
|
);
|
||||||
const right = pointRotateRads(
|
const right = pointRotateRads(
|
||||||
pointScaleFromOrigin(
|
pointScaleFromOrigin(
|
||||||
point(element.x + element.width, element.y + element.height / 2),
|
pointFrom(element.x + element.width, element.y + element.height / 2),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
),
|
),
|
||||||
@@ -100,7 +100,7 @@ export const headingForPointFromElement = <
|
|||||||
);
|
);
|
||||||
const bottom = pointRotateRads(
|
const bottom = pointRotateRads(
|
||||||
pointScaleFromOrigin(
|
pointScaleFromOrigin(
|
||||||
point(element.x + element.width / 2, element.y + element.height),
|
pointFrom(element.x + element.width / 2, element.y + element.height),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
),
|
),
|
||||||
@@ -109,7 +109,7 @@ export const headingForPointFromElement = <
|
|||||||
);
|
);
|
||||||
const left = pointRotateRads(
|
const left = pointRotateRads(
|
||||||
pointScaleFromOrigin(
|
pointScaleFromOrigin(
|
||||||
point(element.x, element.y + element.height / 2),
|
pointFrom(element.x, element.y + element.height / 2),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
),
|
),
|
||||||
@@ -133,22 +133,22 @@ export const headingForPointFromElement = <
|
|||||||
}
|
}
|
||||||
|
|
||||||
const topLeft = pointScaleFromOrigin(
|
const topLeft = pointScaleFromOrigin(
|
||||||
point(aabb[0], aabb[1]),
|
pointFrom(aabb[0], aabb[1]),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
) as Point;
|
) as Point;
|
||||||
const topRight = pointScaleFromOrigin(
|
const topRight = pointScaleFromOrigin(
|
||||||
point(aabb[2], aabb[1]),
|
pointFrom(aabb[2], aabb[1]),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
) as Point;
|
) as Point;
|
||||||
const bottomLeft = pointScaleFromOrigin(
|
const bottomLeft = pointScaleFromOrigin(
|
||||||
point(aabb[0], aabb[3]),
|
pointFrom(aabb[0], aabb[3]),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
) as Point;
|
) as Point;
|
||||||
const bottomRight = pointScaleFromOrigin(
|
const bottomRight = pointScaleFromOrigin(
|
||||||
point(aabb[2], aabb[3]),
|
pointFrom(aabb[2], aabb[3]),
|
||||||
midPoint,
|
midPoint,
|
||||||
SEARCH_CONE_MULTIPLIER,
|
SEARCH_CONE_MULTIPLIER,
|
||||||
) as Point;
|
) as Point;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import type Scene from "../scene/Scene";
|
|||||||
import type { Radians } from "../../math";
|
import type { Radians } from "../../math";
|
||||||
import {
|
import {
|
||||||
pointCenter,
|
pointCenter,
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
pointsEqual,
|
pointsEqual,
|
||||||
vector,
|
vector,
|
||||||
@@ -108,7 +108,7 @@ export class LinearElementEditor {
|
|||||||
this.elementId = element.id as string & {
|
this.elementId = element.id as string & {
|
||||||
_brand: "excalidrawLinearElementId";
|
_brand: "excalidrawLinearElementId";
|
||||||
};
|
};
|
||||||
if (!pointsEqual(element.points[0], point(0, 0))) {
|
if (!pointsEqual(element.points[0], pointFrom(0, 0))) {
|
||||||
console.error("Linear element is not normalized", Error().stack);
|
console.error("Linear element is not normalized", Error().stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ export class LinearElementEditor {
|
|||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
referencePoint,
|
referencePoint,
|
||||||
point(scenePointerX, scenePointerY),
|
pointFrom(scenePointerX, scenePointerY),
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ export class LinearElementEditor {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
index: selectedIndex,
|
index: selectedIndex,
|
||||||
point: point(
|
point: pointFrom(
|
||||||
width + referencePoint[0],
|
width + referencePoint[0],
|
||||||
height + referencePoint[1],
|
height + referencePoint[1],
|
||||||
),
|
),
|
||||||
@@ -329,7 +329,7 @@ export class LinearElementEditor {
|
|||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
)
|
)
|
||||||
: point(
|
: pointFrom(
|
||||||
element.points[pointIndex][0] + deltaX,
|
element.points[pointIndex][0] + deltaX,
|
||||||
element.points[pointIndex][1] + deltaY,
|
element.points[pointIndex][1] + deltaY,
|
||||||
);
|
);
|
||||||
@@ -590,11 +590,11 @@ export class LinearElementEditor {
|
|||||||
linearElementEditor.segmentMidPointHoveredCoords;
|
linearElementEditor.segmentMidPointHoveredCoords;
|
||||||
if (existingSegmentMidpointHitCoords) {
|
if (existingSegmentMidpointHitCoords) {
|
||||||
const distance = pointDistance(
|
const distance = pointDistance(
|
||||||
point(
|
pointFrom(
|
||||||
existingSegmentMidpointHitCoords[0],
|
existingSegmentMidpointHitCoords[0],
|
||||||
existingSegmentMidpointHitCoords[1],
|
existingSegmentMidpointHitCoords[1],
|
||||||
),
|
),
|
||||||
point(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointer.x, scenePointer.y),
|
||||||
);
|
);
|
||||||
if (distance <= threshold) {
|
if (distance <= threshold) {
|
||||||
return existingSegmentMidpointHitCoords;
|
return existingSegmentMidpointHitCoords;
|
||||||
@@ -606,8 +606,8 @@ export class LinearElementEditor {
|
|||||||
while (index < midPoints.length) {
|
while (index < midPoints.length) {
|
||||||
if (midPoints[index] !== null) {
|
if (midPoints[index] !== null) {
|
||||||
const distance = pointDistance(
|
const distance = pointDistance(
|
||||||
point(midPoints[index]![0], midPoints[index]![1]),
|
pointFrom(midPoints[index]![0], midPoints[index]![1]),
|
||||||
point(scenePointer.x, scenePointer.y),
|
pointFrom(scenePointer.x, scenePointer.y),
|
||||||
);
|
);
|
||||||
if (distance <= threshold) {
|
if (distance <= threshold) {
|
||||||
return midPoints[index];
|
return midPoints[index];
|
||||||
@@ -626,8 +626,8 @@ export class LinearElementEditor {
|
|||||||
zoom: AppState["zoom"],
|
zoom: AppState["zoom"],
|
||||||
) {
|
) {
|
||||||
let distance = pointDistance(
|
let distance = pointDistance(
|
||||||
point(startPoint[0], startPoint[1]),
|
pointFrom(startPoint[0], startPoint[1]),
|
||||||
point(endPoint[0], endPoint[1]),
|
pointFrom(endPoint[0], endPoint[1]),
|
||||||
);
|
);
|
||||||
if (element.points.length > 2 && element.roundness) {
|
if (element.points.length > 2 && element.roundness) {
|
||||||
distance = getBezierCurveLength(element, endPoint);
|
distance = getBezierCurveLength(element, endPoint);
|
||||||
@@ -829,11 +829,11 @@ export class LinearElementEditor {
|
|||||||
const targetPoint =
|
const targetPoint =
|
||||||
clickedPointIndex > -1 &&
|
clickedPointIndex > -1 &&
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(
|
pointFrom(
|
||||||
element.x + element.points[clickedPointIndex][0],
|
element.x + element.points[clickedPointIndex][0],
|
||||||
element.y + element.points[clickedPointIndex][1],
|
element.y + element.points[clickedPointIndex][1],
|
||||||
),
|
),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -928,11 +928,11 @@ export class LinearElementEditor {
|
|||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
lastCommittedPoint,
|
lastCommittedPoint,
|
||||||
point(scenePointerX, scenePointerY),
|
pointFrom(scenePointerX, scenePointerY),
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
newPoint = point(
|
newPoint = pointFrom(
|
||||||
width + lastCommittedPoint[0],
|
width + lastCommittedPoint[0],
|
||||||
height + lastCommittedPoint[1],
|
height + lastCommittedPoint[1],
|
||||||
);
|
);
|
||||||
@@ -984,8 +984,8 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const { x, y } = element;
|
const { x, y } = element;
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x + p[0], y + p[1]),
|
pointFrom(x + p[0], y + p[1]),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1001,8 +1001,8 @@ export class LinearElementEditor {
|
|||||||
return element.points.map((p) => {
|
return element.points.map((p) => {
|
||||||
const { x, y } = element;
|
const { x, y } = element;
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x + p[0], y + p[1]),
|
pointFrom(x + p[0], y + p[1]),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1025,8 +1025,12 @@ export class LinearElementEditor {
|
|||||||
const { x, y } = element;
|
const { x, y } = element;
|
||||||
|
|
||||||
return p
|
return p
|
||||||
? pointRotateRads(point(x + p[0], y + p[1]), point(cx, cy), element.angle)
|
? pointRotateRads(
|
||||||
: pointRotateRads(point(x, y), point(cx, cy), element.angle);
|
pointFrom(x + p[0], y + p[1]),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
element.angle,
|
||||||
|
)
|
||||||
|
: pointRotateRads(pointFrom(x, y), pointFrom(cx, cy), element.angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
static pointFromAbsoluteCoords(
|
static pointFromAbsoluteCoords(
|
||||||
@@ -1036,7 +1040,7 @@ export class LinearElementEditor {
|
|||||||
): LocalPoint {
|
): LocalPoint {
|
||||||
if (isElbowArrow(element)) {
|
if (isElbowArrow(element)) {
|
||||||
// No rotation for elbow arrows
|
// No rotation for elbow arrows
|
||||||
return point(
|
return pointFrom(
|
||||||
absoluteCoords[0] - element.x,
|
absoluteCoords[0] - element.x,
|
||||||
absoluteCoords[1] - element.y,
|
absoluteCoords[1] - element.y,
|
||||||
);
|
);
|
||||||
@@ -1046,11 +1050,11 @@ export class LinearElementEditor {
|
|||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const [x, y] = pointRotateRads(
|
const [x, y] = pointRotateRads(
|
||||||
point(absoluteCoords[0], absoluteCoords[1]),
|
pointFrom(absoluteCoords[0], absoluteCoords[1]),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
return point(x - element.x, y - element.y);
|
return pointFrom(x - element.x, y - element.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPointIndexUnderCursor(
|
static getPointIndexUnderCursor(
|
||||||
@@ -1071,7 +1075,7 @@ export class LinearElementEditor {
|
|||||||
while (--idx > -1) {
|
while (--idx > -1) {
|
||||||
const p = pointHandles[idx];
|
const p = pointHandles[idx];
|
||||||
if (
|
if (
|
||||||
pointDistance(point(x, y), point(p[0], p[1])) * zoom.value <
|
pointDistance(pointFrom(x, y), pointFrom(p[0], p[1])) * zoom.value <
|
||||||
// +1px to account for outline stroke
|
// +1px to account for outline stroke
|
||||||
LinearElementEditor.POINT_HANDLE_SIZE + 1
|
LinearElementEditor.POINT_HANDLE_SIZE + 1
|
||||||
) {
|
) {
|
||||||
@@ -1093,12 +1097,12 @@ export class LinearElementEditor {
|
|||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const [rotatedX, rotatedY] = pointRotateRads(
|
const [rotatedX, rotatedY] = pointRotateRads(
|
||||||
point(pointerOnGrid[0], pointerOnGrid[1]),
|
pointFrom(pointerOnGrid[0], pointerOnGrid[1]),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
return point(rotatedX - element.x, rotatedY - element.y);
|
return pointFrom(rotatedX - element.x, rotatedY - element.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1118,7 +1122,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
points: points.map((p) => {
|
points: points.map((p) => {
|
||||||
return point(p[0] - offsetX, p[1] - offsetY);
|
return pointFrom(p[0] - offsetX, p[1] - offsetY);
|
||||||
}),
|
}),
|
||||||
x: element.x + offsetX,
|
x: element.x + offsetX,
|
||||||
y: element.y + offsetY,
|
y: element.y + offsetY,
|
||||||
@@ -1172,8 +1176,8 @@ export class LinearElementEditor {
|
|||||||
}
|
}
|
||||||
acc.push(
|
acc.push(
|
||||||
nextPoint
|
nextPoint
|
||||||
? point((p[0] + nextPoint[0]) / 2, (p[1] + nextPoint[1]) / 2)
|
? pointFrom((p[0] + nextPoint[0]) / 2, (p[1] + nextPoint[1]) / 2)
|
||||||
: point(p[0], p[1]),
|
: pointFrom(p[0], p[1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
nextSelectedIndices.push(indexCursor + 1);
|
nextSelectedIndices.push(indexCursor + 1);
|
||||||
@@ -1194,7 +1198,7 @@ export class LinearElementEditor {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
index: element.points.length - 1,
|
index: element.points.length - 1,
|
||||||
point: point(lastPoint[0] + 30, lastPoint[1] + 30),
|
point: pointFrom(lastPoint[0] + 30, lastPoint[1] + 30),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
elementsMap,
|
elementsMap,
|
||||||
@@ -1235,7 +1239,9 @@ export class LinearElementEditor {
|
|||||||
const nextPoints = element.points.reduce((acc: LocalPoint[], p, idx) => {
|
const nextPoints = element.points.reduce((acc: LocalPoint[], p, idx) => {
|
||||||
if (!pointIndices.includes(idx)) {
|
if (!pointIndices.includes(idx)) {
|
||||||
acc.push(
|
acc.push(
|
||||||
!acc.length ? point(0, 0) : point(p[0] - offsetX, p[1] - offsetY),
|
!acc.length
|
||||||
|
? pointFrom(0, 0)
|
||||||
|
: pointFrom(p[0] - offsetX, p[1] - offsetY),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
@@ -1312,9 +1318,9 @@ export class LinearElementEditor {
|
|||||||
const deltaY =
|
const deltaY =
|
||||||
selectedPointData.point[1] - points[selectedPointData.index][1];
|
selectedPointData.point[1] - points[selectedPointData.index][1];
|
||||||
|
|
||||||
return point(p[0] + deltaX - offsetX, p[1] + deltaY - offsetY);
|
return pointFrom(p[0] + deltaX - offsetX, p[1] + deltaY - offsetY);
|
||||||
}
|
}
|
||||||
return offsetX || offsetY ? point(p[0] - offsetX, p[1] - offsetY) : p;
|
return offsetX || offsetY ? pointFrom(p[0] - offsetX, p[1] - offsetY) : p;
|
||||||
});
|
});
|
||||||
|
|
||||||
LinearElementEditor._updatePoints(
|
LinearElementEditor._updatePoints(
|
||||||
@@ -1368,8 +1374,8 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const origin = linearElementEditor.pointerDownState.origin!;
|
const origin = linearElementEditor.pointerDownState.origin!;
|
||||||
const dist = pointDistance(
|
const dist = pointDistance(
|
||||||
point(origin.x, origin.y),
|
pointFrom(origin.x, origin.y),
|
||||||
point(pointerCoords.x, pointerCoords.y),
|
pointFrom(pointerCoords.x, pointerCoords.y),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
!appState.editingLinearElement &&
|
!appState.editingLinearElement &&
|
||||||
@@ -1493,8 +1499,8 @@ export class LinearElementEditor {
|
|||||||
const dX = prevCenterX - nextCenterX;
|
const dX = prevCenterX - nextCenterX;
|
||||||
const dY = prevCenterY - nextCenterY;
|
const dY = prevCenterY - nextCenterY;
|
||||||
const rotated = pointRotateRads(
|
const rotated = pointRotateRads(
|
||||||
point(offsetX, offsetY),
|
pointFrom(offsetX, offsetY),
|
||||||
point(dX, dY),
|
pointFrom(dX, dY),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
@@ -1540,8 +1546,8 @@ export class LinearElementEditor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(width, height),
|
pointFrom(width, height),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1611,36 +1617,36 @@ export class LinearElementEditor {
|
|||||||
);
|
);
|
||||||
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
||||||
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
||||||
const centerPoint = point(cx, cy);
|
const centerPoint = pointFrom(cx, cy);
|
||||||
|
|
||||||
const topLeftRotatedPoint = pointRotateRads(
|
const topLeftRotatedPoint = pointRotateRads(
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
centerPoint,
|
centerPoint,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const topRightRotatedPoint = pointRotateRads(
|
const topRightRotatedPoint = pointRotateRads(
|
||||||
point(x2, y1),
|
pointFrom(x2, y1),
|
||||||
centerPoint,
|
centerPoint,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
const counterRotateBoundTextTopLeft = pointRotateRads(
|
const counterRotateBoundTextTopLeft = pointRotateRads(
|
||||||
point(boundTextX1, boundTextY1),
|
pointFrom(boundTextX1, boundTextY1),
|
||||||
centerPoint,
|
centerPoint,
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
const counterRotateBoundTextTopRight = pointRotateRads(
|
const counterRotateBoundTextTopRight = pointRotateRads(
|
||||||
point(boundTextX2, boundTextY1),
|
pointFrom(boundTextX2, boundTextY1),
|
||||||
centerPoint,
|
centerPoint,
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
const counterRotateBoundTextBottomLeft = pointRotateRads(
|
const counterRotateBoundTextBottomLeft = pointRotateRads(
|
||||||
point(boundTextX1, boundTextY2),
|
pointFrom(boundTextX1, boundTextY2),
|
||||||
centerPoint,
|
centerPoint,
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
const counterRotateBoundTextBottomRight = pointRotateRads(
|
const counterRotateBoundTextBottomRight = pointRotateRads(
|
||||||
point(boundTextX2, boundTextY2),
|
pointFrom(boundTextX2, boundTextY2),
|
||||||
centerPoint,
|
centerPoint,
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FONT_FAMILY, ROUNDNESS } from "../constants";
|
|||||||
import { isPrimitive } from "../utils";
|
import { isPrimitive } from "../utils";
|
||||||
import type { ExcalidrawLinearElement } from "./types";
|
import type { ExcalidrawLinearElement } from "./types";
|
||||||
import type { LocalPoint } from "../../math";
|
import type { LocalPoint } from "../../math";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
const assertCloneObjects = (source: any, clone: any) => {
|
const assertCloneObjects = (source: any, clone: any) => {
|
||||||
for (const key in clone) {
|
for (const key in clone) {
|
||||||
@@ -38,7 +38,7 @@ describe("duplicating single elements", () => {
|
|||||||
element.__proto__ = { hello: "world" };
|
element.__proto__ = { hello: "world" };
|
||||||
|
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
points: [point<LocalPoint>(1, 2), point<LocalPoint>(3, 4)],
|
points: [pointFrom<LocalPoint>(1, 2), pointFrom<LocalPoint>(3, 4)],
|
||||||
});
|
});
|
||||||
|
|
||||||
const copy = duplicateElement(null, new Map(), element);
|
const copy = duplicateElement(null, new Map(), element);
|
||||||
|
|||||||
@@ -477,6 +477,7 @@ export const newImageElement = (
|
|||||||
status?: ExcalidrawImageElement["status"];
|
status?: ExcalidrawImageElement["status"];
|
||||||
fileId?: ExcalidrawImageElement["fileId"];
|
fileId?: ExcalidrawImageElement["fileId"];
|
||||||
scale?: ExcalidrawImageElement["scale"];
|
scale?: ExcalidrawImageElement["scale"];
|
||||||
|
crop?: ExcalidrawImageElement["crop"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawImageElement> => {
|
): NonDeleted<ExcalidrawImageElement> => {
|
||||||
return {
|
return {
|
||||||
@@ -487,6 +488,7 @@ export const newImageElement = (
|
|||||||
status: opts.status ?? "pending",
|
status: opts.status ?? "pending",
|
||||||
fileId: opts.fileId ?? null,
|
fileId: opts.fileId ?? null,
|
||||||
scale: opts.scale ?? [1, 1],
|
scale: opts.scale ?? [1, 1],
|
||||||
|
crop: opts.crop ?? null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import type { GlobalPoint } from "../../math";
|
|||||||
import {
|
import {
|
||||||
pointCenter,
|
pointCenter,
|
||||||
normalizeRadians,
|
normalizeRadians,
|
||||||
point,
|
pointFrom,
|
||||||
pointFromPair,
|
pointFromPair,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
type Radians,
|
type Radians,
|
||||||
@@ -240,8 +240,8 @@ const resizeSingleTextElement = (
|
|||||||
);
|
);
|
||||||
// rotation pointer with reverse angle
|
// rotation pointer with reverse angle
|
||||||
const [rotatedX, rotatedY] = pointRotateRads(
|
const [rotatedX, rotatedY] = pointRotateRads(
|
||||||
point(pointerX, pointerY),
|
pointFrom(pointerX, pointerY),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
-element.angle as Radians,
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
let scaleX = 0;
|
let scaleX = 0;
|
||||||
@@ -276,23 +276,23 @@ const resizeSingleTextElement = (
|
|||||||
const startBottomRight = [x2, y2];
|
const startBottomRight = [x2, y2];
|
||||||
const startCenter = [cx, cy];
|
const startCenter = [cx, cy];
|
||||||
|
|
||||||
let newTopLeft = point<GlobalPoint>(x1, y1);
|
let newTopLeft = pointFrom<GlobalPoint>(x1, y1);
|
||||||
if (["n", "w", "nw"].includes(transformHandleType)) {
|
if (["n", "w", "nw"].includes(transformHandleType)) {
|
||||||
newTopLeft = point<GlobalPoint>(
|
newTopLeft = pointFrom<GlobalPoint>(
|
||||||
startBottomRight[0] - Math.abs(nextWidth),
|
startBottomRight[0] - Math.abs(nextWidth),
|
||||||
startBottomRight[1] - Math.abs(nextHeight),
|
startBottomRight[1] - Math.abs(nextHeight),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (transformHandleType === "ne") {
|
if (transformHandleType === "ne") {
|
||||||
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
||||||
newTopLeft = point<GlobalPoint>(
|
newTopLeft = pointFrom<GlobalPoint>(
|
||||||
bottomLeft[0],
|
bottomLeft[0],
|
||||||
bottomLeft[1] - Math.abs(nextHeight),
|
bottomLeft[1] - Math.abs(nextHeight),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (transformHandleType === "sw") {
|
if (transformHandleType === "sw") {
|
||||||
const topRight = [startBottomRight[0], startTopLeft[1]];
|
const topRight = [startBottomRight[0], startTopLeft[1]];
|
||||||
newTopLeft = point<GlobalPoint>(
|
newTopLeft = pointFrom<GlobalPoint>(
|
||||||
topRight[0] - Math.abs(nextWidth),
|
topRight[0] - Math.abs(nextWidth),
|
||||||
topRight[1],
|
topRight[1],
|
||||||
);
|
);
|
||||||
@@ -311,12 +311,20 @@ const resizeSingleTextElement = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const angle = element.angle;
|
const angle = element.angle;
|
||||||
const rotatedTopLeft = pointRotateRads(newTopLeft, point(cx, cy), angle);
|
const rotatedTopLeft = pointRotateRads(
|
||||||
const newCenter = point<GlobalPoint>(
|
newTopLeft,
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
const newCenter = pointFrom<GlobalPoint>(
|
||||||
newTopLeft[0] + Math.abs(nextWidth) / 2,
|
newTopLeft[0] + Math.abs(nextWidth) / 2,
|
||||||
newTopLeft[1] + Math.abs(nextHeight) / 2,
|
newTopLeft[1] + Math.abs(nextHeight) / 2,
|
||||||
);
|
);
|
||||||
const rotatedNewCenter = pointRotateRads(newCenter, point(cx, cy), angle);
|
const rotatedNewCenter = pointRotateRads(
|
||||||
|
newCenter,
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
angle,
|
||||||
|
);
|
||||||
newTopLeft = pointRotateRads(
|
newTopLeft = pointRotateRads(
|
||||||
rotatedTopLeft,
|
rotatedTopLeft,
|
||||||
rotatedNewCenter,
|
rotatedNewCenter,
|
||||||
@@ -341,12 +349,12 @@ const resizeSingleTextElement = (
|
|||||||
stateAtResizeStart.height,
|
stateAtResizeStart.height,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const startTopLeft = point<GlobalPoint>(x1, y1);
|
const startTopLeft = pointFrom<GlobalPoint>(x1, y1);
|
||||||
const startBottomRight = point<GlobalPoint>(x2, y2);
|
const startBottomRight = pointFrom<GlobalPoint>(x2, y2);
|
||||||
const startCenter = pointCenter(startTopLeft, startBottomRight);
|
const startCenter = pointCenter(startTopLeft, startBottomRight);
|
||||||
|
|
||||||
const rotatedPointer = pointRotateRads(
|
const rotatedPointer = pointRotateRads(
|
||||||
point(pointerX, pointerY),
|
pointFrom(pointerX, pointerY),
|
||||||
startCenter,
|
startCenter,
|
||||||
-stateAtResizeStart.angle as Radians,
|
-stateAtResizeStart.angle as Radians,
|
||||||
);
|
);
|
||||||
@@ -419,7 +427,7 @@ const resizeSingleTextElement = (
|
|||||||
startCenter,
|
startCenter,
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
const newCenter = point(
|
const newCenter = pointFrom(
|
||||||
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||||
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||||
);
|
);
|
||||||
@@ -461,13 +469,13 @@ export const resizeSingleElement = (
|
|||||||
stateAtResizeStart.height,
|
stateAtResizeStart.height,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const startTopLeft = point(x1, y1);
|
const startTopLeft = pointFrom(x1, y1);
|
||||||
const startBottomRight = point(x2, y2);
|
const startBottomRight = pointFrom(x2, y2);
|
||||||
const startCenter = pointCenter(startTopLeft, startBottomRight);
|
const startCenter = pointCenter(startTopLeft, startBottomRight);
|
||||||
|
|
||||||
// Calculate new dimensions based on cursor position
|
// Calculate new dimensions based on cursor position
|
||||||
const rotatedPointer = pointRotateRads(
|
const rotatedPointer = pointRotateRads(
|
||||||
point(pointerX, pointerY),
|
pointFrom(pointerX, pointerY),
|
||||||
startCenter,
|
startCenter,
|
||||||
-stateAtResizeStart.angle as Radians,
|
-stateAtResizeStart.angle as Radians,
|
||||||
);
|
);
|
||||||
@@ -648,7 +656,7 @@ export const resizeSingleElement = (
|
|||||||
startCenter,
|
startCenter,
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
const newCenter = point(
|
const newCenter = pointFrom(
|
||||||
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||||
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||||
);
|
);
|
||||||
@@ -817,20 +825,20 @@ export const resizeMultipleElements = (
|
|||||||
const direction = transformHandleType;
|
const direction = transformHandleType;
|
||||||
|
|
||||||
const anchorsMap: Record<TransformHandleDirection, GlobalPoint> = {
|
const anchorsMap: Record<TransformHandleDirection, GlobalPoint> = {
|
||||||
ne: point(minX, maxY),
|
ne: pointFrom(minX, maxY),
|
||||||
se: point(minX, minY),
|
se: pointFrom(minX, minY),
|
||||||
sw: point(maxX, minY),
|
sw: pointFrom(maxX, minY),
|
||||||
nw: point(maxX, maxY),
|
nw: pointFrom(maxX, maxY),
|
||||||
e: point(minX, minY + height / 2),
|
e: pointFrom(minX, minY + height / 2),
|
||||||
w: point(maxX, minY + height / 2),
|
w: pointFrom(maxX, minY + height / 2),
|
||||||
n: point(minX + width / 2, maxY),
|
n: pointFrom(minX + width / 2, maxY),
|
||||||
s: point(minX + width / 2, minY),
|
s: pointFrom(minX + width / 2, minY),
|
||||||
};
|
};
|
||||||
|
|
||||||
// anchor point must be on the opposite side of the dragged selection handle
|
// anchor point must be on the opposite side of the dragged selection handle
|
||||||
// or be the center of the selection if shouldResizeFromCenter
|
// or be the center of the selection if shouldResizeFromCenter
|
||||||
const [anchorX, anchorY] = shouldResizeFromCenter
|
const [anchorX, anchorY] = shouldResizeFromCenter
|
||||||
? point(midX, midY)
|
? pointFrom(midX, midY)
|
||||||
: anchorsMap[direction];
|
: anchorsMap[direction];
|
||||||
|
|
||||||
const resizeFromCenterScale = shouldResizeFromCenter ? 2 : 1;
|
const resizeFromCenterScale = shouldResizeFromCenter ? 2 : 1;
|
||||||
@@ -1044,8 +1052,8 @@ const rotateMultipleElements = (
|
|||||||
const origAngle =
|
const origAngle =
|
||||||
originalElements.get(element.id)?.angle ?? element.angle;
|
originalElements.get(element.id)?.angle ?? element.angle;
|
||||||
const [rotatedCX, rotatedCY] = pointRotateRads(
|
const [rotatedCX, rotatedCY] = pointRotateRads(
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
point(centerX, centerY),
|
pointFrom(centerX, centerY),
|
||||||
(centerAngle + origAngle - element.angle) as Radians,
|
(centerAngle + origAngle - element.angle) as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1101,40 +1109,44 @@ export const getResizeOffsetXY = (
|
|||||||
const angle = (
|
const angle = (
|
||||||
selectedElements.length === 1 ? selectedElements[0].angle : 0
|
selectedElements.length === 1 ? selectedElements[0].angle : 0
|
||||||
) as Radians;
|
) as Radians;
|
||||||
[x, y] = pointRotateRads(point(x, y), point(cx, cy), -angle as Radians);
|
[x, y] = pointRotateRads(
|
||||||
|
pointFrom(x, y),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
-angle as Radians,
|
||||||
|
);
|
||||||
switch (transformHandleType) {
|
switch (transformHandleType) {
|
||||||
case "n":
|
case "n":
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x - (x1 + x2) / 2, y - y1),
|
pointFrom(x - (x1 + x2) / 2, y - y1),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
case "s":
|
case "s":
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x - (x1 + x2) / 2, y - y2),
|
pointFrom(x - (x1 + x2) / 2, y - y2),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
case "w":
|
case "w":
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x - x1, y - (y1 + y2) / 2),
|
pointFrom(x - x1, y - (y1 + y2) / 2),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
case "e":
|
case "e":
|
||||||
return pointRotateRads(
|
return pointRotateRads(
|
||||||
point(x - x2, y - (y1 + y2) / 2),
|
pointFrom(x - x2, y - (y1 + y2) / 2),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
case "nw":
|
case "nw":
|
||||||
return pointRotateRads(point(x - x1, y - y1), point(0, 0), angle);
|
return pointRotateRads(pointFrom(x - x1, y - y1), pointFrom(0, 0), angle);
|
||||||
case "ne":
|
case "ne":
|
||||||
return pointRotateRads(point(x - x2, y - y1), point(0, 0), angle);
|
return pointRotateRads(pointFrom(x - x2, y - y1), pointFrom(0, 0), angle);
|
||||||
case "sw":
|
case "sw":
|
||||||
return pointRotateRads(point(x - x1, y - y2), point(0, 0), angle);
|
return pointRotateRads(pointFrom(x - x1, y - y2), pointFrom(0, 0), angle);
|
||||||
case "se":
|
case "se":
|
||||||
return pointRotateRads(point(x - x2, y - y2), point(0, 0), angle);
|
return pointRotateRads(pointFrom(x - x2, y - y2), pointFrom(0, 0), angle);
|
||||||
default:
|
default:
|
||||||
return [0, 0];
|
return [0, 0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import type { AppState, Device, Zoom } from "../types";
|
|||||||
import type { Bounds } from "./bounds";
|
import type { Bounds } from "./bounds";
|
||||||
import { getElementAbsoluteCoords } from "./bounds";
|
import { getElementAbsoluteCoords } from "./bounds";
|
||||||
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
||||||
import { isLinearElement } from "./typeChecks";
|
import { isImageElement, isLinearElement } from "./typeChecks";
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint } from "../../math";
|
import type { GlobalPoint, LineSegment, LocalPoint } from "../../math";
|
||||||
import {
|
import {
|
||||||
point,
|
pointFrom,
|
||||||
pointOnLineSegment,
|
pointOnLineSegment,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
type Radians,
|
type Radians,
|
||||||
@@ -90,18 +90,26 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
|
|
||||||
// do not resize from the sides for linear elements with only two points
|
// do not resize from the sides for linear elements with only two points
|
||||||
if (!(isLinearElement(element) && element.points.length <= 2)) {
|
if (!(isLinearElement(element) && element.points.length <= 2)) {
|
||||||
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
const SPACING = isImageElement(element)
|
||||||
|
? 0
|
||||||
|
: SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||||
|
const ZOOMED_SIDE_RESIZING_THRESHOLD =
|
||||||
|
SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||||
const sides = getSelectionBorders(
|
const sides = getSelectionBorders(
|
||||||
point(x1 - SPACING, y1 - SPACING),
|
pointFrom(x1 - SPACING, y1 - SPACING),
|
||||||
point(x2 + SPACING, y2 + SPACING),
|
pointFrom(x2 + SPACING, y2 + SPACING),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const [dir, side] of Object.entries(sides)) {
|
for (const [dir, side] of Object.entries(sides)) {
|
||||||
// test to see if x, y are on the line segment
|
// test to see if x, y are on the line segment
|
||||||
if (
|
if (
|
||||||
pointOnLineSegment(point(x, y), side as LineSegment<Point>, SPACING)
|
pointOnLineSegment(
|
||||||
|
pointFrom(x, y),
|
||||||
|
side as LineSegment<Point>,
|
||||||
|
ZOOMED_SIDE_RESIZING_THRESHOLD,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return dir as TransformHandleType;
|
return dir as TransformHandleType;
|
||||||
}
|
}
|
||||||
@@ -178,9 +186,9 @@ export const getTransformHandleTypeFromCoords = <
|
|||||||
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||||
|
|
||||||
const sides = getSelectionBorders(
|
const sides = getSelectionBorders(
|
||||||
point(x1 - SPACING, y1 - SPACING),
|
pointFrom(x1 - SPACING, y1 - SPACING),
|
||||||
point(x2 + SPACING, y2 + SPACING),
|
pointFrom(x2 + SPACING, y2 + SPACING),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
0 as Radians,
|
0 as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -188,7 +196,7 @@ export const getTransformHandleTypeFromCoords = <
|
|||||||
// test to see if x, y are on the line segment
|
// test to see if x, y are on the line segment
|
||||||
if (
|
if (
|
||||||
pointOnLineSegment(
|
pointOnLineSegment(
|
||||||
point(scenePointerX, scenePointerY),
|
pointFrom(scenePointerX, scenePointerY),
|
||||||
side as LineSegment<Point>,
|
side as LineSegment<Point>,
|
||||||
SPACING,
|
SPACING,
|
||||||
)
|
)
|
||||||
@@ -265,10 +273,10 @@ const getSelectionBorders = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
center: Point,
|
center: Point,
|
||||||
angle: Radians,
|
angle: Radians,
|
||||||
) => {
|
) => {
|
||||||
const topLeft = pointRotateRads(point(x1, y1), center, angle);
|
const topLeft = pointRotateRads(pointFrom(x1, y1), center, angle);
|
||||||
const topRight = pointRotateRads(point(x2, y1), center, angle);
|
const topRight = pointRotateRads(pointFrom(x2, y1), center, angle);
|
||||||
const bottomLeft = pointRotateRads(point(x1, y2), center, angle);
|
const bottomLeft = pointRotateRads(pointFrom(x1, y2), center, angle);
|
||||||
const bottomRight = pointRotateRads(point(x2, y2), center, angle);
|
const bottomRight = pointRotateRads(pointFrom(x2, y2), center, angle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
n: [topLeft, topRight],
|
n: [topLeft, topRight],
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import type {
|
|||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { ARROW_TYPE } from "../constants";
|
import { ARROW_TYPE } from "../constants";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ describe("elbow arrow routing", () => {
|
|||||||
}) as ExcalidrawElbowArrowElement;
|
}) as ExcalidrawElbowArrowElement;
|
||||||
scene.insertElement(arrow);
|
scene.insertElement(arrow);
|
||||||
mutateElbowArrow(arrow, scene.getNonDeletedElementsMap(), [
|
mutateElbowArrow(arrow, scene.getNonDeletedElementsMap(), [
|
||||||
point(-45 - arrow.x, -100.1 - arrow.y),
|
pointFrom(-45 - arrow.x, -100.1 - arrow.y),
|
||||||
point(45 - arrow.x, 99.9 - arrow.y),
|
pointFrom(45 - arrow.x, 99.9 - arrow.y),
|
||||||
]);
|
]);
|
||||||
expect(arrow.points).toEqual([
|
expect(arrow.points).toEqual([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
@@ -69,7 +69,7 @@ describe("elbow arrow routing", () => {
|
|||||||
y: -100.1,
|
y: -100.1,
|
||||||
width: 90,
|
width: 90,
|
||||||
height: 200,
|
height: 200,
|
||||||
points: [point(0, 0), point(90, 200)],
|
points: [pointFrom(0, 0), pointFrom(90, 200)],
|
||||||
}) as ExcalidrawElbowArrowElement;
|
}) as ExcalidrawElbowArrowElement;
|
||||||
scene.insertElement(rectangle1);
|
scene.insertElement(rectangle1);
|
||||||
scene.insertElement(rectangle2);
|
scene.insertElement(rectangle2);
|
||||||
@@ -81,7 +81,7 @@ describe("elbow arrow routing", () => {
|
|||||||
expect(arrow.startBinding).not.toBe(null);
|
expect(arrow.startBinding).not.toBe(null);
|
||||||
expect(arrow.endBinding).not.toBe(null);
|
expect(arrow.endBinding).not.toBe(null);
|
||||||
|
|
||||||
mutateElbowArrow(arrow, elementsMap, [point(0, 0), point(90, 200)]);
|
mutateElbowArrow(arrow, elementsMap, [pointFrom(0, 0), pointFrom(90, 200)]);
|
||||||
|
|
||||||
expect(arrow.points).toEqual([
|
expect(arrow.points).toEqual([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Radians } from "../../math";
|
import type { Radians } from "../../math";
|
||||||
import {
|
import {
|
||||||
point,
|
pointFrom,
|
||||||
pointScaleFromOrigin,
|
pointScaleFromOrigin,
|
||||||
pointTranslate,
|
pointTranslate,
|
||||||
vector,
|
vector,
|
||||||
@@ -743,13 +743,13 @@ const getDonglePosition = (
|
|||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
switch (heading) {
|
switch (heading) {
|
||||||
case HEADING_UP:
|
case HEADING_UP:
|
||||||
return point(p[0], bounds[1]);
|
return pointFrom(p[0], bounds[1]);
|
||||||
case HEADING_RIGHT:
|
case HEADING_RIGHT:
|
||||||
return point(bounds[2], p[1]);
|
return pointFrom(bounds[2], p[1]);
|
||||||
case HEADING_DOWN:
|
case HEADING_DOWN:
|
||||||
return point(p[0], bounds[3]);
|
return pointFrom(p[0], bounds[3]);
|
||||||
}
|
}
|
||||||
return point(bounds[0], p[1]);
|
return pointFrom(bounds[0], p[1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const estimateSegmentCount = (
|
const estimateSegmentCount = (
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import type {
|
|||||||
import { API } from "../tests/helpers/api";
|
import { API } from "../tests/helpers/api";
|
||||||
import { getOriginalContainerHeightFromCache } from "./containerCache";
|
import { getOriginalContainerHeightFromCache } from "./containerCache";
|
||||||
import { getTextEditor, updateTextEditor } from "../tests/queries/dom";
|
import { getTextEditor, updateTextEditor } from "../tests/queries/dom";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
// Unmount ReactDOM from root
|
// Unmount ReactDOM from root
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
@@ -42,7 +42,7 @@ describe("textWysiwyg", () => {
|
|||||||
type: "line",
|
type: "line",
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 0,
|
height: 0,
|
||||||
points: [point(0, 0), point(100, 0)],
|
points: [pointFrom(0, 0), pointFrom(100, 0)],
|
||||||
});
|
});
|
||||||
const textSize = 20;
|
const textSize = 20;
|
||||||
const text = API.createElement({
|
const text = API.createElement({
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { Device, InteractiveCanvasAppState, Zoom } from "../types";
|
|||||||
import {
|
import {
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import {
|
import {
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
isIOS,
|
isIOS,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import type { Radians } from "../../math";
|
import type { Radians } from "../../math";
|
||||||
import { point, pointRotateRads } from "../../math";
|
import { pointFrom, pointRotateRads } from "../../math";
|
||||||
|
|
||||||
export type TransformHandleDirection =
|
export type TransformHandleDirection =
|
||||||
| "n"
|
| "n"
|
||||||
@@ -95,8 +96,8 @@ const generateTransformHandle = (
|
|||||||
angle: Radians,
|
angle: Radians,
|
||||||
): TransformHandle => {
|
): TransformHandle => {
|
||||||
const [xx, yy] = pointRotateRads(
|
const [xx, yy] = pointRotateRads(
|
||||||
point(x + width / 2, y + height / 2),
|
pointFrom(x + width / 2, y + height / 2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
return [xx - width / 2, yy - height / 2, width, height];
|
return [xx - width / 2, yy - height / 2, width, height];
|
||||||
@@ -129,6 +130,7 @@ export const getTransformHandlesFromCoords = (
|
|||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
omitSides: { [T in TransformHandleType]?: boolean } = {},
|
omitSides: { [T in TransformHandleType]?: boolean } = {},
|
||||||
margin = 4,
|
margin = 4,
|
||||||
|
spacing = DEFAULT_TRANSFORM_HANDLE_SPACING,
|
||||||
): TransformHandles => {
|
): TransformHandles => {
|
||||||
const size = transformHandleSizes[pointerType];
|
const size = transformHandleSizes[pointerType];
|
||||||
const handleWidth = size / zoom.value;
|
const handleWidth = size / zoom.value;
|
||||||
@@ -140,8 +142,7 @@ export const getTransformHandlesFromCoords = (
|
|||||||
const width = x2 - x1;
|
const width = x2 - x1;
|
||||||
const height = y2 - y1;
|
const height = y2 - y1;
|
||||||
const dashedLineMargin = margin / zoom.value;
|
const dashedLineMargin = margin / zoom.value;
|
||||||
const centeringOffset =
|
const centeringOffset = (size - spacing * 2) / (2 * zoom.value);
|
||||||
(size - DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / (2 * zoom.value);
|
|
||||||
|
|
||||||
const transformHandles: TransformHandles = {
|
const transformHandles: TransformHandles = {
|
||||||
nw: omitSides.nw
|
nw: omitSides.nw
|
||||||
@@ -301,8 +302,10 @@ export const getTransformHandles = (
|
|||||||
rotation: true,
|
rotation: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const dashedLineMargin = isLinearElement(element)
|
const margin = isLinearElement(element)
|
||||||
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
||||||
|
: isImageElement(element)
|
||||||
|
? 0
|
||||||
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
||||||
return getTransformHandlesFromCoords(
|
return getTransformHandlesFromCoords(
|
||||||
getElementAbsoluteCoords(element, elementsMap, true),
|
getElementAbsoluteCoords(element, elementsMap, true),
|
||||||
@@ -310,7 +313,8 @@ export const getTransformHandles = (
|
|||||||
zoom,
|
zoom,
|
||||||
pointerType,
|
pointerType,
|
||||||
omitSides,
|
omitSides,
|
||||||
dashedLineMargin,
|
margin,
|
||||||
|
isImageElement(element) ? 0 : undefined,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,15 @@ export type IframeData =
|
|||||||
| { type: "document"; srcdoc: (theme: Theme) => string }
|
| { type: "document"; srcdoc: (theme: Theme) => string }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type ImageCrop = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
naturalWidth: number;
|
||||||
|
naturalHeight: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
||||||
Readonly<{
|
Readonly<{
|
||||||
type: "image";
|
type: "image";
|
||||||
@@ -140,6 +149,8 @@ export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
|||||||
status: "pending" | "saved" | "error";
|
status: "pending" | "saved" | "error";
|
||||||
/** X and Y scale factors <-1, 1>, used for image axis flipping */
|
/** X and Y scale factors <-1, 1>, used for image axis flipping */
|
||||||
scale: [number, number];
|
scale: [number, number];
|
||||||
|
/** whether an element is cropped */
|
||||||
|
crop: ImageCrop | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type InitializedExcalidrawImageElement = MarkNonNullable<
|
export type InitializedExcalidrawImageElement = MarkNonNullable<
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -24,14 +24,14 @@ import Cascadia from "./assets/CascadiaCode-Regular.woff2";
|
|||||||
import ComicShanns from "./assets/ComicShanns-Regular.woff2";
|
import ComicShanns from "./assets/ComicShanns-Regular.woff2";
|
||||||
import LiberationSans from "./assets/LiberationSans-Regular.woff2";
|
import LiberationSans from "./assets/LiberationSans-Regular.woff2";
|
||||||
|
|
||||||
import LilitaLatin from "https://fonts.gstatic.com/s/lilitaone/v15/i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2";
|
import LilitaLatin from "./assets/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2";
|
||||||
import LilitaLatinExt from "https://fonts.gstatic.com/s/lilitaone/v15/i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2";
|
import LilitaLatinExt from "./assets/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2";
|
||||||
|
|
||||||
import NunitoLatin from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2";
|
import NunitoLatin from "./assets/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2";
|
||||||
import NunitoLatinExt from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2";
|
import NunitoLatinExt from "./assets/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2";
|
||||||
import NunitoCyrilic from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2";
|
import NunitoCyrilic from "./assets/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2";
|
||||||
import NunitoCyrilicExt from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2";
|
import NunitoCyrilicExt from "./assets/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2";
|
||||||
import NunitoVietnamese from "https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2";
|
import NunitoVietnamese from "./assets/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2";
|
||||||
|
|
||||||
export class Fonts {
|
export class Fonts {
|
||||||
// it's ok to track fonts across multiple instances only once, so let's use
|
// it's ok to track fonts across multiple instances only once, so let's use
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { getElementLineSegments } from "./element/bounds";
|
|||||||
import { doLineSegmentsIntersect, elementsOverlappingBBox } from "../utils/";
|
import { doLineSegmentsIntersect, elementsOverlappingBBox } from "../utils/";
|
||||||
import { isFrameElement, isFrameLikeElement } from "./element/typeChecks";
|
import { isFrameElement, isFrameLikeElement } from "./element/typeChecks";
|
||||||
import type { ReadonlySetLike } from "./utility-types";
|
import type { ReadonlySetLike } from "./utility-types";
|
||||||
import { isPointWithinBounds, point } from "../math";
|
import { isPointWithinBounds, pointFrom } from "../math";
|
||||||
|
|
||||||
// --------------------------- Frame State ------------------------------------
|
// --------------------------- Frame State ------------------------------------
|
||||||
export const bindElementsToFramesAfterDuplication = (
|
export const bindElementsToFramesAfterDuplication = (
|
||||||
@@ -159,9 +159,9 @@ export const isCursorInFrame = (
|
|||||||
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
|
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
|
|
||||||
return isPointWithinBounds(
|
return isPointWithinBounds(
|
||||||
point(fx1, fy1),
|
pointFrom(fx1, fy1),
|
||||||
point(cursorCoords.x, cursorCoords.y),
|
pointFrom(cursorCoords.x, cursorCoords.y),
|
||||||
point(fx2, fy2),
|
pointFrom(fx2, fy2),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -328,7 +328,9 @@
|
|||||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
||||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
||||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
||||||
"disableSnapping": "Hold CtrlOrCmd to disable snapping"
|
"disableSnapping": "Hold CtrlOrCmd to disable snapping",
|
||||||
|
"enterCropEditor": "Double click the image or press ENTER to crop the image",
|
||||||
|
"leaveCropEditor": "Click outside the image or press ENTER or ESCAPE to finish cropping"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "Cannot show preview",
|
"cannotShowPreview": "Cannot show preview",
|
||||||
@@ -399,7 +401,9 @@
|
|||||||
"zoomToSelection": "Zoom to selection",
|
"zoomToSelection": "Zoom to selection",
|
||||||
"toggleElementLock": "Lock/unlock selection",
|
"toggleElementLock": "Lock/unlock selection",
|
||||||
"movePageUpDown": "Move page up/down",
|
"movePageUpDown": "Move page up/down",
|
||||||
"movePageLeftRight": "Move page left/right"
|
"movePageLeftRight": "Move page left/right",
|
||||||
|
"cropStart": "Crop image",
|
||||||
|
"cropFinish": "Finish image cropping"
|
||||||
},
|
},
|
||||||
"clearCanvasDialog": {
|
"clearCanvasDialog": {
|
||||||
"title": "Clear canvas"
|
"title": "Clear canvas"
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import oc from "open-color";
|
|||||||
import {
|
import {
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
@@ -62,6 +63,7 @@ import type {
|
|||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
|
ExcalidrawImageElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
GroupId,
|
GroupId,
|
||||||
@@ -307,38 +309,42 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ElementSelectionBorder = {
|
||||||
|
angle: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
selectionColors: string[];
|
||||||
|
dashed?: boolean;
|
||||||
|
cx: number;
|
||||||
|
cy: number;
|
||||||
|
activeEmbeddable: boolean;
|
||||||
|
padding?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const renderSelectionBorder = (
|
const renderSelectionBorder = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
elementProperties: {
|
elementProperties: ElementSelectionBorder,
|
||||||
angle: number;
|
|
||||||
elementX1: number;
|
|
||||||
elementY1: number;
|
|
||||||
elementX2: number;
|
|
||||||
elementY2: number;
|
|
||||||
selectionColors: string[];
|
|
||||||
dashed?: boolean;
|
|
||||||
cx: number;
|
|
||||||
cy: number;
|
|
||||||
activeEmbeddable: boolean;
|
|
||||||
},
|
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
angle,
|
angle,
|
||||||
elementX1,
|
x1,
|
||||||
elementY1,
|
y1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors,
|
selectionColors,
|
||||||
cx,
|
cx,
|
||||||
cy,
|
cy,
|
||||||
dashed,
|
dashed,
|
||||||
activeEmbeddable,
|
activeEmbeddable,
|
||||||
} = elementProperties;
|
} = elementProperties;
|
||||||
const elementWidth = elementX2 - elementX1;
|
const elementWidth = x2 - x1;
|
||||||
const elementHeight = elementY2 - elementY1;
|
const elementHeight = y2 - y1;
|
||||||
|
|
||||||
const padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
const padding =
|
||||||
|
elementProperties.padding ?? DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
||||||
|
|
||||||
const linePadding = padding / appState.zoom.value;
|
const linePadding = padding / appState.zoom.value;
|
||||||
const lineWidth = 8 / appState.zoom.value;
|
const lineWidth = 8 / appState.zoom.value;
|
||||||
@@ -360,8 +366,8 @@ const renderSelectionBorder = (
|
|||||||
context.lineDashOffset = (lineWidth + spaceWidth) * index;
|
context.lineDashOffset = (lineWidth + spaceWidth) * index;
|
||||||
strokeRectWithRotation(
|
strokeRectWithRotation(
|
||||||
context,
|
context,
|
||||||
elementX1 - linePadding,
|
x1 - linePadding,
|
||||||
elementY1 - linePadding,
|
y1 - linePadding,
|
||||||
elementWidth + linePadding * 2,
|
elementWidth + linePadding * 2,
|
||||||
elementHeight + linePadding * 2,
|
elementHeight + linePadding * 2,
|
||||||
cx,
|
cx,
|
||||||
@@ -433,18 +439,17 @@ const renderElementsBoxHighlight = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getSelectionFromElements = (elements: ExcalidrawElement[]) => {
|
const getSelectionFromElements = (elements: ExcalidrawElement[]) => {
|
||||||
const [elementX1, elementY1, elementX2, elementY2] =
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||||
getCommonBounds(elements);
|
|
||||||
return {
|
return {
|
||||||
angle: 0,
|
angle: 0,
|
||||||
elementX1,
|
x1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY1,
|
y1,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors: ["rgb(0,118,255)"],
|
selectionColors: ["rgb(0,118,255)"],
|
||||||
dashed: false,
|
dashed: false,
|
||||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
cx: x1 + (x2 - x1) / 2,
|
||||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
cy: y1 + (y2 - y1) / 2,
|
||||||
activeEmbeddable: false,
|
activeEmbeddable: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -594,6 +599,111 @@ const renderTransformHandles = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderCropHandles = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
renderConfig: InteractiveCanvasRenderConfig,
|
||||||
|
appState: InteractiveCanvasAppState,
|
||||||
|
croppingElement: ExcalidrawImageElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): void => {
|
||||||
|
const [x1, y1, , , cx, cy] = getElementAbsoluteCoords(
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const LINE_WIDTH = 3;
|
||||||
|
const LINE_LENGTH = 20;
|
||||||
|
|
||||||
|
const ZOOMED_LINE_WIDTH = LINE_WIDTH / appState.zoom.value;
|
||||||
|
const ZOOMED_HALF_LINE_WIDTH = ZOOMED_LINE_WIDTH / 2;
|
||||||
|
|
||||||
|
const HALF_WIDTH = cx - x1 + ZOOMED_LINE_WIDTH;
|
||||||
|
const HALF_HEIGHT = cy - y1 + ZOOMED_LINE_WIDTH;
|
||||||
|
|
||||||
|
const HORIZONTAL_LINE_LENGTH = Math.min(
|
||||||
|
LINE_LENGTH / appState.zoom.value,
|
||||||
|
HALF_WIDTH,
|
||||||
|
);
|
||||||
|
const VERTICAL_LINE_LENGTH = Math.min(
|
||||||
|
LINE_LENGTH / appState.zoom.value,
|
||||||
|
HALF_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.fillStyle = renderConfig.selectionColor;
|
||||||
|
context.strokeStyle = renderConfig.selectionColor;
|
||||||
|
context.lineWidth = ZOOMED_LINE_WIDTH;
|
||||||
|
|
||||||
|
const handles: Array<
|
||||||
|
[
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
]
|
||||||
|
> = [
|
||||||
|
[
|
||||||
|
// x, y
|
||||||
|
[-HALF_WIDTH, -HALF_HEIGHT],
|
||||||
|
// horizontal line: first start and to
|
||||||
|
[0, ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[HORIZONTAL_LINE_LENGTH, ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
// vertical line: second start and to
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, 0],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, -HALF_HEIGHT],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[
|
||||||
|
-HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
],
|
||||||
|
[0, 0],
|
||||||
|
[0, VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-HALF_WIDTH, HALF_HEIGHT],
|
||||||
|
[0, -ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[HORIZONTAL_LINE_LENGTH, -ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, 0],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, -VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, HALF_HEIGHT],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, -ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[
|
||||||
|
-HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
-ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
],
|
||||||
|
[0, 0],
|
||||||
|
[0, -VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
handles.forEach((handle) => {
|
||||||
|
const [[x, y], [x1s, y1s], [x1t, y1t], [x2s, y2s], [x2t, y2t]] = handle;
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.translate(cx, cy);
|
||||||
|
context.rotate(croppingElement.angle);
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + x1s, y + y1s);
|
||||||
|
context.lineTo(x + x1t, y + y1t);
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + x2s, y + y2s);
|
||||||
|
context.lineTo(x + x2t, y + y2t);
|
||||||
|
context.stroke();
|
||||||
|
context.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
};
|
||||||
|
|
||||||
const renderTextBox = (
|
const renderTextBox = (
|
||||||
text: NonDeleted<ExcalidrawTextElement>,
|
text: NonDeleted<ExcalidrawTextElement>,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
@@ -671,7 +781,7 @@ const _renderInteractiveScene = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Paint selection element
|
// Paint selection element
|
||||||
if (appState.selectionElement) {
|
if (appState.selectionElement && !appState.isCropping) {
|
||||||
try {
|
try {
|
||||||
renderSelectionElement(
|
renderSelectionElement(
|
||||||
appState.selectionElement,
|
appState.selectionElement,
|
||||||
@@ -783,18 +893,7 @@ const _renderInteractiveScene = ({
|
|||||||
// Optimisation for finding quickly relevant element ids
|
// Optimisation for finding quickly relevant element ids
|
||||||
const locallySelectedIds = arrayToMap(selectedElements);
|
const locallySelectedIds = arrayToMap(selectedElements);
|
||||||
|
|
||||||
const selections: {
|
const selections: ElementSelectionBorder[] = [];
|
||||||
angle: number;
|
|
||||||
elementX1: number;
|
|
||||||
elementY1: number;
|
|
||||||
elementX2: number;
|
|
||||||
elementY2: number;
|
|
||||||
selectionColors: string[];
|
|
||||||
dashed?: boolean;
|
|
||||||
cx: number;
|
|
||||||
cy: number;
|
|
||||||
activeEmbeddable: boolean;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
for (const element of elementsMap.values()) {
|
for (const element of elementsMap.values()) {
|
||||||
const selectionColors = [];
|
const selectionColors = [];
|
||||||
@@ -833,14 +932,17 @@ const _renderInteractiveScene = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectionColors.length) {
|
if (selectionColors.length) {
|
||||||
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
getElementAbsoluteCoords(element, elementsMap, true);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
true,
|
||||||
|
);
|
||||||
selections.push({
|
selections.push({
|
||||||
angle: element.angle,
|
angle: element.angle,
|
||||||
elementX1,
|
x1,
|
||||||
elementY1,
|
y1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors,
|
selectionColors,
|
||||||
dashed: !!remoteClients,
|
dashed: !!remoteClients,
|
||||||
cx,
|
cx,
|
||||||
@@ -848,24 +950,28 @@ const _renderInteractiveScene = ({
|
|||||||
activeEmbeddable:
|
activeEmbeddable:
|
||||||
appState.activeEmbeddable?.element === element &&
|
appState.activeEmbeddable?.element === element &&
|
||||||
appState.activeEmbeddable.state === "active",
|
appState.activeEmbeddable.state === "active",
|
||||||
|
padding:
|
||||||
|
element.id === appState.croppingElementId ||
|
||||||
|
isImageElement(element)
|
||||||
|
? 0
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addSelectionForGroupId = (groupId: GroupId) => {
|
const addSelectionForGroupId = (groupId: GroupId) => {
|
||||||
const groupElements = getElementsInGroup(elementsMap, groupId);
|
const groupElements = getElementsInGroup(elementsMap, groupId);
|
||||||
const [elementX1, elementY1, elementX2, elementY2] =
|
const [x1, y1, x2, y2] = getCommonBounds(groupElements);
|
||||||
getCommonBounds(groupElements);
|
|
||||||
selections.push({
|
selections.push({
|
||||||
angle: 0,
|
angle: 0,
|
||||||
elementX1,
|
x1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY1,
|
y1,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors: [oc.black],
|
selectionColors: [oc.black],
|
||||||
dashed: true,
|
dashed: true,
|
||||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
cx: x1 + (x2 - x1) / 2,
|
||||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
cy: y1 + (y2 - y1) / 2,
|
||||||
activeEmbeddable: false,
|
activeEmbeddable: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -900,7 +1006,9 @@ const _renderInteractiveScene = ({
|
|||||||
!appState.viewModeEnabled &&
|
!appState.viewModeEnabled &&
|
||||||
showBoundingBox &&
|
showBoundingBox &&
|
||||||
// do not show transform handles when text is being edited
|
// do not show transform handles when text is being edited
|
||||||
!isTextElement(appState.editingTextElement)
|
!isTextElement(appState.editingTextElement) &&
|
||||||
|
// do not show transform handles when image is being cropped
|
||||||
|
!appState.croppingElementId
|
||||||
) {
|
) {
|
||||||
renderTransformHandles(
|
renderTransformHandles(
|
||||||
context,
|
context,
|
||||||
@@ -910,6 +1018,20 @@ const _renderInteractiveScene = ({
|
|||||||
selectedElements[0].angle,
|
selectedElements[0].angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appState.croppingElementId && !appState.isCropping) {
|
||||||
|
const croppingElement = elementsMap.get(appState.croppingElementId);
|
||||||
|
|
||||||
|
if (croppingElement && isImageElement(croppingElement)) {
|
||||||
|
renderCropHandles(
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (selectedElements.length > 1 && !appState.isRotating) {
|
} else if (selectedElements.length > 1 && !appState.isRotating) {
|
||||||
const dashedLinePadding =
|
const dashedLinePadding =
|
||||||
(DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
(DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
isArrowElement,
|
isArrowElement,
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isMagicFrameElement,
|
isMagicFrameElement,
|
||||||
|
isImageElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
import { getElementAbsoluteCoords } from "../element/bounds";
|
import { getElementAbsoluteCoords } from "../element/bounds";
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
@@ -61,6 +62,7 @@ import { ShapeCache } from "../scene/ShapeCache";
|
|||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { isRightAngleRads } from "../../math";
|
import { isRightAngleRads } from "../../math";
|
||||||
import { getCornerRadius } from "../shapes";
|
import { getCornerRadius } from "../shapes";
|
||||||
|
import { getUncroppedImageElement } from "../element/cropElement";
|
||||||
|
|
||||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||||
// as a temp hack to make images in dark theme look closer to original
|
// as a temp hack to make images in dark theme look closer to original
|
||||||
@@ -434,8 +436,22 @@ const drawElementOnCanvas = (
|
|||||||
);
|
);
|
||||||
context.clip();
|
context.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { x, y, width, height } = element.crop
|
||||||
|
? element.crop
|
||||||
|
: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: img.naturalWidth,
|
||||||
|
height: img.naturalHeight,
|
||||||
|
};
|
||||||
|
|
||||||
context.drawImage(
|
context.drawImage(
|
||||||
img,
|
img,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
0 /* hardcoded for the selection box*/,
|
0 /* hardcoded for the selection box*/,
|
||||||
0,
|
0,
|
||||||
element.width,
|
element.width,
|
||||||
@@ -921,14 +937,53 @@ export const renderElement = (
|
|||||||
context.imageSmoothingEnabled = false;
|
context.imageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawElementFromCanvas(
|
if (
|
||||||
elementWithCanvas,
|
element.id === appState.croppingElementId &&
|
||||||
context,
|
isImageElement(elementWithCanvas.element) &&
|
||||||
|
elementWithCanvas.element.crop !== null
|
||||||
|
) {
|
||||||
|
context.save();
|
||||||
|
context.globalAlpha = 0.1;
|
||||||
|
|
||||||
|
const uncroppedElementCanvas = generateElementCanvas(
|
||||||
|
getUncroppedImageElement(elementWithCanvas.element, elementsMap),
|
||||||
|
allElementsMap,
|
||||||
|
appState.zoom,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uncroppedElementCanvas) {
|
||||||
|
drawElementFromCanvas(
|
||||||
|
uncroppedElementCanvas,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
allElementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
const _elementWithCanvas = generateElementCanvas(
|
||||||
|
elementWithCanvas.element,
|
||||||
|
allElementsMap,
|
||||||
|
appState.zoom,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
appState,
|
appState,
|
||||||
allElementsMap,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (_elementWithCanvas) {
|
||||||
|
drawElementFromCanvas(
|
||||||
|
_elementWithCanvas,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
allElementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// reset
|
// reset
|
||||||
context.imageSmoothingEnabled = currentImageSmoothingStatus;
|
context.imageSmoothingEnabled = currentImageSmoothingStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { point, type GlobalPoint, type LocalPoint } from "../../math";
|
import { pointFrom, type GlobalPoint, type LocalPoint } from "../../math";
|
||||||
import { THEME } from "../constants";
|
import { THEME } from "../constants";
|
||||||
import type { PointSnapLine, PointerSnapLine } from "../snapping";
|
import type { PointSnapLine, PointerSnapLine } from "../snapping";
|
||||||
import type { InteractiveCanvasAppState } from "../types";
|
import type { InteractiveCanvasAppState } from "../types";
|
||||||
@@ -140,27 +140,31 @@ const drawGapLine = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
// (1)
|
// (1)
|
||||||
if (!appState.zenModeEnabled) {
|
if (!appState.zenModeEnabled) {
|
||||||
drawLine(
|
drawLine(
|
||||||
point(from[0], from[1] - FULL),
|
pointFrom(from[0], from[1] - FULL),
|
||||||
point(from[0], from[1] + FULL),
|
pointFrom(from[0], from[1] + FULL),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// (3)
|
// (3)
|
||||||
drawLine(
|
drawLine(
|
||||||
point(halfPoint[0] - QUARTER, halfPoint[1] - HALF),
|
pointFrom(halfPoint[0] - QUARTER, halfPoint[1] - HALF),
|
||||||
point(halfPoint[0] - QUARTER, halfPoint[1] + HALF),
|
pointFrom(halfPoint[0] - QUARTER, halfPoint[1] + HALF),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
drawLine(
|
drawLine(
|
||||||
point(halfPoint[0] + QUARTER, halfPoint[1] - HALF),
|
pointFrom(halfPoint[0] + QUARTER, halfPoint[1] - HALF),
|
||||||
point(halfPoint[0] + QUARTER, halfPoint[1] + HALF),
|
pointFrom(halfPoint[0] + QUARTER, halfPoint[1] + HALF),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!appState.zenModeEnabled) {
|
if (!appState.zenModeEnabled) {
|
||||||
// (4)
|
// (4)
|
||||||
drawLine(point(to[0], to[1] - FULL), point(to[0], to[1] + FULL), context);
|
drawLine(
|
||||||
|
pointFrom(to[0], to[1] - FULL),
|
||||||
|
pointFrom(to[0], to[1] + FULL),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
drawLine(from, to, context);
|
drawLine(from, to, context);
|
||||||
@@ -170,27 +174,31 @@ const drawGapLine = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
// (1)
|
// (1)
|
||||||
if (!appState.zenModeEnabled) {
|
if (!appState.zenModeEnabled) {
|
||||||
drawLine(
|
drawLine(
|
||||||
point(from[0] - FULL, from[1]),
|
pointFrom(from[0] - FULL, from[1]),
|
||||||
point(from[0] + FULL, from[1]),
|
pointFrom(from[0] + FULL, from[1]),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// (3)
|
// (3)
|
||||||
drawLine(
|
drawLine(
|
||||||
point(halfPoint[0] - HALF, halfPoint[1] - QUARTER),
|
pointFrom(halfPoint[0] - HALF, halfPoint[1] - QUARTER),
|
||||||
point(halfPoint[0] + HALF, halfPoint[1] - QUARTER),
|
pointFrom(halfPoint[0] + HALF, halfPoint[1] - QUARTER),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
drawLine(
|
drawLine(
|
||||||
point(halfPoint[0] - HALF, halfPoint[1] + QUARTER),
|
pointFrom(halfPoint[0] - HALF, halfPoint[1] + QUARTER),
|
||||||
point(halfPoint[0] + HALF, halfPoint[1] + QUARTER),
|
pointFrom(halfPoint[0] + HALF, halfPoint[1] + QUARTER),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!appState.zenModeEnabled) {
|
if (!appState.zenModeEnabled) {
|
||||||
// (4)
|
// (4)
|
||||||
drawLine(point(to[0] - FULL, to[1]), point(to[0] + FULL, to[1]), context);
|
drawLine(
|
||||||
|
pointFrom(to[0] - FULL, to[1]),
|
||||||
|
pointFrom(to[0] + FULL, to[1]),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
drawLine(from, to, context);
|
drawLine(from, to, context);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
SVG_NS,
|
SVG_NS,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { normalizeLink, toValidURL } from "../data/url";
|
import { normalizeLink, toValidURL } from "../data/url";
|
||||||
import { getElementAbsoluteCoords } from "../element";
|
import { getElementAbsoluteCoords, hashString } from "../element";
|
||||||
import {
|
import {
|
||||||
createPlaceholderEmbeddableLabel,
|
createPlaceholderEmbeddableLabel,
|
||||||
getEmbedLink,
|
getEmbedLink,
|
||||||
@@ -37,6 +37,7 @@ import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
|
|||||||
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||||
|
import { getUncroppedWidthAndHeight } from "../element/cropElement";
|
||||||
|
|
||||||
const roughSVGDrawWithPrecision = (
|
const roughSVGDrawWithPrecision = (
|
||||||
rsvg: RoughSVG,
|
rsvg: RoughSVG,
|
||||||
@@ -410,19 +411,36 @@ const renderElementToSvg = (
|
|||||||
const fileData =
|
const fileData =
|
||||||
isInitializedImageElement(element) && files[element.fileId];
|
isInitializedImageElement(element) && files[element.fileId];
|
||||||
if (fileData) {
|
if (fileData) {
|
||||||
const symbolId = `image-${fileData.id}`;
|
const cropHash = hashString(JSON.stringify(element.crop));
|
||||||
|
const symbolId = `image-${fileData.id}-${cropHash}`;
|
||||||
let symbol = svgRoot.querySelector(`#${symbolId}`);
|
let symbol = svgRoot.querySelector(`#${symbolId}`);
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
symbol = svgRoot.ownerDocument!.createElementNS(SVG_NS, "symbol");
|
symbol = svgRoot.ownerDocument!.createElementNS(SVG_NS, "symbol");
|
||||||
symbol.id = symbolId;
|
symbol.id = symbolId;
|
||||||
|
|
||||||
const image = svgRoot.ownerDocument!.createElementNS(SVG_NS, "image");
|
const image = svgRoot.ownerDocument!.createElementNS(SVG_NS, "image");
|
||||||
|
|
||||||
image.setAttribute("width", "100%");
|
|
||||||
image.setAttribute("height", "100%");
|
|
||||||
image.setAttribute("href", fileData.dataURL);
|
image.setAttribute("href", fileData.dataURL);
|
||||||
image.setAttribute("preserveAspectRatio", "none");
|
image.setAttribute("preserveAspectRatio", "none");
|
||||||
|
|
||||||
|
if (element.crop) {
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
symbol.setAttribute(
|
||||||
|
"viewBox",
|
||||||
|
`${
|
||||||
|
element.crop.x / (element.crop.naturalWidth / uncroppedWidth)
|
||||||
|
} ${
|
||||||
|
element.crop.y / (element.crop.naturalHeight / uncroppedHeight)
|
||||||
|
} ${width} ${height}`,
|
||||||
|
);
|
||||||
|
image.setAttribute("width", `${uncroppedWidth}`);
|
||||||
|
image.setAttribute("height", `${uncroppedHeight}`);
|
||||||
|
} else {
|
||||||
|
image.setAttribute("width", "100%");
|
||||||
|
image.setAttribute("height", "100%");
|
||||||
|
}
|
||||||
|
|
||||||
symbol.appendChild(image);
|
symbol.appendChild(image);
|
||||||
|
|
||||||
root.prepend(symbol);
|
root.prepend(symbol);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
import { canChangeRoundness } from "./comparisons";
|
import { canChangeRoundness } from "./comparisons";
|
||||||
import type { EmbedsValidationStatus } from "../types";
|
import type { EmbedsValidationStatus } from "../types";
|
||||||
import {
|
import {
|
||||||
point,
|
pointFrom,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
@@ -408,7 +408,7 @@ export const _generateElementShape = (
|
|||||||
// initial position to it
|
// initial position to it
|
||||||
const points = element.points.length
|
const points = element.points.length
|
||||||
? element.points
|
? element.points
|
||||||
: [point<LocalPoint>(0, 0)];
|
: [pointFrom<LocalPoint>(0, 0)];
|
||||||
|
|
||||||
if (isElbowArrow(element)) {
|
if (isElbowArrow(element)) {
|
||||||
shape = [
|
shape = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
isPoint,
|
isPoint,
|
||||||
point,
|
pointFrom,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
pointFromPair,
|
pointFromPair,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
@@ -167,15 +167,15 @@ export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
? getClosedCurveShape<Point>(
|
? getClosedCurveShape<Point>(
|
||||||
element,
|
element,
|
||||||
roughShape,
|
roughShape,
|
||||||
point<Point>(element.x, element.y),
|
pointFrom<Point>(element.x, element.y),
|
||||||
element.angle,
|
element.angle,
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
)
|
)
|
||||||
: getCurveShape<Point>(
|
: getCurveShape<Point>(
|
||||||
roughShape,
|
roughShape,
|
||||||
point<Point>(element.x, element.y),
|
pointFrom<Point>(element.x, element.y),
|
||||||
element.angle,
|
element.angle,
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ export const getElementShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
const [, , , , cx, cy] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
return getFreedrawShape(
|
return getFreedrawShape(
|
||||||
element,
|
element,
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
shouldTestInside(element),
|
shouldTestInside(element),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ export const getControlPointsForBezierCurve = <
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ops = getCurvePathOps(shape[0]);
|
const ops = getCurvePathOps(shape[0]);
|
||||||
let currentP = point<P>(0, 0);
|
let currentP = pointFrom<P>(0, 0);
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
let controlPoints: P[] | null = null;
|
let controlPoints: P[] | null = null;
|
||||||
@@ -249,9 +249,9 @@ export const getControlPointsForBezierCurve = <
|
|||||||
}
|
}
|
||||||
if (op === "bcurveTo") {
|
if (op === "bcurveTo") {
|
||||||
const p0 = currentP;
|
const p0 = currentP;
|
||||||
const p1 = point<P>(data[0], data[1]);
|
const p1 = pointFrom<P>(data[0], data[1]);
|
||||||
const p2 = point<P>(data[2], data[3]);
|
const p2 = pointFrom<P>(data[2], data[3]);
|
||||||
const p3 = point<P>(data[4], data[5]);
|
const p3 = pointFrom<P>(data[4], data[5]);
|
||||||
const distance = pointDistance(p3, endPoint);
|
const distance = pointDistance(p3, endPoint);
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
@@ -279,7 +279,7 @@ export const getBezierXY = <P extends GlobalPoint | LocalPoint>(
|
|||||||
p0[idx] * Math.pow(t, 3);
|
p0[idx] * Math.pow(t, 3);
|
||||||
const tx = equation(t, 0);
|
const tx = equation(t, 0);
|
||||||
const ty = equation(t, 1);
|
const ty = equation(t, 1);
|
||||||
return point(tx, ty);
|
return pointFrom(tx, ty);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
||||||
@@ -301,12 +301,12 @@ const getPointsInBezierCurve = <P extends GlobalPoint | LocalPoint>(
|
|||||||
controlPoints[3],
|
controlPoints[3],
|
||||||
t,
|
t,
|
||||||
);
|
);
|
||||||
pointsOnCurve.push(point(p[0], p[1]));
|
pointsOnCurve.push(pointFrom(p[0], p[1]));
|
||||||
t -= 0.05;
|
t -= 0.05;
|
||||||
}
|
}
|
||||||
if (pointsOnCurve.length) {
|
if (pointsOnCurve.length) {
|
||||||
if (pointsEqual(pointsOnCurve.at(-1)!, endPoint)) {
|
if (pointsEqual(pointsOnCurve.at(-1)!, endPoint)) {
|
||||||
pointsOnCurve.push(point(endPoint[0], endPoint[1]));
|
pointsOnCurve.push(pointFrom(endPoint[0], endPoint[1]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pointsOnCurve;
|
return pointsOnCurve;
|
||||||
@@ -393,24 +393,24 @@ export const aabbForElement = (
|
|||||||
midY: element.y + element.height / 2,
|
midY: element.y + element.height / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const center = point(bbox.midX, bbox.midY);
|
const center = pointFrom(bbox.midX, bbox.midY);
|
||||||
const [topLeftX, topLeftY] = pointRotateRads(
|
const [topLeftX, topLeftY] = pointRotateRads(
|
||||||
point(bbox.minX, bbox.minY),
|
pointFrom(bbox.minX, bbox.minY),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [topRightX, topRightY] = pointRotateRads(
|
const [topRightX, topRightY] = pointRotateRads(
|
||||||
point(bbox.maxX, bbox.minY),
|
pointFrom(bbox.maxX, bbox.minY),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [bottomRightX, bottomRightY] = pointRotateRads(
|
const [bottomRightX, bottomRightY] = pointRotateRads(
|
||||||
point(bbox.maxX, bbox.maxY),
|
pointFrom(bbox.maxX, bbox.maxY),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const [bottomLeftX, bottomLeftY] = pointRotateRads(
|
const [bottomLeftX, bottomLeftY] = pointRotateRads(
|
||||||
point(bbox.minX, bbox.maxY),
|
pointFrom(bbox.minX, bbox.maxY),
|
||||||
center,
|
center,
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
@@ -442,14 +442,14 @@ export const pointInsideBounds = <P extends GlobalPoint | LocalPoint>(
|
|||||||
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];
|
||||||
|
|
||||||
export const aabbsOverlapping = (a: Bounds, b: Bounds) =>
|
export const aabbsOverlapping = (a: Bounds, b: Bounds) =>
|
||||||
pointInsideBounds(point(a[0], a[1]), b) ||
|
pointInsideBounds(pointFrom(a[0], a[1]), b) ||
|
||||||
pointInsideBounds(point(a[2], a[1]), b) ||
|
pointInsideBounds(pointFrom(a[2], a[1]), b) ||
|
||||||
pointInsideBounds(point(a[2], a[3]), b) ||
|
pointInsideBounds(pointFrom(a[2], a[3]), b) ||
|
||||||
pointInsideBounds(point(a[0], a[3]), b) ||
|
pointInsideBounds(pointFrom(a[0], a[3]), b) ||
|
||||||
pointInsideBounds(point(b[0], b[1]), a) ||
|
pointInsideBounds(pointFrom(b[0], b[1]), a) ||
|
||||||
pointInsideBounds(point(b[2], b[1]), a) ||
|
pointInsideBounds(pointFrom(b[2], b[1]), a) ||
|
||||||
pointInsideBounds(point(b[2], b[3]), a) ||
|
pointInsideBounds(pointFrom(b[2], b[3]), a) ||
|
||||||
pointInsideBounds(point(b[0], b[3]), a);
|
pointInsideBounds(pointFrom(b[0], b[3]), a);
|
||||||
|
|
||||||
export const getCornerRadius = (x: number, element: ExcalidrawElement) => {
|
export const getCornerRadius = (x: number, element: ExcalidrawElement) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { InclusiveRange } from "../math";
|
import type { InclusiveRange } from "../math";
|
||||||
import {
|
import {
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
rangeInclusive,
|
rangeInclusive,
|
||||||
rangeIntersection,
|
rangeIntersection,
|
||||||
@@ -228,52 +228,52 @@ export const getElementsCorners = (
|
|||||||
!boundingBoxCorners
|
!boundingBoxCorners
|
||||||
) {
|
) {
|
||||||
const leftMid = pointRotateRads<GlobalPoint>(
|
const leftMid = pointRotateRads<GlobalPoint>(
|
||||||
point(x1, y1 + halfHeight),
|
pointFrom(x1, y1 + halfHeight),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const topMid = pointRotateRads<GlobalPoint>(
|
const topMid = pointRotateRads<GlobalPoint>(
|
||||||
point(x1 + halfWidth, y1),
|
pointFrom(x1 + halfWidth, y1),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const rightMid = pointRotateRads<GlobalPoint>(
|
const rightMid = pointRotateRads<GlobalPoint>(
|
||||||
point(x2, y1 + halfHeight),
|
pointFrom(x2, y1 + halfHeight),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const bottomMid = pointRotateRads<GlobalPoint>(
|
const bottomMid = pointRotateRads<GlobalPoint>(
|
||||||
point(x1 + halfWidth, y2),
|
pointFrom(x1 + halfWidth, y2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const center = point<GlobalPoint>(cx, cy);
|
const center = pointFrom<GlobalPoint>(cx, cy);
|
||||||
|
|
||||||
result = omitCenter
|
result = omitCenter
|
||||||
? [leftMid, topMid, rightMid, bottomMid]
|
? [leftMid, topMid, rightMid, bottomMid]
|
||||||
: [leftMid, topMid, rightMid, bottomMid, center];
|
: [leftMid, topMid, rightMid, bottomMid, center];
|
||||||
} else {
|
} else {
|
||||||
const topLeft = pointRotateRads<GlobalPoint>(
|
const topLeft = pointRotateRads<GlobalPoint>(
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const topRight = pointRotateRads<GlobalPoint>(
|
const topRight = pointRotateRads<GlobalPoint>(
|
||||||
point(x2, y1),
|
pointFrom(x2, y1),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const bottomLeft = pointRotateRads<GlobalPoint>(
|
const bottomLeft = pointRotateRads<GlobalPoint>(
|
||||||
point(x1, y2),
|
pointFrom(x1, y2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const bottomRight = pointRotateRads<GlobalPoint>(
|
const bottomRight = pointRotateRads<GlobalPoint>(
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
point(cx, cy),
|
pointFrom(cx, cy),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const center = point<GlobalPoint>(cx, cy);
|
const center = pointFrom<GlobalPoint>(cx, cy);
|
||||||
|
|
||||||
result = omitCenter
|
result = omitCenter
|
||||||
? [topLeft, topRight, bottomLeft, bottomRight]
|
? [topLeft, topRight, bottomLeft, bottomRight]
|
||||||
@@ -287,18 +287,18 @@ export const getElementsCorners = (
|
|||||||
const width = maxX - minX;
|
const width = maxX - minX;
|
||||||
const height = maxY - minY;
|
const height = maxY - minY;
|
||||||
|
|
||||||
const topLeft = point<GlobalPoint>(minX, minY);
|
const topLeft = pointFrom<GlobalPoint>(minX, minY);
|
||||||
const topRight = point<GlobalPoint>(maxX, minY);
|
const topRight = pointFrom<GlobalPoint>(maxX, minY);
|
||||||
const bottomLeft = point<GlobalPoint>(minX, maxY);
|
const bottomLeft = pointFrom<GlobalPoint>(minX, maxY);
|
||||||
const bottomRight = point<GlobalPoint>(maxX, maxY);
|
const bottomRight = pointFrom<GlobalPoint>(maxX, maxY);
|
||||||
const center = point<GlobalPoint>(minX + width / 2, minY + height / 2);
|
const center = pointFrom<GlobalPoint>(minX + width / 2, minY + height / 2);
|
||||||
|
|
||||||
result = omitCenter
|
result = omitCenter
|
||||||
? [topLeft, topRight, bottomLeft, bottomRight]
|
? [topLeft, topRight, bottomLeft, bottomRight]
|
||||||
: [topLeft, topRight, bottomLeft, bottomRight, center];
|
: [topLeft, topRight, bottomLeft, bottomRight, center];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.map((p) => point(round(p[0]), round(p[1])));
|
return result.map((p) => pointFrom(round(p[0]), round(p[1])));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getReferenceElements = (
|
const getReferenceElements = (
|
||||||
@@ -375,8 +375,11 @@ export const getVisibleGaps = (
|
|||||||
horizontalGaps.push({
|
horizontalGaps.push({
|
||||||
startBounds,
|
startBounds,
|
||||||
endBounds,
|
endBounds,
|
||||||
startSide: [point(startMaxX, startMinY), point(startMaxX, startMaxY)],
|
startSide: [
|
||||||
endSide: [point(endMinX, endMinY), point(endMinX, endMaxY)],
|
pointFrom(startMaxX, startMinY),
|
||||||
|
pointFrom(startMaxX, startMaxY),
|
||||||
|
],
|
||||||
|
endSide: [pointFrom(endMinX, endMinY), pointFrom(endMinX, endMaxY)],
|
||||||
length: endMinX - startMaxX,
|
length: endMinX - startMaxX,
|
||||||
overlap: rangeIntersection(
|
overlap: rangeIntersection(
|
||||||
rangeInclusive(startMinY, startMaxY),
|
rangeInclusive(startMinY, startMaxY),
|
||||||
@@ -415,8 +418,11 @@ export const getVisibleGaps = (
|
|||||||
verticalGaps.push({
|
verticalGaps.push({
|
||||||
startBounds,
|
startBounds,
|
||||||
endBounds,
|
endBounds,
|
||||||
startSide: [point(startMinX, startMaxY), point(startMaxX, startMaxY)],
|
startSide: [
|
||||||
endSide: [point(endMinX, endMinY), point(endMaxX, endMinY)],
|
pointFrom(startMinX, startMaxY),
|
||||||
|
pointFrom(startMaxX, startMaxY),
|
||||||
|
],
|
||||||
|
endSide: [pointFrom(endMinX, endMinY), pointFrom(endMaxX, endMinY)],
|
||||||
length: endMinY - startMaxY,
|
length: endMinY - startMaxY,
|
||||||
overlap: rangeIntersection(
|
overlap: rangeIntersection(
|
||||||
rangeInclusive(startMinX, startMaxX),
|
rangeInclusive(startMinX, startMaxX),
|
||||||
@@ -832,7 +838,7 @@ const createPointSnapLines = (
|
|||||||
}
|
}
|
||||||
snapsX[key].push(
|
snapsX[key].push(
|
||||||
...snap.points.map((p) =>
|
...snap.points.map((p) =>
|
||||||
point<GlobalPoint>(round(p[0]), round(p[1])),
|
pointFrom<GlobalPoint>(round(p[0]), round(p[1])),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -849,7 +855,7 @@ const createPointSnapLines = (
|
|||||||
}
|
}
|
||||||
snapsY[key].push(
|
snapsY[key].push(
|
||||||
...snap.points.map((p) =>
|
...snap.points.map((p) =>
|
||||||
point<GlobalPoint>(round(p[0]), round(p[1])),
|
pointFrom<GlobalPoint>(round(p[0]), round(p[1])),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -863,7 +869,7 @@ const createPointSnapLines = (
|
|||||||
points: dedupePoints(
|
points: dedupePoints(
|
||||||
points
|
points
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
return point<GlobalPoint>(Number(key), p[1]);
|
return pointFrom<GlobalPoint>(Number(key), p[1]);
|
||||||
})
|
})
|
||||||
.sort((a, b) => a[1] - b[1]),
|
.sort((a, b) => a[1] - b[1]),
|
||||||
),
|
),
|
||||||
@@ -876,7 +882,7 @@ const createPointSnapLines = (
|
|||||||
points: dedupePoints(
|
points: dedupePoints(
|
||||||
points
|
points
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
return point<GlobalPoint>(p[0], Number(key));
|
return pointFrom<GlobalPoint>(p[0], Number(key));
|
||||||
})
|
})
|
||||||
.sort((a, b) => a[0] - b[0]),
|
.sort((a, b) => a[0] - b[0]),
|
||||||
),
|
),
|
||||||
@@ -940,16 +946,16 @@ const createGapSnapLines = (
|
|||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
points: [
|
points: [
|
||||||
point(gapSnap.gap.startSide[0][0], gapLineY),
|
pointFrom(gapSnap.gap.startSide[0][0], gapLineY),
|
||||||
point(minX, gapLineY),
|
pointFrom(minX, gapLineY),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
points: [
|
points: [
|
||||||
point(maxX, gapLineY),
|
pointFrom(maxX, gapLineY),
|
||||||
point(gapSnap.gap.endSide[0][0], gapLineY),
|
pointFrom(gapSnap.gap.endSide[0][0], gapLineY),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -966,16 +972,16 @@ const createGapSnapLines = (
|
|||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
points: [
|
points: [
|
||||||
point(gapLineX, gapSnap.gap.startSide[0][1]),
|
pointFrom(gapLineX, gapSnap.gap.startSide[0][1]),
|
||||||
point(gapLineX, minY),
|
pointFrom(gapLineX, minY),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
points: [
|
points: [
|
||||||
point(gapLineX, maxY),
|
pointFrom(gapLineX, maxY),
|
||||||
point(gapLineX, gapSnap.gap.endSide[0][1]),
|
pointFrom(gapLineX, gapSnap.gap.endSide[0][1]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -991,12 +997,15 @@ const createGapSnapLines = (
|
|||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
points: [point(startMaxX, gapLineY), point(endMinX, gapLineY)],
|
points: [
|
||||||
|
pointFrom(startMaxX, gapLineY),
|
||||||
|
pointFrom(endMinX, gapLineY),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
points: [point(endMaxX, gapLineY), point(minX, gapLineY)],
|
points: [pointFrom(endMaxX, gapLineY), pointFrom(minX, gapLineY)],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1011,12 +1020,18 @@ const createGapSnapLines = (
|
|||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
points: [point(maxX, gapLineY), point(startMinX, gapLineY)],
|
points: [
|
||||||
|
pointFrom(maxX, gapLineY),
|
||||||
|
pointFrom(startMinX, gapLineY),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
points: [point(startMaxX, gapLineY), point(endMinX, gapLineY)],
|
points: [
|
||||||
|
pointFrom(startMaxX, gapLineY),
|
||||||
|
pointFrom(endMinX, gapLineY),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1031,12 +1046,18 @@ const createGapSnapLines = (
|
|||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
points: [point(gapLineX, maxY), point(gapLineX, startMinY)],
|
points: [
|
||||||
|
pointFrom(gapLineX, maxY),
|
||||||
|
pointFrom(gapLineX, startMinY),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
points: [point(gapLineX, startMaxY), point(gapLineX, endMinY)],
|
points: [
|
||||||
|
pointFrom(gapLineX, startMaxY),
|
||||||
|
pointFrom(gapLineX, endMinY),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1051,12 +1072,15 @@ const createGapSnapLines = (
|
|||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
points: [point(gapLineX, startMaxY), point(gapLineX, endMinY)],
|
points: [
|
||||||
|
pointFrom(gapLineX, startMaxY),
|
||||||
|
pointFrom(gapLineX, endMinY),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "gap",
|
type: "gap",
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
points: [point(gapLineX, endMaxY), point(gapLineX, minY)],
|
points: [pointFrom(gapLineX, endMaxY), pointFrom(gapLineX, minY)],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1070,7 +1094,7 @@ const createGapSnapLines = (
|
|||||||
return {
|
return {
|
||||||
...gapSnapLine,
|
...gapSnapLine,
|
||||||
points: gapSnapLine.points.map((p) =>
|
points: gapSnapLine.points.map((p) =>
|
||||||
point(round(p[0]), round(p[1])),
|
pointFrom(round(p[0]), round(p[1])),
|
||||||
) as PointPair,
|
) as PointPair,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -1120,35 +1144,35 @@ export const snapResizingElements = (
|
|||||||
if (transformHandle) {
|
if (transformHandle) {
|
||||||
switch (transformHandle) {
|
switch (transformHandle) {
|
||||||
case "e": {
|
case "e": {
|
||||||
selectionSnapPoints.push(point(maxX, minY), point(maxX, maxY));
|
selectionSnapPoints.push(pointFrom(maxX, minY), pointFrom(maxX, maxY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "w": {
|
case "w": {
|
||||||
selectionSnapPoints.push(point(minX, minY), point(minX, maxY));
|
selectionSnapPoints.push(pointFrom(minX, minY), pointFrom(minX, maxY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "n": {
|
case "n": {
|
||||||
selectionSnapPoints.push(point(minX, minY), point(maxX, minY));
|
selectionSnapPoints.push(pointFrom(minX, minY), pointFrom(maxX, minY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "s": {
|
case "s": {
|
||||||
selectionSnapPoints.push(point(minX, maxY), point(maxX, maxY));
|
selectionSnapPoints.push(pointFrom(minX, maxY), pointFrom(maxX, maxY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "ne": {
|
case "ne": {
|
||||||
selectionSnapPoints.push(point(maxX, minY));
|
selectionSnapPoints.push(pointFrom(maxX, minY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "nw": {
|
case "nw": {
|
||||||
selectionSnapPoints.push(point(minX, minY));
|
selectionSnapPoints.push(pointFrom(minX, minY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "se": {
|
case "se": {
|
||||||
selectionSnapPoints.push(point(maxX, maxY));
|
selectionSnapPoints.push(pointFrom(maxX, maxY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "sw": {
|
case "sw": {
|
||||||
selectionSnapPoints.push(point(minX, maxY));
|
selectionSnapPoints.push(pointFrom(minX, maxY));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1191,10 +1215,10 @@ export const snapResizingElements = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const corners: GlobalPoint[] = [
|
const corners: GlobalPoint[] = [
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
point(x1, y2),
|
pointFrom(x1, y2),
|
||||||
point(x2, y1),
|
pointFrom(x2, y1),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
];
|
];
|
||||||
|
|
||||||
getPointSnaps(
|
getPointSnaps(
|
||||||
@@ -1231,7 +1255,7 @@ export const snapNewElement = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectionSnapPoints: GlobalPoint[] = [
|
const selectionSnapPoints: GlobalPoint[] = [
|
||||||
point(origin.x + dragOffset.x, origin.y + dragOffset.y),
|
pointFrom(origin.x + dragOffset.x, origin.y + dragOffset.y),
|
||||||
];
|
];
|
||||||
|
|
||||||
const snapDistance = getSnapDistance(app.state.zoom.value);
|
const snapDistance = getSnapDistance(app.state.zoom.value);
|
||||||
@@ -1331,7 +1355,7 @@ export const getSnapLinesAtPointer = (
|
|||||||
|
|
||||||
verticalSnapLines.push({
|
verticalSnapLines.push({
|
||||||
type: "pointer",
|
type: "pointer",
|
||||||
points: [corner, point(corner[0], pointer.y)],
|
points: [corner, pointFrom(corner[0], pointer.y)],
|
||||||
direction: "vertical",
|
direction: "vertical",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1347,7 +1371,7 @@ export const getSnapLinesAtPointer = (
|
|||||||
|
|
||||||
horizontalSnapLines.push({
|
horizontalSnapLines.push({
|
||||||
type: "pointer",
|
type: "pointer",
|
||||||
points: [corner, point(pointer.x, corner[1])],
|
points: [corner, pointFrom(pointer.x, corner[1])],
|
||||||
direction: "horizontal",
|
direction: "horizontal",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const getObservedAppState = (appState: AppState): ObservedAppState => {
|
|||||||
selectedGroupIds: appState.selectedGroupIds,
|
selectedGroupIds: appState.selectedGroupIds,
|
||||||
editingLinearElementId: appState.editingLinearElement?.elementId || null,
|
editingLinearElementId: appState.editingLinearElement?.elementId || null,
|
||||||
selectedLinearElementId: appState.selectedLinearElement?.elementId || null,
|
selectedLinearElementId: appState.selectedLinearElement?.elementId || null,
|
||||||
|
croppingElementId: appState.croppingElementId,
|
||||||
};
|
};
|
||||||
|
|
||||||
Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, {
|
Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, {
|
||||||
|
|||||||
@@ -116,6 +116,50 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -794,6 +838,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"left": 30,
|
"left": 30,
|
||||||
"top": 40,
|
"top": 40,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -836,6 +881,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1000,6 +1046,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1042,6 +1089,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1216,6 +1264,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1258,6 +1307,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1547,6 +1597,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1589,6 +1640,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1878,6 +1930,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1920,6 +1973,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2094,6 +2148,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2136,6 +2191,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2334,6 +2390,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2376,6 +2433,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2635,6 +2693,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2677,6 +2736,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3004,6 +3064,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3046,6 +3107,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3479,6 +3541,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3521,6 +3584,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3802,6 +3866,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3844,6 +3909,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4125,6 +4191,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4167,6 +4234,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4633,6 +4701,50 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -5311,6 +5423,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"left": -17,
|
"left": -17,
|
||||||
"top": -7,
|
"top": -7,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5353,6 +5466,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5760,6 +5874,50 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -6438,6 +6596,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"left": -17,
|
"left": -17,
|
||||||
"top": -7,
|
"top": -7,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6480,6 +6639,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7373,6 +7533,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"left": -19,
|
"left": -19,
|
||||||
"top": -9,
|
"top": -9,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7415,6 +7576,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7607,6 +7769,50 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -8285,6 +8491,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"left": -17,
|
"left": -17,
|
||||||
"top": -7,
|
"top": -7,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8327,6 +8534,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8501,6 +8709,50 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -9179,6 +9431,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"left": 80,
|
"left": 80,
|
||||||
"top": 90,
|
"top": 90,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9221,6 +9474,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,6 +11,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -53,6 +54,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -613,6 +615,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -655,6 +658,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1119,6 +1123,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1161,6 +1166,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1487,6 +1493,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1529,6 +1536,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1856,6 +1864,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1898,6 +1907,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2123,6 +2133,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2165,6 +2176,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2563,6 +2575,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2605,6 +2618,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2862,6 +2876,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2904,6 +2919,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3146,6 +3162,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3188,6 +3205,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3440,6 +3458,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3482,6 +3501,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3726,6 +3746,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3768,6 +3789,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3961,6 +3983,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4003,6 +4026,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4220,6 +4244,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4262,6 +4287,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4493,6 +4519,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4535,6 +4562,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4724,6 +4752,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4766,6 +4795,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4955,6 +4985,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4997,6 +5028,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5184,6 +5216,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5226,6 +5259,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5413,6 +5447,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5455,6 +5490,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5672,6 +5708,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5714,6 +5751,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6003,6 +6041,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6045,6 +6084,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6428,6 +6468,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6470,6 +6511,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6806,6 +6848,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6848,6 +6891,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7125,6 +7169,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7167,6 +7212,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7423,6 +7469,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7465,6 +7512,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7652,6 +7700,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7694,6 +7743,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8007,6 +8057,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8049,6 +8100,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8362,6 +8414,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8404,6 +8457,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8766,6 +8820,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8808,6 +8863,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9053,6 +9109,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9095,6 +9152,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9318,6 +9376,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9360,6 +9419,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9582,6 +9642,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9624,6 +9685,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9813,6 +9875,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9855,6 +9918,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10114,6 +10178,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10156,6 +10221,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10454,6 +10520,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10496,6 +10563,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10689,6 +10757,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10731,6 +10800,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11142,6 +11212,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11184,6 +11255,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11396,6 +11468,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11438,6 +11511,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11635,6 +11709,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11677,6 +11752,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11876,6 +11952,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11918,6 +11995,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12277,6 +12355,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12319,6 +12398,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12524,6 +12604,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12566,6 +12647,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12765,6 +12847,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12807,6 +12890,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13006,6 +13090,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13048,6 +13133,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13253,6 +13339,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13295,6 +13382,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13585,6 +13673,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13627,6 +13716,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13757,6 +13847,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13799,6 +13890,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14045,6 +14137,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14087,6 +14180,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14312,6 +14406,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14354,6 +14449,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14587,6 +14683,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14629,6 +14726,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14748,6 +14846,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14790,6 +14889,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -15444,6 +15544,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -15486,6 +15587,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -16064,6 +16166,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -16106,6 +16209,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -16684,6 +16788,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -16726,6 +16831,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -17396,6 +17502,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -17438,6 +17545,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -18146,6 +18254,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -18188,6 +18297,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -18620,6 +18730,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -18662,6 +18773,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -19142,6 +19254,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -19184,6 +19297,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -19598,6 +19712,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -19640,6 +19755,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -53,6 +54,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -423,6 +425,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -465,6 +468,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -826,6 +830,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -868,6 +873,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": false,
|
"isBindingEnabled": false,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1368,6 +1374,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1410,6 +1417,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1569,6 +1577,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1611,6 +1620,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1941,6 +1951,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1983,6 +1994,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2178,6 +2190,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2220,6 +2233,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2355,6 +2369,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2397,6 +2412,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2672,6 +2688,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2714,6 +2731,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2915,6 +2933,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2957,6 +2976,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3155,6 +3175,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3197,6 +3218,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3382,6 +3404,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3424,6 +3447,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3635,6 +3659,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3677,6 +3702,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3943,6 +3969,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3985,6 +4012,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4354,6 +4382,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4396,6 +4425,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4634,6 +4664,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4676,6 +4707,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4884,6 +4916,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4926,6 +4959,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5091,6 +5125,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5133,6 +5168,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5287,6 +5323,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5329,6 +5366,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5666,6 +5704,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5708,6 +5747,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5953,6 +5993,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5995,6 +6036,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6758,6 +6800,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6800,6 +6843,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7085,6 +7129,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7127,6 +7172,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7358,6 +7404,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7400,6 +7447,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7589,6 +7637,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7631,6 +7680,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7823,6 +7873,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7865,6 +7916,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8000,6 +8052,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8042,6 +8095,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8177,6 +8231,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8219,6 +8274,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8354,6 +8410,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8396,6 +8453,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8574,6 +8632,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8616,6 +8675,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8793,6 +8853,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8835,6 +8896,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8984,6 +9046,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9026,6 +9089,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9204,6 +9268,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9246,6 +9311,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9381,6 +9447,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9423,6 +9490,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9600,6 +9668,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9642,6 +9711,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9777,6 +9847,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9819,6 +9890,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9968,6 +10040,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10010,6 +10083,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10145,6 +10219,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10187,6 +10262,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10656,6 +10732,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10698,6 +10775,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10930,6 +11008,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10972,6 +11051,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11053,6 +11133,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11095,6 +11176,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11249,6 +11331,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11291,6 +11374,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11557,6 +11641,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11599,6 +11684,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11966,6 +12052,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12008,6 +12095,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12576,6 +12664,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12618,6 +12707,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12702,6 +12792,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12744,6 +12835,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13283,6 +13375,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13325,6 +13418,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13618,6 +13712,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13660,6 +13755,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13880,6 +13976,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13922,6 +14019,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14003,6 +14101,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14045,6 +14144,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14379,6 +14479,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14421,6 +14522,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14502,6 +14604,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14544,6 +14647,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { API } from "./helpers/api";
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -32,7 +32,12 @@ describe("element binding", () => {
|
|||||||
y: 0,
|
y: 0,
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 1,
|
height: 1,
|
||||||
points: [point(0, 0), point(0, 0), point(100, 0), point(100, 0)],
|
points: [
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(100, 0),
|
||||||
|
pointFrom(100, 0),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
API.setElements([rect, arrow]);
|
API.setElements([rect, arrow]);
|
||||||
expect(arrow.startBinding).toBe(null);
|
expect(arrow.startBinding).toBe(null);
|
||||||
@@ -310,7 +315,7 @@ describe("element binding", () => {
|
|||||||
const arrow1 = API.createElement({
|
const arrow1 = API.createElement({
|
||||||
type: "arrow",
|
type: "arrow",
|
||||||
id: "arrow1",
|
id: "arrow1",
|
||||||
points: [point(0, 0), point(0, -87.45777932247563)],
|
points: [pointFrom(0, 0), pointFrom(0, -87.45777932247563)],
|
||||||
startBinding: {
|
startBinding: {
|
||||||
elementId: "rectangle1",
|
elementId: "rectangle1",
|
||||||
focus: 0.2,
|
focus: 0.2,
|
||||||
@@ -328,7 +333,7 @@ describe("element binding", () => {
|
|||||||
const arrow2 = API.createElement({
|
const arrow2 = API.createElement({
|
||||||
type: "arrow",
|
type: "arrow",
|
||||||
id: "arrow2",
|
id: "arrow2",
|
||||||
points: [point(0, 0), point(0, -87.45777932247563)],
|
points: [pointFrom(0, 0), pointFrom(0, -87.45777932247563)],
|
||||||
startBinding: {
|
startBinding: {
|
||||||
elementId: "text1",
|
elementId: "text1",
|
||||||
focus: 0.2,
|
focus: 0.2,
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { vi } from "vitest";
|
||||||
|
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||||
|
import type { ExcalidrawImageElement, ImageCrop } from "../element/types";
|
||||||
|
import { act, GlobalTestState, render } from "./test-utils";
|
||||||
|
import { Excalidraw, exportToCanvas, exportToSvg } from "..";
|
||||||
|
import { API } from "./helpers/api";
|
||||||
|
import type { NormalizedZoomValue } from "../types";
|
||||||
|
import { KEYS } from "../keys";
|
||||||
|
import { duplicateElement } from "../element";
|
||||||
|
import { cloneJSON } from "../utils";
|
||||||
|
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||||
|
|
||||||
|
const { h } = window;
|
||||||
|
const mouse = new Pointer("mouse");
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Unmount ReactDOM from root
|
||||||
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
|
||||||
|
mouse.reset();
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
Object.assign(document, {
|
||||||
|
elementFromPoint: () => GlobalTestState.canvas,
|
||||||
|
});
|
||||||
|
await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
|
||||||
|
API.setAppState({
|
||||||
|
zoom: {
|
||||||
|
value: 1 as NormalizedZoomValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const image = API.createElement({ type: "image", width: 200, height: 100 });
|
||||||
|
API.setElements([image]);
|
||||||
|
API.setAppState({
|
||||||
|
selectedElementIds: {
|
||||||
|
[image.id]: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateRandomNaturalWidthAndHeight = (image: ExcalidrawImageElement) => {
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const scale = 1 + Math.random() * 5;
|
||||||
|
|
||||||
|
return {
|
||||||
|
naturalWidth: initialWidth * scale,
|
||||||
|
naturalHeight: initialHeight * scale,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const compareCrops = (cropA: ImageCrop, cropB: ImageCrop) => {
|
||||||
|
(Object.keys(cropA) as [keyof ImageCrop]).forEach((key) => {
|
||||||
|
const propA = cropA[key];
|
||||||
|
const propB = cropB[key];
|
||||||
|
|
||||||
|
expect(propA as number).toBeCloseTo(propB as number);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Enter and leave the crop editor", () => {
|
||||||
|
it("enter the editor by double clicking", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
expect(h.state.croppingElementId).toBe(image.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enter the editor by pressing enter", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
Keyboard.keyDown(KEYS.ENTER);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
expect(h.state.croppingElementId).toBe(image.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leave the editor by clicking outside", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
Keyboard.keyDown(KEYS.ENTER);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
|
||||||
|
mouse.click(image.x - 20, image.y - 20);
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leave the editor by pressing escape", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Crop an image", () => {
|
||||||
|
it("Cropping changes the dimension", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [initialWidth / 2, 0]);
|
||||||
|
|
||||||
|
expect(image.width).toBeLessThan(initialWidth);
|
||||||
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, initialHeight / 2]);
|
||||||
|
expect(image.height).toBeLessThan(initialHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Cropping has minimal sizes", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [initialWidth, 0]);
|
||||||
|
expect(image.width).toBeLessThan(initialWidth);
|
||||||
|
expect(image.width).toBeGreaterThan(0);
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [-initialWidth, 0]);
|
||||||
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, initialHeight]);
|
||||||
|
expect(image.height).toBeLessThan(initialHeight);
|
||||||
|
expect(image.height).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Preserve aspect ratio", async () => {
|
||||||
|
let image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [initialWidth / 3, 0]);
|
||||||
|
|
||||||
|
let resizedWidth = image.width;
|
||||||
|
let resizedHeight = image.height;
|
||||||
|
|
||||||
|
// max height, cropping should not change anything
|
||||||
|
UI.crop(
|
||||||
|
image,
|
||||||
|
"w",
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
|
[-initialWidth / 3, 0],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(image.width).toBe(resizedWidth);
|
||||||
|
expect(image.height).toBe(resizedHeight);
|
||||||
|
|
||||||
|
// re-crop to initial state
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [-initialWidth / 3, 0]);
|
||||||
|
// change crop height and width
|
||||||
|
UI.crop(image, "s", naturalWidth, naturalHeight, [0, -initialHeight / 2]);
|
||||||
|
UI.crop(image, "e", naturalWidth, naturalHeight, [-initialWidth / 3, 0]);
|
||||||
|
|
||||||
|
resizedWidth = image.width;
|
||||||
|
resizedHeight = image.height;
|
||||||
|
|
||||||
|
// test corner handle aspect ratio preserving
|
||||||
|
UI.crop(image, "se", naturalWidth, naturalHeight, [initialWidth, 0], true);
|
||||||
|
expect(image.width / image.height).toBe(resizedWidth / resizedHeight);
|
||||||
|
expect(image.width).toBeLessThanOrEqual(initialWidth);
|
||||||
|
expect(image.height).toBeLessThanOrEqual(initialHeight);
|
||||||
|
|
||||||
|
// reset
|
||||||
|
image = API.createElement({ type: "image", width: 200, height: 100 });
|
||||||
|
API.setElements([image]);
|
||||||
|
API.setAppState({
|
||||||
|
selectedElementIds: {
|
||||||
|
[image.id]: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 50 x 50 square
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [150, 50]);
|
||||||
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, -100], true);
|
||||||
|
expect(image.width).toBeCloseTo(image.height);
|
||||||
|
// image is at the corner, not space to its right to expand, should not be able to resize
|
||||||
|
expect(image.height).toBeCloseTo(50);
|
||||||
|
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [-150, -100], true);
|
||||||
|
expect(image.width).toBeCloseTo(image.height);
|
||||||
|
// max height should be reached
|
||||||
|
expect(image.height).toBeCloseTo(initialHeight);
|
||||||
|
expect(image.width).toBe(initialHeight);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Cropping and other features", async () => {
|
||||||
|
it("Cropping works independently of duplication", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 2,
|
||||||
|
]);
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
const duplicatedImage = duplicateElement(null, new Map(), image, {});
|
||||||
|
act(() => {
|
||||||
|
h.app.scene.insertElement(duplicatedImage);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(duplicatedImage.width).toBe(image.width);
|
||||||
|
expect(duplicatedImage.height).toBe(image.height);
|
||||||
|
|
||||||
|
UI.crop(duplicatedImage, "nw", naturalWidth, naturalHeight, [
|
||||||
|
-initialWidth / 2,
|
||||||
|
-initialHeight / 2,
|
||||||
|
]);
|
||||||
|
expect(duplicatedImage.width).toBe(initialWidth);
|
||||||
|
expect(duplicatedImage.height).toBe(initialHeight);
|
||||||
|
const resizedWidth = image.width;
|
||||||
|
const resizedHeight = image.height;
|
||||||
|
|
||||||
|
expect(image.width).not.toBe(duplicatedImage.width);
|
||||||
|
expect(image.height).not.toBe(duplicatedImage.height);
|
||||||
|
UI.crop(duplicatedImage, "se", naturalWidth, naturalHeight, [
|
||||||
|
-initialWidth / 1.5,
|
||||||
|
-initialHeight / 1.5,
|
||||||
|
]);
|
||||||
|
expect(duplicatedImage.width).not.toBe(initialWidth);
|
||||||
|
expect(image.width).toBe(resizedWidth);
|
||||||
|
expect(duplicatedImage.height).not.toBe(initialHeight);
|
||||||
|
expect(image.height).toBe(resizedHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Resizing should not affect crop", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 2,
|
||||||
|
]);
|
||||||
|
const cropBeforeResizing = image.crop;
|
||||||
|
const cropBeforeResizingCloned = cloneJSON(image.crop) as ImageCrop;
|
||||||
|
expect(cropBeforeResizing).not.toBe(null);
|
||||||
|
|
||||||
|
UI.crop(image, "e", naturalWidth, naturalHeight, [200, 0]);
|
||||||
|
expect(cropBeforeResizing).toBe(image.crop);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
|
||||||
|
UI.resize(image, "s", [0, -100]);
|
||||||
|
expect(cropBeforeResizing).toBe(image.crop);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
|
||||||
|
UI.resize(image, "ne", [-50, -50]);
|
||||||
|
expect(cropBeforeResizing).toBe(image.crop);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Flipping does not change crop", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 2,
|
||||||
|
]);
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
const cropBeforeResizing = image.crop;
|
||||||
|
const cropBeforeResizingCloned = cloneJSON(image.crop) as ImageCrop;
|
||||||
|
|
||||||
|
API.executeAction(actionFlipHorizontal);
|
||||||
|
expect(image.crop).toBe(cropBeforeResizing);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
|
||||||
|
API.executeAction(actionFlipVertical);
|
||||||
|
expect(image.crop).toBe(cropBeforeResizing);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Exports should preserve crops", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 4,
|
||||||
|
]);
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
const widthToHeightRatio = image.width / image.height;
|
||||||
|
|
||||||
|
const canvas = await exportToCanvas({
|
||||||
|
elements: [image],
|
||||||
|
appState: h.state,
|
||||||
|
files: h.app.files,
|
||||||
|
exportPadding: 0,
|
||||||
|
});
|
||||||
|
const exportedCanvasRatio = canvas.width / canvas.height;
|
||||||
|
|
||||||
|
expect(widthToHeightRatio).toBeCloseTo(exportedCanvasRatio);
|
||||||
|
|
||||||
|
const svg = await exportToSvg({
|
||||||
|
elements: [image],
|
||||||
|
appState: h.state,
|
||||||
|
files: h.app.files,
|
||||||
|
exportPadding: 0,
|
||||||
|
});
|
||||||
|
const svgWidth = svg.getAttribute("width");
|
||||||
|
const svgHeight = svg.getAttribute("height");
|
||||||
|
|
||||||
|
expect(svgWidth).toBeDefined();
|
||||||
|
expect(svgHeight).toBeDefined();
|
||||||
|
|
||||||
|
const exportedSvgRatio = Number(svgWidth) / Number(svgHeight);
|
||||||
|
expect(widthToHeightRatio).toBeCloseTo(exportedSvgRatio);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -28,7 +28,7 @@ import { getBoundTextElementPosition } from "../element/textElement";
|
|||||||
import { createPasteEvent } from "../clipboard";
|
import { createPasteEvent } from "../clipboard";
|
||||||
import { arrayToMap, cloneJSON } from "../utils";
|
import { arrayToMap, cloneJSON } from "../utils";
|
||||||
import type { LocalPoint } from "../../math";
|
import type { LocalPoint } from "../../math";
|
||||||
import { point, type Radians } from "../../math";
|
import { pointFrom, type Radians } from "../../math";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
@@ -146,9 +146,9 @@ const createLinearElementWithCurveInsideMinMaxPoints = (
|
|||||||
link: null,
|
link: null,
|
||||||
locked: false,
|
locked: false,
|
||||||
points: [
|
points: [
|
||||||
point<LocalPoint>(0, 0),
|
pointFrom<LocalPoint>(0, 0),
|
||||||
point<LocalPoint>(-922.4761962890625, 300.3277587890625),
|
pointFrom<LocalPoint>(-922.4761962890625, 300.3277587890625),
|
||||||
point<LocalPoint>(828.0126953125, 410.51605224609375),
|
pointFrom<LocalPoint>(828.0126953125, 410.51605224609375),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import type App from "../../components/App";
|
|||||||
import { createTestHook } from "../../components/App";
|
import { createTestHook } from "../../components/App";
|
||||||
import type { Action } from "../../actions/types";
|
import type { Action } from "../../actions/types";
|
||||||
import { mutateElement } from "../../element/mutateElement";
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
import { point, type LocalPoint, type Radians } from "../../../math";
|
import { pointFrom, type LocalPoint, type Radians } from "../../../math";
|
||||||
|
|
||||||
const readFile = util.promisify(fs.readFile);
|
const readFile = util.promisify(fs.readFile);
|
||||||
// so that window.h is available when App.tsx is not imported as well.
|
// so that window.h is available when App.tsx is not imported as well.
|
||||||
@@ -307,8 +307,8 @@ export class API {
|
|||||||
height,
|
height,
|
||||||
type,
|
type,
|
||||||
points: rest.points ?? [
|
points: rest.points ?? [
|
||||||
point<LocalPoint>(0, 0),
|
pointFrom<LocalPoint>(0, 0),
|
||||||
point<LocalPoint>(100, 100),
|
pointFrom<LocalPoint>(100, 100),
|
||||||
],
|
],
|
||||||
elbowed: rest.elbowed ?? false,
|
elbowed: rest.elbowed ?? false,
|
||||||
});
|
});
|
||||||
@@ -320,8 +320,8 @@ export class API {
|
|||||||
height,
|
height,
|
||||||
type,
|
type,
|
||||||
points: rest.points ?? [
|
points: rest.points ?? [
|
||||||
point<LocalPoint>(0, 0),
|
pointFrom<LocalPoint>(0, 0),
|
||||||
point<LocalPoint>(100, 100),
|
pointFrom<LocalPoint>(100, 100),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { ToolType } from "../../types";
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
@@ -9,6 +8,7 @@ import type {
|
|||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
|
ExcalidrawImageElement,
|
||||||
} from "../../element/types";
|
} from "../../element/types";
|
||||||
import type { TransformHandleType } from "../../element/transformHandles";
|
import type { TransformHandleType } from "../../element/transformHandles";
|
||||||
import {
|
import {
|
||||||
@@ -34,7 +34,9 @@ import { getTextEditor } from "../queries/dom";
|
|||||||
import { arrayToMap } from "../../utils";
|
import { arrayToMap } from "../../utils";
|
||||||
import { createTestHook } from "../../components/App";
|
import { createTestHook } from "../../components/App";
|
||||||
import type { GlobalPoint, LocalPoint, Radians } from "../../../math";
|
import type { GlobalPoint, LocalPoint, Radians } from "../../../math";
|
||||||
import { point, pointRotateRads } from "../../../math";
|
import { pointFrom, pointRotateRads } from "../../../math";
|
||||||
|
import { cropElement } from "../../element/cropElement";
|
||||||
|
import type { ToolType } from "../../types";
|
||||||
|
|
||||||
// so that window.h is available when App.tsx is not imported as well.
|
// so that window.h is available when App.tsx is not imported as well.
|
||||||
createTestHook();
|
createTestHook();
|
||||||
@@ -142,7 +144,7 @@ const getElementPointForSelection = (
|
|||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
const { x, y, width, height, angle } = element;
|
const { x, y, width, height, angle } = element;
|
||||||
const target = point<GlobalPoint>(
|
const target = pointFrom<GlobalPoint>(
|
||||||
x +
|
x +
|
||||||
(isLinearElement(element) || isFreeDrawElement(element) ? 0 : width / 2),
|
(isLinearElement(element) || isFreeDrawElement(element) ? 0 : width / 2),
|
||||||
y,
|
y,
|
||||||
@@ -151,9 +153,12 @@ const getElementPointForSelection = (
|
|||||||
|
|
||||||
if (isLinearElement(element)) {
|
if (isLinearElement(element)) {
|
||||||
const bounds = getElementPointsCoords(element, element.points);
|
const bounds = getElementPointsCoords(element, element.points);
|
||||||
center = point((bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2);
|
center = pointFrom(
|
||||||
|
(bounds[0] + bounds[2]) / 2,
|
||||||
|
(bounds[1] + bounds[3]) / 2,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
center = point(x + width / 2, y + height / 2);
|
center = pointFrom(x + width / 2, y + height / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
@@ -469,8 +474,8 @@ export class UI {
|
|||||||
const width = initialWidth ?? initialHeight ?? size;
|
const width = initialWidth ?? initialHeight ?? size;
|
||||||
const height = initialHeight ?? size;
|
const height = initialHeight ?? size;
|
||||||
const points: LocalPoint[] = initialPoints ?? [
|
const points: LocalPoint[] = initialPoints ?? [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(width, height),
|
pointFrom(width, height),
|
||||||
];
|
];
|
||||||
|
|
||||||
UI.clickTool(type);
|
UI.clickTool(type);
|
||||||
@@ -558,6 +563,38 @@ export class UI {
|
|||||||
return transform(element, handle, mouseMove, keyboardModifiers);
|
return transform(element, handle, mouseMove, keyboardModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static crop(
|
||||||
|
element: ExcalidrawImageElement,
|
||||||
|
handle: TransformHandleDirection,
|
||||||
|
naturalWidth: number,
|
||||||
|
naturalHeight: number,
|
||||||
|
mouseMove: [deltaX: number, deltaY: number],
|
||||||
|
keepAspectRatio = false,
|
||||||
|
) {
|
||||||
|
const handleCoords = getTransformHandles(
|
||||||
|
element,
|
||||||
|
h.state.zoom,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
"mouse",
|
||||||
|
{},
|
||||||
|
)[handle]!;
|
||||||
|
|
||||||
|
const clientX = handleCoords[0] + handleCoords[2] / 2;
|
||||||
|
const clientY = handleCoords[1] + handleCoords[3] / 2;
|
||||||
|
|
||||||
|
const mutations = cropElement(
|
||||||
|
element,
|
||||||
|
handle,
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
|
clientX + mouseMove[0],
|
||||||
|
clientY + mouseMove[1],
|
||||||
|
keepAspectRatio ? element.width / element.height : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
API.updateElement(element, mutations);
|
||||||
|
}
|
||||||
|
|
||||||
static rotate(
|
static rotate(
|
||||||
element: ExcalidrawElement | ExcalidrawElement[],
|
element: ExcalidrawElement | ExcalidrawElement[],
|
||||||
mouseMove: [deltaX: number, deltaY: number],
|
mouseMove: [deltaX: number, deltaY: number],
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import { HistoryEntry } from "../history";
|
|||||||
import { AppStateChange, ElementsChange } from "../change";
|
import { AppStateChange, ElementsChange } from "../change";
|
||||||
import { Snapshot, StoreAction } from "../store";
|
import { Snapshot, StoreAction } from "../store";
|
||||||
import type { LocalPoint, Radians } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -2041,9 +2041,9 @@ describe("history", () => {
|
|||||||
width: 178.9000000000001,
|
width: 178.9000000000001,
|
||||||
height: 236.10000000000002,
|
height: 236.10000000000002,
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(178.9000000000001, 0),
|
pointFrom(178.9000000000001, 0),
|
||||||
point(178.9000000000001, 236.10000000000002),
|
pointFrom(178.9000000000001, 236.10000000000002),
|
||||||
],
|
],
|
||||||
startBinding: {
|
startBinding: {
|
||||||
elementId: "KPrBI4g_v9qUB1XxYLgSz",
|
elementId: "KPrBI4g_v9qUB1XxYLgSz",
|
||||||
@@ -2159,11 +2159,11 @@ describe("history", () => {
|
|||||||
elements: [
|
elements: [
|
||||||
newElementWith(h.elements[0] as ExcalidrawLinearElement, {
|
newElementWith(h.elements[0] as ExcalidrawLinearElement, {
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(5, 5),
|
pointFrom(5, 5),
|
||||||
point(10, 10),
|
pointFrom(10, 10),
|
||||||
point(15, 15),
|
pointFrom(15, 15),
|
||||||
point(20, 20),
|
pointFrom(20, 20),
|
||||||
] as LocalPoint[],
|
] as LocalPoint[],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { ROUNDNESS, VERTICAL_ALIGN } from "../constants";
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import type { GlobalPoint } from "../../math";
|
import type { GlobalPoint } from "../../math";
|
||||||
import { pointCenter, point } from "../../math";
|
import { pointCenter, pointFrom } from "../../math";
|
||||||
|
|
||||||
const renderInteractiveScene = vi.spyOn(
|
const renderInteractiveScene = vi.spyOn(
|
||||||
InteractiveCanvas,
|
InteractiveCanvas,
|
||||||
@@ -57,8 +57,8 @@ describe("Test Linear Elements", () => {
|
|||||||
interactiveCanvas = container.querySelector("canvas.interactive")!;
|
interactiveCanvas = container.querySelector("canvas.interactive")!;
|
||||||
});
|
});
|
||||||
|
|
||||||
const p1 = point<GlobalPoint>(20, 20);
|
const p1 = pointFrom<GlobalPoint>(20, 20);
|
||||||
const p2 = point<GlobalPoint>(60, 20);
|
const p2 = pointFrom<GlobalPoint>(60, 20);
|
||||||
const midpoint = pointCenter<GlobalPoint>(p1, p2);
|
const midpoint = pointCenter<GlobalPoint>(p1, p2);
|
||||||
const delta = 50;
|
const delta = 50;
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
@@ -75,7 +75,7 @@ describe("Test Linear Elements", () => {
|
|||||||
height: 0,
|
height: 0,
|
||||||
type,
|
type,
|
||||||
roughness,
|
roughness,
|
||||||
points: [point(0, 0), point(p2[0] - p1[0], p2[1] - p1[1])],
|
points: [pointFrom(0, 0), pointFrom(p2[0] - p1[0], p2[1] - p1[1])],
|
||||||
roundness,
|
roundness,
|
||||||
});
|
});
|
||||||
API.setElements([line]);
|
API.setElements([line]);
|
||||||
@@ -99,9 +99,9 @@ describe("Test Linear Elements", () => {
|
|||||||
type,
|
type,
|
||||||
roughness,
|
roughness,
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(p3[0], p3[1]),
|
pointFrom(p3[0], p3[1]),
|
||||||
point(p2[0] - p1[0], p2[1] - p1[1]),
|
pointFrom(p2[0] - p1[0], p2[1] - p1[1]),
|
||||||
],
|
],
|
||||||
roundness,
|
roundness,
|
||||||
});
|
});
|
||||||
@@ -161,7 +161,7 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(line.points.length).toEqual(2);
|
expect(line.points.length).toEqual(2);
|
||||||
|
|
||||||
mouse.clickAt(midpoint[0], midpoint[1]);
|
mouse.clickAt(midpoint[0], midpoint[1]);
|
||||||
drag(midpoint, point(midpoint[0] + 1, midpoint[1] + 1));
|
drag(midpoint, pointFrom(midpoint[0] + 1, midpoint[1] + 1));
|
||||||
|
|
||||||
expect(line.points.length).toEqual(2);
|
expect(line.points.length).toEqual(2);
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(line.y).toBe(originalY);
|
expect(line.y).toBe(originalY);
|
||||||
expect(line.points.length).toEqual(2);
|
expect(line.points.length).toEqual(2);
|
||||||
|
|
||||||
drag(midpoint, point(midpoint[0] + delta, midpoint[1] + delta));
|
drag(midpoint, pointFrom(midpoint[0] + delta, midpoint[1] + delta));
|
||||||
expect(line.x).toBe(originalX);
|
expect(line.x).toBe(originalX);
|
||||||
expect(line.y).toBe(originalY);
|
expect(line.y).toBe(originalY);
|
||||||
expect(line.points.length).toEqual(3);
|
expect(line.points.length).toEqual(3);
|
||||||
@@ -184,7 +184,7 @@ describe("Test Linear Elements", () => {
|
|||||||
expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2);
|
expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2);
|
||||||
|
|
||||||
// drag line from midpoint
|
// drag line from midpoint
|
||||||
drag(midpoint, point(midpoint[0] + delta, midpoint[1] + delta));
|
drag(midpoint, pointFrom(midpoint[0] + delta, midpoint[1] + delta));
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`9`);
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(`9`);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`7`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`7`);
|
||||||
expect(line.points.length).toEqual(3);
|
expect(line.points.length).toEqual(3);
|
||||||
@@ -248,7 +248,7 @@ describe("Test Linear Elements", () => {
|
|||||||
mouse.clickAt(midpoint[0], midpoint[1]);
|
mouse.clickAt(midpoint[0], midpoint[1]);
|
||||||
expect(line.points.length).toEqual(2);
|
expect(line.points.length).toEqual(2);
|
||||||
|
|
||||||
drag(midpoint, point(midpoint[0] + 1, midpoint[1] + 1));
|
drag(midpoint, pointFrom(midpoint[0] + 1, midpoint[1] + 1));
|
||||||
expect(line.x).toBe(originalX);
|
expect(line.x).toBe(originalX);
|
||||||
expect(line.y).toBe(originalY);
|
expect(line.y).toBe(originalY);
|
||||||
expect(line.points.length).toEqual(3);
|
expect(line.points.length).toEqual(3);
|
||||||
@@ -261,7 +261,7 @@ describe("Test Linear Elements", () => {
|
|||||||
enterLineEditingMode(line);
|
enterLineEditingMode(line);
|
||||||
|
|
||||||
// drag line from midpoint
|
// drag line from midpoint
|
||||||
drag(midpoint, point(midpoint[0] + delta, midpoint[1] + delta));
|
drag(midpoint, pointFrom(midpoint[0] + delta, midpoint[1] + delta));
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
`12`,
|
`12`,
|
||||||
);
|
);
|
||||||
@@ -356,7 +356,7 @@ describe("Test Linear Elements", () => {
|
|||||||
const startPoint = pointCenter(points[0], midPoints[0]!);
|
const startPoint = pointCenter(points[0], midPoints[0]!);
|
||||||
const deltaX = 50;
|
const deltaX = 50;
|
||||||
const deltaY = 20;
|
const deltaY = 20;
|
||||||
const endPoint = point<GlobalPoint>(
|
const endPoint = pointFrom<GlobalPoint>(
|
||||||
startPoint[0] + deltaX,
|
startPoint[0] + deltaX,
|
||||||
startPoint[1] + deltaY,
|
startPoint[1] + deltaY,
|
||||||
);
|
);
|
||||||
@@ -399,8 +399,8 @@ describe("Test Linear Elements", () => {
|
|||||||
// This is the expected midpoint for line with round edge
|
// This is the expected midpoint for line with round edge
|
||||||
// hence hardcoding it so if later some bug is introduced
|
// hence hardcoding it so if later some bug is introduced
|
||||||
// this will fail and we can fix it
|
// this will fail and we can fix it
|
||||||
const firstSegmentMidpoint = point<GlobalPoint>(55, 45);
|
const firstSegmentMidpoint = pointFrom<GlobalPoint>(55, 45);
|
||||||
const lastSegmentMidpoint = point<GlobalPoint>(75, 40);
|
const lastSegmentMidpoint = pointFrom<GlobalPoint>(75, 40);
|
||||||
|
|
||||||
let line: ExcalidrawLinearElement;
|
let line: ExcalidrawLinearElement;
|
||||||
|
|
||||||
@@ -416,7 +416,7 @@ describe("Test Linear Elements", () => {
|
|||||||
// drag line via first segment midpoint
|
// drag line via first segment midpoint
|
||||||
drag(
|
drag(
|
||||||
firstSegmentMidpoint,
|
firstSegmentMidpoint,
|
||||||
point(
|
pointFrom(
|
||||||
firstSegmentMidpoint[0] + delta,
|
firstSegmentMidpoint[0] + delta,
|
||||||
firstSegmentMidpoint[1] + delta,
|
firstSegmentMidpoint[1] + delta,
|
||||||
),
|
),
|
||||||
@@ -426,7 +426,10 @@ describe("Test Linear Elements", () => {
|
|||||||
// drag line from last segment midpoint
|
// drag line from last segment midpoint
|
||||||
drag(
|
drag(
|
||||||
lastSegmentMidpoint,
|
lastSegmentMidpoint,
|
||||||
point(lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta),
|
pointFrom(
|
||||||
|
lastSegmentMidpoint[0] + delta,
|
||||||
|
lastSegmentMidpoint[1] + delta,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
@@ -475,10 +478,10 @@ describe("Test Linear Elements", () => {
|
|||||||
h.state,
|
h.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
|
const hitCoords = pointFrom<GlobalPoint>(points[0][0], points[0][1]);
|
||||||
|
|
||||||
// Drag from first point
|
// Drag from first point
|
||||||
drag(hitCoords, point(hitCoords[0] - delta, hitCoords[1] - delta));
|
drag(hitCoords, pointFrom(hitCoords[0] - delta, hitCoords[1] - delta));
|
||||||
|
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
`12`,
|
`12`,
|
||||||
@@ -516,10 +519,10 @@ describe("Test Linear Elements", () => {
|
|||||||
h.state,
|
h.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
|
const hitCoords = pointFrom<GlobalPoint>(points[0][0], points[0][1]);
|
||||||
|
|
||||||
// Drag from first point
|
// Drag from first point
|
||||||
drag(hitCoords, point(hitCoords[0] + delta, hitCoords[1] + delta));
|
drag(hitCoords, pointFrom(hitCoords[0] + delta, hitCoords[1] + delta));
|
||||||
|
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
`12`,
|
`12`,
|
||||||
@@ -556,7 +559,7 @@ describe("Test Linear Elements", () => {
|
|||||||
// dragging line from last segment midpoint
|
// dragging line from last segment midpoint
|
||||||
drag(
|
drag(
|
||||||
lastSegmentMidpoint,
|
lastSegmentMidpoint,
|
||||||
point(lastSegmentMidpoint[0] + 50, lastSegmentMidpoint[1] + 50),
|
pointFrom(lastSegmentMidpoint[0] + 50, lastSegmentMidpoint[1] + 50),
|
||||||
);
|
);
|
||||||
expect(line.points.length).toEqual(4);
|
expect(line.points.length).toEqual(4);
|
||||||
|
|
||||||
@@ -589,11 +592,11 @@ describe("Test Linear Elements", () => {
|
|||||||
// This is the expected midpoint for line with round edge
|
// This is the expected midpoint for line with round edge
|
||||||
// hence hardcoding it so if later some bug is introduced
|
// hence hardcoding it so if later some bug is introduced
|
||||||
// this will fail and we can fix it
|
// this will fail and we can fix it
|
||||||
const firstSegmentMidpoint = point<GlobalPoint>(
|
const firstSegmentMidpoint = pointFrom<GlobalPoint>(
|
||||||
55.9697848965255,
|
55.9697848965255,
|
||||||
47.442326230998205,
|
47.442326230998205,
|
||||||
);
|
);
|
||||||
const lastSegmentMidpoint = point<GlobalPoint>(
|
const lastSegmentMidpoint = pointFrom<GlobalPoint>(
|
||||||
76.08587175006699,
|
76.08587175006699,
|
||||||
43.294165939653226,
|
43.294165939653226,
|
||||||
);
|
);
|
||||||
@@ -612,7 +615,7 @@ describe("Test Linear Elements", () => {
|
|||||||
// drag line from first segment midpoint
|
// drag line from first segment midpoint
|
||||||
drag(
|
drag(
|
||||||
firstSegmentMidpoint,
|
firstSegmentMidpoint,
|
||||||
point(
|
pointFrom(
|
||||||
firstSegmentMidpoint[0] + delta,
|
firstSegmentMidpoint[0] + delta,
|
||||||
firstSegmentMidpoint[1] + delta,
|
firstSegmentMidpoint[1] + delta,
|
||||||
),
|
),
|
||||||
@@ -622,7 +625,10 @@ describe("Test Linear Elements", () => {
|
|||||||
// drag line from last segment midpoint
|
// drag line from last segment midpoint
|
||||||
drag(
|
drag(
|
||||||
lastSegmentMidpoint,
|
lastSegmentMidpoint,
|
||||||
point(lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta),
|
pointFrom(
|
||||||
|
lastSegmentMidpoint[0] + delta,
|
||||||
|
lastSegmentMidpoint[1] + delta,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
`16`,
|
`16`,
|
||||||
@@ -669,10 +675,10 @@ describe("Test Linear Elements", () => {
|
|||||||
h.state,
|
h.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
|
const hitCoords = pointFrom<GlobalPoint>(points[0][0], points[0][1]);
|
||||||
|
|
||||||
// Drag from first point
|
// Drag from first point
|
||||||
drag(hitCoords, point(hitCoords[0] - delta, hitCoords[1] - delta));
|
drag(hitCoords, pointFrom(hitCoords[0] - delta, hitCoords[1] - delta));
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
line,
|
line,
|
||||||
@@ -717,10 +723,10 @@ describe("Test Linear Elements", () => {
|
|||||||
h.state,
|
h.state,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hitCoords = point<GlobalPoint>(points[0][0], points[0][1]);
|
const hitCoords = pointFrom<GlobalPoint>(points[0][0], points[0][1]);
|
||||||
|
|
||||||
// Drag from first point
|
// Drag from first point
|
||||||
drag(hitCoords, point(hitCoords[0] + delta, hitCoords[1] + delta));
|
drag(hitCoords, pointFrom(hitCoords[0] + delta, hitCoords[1] + delta));
|
||||||
|
|
||||||
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
expect(renderInteractiveScene.mock.calls.length).toMatchInlineSnapshot(
|
||||||
`12`,
|
`12`,
|
||||||
@@ -751,7 +757,10 @@ describe("Test Linear Elements", () => {
|
|||||||
|
|
||||||
drag(
|
drag(
|
||||||
lastSegmentMidpoint,
|
lastSegmentMidpoint,
|
||||||
point(lastSegmentMidpoint[0] + delta, lastSegmentMidpoint[1] + delta),
|
pointFrom(
|
||||||
|
lastSegmentMidpoint[0] + delta,
|
||||||
|
lastSegmentMidpoint[1] + delta,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(line.points.length).toEqual(4);
|
expect(line.points.length).toEqual(4);
|
||||||
|
|
||||||
@@ -811,8 +820,8 @@ describe("Test Linear Elements", () => {
|
|||||||
API.setSelectedElements([line]);
|
API.setSelectedElements([line]);
|
||||||
enterLineEditingMode(line, true);
|
enterLineEditingMode(line, true);
|
||||||
drag(
|
drag(
|
||||||
point(line.points[0][0] + line.x, line.points[0][1] + line.y),
|
pointFrom(line.points[0][0] + line.x, line.points[0][1] + line.y),
|
||||||
point(
|
pointFrom(
|
||||||
dragEndPositionOffset[0] + line.x,
|
dragEndPositionOffset[0] + line.x,
|
||||||
dragEndPositionOffset[1] + line.y,
|
dragEndPositionOffset[1] + line.y,
|
||||||
),
|
),
|
||||||
@@ -927,14 +936,14 @@ describe("Test Linear Elements", () => {
|
|||||||
// This is the expected midpoint for line with round edge
|
// This is the expected midpoint for line with round edge
|
||||||
// hence hardcoding it so if later some bug is introduced
|
// hence hardcoding it so if later some bug is introduced
|
||||||
// this will fail and we can fix it
|
// this will fail and we can fix it
|
||||||
const firstSegmentMidpoint = point<GlobalPoint>(
|
const firstSegmentMidpoint = pointFrom<GlobalPoint>(
|
||||||
55.9697848965255,
|
55.9697848965255,
|
||||||
47.442326230998205,
|
47.442326230998205,
|
||||||
);
|
);
|
||||||
// drag line from first segment midpoint
|
// drag line from first segment midpoint
|
||||||
drag(
|
drag(
|
||||||
firstSegmentMidpoint,
|
firstSegmentMidpoint,
|
||||||
point(
|
pointFrom(
|
||||||
firstSegmentMidpoint[0] + delta,
|
firstSegmentMidpoint[0] + delta,
|
||||||
firstSegmentMidpoint[1] + delta,
|
firstSegmentMidpoint[1] + delta,
|
||||||
),
|
),
|
||||||
@@ -1151,7 +1160,7 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Drag from last point
|
// Drag from last point
|
||||||
drag(points[1], point(points[1][0] + 300, points[1][1]));
|
drag(points[1], pointFrom(points[1][0] + 300, points[1][1]));
|
||||||
|
|
||||||
expect({ width: container.width, height: container.height })
|
expect({ width: container.width, height: container.height })
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
@@ -1350,11 +1359,11 @@ describe("Test Linear Elements", () => {
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
index: 0,
|
index: 0,
|
||||||
point: point(line.points[0][0] + 10, line.points[0][1] + 10),
|
point: pointFrom(line.points[0][0] + 10, line.points[0][1] + 10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
index: line.points.length - 1,
|
index: line.points.length - 1,
|
||||||
point: point(
|
point: pointFrom(
|
||||||
line.points[line.points.length - 1][0] - 10,
|
line.points[line.points.length - 1][0] - 10,
|
||||||
line.points[line.points.length - 1][1] - 10,
|
line.points[line.points.length - 1][1] - 10,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { isLinearElement } from "../element/typeChecks";
|
|||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
import type { LocalPoint } from "../../math";
|
import type { LocalPoint } from "../../math";
|
||||||
import { point } from "../../math";
|
import { pointFrom } from "../../math";
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
|
||||||
@@ -220,12 +220,17 @@ describe("generic element", () => {
|
|||||||
|
|
||||||
describe.each(["line", "freedraw"] as const)("%s element", (type) => {
|
describe.each(["line", "freedraw"] as const)("%s element", (type) => {
|
||||||
const points: Record<typeof type, LocalPoint[]> = {
|
const points: Record<typeof type, LocalPoint[]> = {
|
||||||
line: [point(0, 0), point(60, -20), point(20, 40), point(-40, 0)],
|
line: [
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(60, -20),
|
||||||
|
pointFrom(20, 40),
|
||||||
|
pointFrom(-40, 0),
|
||||||
|
],
|
||||||
freedraw: [
|
freedraw: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(-2.474600807561444, 41.021700699972),
|
pointFrom(-2.474600807561444, 41.021700699972),
|
||||||
point(3.6627956000014024, 47.84174560617245),
|
pointFrom(3.6627956000014024, 47.84174560617245),
|
||||||
point(40.495224145598115, 47.15909710753482),
|
pointFrom(40.495224145598115, 47.15909710753482),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -293,11 +298,11 @@ describe("arrow element", () => {
|
|||||||
it("resizes with a label", async () => {
|
it("resizes with a label", async () => {
|
||||||
const arrow = UI.createElement("arrow", {
|
const arrow = UI.createElement("arrow", {
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(40, 140),
|
pointFrom(40, 140),
|
||||||
point(80, 60), // label's anchor
|
pointFrom(80, 60), // label's anchor
|
||||||
point(180, 20),
|
pointFrom(180, 20),
|
||||||
point(200, 120),
|
pointFrom(200, 120),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const label = await UI.editText(arrow, "Hello");
|
const label = await UI.editText(arrow, "Hello");
|
||||||
@@ -747,24 +752,24 @@ describe("multiple selection", () => {
|
|||||||
x: 60,
|
x: 60,
|
||||||
y: 40,
|
y: 40,
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(-40, 40),
|
pointFrom(-40, 40),
|
||||||
point(-60, 0),
|
pointFrom(-60, 0),
|
||||||
point(0, -40),
|
pointFrom(0, -40),
|
||||||
point(40, 20),
|
pointFrom(40, 20),
|
||||||
point(0, 40),
|
pointFrom(0, 40),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const freedraw = UI.createElement("freedraw", {
|
const freedraw = UI.createElement("freedraw", {
|
||||||
x: 63.56072661326618,
|
x: 63.56072661326618,
|
||||||
y: 100,
|
y: 100,
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(-43.56072661326618, 18.15048126846341),
|
pointFrom(-43.56072661326618, 18.15048126846341),
|
||||||
point(-43.56072661326618, 29.041198460587566),
|
pointFrom(-43.56072661326618, 29.041198460587566),
|
||||||
point(-38.115368017204105, 42.652452795512204),
|
pointFrom(-38.115368017204105, 42.652452795512204),
|
||||||
point(-19.964886748740696, 66.24829266003775),
|
pointFrom(-19.964886748740696, 66.24829266003775),
|
||||||
point(19.056612930986716, 77.1390098521619),
|
pointFrom(19.056612930986716, 77.1390098521619),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1101,13 +1106,13 @@ describe("multiple selection", () => {
|
|||||||
x: 60,
|
x: 60,
|
||||||
y: 0,
|
y: 0,
|
||||||
points: [
|
points: [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(-40, 40),
|
pointFrom(-40, 40),
|
||||||
point(-20, 60),
|
pointFrom(-20, 60),
|
||||||
point(20, 20),
|
pointFrom(20, 20),
|
||||||
point(40, 40),
|
pointFrom(40, 40),
|
||||||
point(-20, 100),
|
pointFrom(-20, 100),
|
||||||
point(-60, 60),
|
pointFrom(-60, 60),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -176,6 +176,8 @@ export type StaticCanvasAppState = Readonly<
|
|||||||
gridStep: AppState["gridStep"];
|
gridStep: AppState["gridStep"];
|
||||||
frameRendering: AppState["frameRendering"];
|
frameRendering: AppState["frameRendering"];
|
||||||
currentHoveredFontFamily: AppState["currentHoveredFontFamily"];
|
currentHoveredFontFamily: AppState["currentHoveredFontFamily"];
|
||||||
|
// Cropping
|
||||||
|
croppingElementId: AppState["croppingElementId"];
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -198,6 +200,9 @@ export type InteractiveCanvasAppState = Readonly<
|
|||||||
snapLines: AppState["snapLines"];
|
snapLines: AppState["snapLines"];
|
||||||
zenModeEnabled: AppState["zenModeEnabled"];
|
zenModeEnabled: AppState["zenModeEnabled"];
|
||||||
editingTextElement: AppState["editingTextElement"];
|
editingTextElement: AppState["editingTextElement"];
|
||||||
|
// Cropping
|
||||||
|
isCropping: AppState["isCropping"];
|
||||||
|
croppingElementId: AppState["croppingElementId"];
|
||||||
// Search matches
|
// Search matches
|
||||||
searchMatches: AppState["searchMatches"];
|
searchMatches: AppState["searchMatches"];
|
||||||
}
|
}
|
||||||
@@ -219,6 +224,7 @@ export type ObservedElementsAppState = {
|
|||||||
editingLinearElementId: LinearElementEditor["elementId"] | null;
|
editingLinearElementId: LinearElementEditor["elementId"] | null;
|
||||||
// Right now it's coupled to `editingLinearElement`, ideally it should not be really needed as we already have selectedElementIds & editingLinearElementId
|
// Right now it's coupled to `editingLinearElement`, ideally it should not be really needed as we already have selectedElementIds & editingLinearElementId
|
||||||
selectedLinearElementId: LinearElementEditor["elementId"] | null;
|
selectedLinearElementId: LinearElementEditor["elementId"] | null;
|
||||||
|
croppingElementId: AppState["croppingElementId"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
@@ -386,6 +392,11 @@ export interface AppState {
|
|||||||
userToFollow: UserToFollow | null;
|
userToFollow: UserToFollow | null;
|
||||||
/** the socket ids of the users following the current user */
|
/** the socket ids of the users following the current user */
|
||||||
followedBy: Set<SocketId>;
|
followedBy: Set<SocketId>;
|
||||||
|
|
||||||
|
/** image cropping */
|
||||||
|
isCropping: boolean;
|
||||||
|
croppingElementId: ExcalidrawElement["id"] | null;
|
||||||
|
|
||||||
searchMatches: readonly SearchMatch[];
|
searchMatches: readonly SearchMatch[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { isLineSegment, lineSegment, point, type GlobalPoint } from "../math";
|
import {
|
||||||
|
isLineSegment,
|
||||||
|
lineSegment,
|
||||||
|
pointFrom,
|
||||||
|
type GlobalPoint,
|
||||||
|
} from "../math";
|
||||||
import type { LineSegment } from "../utils";
|
import type { LineSegment } from "../utils";
|
||||||
import type { BoundingBox, Bounds } from "./element/bounds";
|
import type { BoundingBox, Bounds } from "./element/bounds";
|
||||||
import { isBounds } from "./element/typeChecks";
|
import { isBounds } from "./element/typeChecks";
|
||||||
@@ -52,8 +57,8 @@ export const debugDrawPoint = (
|
|||||||
|
|
||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset - 10),
|
pointFrom<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset - 10),
|
||||||
point<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset + 10),
|
pointFrom<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset + 10),
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
color: opts?.color ?? "cyan",
|
color: opts?.color ?? "cyan",
|
||||||
@@ -62,8 +67,8 @@ export const debugDrawPoint = (
|
|||||||
);
|
);
|
||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset + 10),
|
pointFrom<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset + 10),
|
||||||
point<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset - 10),
|
pointFrom<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset - 10),
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
color: opts?.color ?? "cyan",
|
color: opts?.color ?? "cyan",
|
||||||
@@ -83,20 +88,20 @@ export const debugDrawBoundingBox = (
|
|||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
[
|
[
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox.minX, bbox.minY),
|
pointFrom<GlobalPoint>(bbox.minX, bbox.minY),
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.minY),
|
pointFrom<GlobalPoint>(bbox.maxX, bbox.minY),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.minY),
|
pointFrom<GlobalPoint>(bbox.maxX, bbox.minY),
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.maxY),
|
pointFrom<GlobalPoint>(bbox.maxX, bbox.maxY),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox.maxX, bbox.maxY),
|
pointFrom<GlobalPoint>(bbox.maxX, bbox.maxY),
|
||||||
point<GlobalPoint>(bbox.minX, bbox.maxY),
|
pointFrom<GlobalPoint>(bbox.minX, bbox.maxY),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox.minX, bbox.maxY),
|
pointFrom<GlobalPoint>(bbox.minX, bbox.maxY),
|
||||||
point<GlobalPoint>(bbox.minX, bbox.minY),
|
pointFrom<GlobalPoint>(bbox.minX, bbox.minY),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
@@ -118,20 +123,20 @@ export const debugDrawBounds = (
|
|||||||
debugDrawLine(
|
debugDrawLine(
|
||||||
[
|
[
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox[0], bbox[1]),
|
pointFrom<GlobalPoint>(bbox[0], bbox[1]),
|
||||||
point<GlobalPoint>(bbox[2], bbox[1]),
|
pointFrom<GlobalPoint>(bbox[2], bbox[1]),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox[2], bbox[1]),
|
pointFrom<GlobalPoint>(bbox[2], bbox[1]),
|
||||||
point<GlobalPoint>(bbox[2], bbox[3]),
|
pointFrom<GlobalPoint>(bbox[2], bbox[3]),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox[2], bbox[3]),
|
pointFrom<GlobalPoint>(bbox[2], bbox[3]),
|
||||||
point<GlobalPoint>(bbox[0], bbox[3]),
|
pointFrom<GlobalPoint>(bbox[0], bbox[3]),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
point<GlobalPoint>(bbox[0], bbox[3]),
|
pointFrom<GlobalPoint>(bbox[0], bbox[3]),
|
||||||
point<GlobalPoint>(bbox[0], bbox[1]),
|
pointFrom<GlobalPoint>(bbox[0], bbox[1]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { isPointOnSymmetricArc } from "./arc";
|
import { isPointOnSymmetricArc } from "./arc";
|
||||||
import { point } from "./point";
|
import { pointFrom } from "./point";
|
||||||
|
|
||||||
describe("point on arc", () => {
|
describe("point on arc", () => {
|
||||||
it("should detect point on simple arc", () => {
|
it("should detect point on simple arc", () => {
|
||||||
@@ -10,7 +10,7 @@ describe("point on arc", () => {
|
|||||||
startAngle: -Math.PI / 4,
|
startAngle: -Math.PI / 4,
|
||||||
endAngle: Math.PI / 4,
|
endAngle: Math.PI / 4,
|
||||||
},
|
},
|
||||||
point(0.92291667, 0.385),
|
pointFrom(0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -22,7 +22,7 @@ describe("point on arc", () => {
|
|||||||
startAngle: -Math.PI / 4,
|
startAngle: -Math.PI / 4,
|
||||||
endAngle: Math.PI / 4,
|
endAngle: Math.PI / 4,
|
||||||
},
|
},
|
||||||
point(-0.92291667, 0.385),
|
pointFrom(-0.92291667, 0.385),
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -34,7 +34,7 @@ describe("point on arc", () => {
|
|||||||
startAngle: -Math.PI / 4,
|
startAngle: -Math.PI / 4,
|
||||||
endAngle: Math.PI / 4,
|
endAngle: Math.PI / 4,
|
||||||
},
|
},
|
||||||
point(-0.5, 0.5),
|
pointFrom(-0.5, 0.5),
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-11
@@ -1,4 +1,4 @@
|
|||||||
import { point, pointRotateRads } from "./point";
|
import { pointFrom, pointRotateRads } from "./point";
|
||||||
import type { Curve, GlobalPoint, LocalPoint, Radians } from "./types";
|
import type { Curve, GlobalPoint, LocalPoint, Radians } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,10 +43,10 @@ export function curveToBezier<Point extends LocalPoint | GlobalPoint>(
|
|||||||
const out: Point[] = [];
|
const out: Point[] = [];
|
||||||
if (len === 3) {
|
if (len === 3) {
|
||||||
out.push(
|
out.push(
|
||||||
point(pointsIn[0][0], pointsIn[0][1]), // Points need to be cloned
|
pointFrom(pointsIn[0][0], pointsIn[0][1]), // Points need to be cloned
|
||||||
point(pointsIn[1][0], pointsIn[1][1]), // Points need to be cloned
|
pointFrom(pointsIn[1][0], pointsIn[1][1]), // Points need to be cloned
|
||||||
point(pointsIn[2][0], pointsIn[2][1]), // Points need to be cloned
|
pointFrom(pointsIn[2][0], pointsIn[2][1]), // Points need to be cloned
|
||||||
point(pointsIn[2][0], pointsIn[2][1]), // Points need to be cloned
|
pointFrom(pointsIn[2][0], pointsIn[2][1]), // Points need to be cloned
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const points: Point[] = [];
|
const points: Point[] = [];
|
||||||
@@ -59,19 +59,19 @@ export function curveToBezier<Point extends LocalPoint | GlobalPoint>(
|
|||||||
}
|
}
|
||||||
const b: Point[] = [];
|
const b: Point[] = [];
|
||||||
const s = 1 - curveTightness;
|
const s = 1 - curveTightness;
|
||||||
out.push(point(points[0][0], points[0][1]));
|
out.push(pointFrom(points[0][0], points[0][1]));
|
||||||
for (let i = 1; i + 2 < points.length; i++) {
|
for (let i = 1; i + 2 < points.length; i++) {
|
||||||
const cachedVertArray = points[i];
|
const cachedVertArray = points[i];
|
||||||
b[0] = point(cachedVertArray[0], cachedVertArray[1]);
|
b[0] = pointFrom(cachedVertArray[0], cachedVertArray[1]);
|
||||||
b[1] = point(
|
b[1] = pointFrom(
|
||||||
cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6,
|
cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6,
|
||||||
cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6,
|
cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6,
|
||||||
);
|
);
|
||||||
b[2] = point(
|
b[2] = pointFrom(
|
||||||
points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6,
|
points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6,
|
||||||
points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6,
|
points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6,
|
||||||
);
|
);
|
||||||
b[3] = point(points[i + 1][0], points[i + 1][1]);
|
b[3] = pointFrom(points[i + 1][0], points[i + 1][1]);
|
||||||
out.push(b[1], b[2], b[3]);
|
out.push(b[1], b[2], b[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ export const cubicBezierPoint = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
3 * (1 - t) * Math.pow(t, 2) * p2[1] +
|
3 * (1 - t) * Math.pow(t, 2) * p2[1] +
|
||||||
Math.pow(t, 3) * p3[1];
|
Math.pow(t, 3) * p3[1];
|
||||||
|
|
||||||
return point(x, y);
|
return pointFrom(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { point, pointRotateRads } from "./point";
|
import { pointFrom, pointRotateRads } from "./point";
|
||||||
import type { Radians } from "./types";
|
import type { Radians } from "./types";
|
||||||
|
|
||||||
describe("rotate", () => {
|
describe("rotate", () => {
|
||||||
@@ -9,14 +9,14 @@ describe("rotate", () => {
|
|||||||
const y2 = 30;
|
const y2 = 30;
|
||||||
const angle = (Math.PI / 2) as Radians;
|
const angle = (Math.PI / 2) as Radians;
|
||||||
const [rotatedX, rotatedY] = pointRotateRads(
|
const [rotatedX, rotatedY] = pointRotateRads(
|
||||||
point(x1, y1),
|
pointFrom(x1, y1),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
angle,
|
angle,
|
||||||
);
|
);
|
||||||
expect([rotatedX, rotatedY]).toEqual([30, 20]);
|
expect([rotatedX, rotatedY]).toEqual([30, 20]);
|
||||||
const res2 = pointRotateRads(
|
const res2 = pointRotateRads(
|
||||||
point(rotatedX, rotatedY),
|
pointFrom(rotatedX, rotatedY),
|
||||||
point(x2, y2),
|
pointFrom(x2, y2),
|
||||||
-angle as Radians,
|
-angle as Radians,
|
||||||
);
|
);
|
||||||
expect(res2).toEqual([x1, x2]);
|
expect(res2).toEqual([x1, x2]);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { vectorFromPoint, vectorScale } from "./vector";
|
|||||||
* @param y The Y coordinate
|
* @param y The Y coordinate
|
||||||
* @returns The branded and created point
|
* @returns The branded and created point
|
||||||
*/
|
*/
|
||||||
export function point<Point extends GlobalPoint | LocalPoint>(
|
export function pointFrom<Point extends GlobalPoint | LocalPoint>(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
): Point {
|
): Point {
|
||||||
@@ -33,7 +33,7 @@ export function pointFromArray<Point extends GlobalPoint | LocalPoint>(
|
|||||||
numberArray: number[],
|
numberArray: number[],
|
||||||
): Point | undefined {
|
): Point | undefined {
|
||||||
return numberArray.length === 2
|
return numberArray.length === 2
|
||||||
? point<Point>(numberArray[0], numberArray[1])
|
? pointFrom<Point>(numberArray[0], numberArray[1])
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ export function pointRotateRads<Point extends GlobalPoint | LocalPoint>(
|
|||||||
[cx, cy]: Point,
|
[cx, cy]: Point,
|
||||||
angle: Radians,
|
angle: Radians,
|
||||||
): Point {
|
): Point {
|
||||||
return point(
|
return pointFrom(
|
||||||
(x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx,
|
(x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx,
|
||||||
(x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy,
|
(x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy,
|
||||||
);
|
);
|
||||||
@@ -146,7 +146,7 @@ export function pointTranslate<
|
|||||||
From extends GlobalPoint | LocalPoint,
|
From extends GlobalPoint | LocalPoint,
|
||||||
To extends GlobalPoint | LocalPoint,
|
To extends GlobalPoint | LocalPoint,
|
||||||
>(p: From, v: Vector = [0, 0] as Vector): To {
|
>(p: From, v: Vector = [0, 0] as Vector): To {
|
||||||
return point(p[0] + v[0], p[1] + v[1]);
|
return pointFrom(p[0] + v[0], p[1] + v[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,7 +157,7 @@ export function pointTranslate<
|
|||||||
* @returns The middle point
|
* @returns The middle point
|
||||||
*/
|
*/
|
||||||
export function pointCenter<P extends LocalPoint | GlobalPoint>(a: P, b: P): P {
|
export function pointCenter<P extends LocalPoint | GlobalPoint>(a: P, b: P): P {
|
||||||
return point((a[0] + b[0]) / 2, (a[1] + b[1]) / 2);
|
return pointFrom((a[0] + b[0]) / 2, (a[1] + b[1]) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,7 +172,7 @@ export function pointAdd<Point extends LocalPoint | GlobalPoint>(
|
|||||||
a: Point,
|
a: Point,
|
||||||
b: Point,
|
b: Point,
|
||||||
): Point {
|
): Point {
|
||||||
return point(a[0] + b[0], a[1] + b[1]);
|
return pointFrom(a[0] + b[0], a[1] + b[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,7 +187,7 @@ export function pointSubtract<Point extends LocalPoint | GlobalPoint>(
|
|||||||
a: Point,
|
a: Point,
|
||||||
b: Point,
|
b: Point,
|
||||||
): Point {
|
): Point {
|
||||||
return point(a[0] - b[0], a[1] - b[1]);
|
return pointFrom(a[0] - b[0], a[1] - b[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,3 +28,6 @@ export const average = (a: number, b: number) => (a + b) / 2;
|
|||||||
export const isFiniteNumber = (value: any): value is number => {
|
export const isFiniteNumber = (value: any): value is number => {
|
||||||
return typeof value === "number" && Number.isFinite(value);
|
return typeof value === "number" && Number.isFinite(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isCloseTo = (a: number, b: number, precision = PRECISION) =>
|
||||||
|
Math.abs(a - b) < precision;
|
||||||
|
|||||||
@@ -139,3 +139,10 @@ export const vectorNormalize = (v: Vector): Vector => {
|
|||||||
|
|
||||||
return vector(v[0] / m, v[1] / m);
|
return vector(v[0] / m, v[1] / m);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project the first vector onto the second vector
|
||||||
|
*/
|
||||||
|
export const vectorProjection = (a: Vector, b: Vector) => {
|
||||||
|
return vectorScale(b, vectorDot(a, b) / vectorDot(b, b));
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -53,6 +54,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
"gridSize": 20,
|
"gridSize": 20,
|
||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
lineSegmentRotate,
|
lineSegmentRotate,
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateDegs,
|
pointRotateDegs,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
import { pointOnCurve, pointOnPolyline } from "./collision";
|
import { pointOnCurve, pointOnPolyline } from "./collision";
|
||||||
@@ -12,21 +12,21 @@ import type { Polyline } from "./geometry/shape";
|
|||||||
|
|
||||||
describe("point and curve", () => {
|
describe("point and curve", () => {
|
||||||
const c: Curve<GlobalPoint> = curve(
|
const c: Curve<GlobalPoint> = curve(
|
||||||
point(1.4, 1.65),
|
pointFrom(1.4, 1.65),
|
||||||
point(1.9, 7.9),
|
pointFrom(1.9, 7.9),
|
||||||
point(5.9, 1.65),
|
pointFrom(5.9, 1.65),
|
||||||
point(6.44, 4.84),
|
pointFrom(6.44, 4.84),
|
||||||
);
|
);
|
||||||
|
|
||||||
it("point on curve", () => {
|
it("point on curve", () => {
|
||||||
expect(pointOnCurve(c[0], c, 10e-5)).toBe(true);
|
expect(pointOnCurve(c[0], c, 10e-5)).toBe(true);
|
||||||
expect(pointOnCurve(c[3], c, 10e-5)).toBe(true);
|
expect(pointOnCurve(c[3], c, 10e-5)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnCurve(point(2, 4), c, 0.1)).toBe(true);
|
expect(pointOnCurve(pointFrom(2, 4), c, 0.1)).toBe(true);
|
||||||
expect(pointOnCurve(point(4, 4.4), c, 0.1)).toBe(true);
|
expect(pointOnCurve(pointFrom(4, 4.4), c, 0.1)).toBe(true);
|
||||||
expect(pointOnCurve(point(5.6, 3.85), c, 0.1)).toBe(true);
|
expect(pointOnCurve(pointFrom(5.6, 3.85), c, 0.1)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnCurve(point(5.6, 4), c, 0.1)).toBe(false);
|
expect(pointOnCurve(pointFrom(5.6, 4), c, 0.1)).toBe(false);
|
||||||
expect(pointOnCurve(c[1], c, 0.1)).toBe(false);
|
expect(pointOnCurve(c[1], c, 0.1)).toBe(false);
|
||||||
expect(pointOnCurve(c[2], c, 0.1)).toBe(false);
|
expect(pointOnCurve(c[2], c, 0.1)).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -34,52 +34,52 @@ describe("point and curve", () => {
|
|||||||
|
|
||||||
describe("point and polylines", () => {
|
describe("point and polylines", () => {
|
||||||
const polyline: Polyline<GlobalPoint> = [
|
const polyline: Polyline<GlobalPoint> = [
|
||||||
lineSegment(point(1, 0), point(1, 2)),
|
lineSegment(pointFrom(1, 0), pointFrom(1, 2)),
|
||||||
lineSegment(point(1, 2), point(2, 2)),
|
lineSegment(pointFrom(1, 2), pointFrom(2, 2)),
|
||||||
lineSegment(point(2, 2), point(2, 1)),
|
lineSegment(pointFrom(2, 2), pointFrom(2, 1)),
|
||||||
lineSegment(point(2, 1), point(3, 1)),
|
lineSegment(pointFrom(2, 1), pointFrom(3, 1)),
|
||||||
];
|
];
|
||||||
|
|
||||||
it("point on the line", () => {
|
it("point on the line", () => {
|
||||||
expect(pointOnPolyline(point(1, 0), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(1, 0), polyline)).toBe(true);
|
||||||
expect(pointOnPolyline(point(1, 2), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(1, 2), polyline)).toBe(true);
|
||||||
expect(pointOnPolyline(point(2, 2), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(2, 2), polyline)).toBe(true);
|
||||||
expect(pointOnPolyline(point(2, 1), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(2, 1), polyline)).toBe(true);
|
||||||
expect(pointOnPolyline(point(3, 1), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(3, 1), polyline)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnPolyline(point(1, 1), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(1, 1), polyline)).toBe(true);
|
||||||
expect(pointOnPolyline(point(2, 1.5), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(2, 1.5), polyline)).toBe(true);
|
||||||
expect(pointOnPolyline(point(2.5, 1), polyline)).toBe(true);
|
expect(pointOnPolyline(pointFrom(2.5, 1), polyline)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnPolyline(point(0, 1), polyline)).toBe(false);
|
expect(pointOnPolyline(pointFrom(0, 1), polyline)).toBe(false);
|
||||||
expect(pointOnPolyline(point(2.1, 1.5), polyline)).toBe(false);
|
expect(pointOnPolyline(pointFrom(2.1, 1.5), polyline)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("point on the line with rotation", () => {
|
it("point on the line with rotation", () => {
|
||||||
const truePoints = [
|
const truePoints = [
|
||||||
point(1, 0),
|
pointFrom(1, 0),
|
||||||
point(1, 2),
|
pointFrom(1, 2),
|
||||||
point(2, 2),
|
pointFrom(2, 2),
|
||||||
point(2, 1),
|
pointFrom(2, 1),
|
||||||
point(3, 1),
|
pointFrom(3, 1),
|
||||||
];
|
];
|
||||||
|
|
||||||
truePoints.forEach((p) => {
|
truePoints.forEach((p) => {
|
||||||
const rotation = (Math.random() * 360) as Degrees;
|
const rotation = (Math.random() * 360) as Degrees;
|
||||||
const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation);
|
const rotatedPoint = pointRotateDegs(p, pointFrom(0, 0), rotation);
|
||||||
const rotatedPolyline = polyline.map((line) =>
|
const rotatedPolyline = polyline.map((line) =>
|
||||||
lineSegmentRotate(line, degreesToRadians(rotation), point(0, 0)),
|
lineSegmentRotate(line, degreesToRadians(rotation), pointFrom(0, 0)),
|
||||||
);
|
);
|
||||||
expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(true);
|
expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const falsePoints = [point(0, 1), point(2.1, 1.5)];
|
const falsePoints = [pointFrom(0, 1), pointFrom(2.1, 1.5)];
|
||||||
|
|
||||||
falsePoints.forEach((p) => {
|
falsePoints.forEach((p) => {
|
||||||
const rotation = (Math.random() * 360) as Degrees;
|
const rotation = (Math.random() * 360) as Degrees;
|
||||||
const rotatedPoint = pointRotateDegs(p, point(0, 0), rotation);
|
const rotatedPoint = pointRotateDegs(p, pointFrom(0, 0), rotation);
|
||||||
const rotatedPolyline = polyline.map((line) =>
|
const rotatedPolyline = polyline.map((line) =>
|
||||||
lineSegmentRotate(line, degreesToRadians(rotation), point(0, 0)),
|
lineSegmentRotate(line, degreesToRadians(rotation), pointFrom(0, 0)),
|
||||||
);
|
);
|
||||||
expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(false);
|
expect(pointOnPolyline(rotatedPoint, rotatedPolyline)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import type { Curve } from "../math";
|
import type { Curve } from "../math";
|
||||||
import {
|
import {
|
||||||
lineSegment,
|
lineSegment,
|
||||||
point,
|
pointFrom,
|
||||||
polygonIncludesPoint,
|
polygonIncludesPoint,
|
||||||
pointOnLineSegment,
|
pointOnLineSegment,
|
||||||
pointOnPolygon,
|
pointOnPolygon,
|
||||||
@@ -110,7 +110,7 @@ const polyLineFromCurve = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
for (let i = 0; i < segments; i++) {
|
for (let i = 0; i < segments; i++) {
|
||||||
t += increment;
|
t += increment;
|
||||||
if (t <= 1) {
|
if (t <= 1) {
|
||||||
const nextPoint: Point = point(equation(t, 0), equation(t, 1));
|
const nextPoint: Point = pointFrom(equation(t, 0), equation(t, 1));
|
||||||
lineSegments.push(lineSegment(startingPoint, nextPoint));
|
lineSegments.push(lineSegment(startingPoint, nextPoint));
|
||||||
startingPoint = nextPoint;
|
startingPoint = nextPoint;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { GlobalPoint, LineSegment, Polygon, Radians } from "../../math";
|
import type { GlobalPoint, LineSegment, Polygon, Radians } from "../../math";
|
||||||
import {
|
import {
|
||||||
point,
|
pointFrom,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
polygon,
|
polygon,
|
||||||
pointOnLineSegment,
|
pointOnLineSegment,
|
||||||
@@ -23,93 +23,127 @@ describe("point and line", () => {
|
|||||||
// expect(pointRightofLine(point(2, 1), l)).toBe(true);
|
// expect(pointRightofLine(point(2, 1), l)).toBe(true);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const s: LineSegment<GlobalPoint> = lineSegment(point(1, 0), point(1, 2));
|
const s: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
pointFrom(1, 0),
|
||||||
|
pointFrom(1, 2),
|
||||||
|
);
|
||||||
|
|
||||||
it("point on the line", () => {
|
it("point on the line", () => {
|
||||||
expect(pointOnLineSegment(point(0, 1), s)).toBe(false);
|
expect(pointOnLineSegment(pointFrom(0, 1), s)).toBe(false);
|
||||||
expect(pointOnLineSegment(point(1, 1), s, 0)).toBe(true);
|
expect(pointOnLineSegment(pointFrom(1, 1), s, 0)).toBe(true);
|
||||||
expect(pointOnLineSegment(point(2, 1), s)).toBe(false);
|
expect(pointOnLineSegment(pointFrom(2, 1), s)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("point and polygon", () => {
|
describe("point and polygon", () => {
|
||||||
const poly: Polygon<GlobalPoint> = polygon(
|
const poly: Polygon<GlobalPoint> = polygon(
|
||||||
point(10, 10),
|
pointFrom(10, 10),
|
||||||
point(50, 10),
|
pointFrom(50, 10),
|
||||||
point(50, 50),
|
pointFrom(50, 50),
|
||||||
point(10, 50),
|
pointFrom(10, 50),
|
||||||
);
|
);
|
||||||
|
|
||||||
it("point on polygon", () => {
|
it("point on polygon", () => {
|
||||||
expect(pointOnPolygon(point(30, 10), poly)).toBe(true);
|
expect(pointOnPolygon(pointFrom(30, 10), poly)).toBe(true);
|
||||||
expect(pointOnPolygon(point(50, 30), poly)).toBe(true);
|
expect(pointOnPolygon(pointFrom(50, 30), poly)).toBe(true);
|
||||||
expect(pointOnPolygon(point(30, 50), poly)).toBe(true);
|
expect(pointOnPolygon(pointFrom(30, 50), poly)).toBe(true);
|
||||||
expect(pointOnPolygon(point(10, 30), poly)).toBe(true);
|
expect(pointOnPolygon(pointFrom(10, 30), poly)).toBe(true);
|
||||||
expect(pointOnPolygon(point(30, 30), poly)).toBe(false);
|
expect(pointOnPolygon(pointFrom(30, 30), poly)).toBe(false);
|
||||||
expect(pointOnPolygon(point(30, 70), poly)).toBe(false);
|
expect(pointOnPolygon(pointFrom(30, 70), poly)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("point in polygon", () => {
|
it("point in polygon", () => {
|
||||||
const poly: Polygon<GlobalPoint> = polygon(
|
const poly: Polygon<GlobalPoint> = polygon(
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(2, 0),
|
pointFrom(2, 0),
|
||||||
point(2, 2),
|
pointFrom(2, 2),
|
||||||
point(0, 2),
|
pointFrom(0, 2),
|
||||||
);
|
);
|
||||||
expect(polygonIncludesPoint(point(1, 1), poly)).toBe(true);
|
expect(polygonIncludesPoint(pointFrom(1, 1), poly)).toBe(true);
|
||||||
expect(polygonIncludesPoint(point(3, 3), poly)).toBe(false);
|
expect(polygonIncludesPoint(pointFrom(3, 3), poly)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("point and ellipse", () => {
|
describe("point and ellipse", () => {
|
||||||
const ellipse: Ellipse<GlobalPoint> = {
|
const ellipse: Ellipse<GlobalPoint> = {
|
||||||
center: point(0, 0),
|
center: pointFrom(0, 0),
|
||||||
angle: 0 as Radians,
|
angle: 0 as Radians,
|
||||||
halfWidth: 2,
|
halfWidth: 2,
|
||||||
halfHeight: 1,
|
halfHeight: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
it("point on ellipse", () => {
|
it("point on ellipse", () => {
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[
|
||||||
|
pointFrom(0, 1),
|
||||||
|
pointFrom(0, -1),
|
||||||
|
pointFrom(2, 0),
|
||||||
|
pointFrom(-2, 0),
|
||||||
|
].forEach((p) => {
|
||||||
expect(pointOnEllipse(p, ellipse)).toBe(true);
|
expect(pointOnEllipse(p, ellipse)).toBe(true);
|
||||||
});
|
});
|
||||||
expect(pointOnEllipse(point(-1.4, 0.7), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1.4, 0.7), ellipse, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(-1.4, 0.71), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1.4, 0.71), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(1.4, 0.7), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1.4, 0.7), ellipse, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(1.4, 0.71), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1.4, 0.71), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(1, -0.86), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1, -0.86), ellipse, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(1, -0.86), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(1, -0.86), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(-1, -0.86), ellipse, 0.1)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1, -0.86), ellipse, 0.1)).toBe(true);
|
||||||
expect(pointOnEllipse(point(-1, -0.86), ellipse, 0.01)).toBe(true);
|
expect(pointOnEllipse(pointFrom(-1, -0.86), ellipse, 0.01)).toBe(true);
|
||||||
|
|
||||||
expect(pointOnEllipse(point(-1, 0.8), ellipse)).toBe(false);
|
expect(pointOnEllipse(pointFrom(-1, 0.8), ellipse)).toBe(false);
|
||||||
expect(pointOnEllipse(point(1, -0.8), ellipse)).toBe(false);
|
expect(pointOnEllipse(pointFrom(1, -0.8), ellipse)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("point in ellipse", () => {
|
it("point in ellipse", () => {
|
||||||
[point(0, 1), point(0, -1), point(2, 0), point(-2, 0)].forEach((p) => {
|
[
|
||||||
|
pointFrom(0, 1),
|
||||||
|
pointFrom(0, -1),
|
||||||
|
pointFrom(2, 0),
|
||||||
|
pointFrom(-2, 0),
|
||||||
|
].forEach((p) => {
|
||||||
expect(pointInEllipse(p, ellipse)).toBe(true);
|
expect(pointInEllipse(p, ellipse)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(pointInEllipse(point(-1, 0.8), ellipse)).toBe(true);
|
expect(pointInEllipse(pointFrom(-1, 0.8), ellipse)).toBe(true);
|
||||||
expect(pointInEllipse(point(1, -0.8), ellipse)).toBe(true);
|
expect(pointInEllipse(pointFrom(1, -0.8), ellipse)).toBe(true);
|
||||||
|
|
||||||
expect(pointInEllipse(point(-1, 1), ellipse)).toBe(false);
|
expect(pointInEllipse(pointFrom(-1, 1), ellipse)).toBe(false);
|
||||||
expect(pointInEllipse(point(-1.4, 0.8), ellipse)).toBe(false);
|
expect(pointInEllipse(pointFrom(-1.4, 0.8), ellipse)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("line and line", () => {
|
describe("line and line", () => {
|
||||||
const lineA: LineSegment<GlobalPoint> = lineSegment(point(1, 4), point(3, 4));
|
const lineA: LineSegment<GlobalPoint> = lineSegment(
|
||||||
const lineB: LineSegment<GlobalPoint> = lineSegment(point(2, 1), point(2, 7));
|
pointFrom(1, 4),
|
||||||
const lineC: LineSegment<GlobalPoint> = lineSegment(point(1, 8), point(3, 8));
|
pointFrom(3, 4),
|
||||||
const lineD: LineSegment<GlobalPoint> = lineSegment(point(1, 8), point(3, 8));
|
);
|
||||||
const lineE: LineSegment<GlobalPoint> = lineSegment(point(1, 9), point(3, 9));
|
const lineB: LineSegment<GlobalPoint> = lineSegment(
|
||||||
const lineF: LineSegment<GlobalPoint> = lineSegment(point(1, 2), point(3, 4));
|
pointFrom(2, 1),
|
||||||
const lineG: LineSegment<GlobalPoint> = lineSegment(point(0, 1), point(2, 3));
|
pointFrom(2, 7),
|
||||||
|
);
|
||||||
|
const lineC: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
pointFrom(1, 8),
|
||||||
|
pointFrom(3, 8),
|
||||||
|
);
|
||||||
|
const lineD: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
pointFrom(1, 8),
|
||||||
|
pointFrom(3, 8),
|
||||||
|
);
|
||||||
|
const lineE: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
pointFrom(1, 9),
|
||||||
|
pointFrom(3, 9),
|
||||||
|
);
|
||||||
|
const lineF: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
pointFrom(1, 2),
|
||||||
|
pointFrom(3, 4),
|
||||||
|
);
|
||||||
|
const lineG: LineSegment<GlobalPoint> = lineSegment(
|
||||||
|
pointFrom(0, 1),
|
||||||
|
pointFrom(2, 3),
|
||||||
|
);
|
||||||
|
|
||||||
it("intersection", () => {
|
it("intersection", () => {
|
||||||
expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]);
|
expect(segmentsIntersectAt(lineA, lineB)).toEqual([2, 4]);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type { Curve, LineSegment, Polygon, Radians } from "../../math";
|
|||||||
import {
|
import {
|
||||||
curve,
|
curve,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
point,
|
pointFrom,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
pointFromVector,
|
pointFromVector,
|
||||||
@@ -118,23 +118,23 @@ export const getPolygonShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
const cx = x + width / 2;
|
const cx = x + width / 2;
|
||||||
const cy = y + height / 2;
|
const cy = y + height / 2;
|
||||||
|
|
||||||
const center: Point = point(cx, cy);
|
const center: Point = pointFrom(cx, cy);
|
||||||
|
|
||||||
let data: Polygon<Point>;
|
let data: Polygon<Point>;
|
||||||
|
|
||||||
if (element.type === "diamond") {
|
if (element.type === "diamond") {
|
||||||
data = polygon(
|
data = polygon(
|
||||||
pointRotateRads(point(cx, y), center, angle),
|
pointRotateRads(pointFrom(cx, y), center, angle),
|
||||||
pointRotateRads(point(x + width, cy), center, angle),
|
pointRotateRads(pointFrom(x + width, cy), center, angle),
|
||||||
pointRotateRads(point(cx, y + height), center, angle),
|
pointRotateRads(pointFrom(cx, y + height), center, angle),
|
||||||
pointRotateRads(point(x, cy), center, angle),
|
pointRotateRads(pointFrom(x, cy), center, angle),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
data = polygon(
|
data = polygon(
|
||||||
pointRotateRads(point(x, y), center, angle),
|
pointRotateRads(pointFrom(x, y), center, angle),
|
||||||
pointRotateRads(point(x + width, y), center, angle),
|
pointRotateRads(pointFrom(x + width, y), center, angle),
|
||||||
pointRotateRads(point(x + width, y + height), center, angle),
|
pointRotateRads(pointFrom(x + width, y + height), center, angle),
|
||||||
pointRotateRads(point(x, y + height), center, angle),
|
pointRotateRads(pointFrom(x, y + height), center, angle),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +162,11 @@ export const getSelectionBoxShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
y2 += padding;
|
y2 += padding;
|
||||||
|
|
||||||
//const angleInDegrees = angleToDegrees(element.angle);
|
//const angleInDegrees = angleToDegrees(element.angle);
|
||||||
const center = point(cx, cy);
|
const center = pointFrom(cx, cy);
|
||||||
const topLeft = pointRotateRads(point(x1, y1), center, element.angle);
|
const topLeft = pointRotateRads(pointFrom(x1, y1), center, element.angle);
|
||||||
const topRight = pointRotateRads(point(x2, y1), center, element.angle);
|
const topRight = pointRotateRads(pointFrom(x2, y1), center, element.angle);
|
||||||
const bottomLeft = pointRotateRads(point(x1, y2), center, element.angle);
|
const bottomLeft = pointRotateRads(pointFrom(x1, y2), center, element.angle);
|
||||||
const bottomRight = pointRotateRads(point(x2, y2), center, element.angle);
|
const bottomRight = pointRotateRads(pointFrom(x2, y2), center, element.angle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "polygon",
|
type: "polygon",
|
||||||
@@ -183,7 +183,7 @@ export const getEllipseShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
return {
|
return {
|
||||||
type: "ellipse",
|
type: "ellipse",
|
||||||
data: {
|
data: {
|
||||||
center: point(x + width / 2, y + height / 2),
|
center: pointFrom(x + width / 2, y + height / 2),
|
||||||
angle,
|
angle,
|
||||||
halfWidth: width / 2,
|
halfWidth: width / 2,
|
||||||
halfHeight: height / 2,
|
halfHeight: height / 2,
|
||||||
@@ -203,20 +203,20 @@ export const getCurvePathOps = (shape: Drawable): Op[] => {
|
|||||||
// linear
|
// linear
|
||||||
export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
roughShape: Drawable,
|
roughShape: Drawable,
|
||||||
startingPoint: Point = point(0, 0),
|
startingPoint: Point = pointFrom(0, 0),
|
||||||
angleInRadian: Radians,
|
angleInRadian: Radians,
|
||||||
center: Point,
|
center: Point,
|
||||||
): GeometricShape<Point> => {
|
): GeometricShape<Point> => {
|
||||||
const transform = (p: Point): Point =>
|
const transform = (p: Point): Point =>
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
||||||
center,
|
center,
|
||||||
angleInRadian,
|
angleInRadian,
|
||||||
);
|
);
|
||||||
|
|
||||||
const ops = getCurvePathOps(roughShape);
|
const ops = getCurvePathOps(roughShape);
|
||||||
const polycurve: Polycurve<Point> = [];
|
const polycurve: Polycurve<Point> = [];
|
||||||
let p0 = point<Point>(0, 0);
|
let p0 = pointFrom<Point>(0, 0);
|
||||||
|
|
||||||
for (const op of ops) {
|
for (const op of ops) {
|
||||||
if (op.op === "move") {
|
if (op.op === "move") {
|
||||||
@@ -225,9 +225,9 @@ export const getCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
p0 = transform(p);
|
p0 = transform(p);
|
||||||
}
|
}
|
||||||
if (op.op === "bcurveTo") {
|
if (op.op === "bcurveTo") {
|
||||||
const p1 = transform(point<Point>(op.data[0], op.data[1]));
|
const p1 = transform(pointFrom<Point>(op.data[0], op.data[1]));
|
||||||
const p2 = transform(point<Point>(op.data[2], op.data[3]));
|
const p2 = transform(pointFrom<Point>(op.data[2], op.data[3]));
|
||||||
const p3 = transform(point<Point>(op.data[4], op.data[5]));
|
const p3 = transform(pointFrom<Point>(op.data[4], op.data[5]));
|
||||||
polycurve.push(curve<Point>(p0, p1, p2, p3));
|
polycurve.push(curve<Point>(p0, p1, p2, p3));
|
||||||
p0 = p3;
|
p0 = p3;
|
||||||
}
|
}
|
||||||
@@ -288,13 +288,13 @@ export const getFreedrawShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
roughShape: Drawable,
|
roughShape: Drawable,
|
||||||
startingPoint: Point = point<Point>(0, 0),
|
startingPoint: Point = pointFrom<Point>(0, 0),
|
||||||
angleInRadian: Radians,
|
angleInRadian: Radians,
|
||||||
center: Point,
|
center: Point,
|
||||||
): GeometricShape<Point> => {
|
): GeometricShape<Point> => {
|
||||||
const transform = (p: Point) =>
|
const transform = (p: Point) =>
|
||||||
pointRotateRads(
|
pointRotateRads(
|
||||||
point(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
pointFrom(p[0] + startingPoint[0], p[1] + startingPoint[1]),
|
||||||
center,
|
center,
|
||||||
angleInRadian,
|
angleInRadian,
|
||||||
);
|
);
|
||||||
@@ -316,17 +316,17 @@ export const getClosedCurveShape = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
if (operation.op === "move") {
|
if (operation.op === "move") {
|
||||||
odd = !odd;
|
odd = !odd;
|
||||||
if (odd) {
|
if (odd) {
|
||||||
points.push(point(operation.data[0], operation.data[1]));
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
}
|
}
|
||||||
} else if (operation.op === "bcurveTo") {
|
} else if (operation.op === "bcurveTo") {
|
||||||
if (odd) {
|
if (odd) {
|
||||||
points.push(point(operation.data[0], operation.data[1]));
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
points.push(point(operation.data[2], operation.data[3]));
|
points.push(pointFrom(operation.data[2], operation.data[3]));
|
||||||
points.push(point(operation.data[4], operation.data[5]));
|
points.push(pointFrom(operation.data[4], operation.data[5]));
|
||||||
}
|
}
|
||||||
} else if (operation.op === "lineTo") {
|
} else if (operation.op === "lineTo") {
|
||||||
if (odd) {
|
if (odd) {
|
||||||
points.push(point(operation.data[0], operation.data[1]));
|
points.push(pointFrom(operation.data[0], operation.data[1]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,27 +364,27 @@ export const segmentIntersectRectangleElement = <
|
|||||||
element.x + element.width + gap,
|
element.x + element.width + gap,
|
||||||
element.y + element.height + gap,
|
element.y + element.height + gap,
|
||||||
];
|
];
|
||||||
const center = point(
|
const center = pointFrom(
|
||||||
(bounds[0] + bounds[2]) / 2,
|
(bounds[0] + bounds[2]) / 2,
|
||||||
(bounds[1] + bounds[3]) / 2,
|
(bounds[1] + bounds[3]) / 2,
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
lineSegment(
|
lineSegment(
|
||||||
pointRotateRads(point(bounds[0], bounds[1]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[0], bounds[1]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[2], bounds[1]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[2], bounds[1]), center, element.angle),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
pointRotateRads(point(bounds[2], bounds[1]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[2], bounds[1]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[2], bounds[3]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[2], bounds[3]), center, element.angle),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
pointRotateRads(point(bounds[2], bounds[3]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[2], bounds[3]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[0], bounds[3]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[0], bounds[3]), center, element.angle),
|
||||||
),
|
),
|
||||||
lineSegment(
|
lineSegment(
|
||||||
pointRotateRads(point(bounds[0], bounds[3]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[0], bounds[3]), center, element.angle),
|
||||||
pointRotateRads(point(bounds[0], bounds[1]), center, element.angle),
|
pointRotateRads(pointFrom(bounds[0], bounds[1]), center, element.angle),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.map((s) => segmentsIntersectAt(segment, s))
|
.map((s) => segmentsIntersectAt(segment, s))
|
||||||
@@ -404,7 +404,7 @@ const distanceToEllipse = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
);
|
);
|
||||||
const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
||||||
pointFromVector(translatedPoint),
|
pointFromVector(translatedPoint),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
-angle as Radians,
|
-angle as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -442,7 +442,10 @@ const distanceToEllipse = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
b * ty * Math.sign(rotatedPointY),
|
b * ty * Math.sign(rotatedPointY),
|
||||||
];
|
];
|
||||||
|
|
||||||
return pointDistance(point(rotatedPointX, rotatedPointY), point(minX, minY));
|
return pointDistance(
|
||||||
|
pointFrom(rotatedPointX, rotatedPointY),
|
||||||
|
pointFrom(minX, minY),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pointOnEllipse = <Point extends LocalPoint | GlobalPoint>(
|
export const pointOnEllipse = <Point extends LocalPoint | GlobalPoint>(
|
||||||
@@ -464,7 +467,7 @@ export const pointInEllipse = <Point extends LocalPoint | GlobalPoint>(
|
|||||||
);
|
);
|
||||||
const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
const [rotatedPointX, rotatedPointY] = pointRotateRads(
|
||||||
pointFromVector(translatedPoint),
|
pointFromVector(translatedPoint),
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
-angle as Radians,
|
-angle as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,6 @@
|
|||||||
"css-loader": "6.7.1",
|
"css-loader": "6.7.1",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"fonteditor-core": "2.4.0",
|
"fonteditor-core": "2.4.0",
|
||||||
"node-fetch": "3.3.2",
|
|
||||||
"sass-loader": "13.0.2",
|
"sass-loader": "13.0.2",
|
||||||
"ts-loader": "9.3.1",
|
"ts-loader": "9.3.1",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { arrayToMap } from "../excalidraw/utils";
|
|||||||
import type { LocalPoint } from "../math";
|
import type { LocalPoint } from "../math";
|
||||||
import {
|
import {
|
||||||
rangeIncludesValue,
|
rangeIncludesValue,
|
||||||
point,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
rangeInclusive,
|
rangeInclusive,
|
||||||
} from "../math";
|
} from "../math";
|
||||||
@@ -41,17 +41,17 @@ const getNonLinearElementRelativePoints = (
|
|||||||
] => {
|
] => {
|
||||||
if (element.type === "diamond") {
|
if (element.type === "diamond") {
|
||||||
return [
|
return [
|
||||||
point(element.width / 2, 0),
|
pointFrom(element.width / 2, 0),
|
||||||
point(element.width, element.height / 2),
|
pointFrom(element.width, element.height / 2),
|
||||||
point(element.width / 2, element.height),
|
pointFrom(element.width / 2, element.height),
|
||||||
point(0, element.height / 2),
|
pointFrom(0, element.height / 2),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
point(0, 0),
|
pointFrom(0, 0),
|
||||||
point(0 + element.width, 0),
|
pointFrom(0 + element.width, 0),
|
||||||
point(0 + element.width, element.height),
|
pointFrom(0 + element.width, element.height),
|
||||||
point(0, element.height),
|
pointFrom(0, element.height),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ const getRotatedBBox = (element: Element): Bounds => {
|
|||||||
const points = getElementRelativePoints(element);
|
const points = getElementRelativePoints(element);
|
||||||
|
|
||||||
const { cx, cy } = getMinMaxPoints(points);
|
const { cx, cy } = getMinMaxPoints(points);
|
||||||
const centerPoint = point<LocalPoint>(cx, cy);
|
const centerPoint = pointFrom<LocalPoint>(cx, cy);
|
||||||
|
|
||||||
const rotatedPoints = points.map((p) =>
|
const rotatedPoints = points.map((p) =>
|
||||||
pointRotateRads(p, centerPoint, element.angle),
|
pointRotateRads(p, centerPoint, element.angle),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const { build } = require("esbuild");
|
const { build } = require("esbuild");
|
||||||
const { sassPlugin } = require("esbuild-sass-plugin");
|
const { sassPlugin } = require("esbuild-sass-plugin");
|
||||||
const { externalGlobalPlugin } = require("esbuild-plugin-external-global");
|
const { externalGlobalPlugin } = require("esbuild-plugin-external-global");
|
||||||
const { woff2BrowserPlugin } = require("./woff2/woff2-esbuild-plugins");
|
|
||||||
|
|
||||||
// Will be used later for treeshaking
|
// Will be used later for treeshaking
|
||||||
//const fs = require("fs");
|
//const fs = require("fs");
|
||||||
@@ -45,13 +44,15 @@ const browserConfig = {
|
|||||||
format: "esm",
|
format: "esm",
|
||||||
plugins: [
|
plugins: [
|
||||||
sassPlugin(),
|
sassPlugin(),
|
||||||
woff2BrowserPlugin(),
|
|
||||||
externalGlobalPlugin({
|
externalGlobalPlugin({
|
||||||
react: "React",
|
react: "React",
|
||||||
"react-dom": "ReactDOM",
|
"react-dom": "ReactDOM",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
splitting: true,
|
splitting: true,
|
||||||
|
loader: {
|
||||||
|
".woff2": "file",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const createESMBrowserBuild = async () => {
|
const createESMBrowserBuild = async () => {
|
||||||
// Development unminified build with source maps
|
// Development unminified build with source maps
|
||||||
@@ -100,9 +101,10 @@ const rawConfig = {
|
|||||||
entryPoints: ["index.tsx"],
|
entryPoints: ["index.tsx"],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
plugins: [sassPlugin(), woff2BrowserPlugin()],
|
plugins: [sassPlugin()],
|
||||||
loader: {
|
loader: {
|
||||||
".json": "copy",
|
".json": "copy",
|
||||||
|
".woff2": "file",
|
||||||
},
|
},
|
||||||
packages: "external",
|
packages: "external",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const { build } = require("esbuild");
|
const { build } = require("esbuild");
|
||||||
const { sassPlugin } = require("esbuild-sass-plugin");
|
const { sassPlugin } = require("esbuild-sass-plugin");
|
||||||
const {
|
const { woff2ServerPlugin } = require("./woff2/woff2-esbuild-plugins");
|
||||||
woff2BrowserPlugin,
|
|
||||||
woff2ServerPlugin,
|
|
||||||
} = require("./woff2/woff2-esbuild-plugins");
|
|
||||||
|
|
||||||
const browserConfig = {
|
const browserConfig = {
|
||||||
entryPoints: ["index.ts"],
|
entryPoints: ["index.ts"],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
plugins: [sassPlugin(), woff2BrowserPlugin()],
|
plugins: [sassPlugin()],
|
||||||
assetNames: "assets/[name]",
|
assetNames: "assets/[name]",
|
||||||
|
loader: {
|
||||||
|
".woff2": "file",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Will be used later for treeshaking
|
// Will be used later for treeshaking
|
||||||
|
|||||||
@@ -2,45 +2,9 @@ const fs = require("fs");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
const which = require("which");
|
const which = require("which");
|
||||||
const fetch = require("node-fetch");
|
|
||||||
const wawoff = require("wawoff2");
|
const wawoff = require("wawoff2");
|
||||||
const { Font } = require("fonteditor-core");
|
const { Font } = require("fonteditor-core");
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom esbuild plugin to convert url woff2 imports into a text.
|
|
||||||
* Other woff2 imports are handled by a "file" loader.
|
|
||||||
*
|
|
||||||
* @returns {import("esbuild").Plugin}
|
|
||||||
*/
|
|
||||||
module.exports.woff2BrowserPlugin = () => {
|
|
||||||
return {
|
|
||||||
name: "woff2BrowserPlugin",
|
|
||||||
setup(build) {
|
|
||||||
build.initialOptions.loader = {
|
|
||||||
".woff2": "file",
|
|
||||||
...build.initialOptions.loader,
|
|
||||||
};
|
|
||||||
|
|
||||||
build.onResolve({ filter: /^https:\/\/.+?\.woff2$/ }, (args) => {
|
|
||||||
return {
|
|
||||||
path: args.path,
|
|
||||||
namespace: "woff2BrowserPlugin",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
build.onLoad(
|
|
||||||
{ filter: /.*/, namespace: "woff2BrowserPlugin" },
|
|
||||||
async (args) => {
|
|
||||||
return {
|
|
||||||
contents: args.path,
|
|
||||||
loader: "text",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom esbuild plugin to:
|
* Custom esbuild plugin to:
|
||||||
* 1. inline all woff2 (url and relative imports) as base64 for server-side use cases (no need for additional font fetch; works in both esm and commonjs)
|
* 1. inline all woff2 (url and relative imports) as base64 for server-side use cases (no need for additional font fetch; works in both esm and commonjs)
|
||||||
@@ -53,27 +17,6 @@ module.exports.woff2BrowserPlugin = () => {
|
|||||||
* @returns {import("esbuild").Plugin}
|
* @returns {import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
module.exports.woff2ServerPlugin = (options = {}) => {
|
module.exports.woff2ServerPlugin = (options = {}) => {
|
||||||
// google CDN fails time to time, so let's retry
|
|
||||||
async function fetchRetry(url, options = {}, retries = 0, delay = 1000) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, options);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Status: ${response.status}, ${await response.json()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
if (retries > 0) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
||||||
return fetchRetry(url, options, retries - 1, delay * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`Couldn't fetch: ${url}, error: ${e.message}`);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "woff2ServerPlugin",
|
name: "woff2ServerPlugin",
|
||||||
setup(build) {
|
setup(build) {
|
||||||
@@ -82,9 +25,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
|
|||||||
const fonts = new Map();
|
const fonts = new Map();
|
||||||
|
|
||||||
build.onResolve({ filter: /\.woff2$/ }, (args) => {
|
build.onResolve({ filter: /\.woff2$/ }, (args) => {
|
||||||
const resolvedPath = args.path.startsWith("http")
|
const resolvedPath = path.resolve(args.resolveDir, args.path);
|
||||||
? args.path // url
|
|
||||||
: path.resolve(args.resolveDir, args.path); // absolute path
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: resolvedPath,
|
path: resolvedPath,
|
||||||
@@ -101,9 +42,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
|
|||||||
// read local woff2 as a buffer (WARN: `readFileSync` does not work!)
|
// read local woff2 as a buffer (WARN: `readFileSync` does not work!)
|
||||||
woff2Buffer = await fs.promises.readFile(args.path);
|
woff2Buffer = await fs.promises.readFile(args.path);
|
||||||
} else {
|
} else {
|
||||||
// fetch remote woff2 as a buffer (i.e. from a cdn)
|
throw new Error(`Font path has to be absolute! "${args.path}"`);
|
||||||
const response = await fetchRetry(args.path, {}, 3);
|
|
||||||
woff2Buffer = await response.buffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// google's brotli decompression into snft
|
// google's brotli decompression into snft
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
|
// `EXCALIDRAW_ASSET_PATH` as a SSOT
|
||||||
const OSS_FONTS_CDN =
|
const OSS_FONTS_CDN =
|
||||||
"https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/oss/";
|
"https://excalidraw.nyc3.cdn.digitaloceanspaces.com/fonts/oss/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom vite plugin to convert url woff2 imports into a text.
|
* Custom vite plugin for auto-prefixing `EXCALIDRAW_ASSET_PATH` woff2 fonts in `excalidraw-app`.
|
||||||
* Other woff2 imports are automatically served and resolved as a file uri.
|
|
||||||
*
|
*
|
||||||
* @returns {import("vite").PluginOption}
|
* @returns {import("vite").PluginOption}
|
||||||
*/
|
*/
|
||||||
module.exports.woff2BrowserPlugin = () => {
|
module.exports.woff2BrowserPlugin = () => {
|
||||||
// for now limited to woff2 only, might be extended to any assets in the future
|
|
||||||
const regex = /^https:\/\/.+?\.woff2$/;
|
|
||||||
let isDev;
|
let isDev;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -18,34 +16,9 @@ module.exports.woff2BrowserPlugin = () => {
|
|||||||
config(_, { command }) {
|
config(_, { command }) {
|
||||||
isDev = command === "serve";
|
isDev = command === "serve";
|
||||||
},
|
},
|
||||||
resolveId(source) {
|
|
||||||
if (!regex.test(source)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// getting the url to the dependency tree
|
|
||||||
return source;
|
|
||||||
},
|
|
||||||
load(id) {
|
|
||||||
if (!regex.test(id)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// loading the url as string
|
|
||||||
return `export default "${id}"`;
|
|
||||||
},
|
|
||||||
// necessary for dev as vite / rollup does skips https imports in serve (~dev) mode
|
|
||||||
// aka dev mode equivalent of "export default x" above (resolveId + load)
|
|
||||||
transform(code, id) {
|
transform(code, id) {
|
||||||
// treat https woff2 imports as a text
|
// using copy / replace as fonts defined in the `.css` don't have to be manually copied over (vite/rollup does this automatically),
|
||||||
if (isDev && id.endsWith("/excalidraw/fonts/index.ts")) {
|
// but at the same time can't be easily prefixed with the `EXCALIDRAW_ASSET_PATH` only for the `excalidraw-app`
|
||||||
return code.replaceAll(
|
|
||||||
/import\s+(\w+)\s+from\s+(["']https:\/\/.+?\.woff2["'])/g,
|
|
||||||
`const $1 = $2`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// use CDN for Assistant
|
|
||||||
if (!isDev && id.endsWith("/excalidraw/fonts/assets/fonts.css")) {
|
if (!isDev && id.endsWith("/excalidraw/fonts/assets/fonts.css")) {
|
||||||
return `/* WARN: The following content is generated during excalidraw-app build */
|
return `/* WARN: The following content is generated during excalidraw-app build */
|
||||||
|
|
||||||
@@ -90,7 +63,6 @@ module.exports.woff2BrowserPlugin = () => {
|
|||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// using EXCALIDRAW_ASSET_PATH as a SSOT
|
|
||||||
if (!isDev && id.endsWith("excalidraw-app/index.html")) {
|
if (!isDev && id.endsWith("excalidraw-app/index.html")) {
|
||||||
return code.replace(
|
return code.replace(
|
||||||
"<!-- PLACEHOLDER:EXCALIDRAW_APP_FONTS -->",
|
"<!-- PLACEHOLDER:EXCALIDRAW_APP_FONTS -->",
|
||||||
@@ -110,9 +82,10 @@ module.exports.woff2BrowserPlugin = () => {
|
|||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
<!-- For Nunito only preload the latin range, which should be good enough for now -->
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="${OSS_FONTS_CDN}Virgil-Regular-hO16qHwV.woff2"
|
href="${OSS_FONTS_CDN}Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg-DqUjjPte.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
@@ -124,6 +97,13 @@ module.exports.woff2BrowserPlugin = () => {
|
|||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
href="${OSS_FONTS_CDN}Virgil-Regular-hO16qHwV.woff2"
|
||||||
|
as="font"
|
||||||
|
type="font/woff2"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user