Compare commits
35 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 | |||
| 50e4a0b37d | |||
| 9e80c9e3dd | |||
| afdf38e47c | |||
| 06a7a51baa | |||
| 064bede0c5 | |||
| 997fec6c75 | |||
| 71ed96eabb | |||
| f4bebaaa50 | |||
| 3d92a3bf1f |
@@ -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();
|
||||||
@@ -3857,21 +3871,17 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
public getEditorUIOffsets = (): Offsets => {
|
public getEditorUIOffsets = (): Offsets => {
|
||||||
const toolbarBottom =
|
const toolbarBottom =
|
||||||
(this.excalidrawContainerRef?.current
|
this.excalidrawContainerRef?.current
|
||||||
?.querySelector(".App-toolbar")
|
?.querySelector(".App-toolbar")
|
||||||
?.getBoundingClientRect()?.bottom ?? 0) - this.state.offsetTop;
|
?.getBoundingClientRect()?.bottom ?? 0;
|
||||||
const sidebarRect = this.excalidrawContainerRef?.current
|
const sidebarRect = this.excalidrawContainerRef?.current
|
||||||
?.querySelector(".sidebar")
|
?.querySelector(".sidebar")
|
||||||
?.getBoundingClientRect();
|
?.getBoundingClientRect();
|
||||||
const propertiesPanelRect = this.excalidrawContainerRef?.current
|
const propertiesPanelRect = this.excalidrawContainerRef?.current
|
||||||
?.querySelector(".App-menu__left")
|
?.querySelector(".App-menu__left")
|
||||||
?.getBoundingClientRect();
|
?.getBoundingClientRect();
|
||||||
const PADDING = 16;
|
|
||||||
|
|
||||||
const adjustRectValueForOffset = (
|
const PADDING = 16;
|
||||||
value: number | undefined,
|
|
||||||
fallback: number,
|
|
||||||
) => (value ?? fallback + this.state.offsetLeft) - this.state.offsetLeft;
|
|
||||||
|
|
||||||
return getLanguage().rtl
|
return getLanguage().rtl
|
||||||
? {
|
? {
|
||||||
@@ -3879,31 +3889,22 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
right:
|
right:
|
||||||
Math.max(
|
Math.max(
|
||||||
this.state.width -
|
this.state.width -
|
||||||
adjustRectValueForOffset(
|
(propertiesPanelRect?.left ?? this.state.width),
|
||||||
propertiesPanelRect?.left,
|
|
||||||
this.state.width,
|
|
||||||
),
|
|
||||||
0,
|
0,
|
||||||
) + PADDING,
|
) + PADDING,
|
||||||
bottom: PADDING,
|
bottom: PADDING,
|
||||||
left:
|
left: Math.max(sidebarRect?.right ?? 0, 0) + PADDING,
|
||||||
Math.max(adjustRectValueForOffset(sidebarRect?.right, 0), 0) +
|
|
||||||
PADDING,
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
top: toolbarBottom + PADDING,
|
top: toolbarBottom + PADDING,
|
||||||
right: Math.max(
|
right: Math.max(
|
||||||
this.state.width -
|
this.state.width -
|
||||||
adjustRectValueForOffset(sidebarRect?.left, this.state.width) +
|
(sidebarRect?.left ?? this.state.width) +
|
||||||
PADDING,
|
PADDING,
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
bottom: PADDING,
|
bottom: PADDING,
|
||||||
left:
|
left: Math.max(propertiesPanelRect?.right ?? 0, 0) + PADDING,
|
||||||
Math.max(
|
|
||||||
adjustRectValueForOffset(propertiesPanelRect?.right, 0),
|
|
||||||
0,
|
|
||||||
) + PADDING,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3936,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
|
||||||
@@ -4920,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
|
||||||
@@ -5149,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>,
|
||||||
) => {
|
) => {
|
||||||
@@ -5180,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(
|
||||||
@@ -5282,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,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -5294,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 ||
|
||||||
@@ -5317,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(
|
||||||
@@ -5328,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) {
|
||||||
@@ -5578,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
|
||||||
) {
|
) {
|
||||||
@@ -5587,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,
|
||||||
@@ -5601,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
|
||||||
) {
|
) {
|
||||||
@@ -5649,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,
|
||||||
),
|
),
|
||||||
@@ -5668,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,
|
||||||
),
|
),
|
||||||
@@ -5897,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 };
|
||||||
@@ -6410,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);
|
||||||
@@ -6746,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(
|
||||||
@@ -6817,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(
|
||||||
@@ -7101,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],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -7310,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
|
||||||
) {
|
) {
|
||||||
@@ -7412,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,
|
||||||
@@ -7615,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
|
||||||
@@ -7637,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;
|
||||||
@@ -7665,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;
|
||||||
@@ -7675,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;
|
||||||
}
|
}
|
||||||
@@ -7848,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.
|
||||||
@@ -7981,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8015,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,
|
||||||
@@ -8044,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,
|
||||||
);
|
);
|
||||||
@@ -8052,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,
|
||||||
{
|
{
|
||||||
@@ -8064,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,
|
||||||
);
|
);
|
||||||
@@ -8229,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,
|
||||||
@@ -8247,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);
|
||||||
|
|
||||||
@@ -8373,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);
|
||||||
@@ -8422,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,
|
||||||
),
|
),
|
||||||
@@ -8729,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;
|
||||||
|
|
||||||
@@ -8736,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) {
|
||||||
@@ -8984,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
|
||||||
@@ -9198,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,
|
||||||
@@ -9351,7 +9542,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private initializeImageDimensions = (
|
initializeImageDimensions = (
|
||||||
imageElement: ExcalidrawImageElement,
|
imageElement: ExcalidrawImageElement,
|
||||||
forceNaturalSize = false,
|
forceNaturalSize = false,
|
||||||
) => {
|
) => {
|
||||||
@@ -9399,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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -9938,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,
|
||||||
@@ -9954,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;
|
||||||
}
|
}
|
||||||
@@ -10129,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,
|
||||||
|
);
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|
||||||
@@ -917,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,
|
||||||
@@ -940,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,
|
||||||
|
|||||||
@@ -54,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";
|
||||||
@@ -537,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,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -550,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",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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<
|
||||||
|
|||||||
@@ -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),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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
-1
@@ -104,7 +104,7 @@ console.error = (...args) => {
|
|||||||
// the react's act() warning usually doesn't contain any useful stack trace
|
// the react's act() warning usually doesn't contain any useful stack trace
|
||||||
// so we're catching the log and re-logging the message with the test name,
|
// so we're catching the log and re-logging the message with the test name,
|
||||||
// also stripping the actual component stack trace as it's not useful
|
// also stripping the actual component stack trace as it's not useful
|
||||||
if (args[0]?.includes("act(")) {
|
if (args[0]?.includes?.("act(")) {
|
||||||
_consoleError(
|
_consoleError(
|
||||||
yellow(
|
yellow(
|
||||||
`<<< WARNING: test "${
|
`<<< WARNING: test "${
|
||||||
|
|||||||
Reference in New Issue
Block a user