diff --git a/src/actions/actionDeleteSelected.tsx b/src/actions/actionDeleteSelected.tsx index 3dbcf3eb..065c4b49 100644 --- a/src/actions/actionDeleteSelected.tsx +++ b/src/actions/actionDeleteSelected.tsx @@ -10,6 +10,7 @@ import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; import { newElementWith } from "../element/mutateElement"; import { getElementsInGroup } from "../groups"; +import { LinearElementEditor } from "../element/linearElementEditor"; const deleteSelectedElements = ( elements: readonly ExcalidrawElement[], @@ -29,26 +30,80 @@ const deleteSelectedElements = ( }; }; +function handleGroupEditingState( + appState: AppState, + elements: readonly ExcalidrawElement[], +): AppState { + if (appState.editingGroupId) { + const siblingElements = getElementsInGroup( + getNonDeletedElements(elements), + appState.editingGroupId!, + ); + if (siblingElements.length) { + return { + ...appState, + selectedElementIds: { [siblingElements[0].id]: true }, + }; + } + } + return appState; +} + export const actionDeleteSelected = register({ name: "deleteSelectedElements", perform: (elements, appState) => { + if ( + appState.editingLinearElement?.activePointIndex != null && + appState.editingLinearElement?.activePointIndex > -1 + ) { + const { elementId } = appState.editingLinearElement; + const element = LinearElementEditor.getElement(elementId); + if (element) { + // case: deleting last point + if (element.points.length < 2) { + const nextElements = elements.filter((el) => el.id !== element.id); + const nextAppState = handleGroupEditingState(appState, nextElements); + + return { + elements: nextElements, + appState: { + ...nextAppState, + editingLinearElement: null, + }, + commitToHistory: false, + }; + } + + LinearElementEditor.movePoint( + element, + appState.editingLinearElement.activePointIndex, + "delete", + ); + + return { + elements: elements, + appState: { + ...appState, + editingLinearElement: { + ...appState.editingLinearElement, + activePointIndex: + appState.editingLinearElement.activePointIndex > 0 + ? appState.editingLinearElement.activePointIndex - 1 + : 0, + }, + }, + commitToHistory: true, + }; + } + } + let { elements: nextElements, appState: nextAppState, } = deleteSelectedElements(elements, appState); - if (appState.editingGroupId) { - const siblingElements = getElementsInGroup( - getNonDeletedElements(nextElements), - appState.editingGroupId!, - ); - if (siblingElements.length) { - nextAppState = { - ...nextAppState, - selectedElementIds: { [siblingElements[0].id]: true }, - }; - } - } + nextAppState = handleGroupEditingState(nextAppState, nextElements); + return { elements: nextElements, appState: { diff --git a/src/actions/actionFinalize.tsx b/src/actions/actionFinalize.tsx index eb7f9d07..25c07dd2 100644 --- a/src/actions/actionFinalize.tsx +++ b/src/actions/actionFinalize.tsx @@ -8,10 +8,30 @@ import { t } from "../i18n"; import { register } from "./register"; import { mutateElement } from "../element/mutateElement"; import { isPathALoop } from "../math"; +import { LinearElementEditor } from "../element/linearElementEditor"; export const actionFinalize = register({ name: "finalize", perform: (elements, appState) => { + if (appState.editingLinearElement) { + const { elementId } = appState.editingLinearElement; + const element = LinearElementEditor.getElement(elementId); + + if (element) { + return { + elements: + element.points.length < 2 || isInvisiblySmallElement(element) + ? elements.filter((el) => el.id !== element.id) + : undefined, + appState: { + ...appState, + editingLinearElement: null, + }, + commitToHistory: true, + }; + } + } + let newElements = elements; if (window.document.activeElement instanceof HTMLElement) { window.document.activeElement.blur(); @@ -94,8 +114,8 @@ export const actionFinalize = register({ }, keyTest: (event, appState) => (event.key === KEYS.ESCAPE && - !appState.draggingElement && - appState.multiElement === null) || + (appState.editingLinearElement !== null || + (!appState.draggingElement && appState.multiElement === null))) || ((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) && appState.multiElement !== null), PanelComponent: ({ appState, updateData }) => ( diff --git a/src/actions/actionHistory.tsx b/src/actions/actionHistory.tsx index c23a8e73..45278ef4 100644 --- a/src/actions/actionHistory.tsx +++ b/src/actions/actionHistory.tsx @@ -30,23 +30,26 @@ const writeData = ( const prevElementMap = getElementMap(prevElements); const nextElements = data.elements; const nextElementMap = getElementMap(nextElements); - return { - elements: nextElements - .map((nextElement) => - newElementWith( - prevElementMap[nextElement.id] || nextElement, - nextElement, - ), - ) - .concat( - prevElements - .filter( - (prevElement) => !nextElementMap.hasOwnProperty(prevElement.id), - ) - .map((prevElement) => - newElementWith(prevElement, { isDeleted: true }), - ), + + const elements = nextElements + .map((nextElement) => + newElementWith( + prevElementMap[nextElement.id] || nextElement, + nextElement, ), + ) + .concat( + prevElements + .filter( + (prevElement) => !nextElementMap.hasOwnProperty(prevElement.id), + ) + .map((prevElement) => + newElementWith(prevElement, { isDeleted: true }), + ), + ); + + return { + elements, appState: { ...appState, ...data.appState }, commitToHistory, syncHistory: true, diff --git a/src/appState.ts b/src/appState.ts index 92e21111..d6dceb63 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -16,6 +16,7 @@ export const getDefaultAppState = (): AppState => { resizingElement: null, multiElement: null, editingElement: null, + editingLinearElement: null, elementType: "selection", elementLocked: false, exportBackground: true, @@ -70,6 +71,7 @@ export const clearAppStateForLocalStorage = (appState: AppState) => { isLoading, errorMessage, showShortcutsDialog, + editingLinearElement, ...exportedState } = appState; return exportedState; diff --git a/src/components/App.tsx b/src/components/App.tsx index 99f60aa1..5e4892c7 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -132,6 +132,7 @@ import { } from "../data/localStorage"; import throttle from "lodash.throttle"; +import { LinearElementEditor } from "../element/linearElementEditor"; import { getSelectedGroupIds, selectGroupsForSelectedElements, @@ -502,6 +503,16 @@ class App extends React.Component { this.initializeSocketClient({ showLoadingState: true }); } + if ( + this.state.editingLinearElement && + !this.state.selectedElementIds[this.state.editingLinearElement.elementId] + ) { + // defer so that the commitToHistory flag isn't reset via current update + setTimeout(() => { + this.actionManager.executeAction(actionFinalize); + }); + } + const cursorButton: { [id: string]: string | undefined; } = {}; @@ -1182,6 +1193,19 @@ class App extends React.Component { ); if ( + selectedElements.length === 1 && + isLinearElement(selectedElements[0]) + ) { + if ( + !this.state.editingLinearElement || + this.state.editingLinearElement.elementId !== selectedElements[0].id + ) { + history.resumeRecording(); + this.setState({ + editingLinearElement: new LinearElementEditor(selectedElements[0]), + }); + } + } else if ( selectedElements.length === 1 && !isLinearElement(selectedElements[0]) ) { @@ -1482,6 +1506,26 @@ class App extends React.Component { return; } + const selectedElements = getSelectedElements( + globalSceneState.getElements(), + this.state, + ); + + if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) { + if ( + !this.state.editingLinearElement || + this.state.editingLinearElement.elementId !== selectedElements[0].id + ) { + history.resumeRecording(); + this.setState({ + editingLinearElement: new LinearElementEditor(selectedElements[0]), + }); + } + return; + } + + resetCursor(); + const { x, y } = viewportCoordsToSceneCoords( event, this.state, @@ -1581,12 +1625,28 @@ class App extends React.Component { } } - const { x, y } = viewportCoordsToSceneCoords( + const { x: scenePointerX, y: scenePointerY } = viewportCoordsToSceneCoords( event, this.state, this.canvas, window.devicePixelRatio, ); + + if ( + this.state.editingLinearElement && + this.state.editingLinearElement.draggingElementPointIndex === null + ) { + const editingLinearElement = LinearElementEditor.handlePointerMove( + event, + scenePointerX, + scenePointerY, + this.state.editingLinearElement, + ); + if (editingLinearElement !== this.state.editingLinearElement) { + this.setState({ editingLinearElement }); + } + } + if (this.state.multiElement) { const { multiElement } = this.state; const { x: rx, y: ry } = multiElement; @@ -1600,11 +1660,15 @@ class App extends React.Component { // if we haven't yet created a temp point and we're beyond commit-zone // threshold, add a point if ( - distance2d(x - rx, y - ry, lastPoint[0], lastPoint[1]) >= - LINE_CONFIRM_THRESHOLD + distance2d( + scenePointerX - rx, + scenePointerY - ry, + lastPoint[0], + lastPoint[1], + ) >= LINE_CONFIRM_THRESHOLD ) { mutateElement(multiElement, { - points: [...points, [x - rx, y - ry]], + points: [...points, [scenePointerX - rx, scenePointerY - ry]], }); } else { document.documentElement.style.cursor = CURSOR_TYPE.POINTER; @@ -1618,8 +1682,8 @@ class App extends React.Component { points.length > 2 && lastCommittedPoint && distance2d( - x - rx, - y - ry, + scenePointerX - rx, + scenePointerY - ry, lastCommittedPoint[0], lastCommittedPoint[1], ) < LINE_CONFIRM_THRESHOLD @@ -1634,7 +1698,10 @@ class App extends React.Component { } // update last uncommitted point mutateElement(multiElement, { - points: [...points.slice(0, -1), [x - rx, y - ry]], + points: [ + ...points.slice(0, -1), + [scenePointerX - rx, scenePointerY - ry], + ], }); } } @@ -1653,11 +1720,16 @@ class App extends React.Component { const elements = globalSceneState.getElements(); const selectedElements = getSelectedElements(elements, this.state); - if (selectedElements.length === 1 && !isOverScrollBar) { + if ( + selectedElements.length === 1 && + !isOverScrollBar && + !this.state.editingLinearElement + ) { const elementWithResizeHandler = getElementWithResizeHandler( elements, this.state, - { x, y }, + scenePointerX, + scenePointerY, this.state.zoom, event.pointerType, ); @@ -1671,7 +1743,8 @@ class App extends React.Component { if (canResizeMutlipleElements(selectedElements)) { const resizeHandle = getResizeHandlerFromCoords( getCommonBounds(selectedElements), - { x, y }, + scenePointerX, + scenePointerY, this.state.zoom, event.pointerType, ); @@ -1686,8 +1759,8 @@ class App extends React.Component { const hitElement = getElementAtPosition( elements, this.state, - x, - y, + scenePointerX, + scenePointerY, this.state.zoom, ); if (this.state.elementType === "text") { @@ -1928,11 +2001,12 @@ class App extends React.Component { if (this.state.elementType === "selection") { const elements = globalSceneState.getElements(); const selectedElements = getSelectedElements(elements, this.state); - if (selectedElements.length === 1) { + if (selectedElements.length === 1 && !this.state.editingLinearElement) { const elementWithResizeHandler = getElementWithResizeHandler( elements, this.state, - { x, y }, + x, + y, this.state.zoom, event.pointerType, ); @@ -1952,7 +2026,8 @@ class App extends React.Component { if (canResizeMutlipleElements(selectedElements)) { resizeHandle = getResizeHandlerFromCoords( getCommonBounds(selectedElements), - { x, y }, + x, + y, this.state.zoom, event.pointerType, ); @@ -1985,13 +2060,28 @@ class App extends React.Component { } } if (!isResizingElements) { - hitElement = getElementAtPosition( - elements, - this.state, - x, - y, - this.state.zoom, - ); + if (this.state.editingLinearElement) { + const ret = LinearElementEditor.handlePointerDown( + event, + this.state, + (appState) => this.setState(appState), + history, + x, + y, + ); + if (ret.hitElement) { + hitElement = ret.hitElement; + } + if (ret.didAddPoint) { + return; + } + } + + // hitElement may already be set above, so check first + hitElement = + hitElement || + getElementAtPosition(elements, this.state, x, y, this.state.zoom); + // clear selection if shift is not clicked if ( !(hitElement && this.state.selectedElementIds[hitElement.id]) && @@ -2271,6 +2361,23 @@ class App extends React.Component { } } + if (this.state.editingLinearElement) { + const didDrag = LinearElementEditor.handlePointDragging( + this.state, + (appState) => this.setState(appState), + x, + y, + lastX, + lastY, + ); + + if (didDrag) { + lastX = x; + lastY = y; + return; + } + } + if (hitElement && this.state.selectedElementIds[hitElement.id]) { // Marking that click was used for dragging to check // if elements should be deselected on pointerup @@ -2457,6 +2564,17 @@ class App extends React.Component { this.savePointer(childEvent.clientX, childEvent.clientY, "up"); + // if moving start/end point towards start/end point within threshold, + // close the loop + if (this.state.editingLinearElement) { + const editingLinearElement = LinearElementEditor.handlePointerUp( + this.state.editingLinearElement, + ); + if (editingLinearElement !== this.state.editingLinearElement) { + this.setState({ editingLinearElement }); + } + } + lastPointerUp = null; window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove); diff --git a/src/components/HintViewer.tsx b/src/components/HintViewer.tsx index 80a25349..7bcb1dde 100644 --- a/src/components/HintViewer.tsx +++ b/src/components/HintViewer.tsx @@ -6,6 +6,7 @@ import { getSelectedElements } from "../scene"; import "./HintViewer.scss"; import { AppState } from "../types"; import { isLinearElement } from "../element/typeChecks"; +import { getShortcutKey } from "../utils"; interface Hint { appState: AppState; @@ -43,11 +44,20 @@ const getHints = ({ appState, elements }: Hint) => { return t("hints.rotate"); } + if (selectedElements.length === 1 && isLinearElement(selectedElements[0])) { + if (appState.editingLinearElement) { + return appState.editingLinearElement.activePointIndex + ? t("hints.lineEditor_pointSelected") + : t("hints.lineEditor_nothingSelected"); + } + return t("hints.lineEditor_info"); + } + return null; }; export const HintViewer = ({ appState, elements }: Hint) => { - const hint = getHints({ + let hint = getHints({ appState, elements, }); @@ -55,6 +65,8 @@ export const HintViewer = ({ appState, elements }: Hint) => { return null; } + hint = getShortcutKey(hint); + return (
{hint} diff --git a/src/element/bounds.ts b/src/element/bounds.ts index aa57c693..93b5c99c 100644 --- a/src/element/bounds.ts +++ b/src/element/bounds.ts @@ -343,6 +343,26 @@ export const getResizedElementAbsoluteCoords = ( ]; }; +export const getElementPointsCoords = ( + element: ExcalidrawLinearElement, + points: readonly (readonly [number, number])[], +): [number, number, number, number] => { + // This might be computationally heavey + const gen = rough.generator(); + const curve = gen.curve( + points as [number, number][], + generateRoughOptions(element), + ); + const ops = getCurvePathOps(curve); + const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops); + return [ + minX + element.x, + minY + element.y, + maxX + element.x, + maxY + element.y, + ]; +}; + export const getClosestElementBounds = ( elements: readonly ExcalidrawElement[], from: { x: number; y: number }, diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts new file mode 100644 index 00000000..97e049ff --- /dev/null +++ b/src/element/linearElementEditor.ts @@ -0,0 +1,409 @@ +import { + NonDeleted, + ExcalidrawLinearElement, + ExcalidrawElement, +} from "./types"; +import { distance2d, rotate, isPathALoop } from "../math"; +import { getElementAbsoluteCoords } from "."; +import { getElementPointsCoords } from "./bounds"; +import { Point, AppState } from "../types"; +import { mutateElement } from "./mutateElement"; +import { SceneHistory } from "../history"; +import { globalSceneState } from "../scene"; + +export class LinearElementEditor { + public elementId: ExcalidrawElement["id"]; + public activePointIndex: number | null; + public draggingElementPointIndex: number | null; + public lastUncommittedPoint: Point | null; + + constructor(element: NonDeleted) { + LinearElementEditor.normalizePoints(element); + + this.elementId = element.id; + this.activePointIndex = null; + this.lastUncommittedPoint = null; + this.draggingElementPointIndex = null; + } + + // --------------------------------------------------------------------------- + // static methods + // --------------------------------------------------------------------------- + + static POINT_HANDLE_SIZE = 20; + + static getElement(id: ExcalidrawElement["id"]) { + const element = globalSceneState.getNonDeletedElement(id); + if (element) { + return element as NonDeleted; + } + return null; + } + + /** @returns whether point was dragged */ + static handlePointDragging( + appState: AppState, + setState: React.Component["setState"], + scenePointerX: number, + scenePointerY: number, + lastX: number, + lastY: number, + ): boolean { + if (!appState.editingLinearElement) { + return false; + } + const { editingLinearElement } = appState; + let { draggingElementPointIndex, elementId } = editingLinearElement; + + const element = LinearElementEditor.getElement(elementId); + if (!element) { + return false; + } + + const clickedPointIndex = + draggingElementPointIndex ?? + LinearElementEditor.getPointIndexUnderCursor( + element, + appState.zoom, + scenePointerX, + scenePointerY, + ); + + draggingElementPointIndex = draggingElementPointIndex ?? clickedPointIndex; + if (draggingElementPointIndex > -1) { + if ( + editingLinearElement.draggingElementPointIndex !== + draggingElementPointIndex || + editingLinearElement.activePointIndex !== clickedPointIndex + ) { + setState({ + editingLinearElement: { + ...editingLinearElement, + draggingElementPointIndex, + activePointIndex: clickedPointIndex, + }, + }); + } + + const [deltaX, deltaY] = rotate( + scenePointerX - lastX, + scenePointerY - lastY, + 0, + 0, + -element.angle, + ); + const targetPoint = element.points[clickedPointIndex]; + LinearElementEditor.movePoint(element, clickedPointIndex, [ + targetPoint[0] + deltaX, + targetPoint[1] + deltaY, + ]); + return true; + } + return false; + } + + static handlePointerUp( + editingLinearElement: LinearElementEditor, + ): LinearElementEditor { + const { elementId, draggingElementPointIndex } = editingLinearElement; + const element = LinearElementEditor.getElement(elementId); + if (!element) { + return editingLinearElement; + } + + if ( + draggingElementPointIndex !== null && + (draggingElementPointIndex === 0 || + draggingElementPointIndex === element.points.length - 1) && + isPathALoop(element.points) + ) { + LinearElementEditor.movePoint( + element, + draggingElementPointIndex, + draggingElementPointIndex === 0 + ? element.points[element.points.length - 1] + : element.points[0], + ); + } + if (draggingElementPointIndex !== null) { + return { + ...editingLinearElement, + draggingElementPointIndex: null, + }; + } + return editingLinearElement; + } + + static handlePointerDown( + event: React.PointerEvent, + appState: AppState, + setState: React.Component["setState"], + history: SceneHistory, + scenePointerX: number, + scenePointerY: number, + ): { + didAddPoint: boolean; + hitElement: ExcalidrawElement | null; + } { + const ret: ReturnType = { + didAddPoint: false, + hitElement: null, + }; + + if (!appState.editingLinearElement) { + return ret; + } + + const { elementId } = appState.editingLinearElement; + const element = LinearElementEditor.getElement(elementId); + + if (!element) { + return ret; + } + + if (event.altKey) { + if (!appState.editingLinearElement.lastUncommittedPoint) { + mutateElement(element, { + points: [ + ...element.points, + LinearElementEditor.createPointAt( + element, + scenePointerX, + scenePointerY, + ), + ], + }); + } + if (appState.editingLinearElement.lastUncommittedPoint !== null) { + history.resumeRecording(); + } + setState({ + editingLinearElement: { + ...appState.editingLinearElement, + activePointIndex: element.points.length - 1, + lastUncommittedPoint: null, + }, + }); + ret.didAddPoint = true; + return ret; + } + + const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor( + element, + appState.zoom, + scenePointerX, + scenePointerY, + ); + + // if we clicked on a point, set the element as hitElement otherwise + // it would get deselected if the point is outside the hitbox area + if (clickedPointIndex > -1) { + ret.hitElement = element; + } + + setState({ + editingLinearElement: { + ...appState.editingLinearElement, + activePointIndex: clickedPointIndex > -1 ? clickedPointIndex : null, + }, + }); + return ret; + } + + static handlePointerMove( + event: React.PointerEvent, + scenePointerX: number, + scenePointerY: number, + editingLinearElement: LinearElementEditor, + ): LinearElementEditor { + const { elementId, lastUncommittedPoint } = editingLinearElement; + const element = LinearElementEditor.getElement(elementId); + if (!element) { + return editingLinearElement; + } + + const { points } = element; + const lastPoint = points[points.length - 1]; + + if (!event.altKey) { + if (lastPoint === lastUncommittedPoint) { + LinearElementEditor.movePoint(element, points.length - 1, "delete"); + } + return editingLinearElement; + } + + const newPoint = LinearElementEditor.createPointAt( + element, + scenePointerX, + scenePointerY, + ); + + if (lastPoint === lastUncommittedPoint) { + LinearElementEditor.movePoint( + element, + element.points.length - 1, + newPoint, + ); + } else { + LinearElementEditor.movePoint(element, "new", newPoint); + } + + return { + ...editingLinearElement, + lastUncommittedPoint: element.points[element.points.length - 1], + }; + } + + static getPointsGlobalCoordinates( + element: NonDeleted, + ) { + const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); + const cx = (x1 + x2) / 2; + const cy = (y1 + y2) / 2; + return element.points.map((point) => { + let { x, y } = element; + [x, y] = rotate(x + point[0], y + point[1], cx, cy, element.angle); + return [x, y]; + }); + } + + static getPointIndexUnderCursor( + element: NonDeleted, + zoom: AppState["zoom"], + x: number, + y: number, + ) { + const pointHandles = this.getPointsGlobalCoordinates(element); + let idx = pointHandles.length; + // loop from right to left because points on the right are rendered over + // points on the left, thus should take precedence when clicking, if they + // overlap + while (--idx > -1) { + const point = pointHandles[idx]; + if ( + distance2d(x, y, point[0], point[1]) * zoom < + // +1px to account for outline stroke + this.POINT_HANDLE_SIZE / 2 + 1 + ) { + return idx; + } + } + return -1; + } + + static createPointAt( + element: NonDeleted, + scenePointerX: number, + scenePointerY: number, + ): Point { + const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); + const cx = (x1 + x2) / 2; + const cy = (y1 + y2) / 2; + const [rotatedX, rotatedY] = rotate( + scenePointerX, + scenePointerY, + cx, + cy, + -element.angle, + ); + + return [rotatedX - element.x, rotatedY - element.y]; + } + + // element-mutating methods + // --------------------------------------------------------------------------- + + /** + * Normalizes line points so that the start point is at [0,0]. This is + * expected in various parts of the codebase. + */ + static normalizePoints(element: NonDeleted) { + const { points } = element; + + const offsetX = points[0][0]; + const offsetY = points[0][1]; + + mutateElement(element, { + points: points.map((point, _idx) => { + return [point[0] - offsetX, point[1] - offsetY] as const; + }), + x: element.x + offsetX, + y: element.y + offsetY, + }); + } + + static movePoint( + element: NonDeleted, + pointIndex: number | "new", + targetPosition: Point | "delete", + ) { + const { points } = element; + + // in case we're moving start point, instead of modifying its position + // which would break the invariant of it being at [0,0], we move + // all the other points in the opposite direction by delta to + // offset it. We do the same with actual element.x/y position, so + // this hacks are completely transparent to the user. + let offsetX = 0; + let offsetY = 0; + + let nextPoints: (readonly [number, number])[]; + if (targetPosition === "delete") { + // remove point + if (pointIndex === "new") { + throw new Error("invalid args in movePoint"); + } + nextPoints = points.slice(); + nextPoints.splice(pointIndex, 1); + if (pointIndex === 0) { + // if deleting first point, make the next to be [0,0] and recalculate + // positions of the rest with respect to it + offsetX = nextPoints[0][0]; + offsetY = nextPoints[0][1]; + nextPoints = nextPoints.map((point, idx) => { + if (idx === 0) { + return [0, 0]; + } + return [point[0] - offsetX, point[1] - offsetY]; + }); + } + } else if (pointIndex === "new") { + nextPoints = [...points, targetPosition]; + } else { + const deltaX = targetPosition[0] - points[pointIndex][0]; + const deltaY = targetPosition[1] - points[pointIndex][1]; + nextPoints = points.map((point, idx) => { + if (idx === pointIndex) { + if (idx === 0) { + offsetX = deltaX; + offsetY = deltaY; + return point; + } + offsetX = 0; + offsetY = 0; + + return [point[0] + deltaX, point[1] + deltaY] as const; + } + return offsetX || offsetY + ? ([point[0] - offsetX, point[1] - offsetY] as const) + : point; + }); + } + + const nextCoords = getElementPointsCoords(element, nextPoints); + const prevCoords = getElementPointsCoords(element, points); + const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2; + const nextCenterY = (nextCoords[1] + nextCoords[3]) / 2; + const prevCenterX = (prevCoords[0] + prevCoords[2]) / 2; + const prevCenterY = (prevCoords[1] + prevCoords[3]) / 2; + const dX = prevCenterX - nextCenterX; + const dY = prevCenterY - nextCenterY; + const rotated = rotate(offsetX, offsetY, dX, dY, element.angle); + + mutateElement(element, { + points: nextPoints, + x: element.x + rotated[0], + y: element.y + rotated[1], + }); + } +} diff --git a/src/element/mutateElement.ts b/src/element/mutateElement.ts index cded93f7..63366765 100644 --- a/src/element/mutateElement.ts +++ b/src/element/mutateElement.ts @@ -3,6 +3,7 @@ import { invalidateShapeForElement } from "../renderer/renderElement"; import { globalSceneState } from "../scene"; import { getSizeFromPoints } from "../points"; import { randomInteger } from "../random"; +import { Point } from "../types"; type ElementUpdate = Omit< Partial, @@ -24,7 +25,6 @@ export const mutateElement = >( const { points } = updates as any; if (typeof points !== "undefined") { - didChange = true; updates = { ...getSizeFromPoints(points), ...updates }; } @@ -38,6 +38,30 @@ export const mutateElement = >( ) { continue; } + + if (key === "points") { + const prevPoints = (element as any)[key]; + const nextPoints = value; + if (prevPoints.length === nextPoints.length) { + let didChangePoints = false; + let i = prevPoints.length; + while (--i) { + const prevPoint: Point = prevPoints[i]; + const nextPoint: Point = nextPoints[i]; + if ( + prevPoint[0] !== nextPoint[0] || + prevPoint[1] !== nextPoint[1] + ) { + didChangePoints = true; + break; + } + } + if (!didChangePoints) { + continue; + } + } + } + (element as any)[key] = value; didChange = true; } diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index 67fc80bf..d831e6bf 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -63,21 +63,31 @@ export const resizeTest = ( export const getElementWithResizeHandler = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, - { x, y }: { x: number; y: number }, + scenePointerX: number, + scenePointerY: number, zoom: number, pointerType: PointerType, -) => - elements.reduce((result, element) => { +) => { + return elements.reduce((result, element) => { if (result) { return result; } - const resizeHandle = resizeTest(element, appState, x, y, zoom, pointerType); + const resizeHandle = resizeTest( + element, + appState, + scenePointerX, + scenePointerY, + zoom, + pointerType, + ); return resizeHandle ? { element, resizeHandle } : null; }, null as { element: NonDeletedExcalidrawElement; resizeHandle: ReturnType } | null); +}; export const getResizeHandlerFromCoords = ( [x1, y1, x2, y2]: readonly [number, number, number, number], - { x, y }: { x: number; y: number }, + scenePointerX: number, + scenePointerY: number, zoom: number, pointerType: PointerType, ) => { @@ -91,7 +101,7 @@ export const getResizeHandlerFromCoords = ( const found = Object.keys(handlers).find((key) => { const handler = handlers[key as Exclude]!; - return handler && isInHandlerRect(handler, x, y); + return handler && isInHandlerRect(handler, scenePointerX, scenePointerY); }); return (found || false) as HandlerRectanglesRet; }; diff --git a/src/element/types.ts b/src/element/types.ts index 7d0c139e..cae69b32 100644 --- a/src/element/types.ts +++ b/src/element/types.ts @@ -65,7 +65,7 @@ export type ExcalidrawTextElement = _ExcalidrawElementBase & export type ExcalidrawLinearElement = _ExcalidrawElementBase & Readonly<{ type: "arrow" | "line" | "draw"; - points: Point[]; + points: readonly Point[]; lastCommittedPoint?: Point | null; }>; diff --git a/src/history.ts b/src/history.ts index c15212f3..d0b29a25 100644 --- a/src/history.ts +++ b/src/history.ts @@ -22,6 +22,7 @@ const clearAppStatePropertiesForHistory = (appState: AppState) => { return { selectedElementIds: appState.selectedElementIds, viewBackgroundColor: appState.viewBackgroundColor, + editingLinearElement: appState.editingLinearElement, editingGroupId: appState.editingGroupId, name: appState.name, }; @@ -160,6 +161,14 @@ export class SceneHistory { // note: this is safe because entry's appState is guaranteed no excess props let key: keyof typeof nextEntry.appState; for (key in nextEntry.appState) { + if (key === "editingLinearElement") { + if ( + nextEntry.appState[key]?.elementId === + lastEntry.appState[key]?.elementId + ) { + continue; + } + } if (key === "selectedElementIds") { continue; } diff --git a/src/locales/en.json b/src/locales/en.json index c26bba9c..276fd72d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -121,7 +121,10 @@ "freeDraw": "Click and drag, release when you're finished", "linearElementMulti": "Click on last point or press Escape or Enter to finish", "resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center", - "rotate": "You can constrain angles by holding SHIFT while rotating" + "rotate": "You can constrain angles by holding SHIFT while rotating", + "lineEditor_info": "Double-click or press Enter to edit points", + "lineEditor_pointSelected": "Press Delete to remove point or drag to move", + "lineEditor_nothingSelected": "Select a point to move or remove, or hold Alt and click to add new points" }, "errorSplash": { "headingMain_pre": "Encountered an error. Try ", diff --git a/src/math.ts b/src/math.ts index cc51630d..44164e9d 100644 --- a/src/math.ts +++ b/src/math.ts @@ -1,5 +1,6 @@ import { Point } from "./types"; import { LINE_CONFIRM_THRESHOLD } from "./constants"; +import { ExcalidrawLinearElement } from "./element/types"; // https://stackoverflow.com/a/6853926/232122 export const distanceBetweenPointAndSegment = ( @@ -240,7 +241,9 @@ export const distance2d = (x1: number, y1: number, x2: number, y2: number) => { // Checks if the first and last point are close enough // to be considered a loop -export const isPathALoop = (points: Point[]): boolean => { +export const isPathALoop = ( + points: ExcalidrawLinearElement["points"], +): boolean => { if (points.length >= 3) { const [firstPoint, lastPoint] = [points[0], points[points.length - 1]]; return ( diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 3f2b1239..11037071 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -6,6 +6,8 @@ import { FlooredNumber, AppState } from "../types"; import { ExcalidrawElement, NonDeletedExcalidrawElement, + ExcalidrawLinearElement, + NonDeleted, GroupId, } from "../element/types"; import { @@ -28,6 +30,8 @@ import { getSelectedElements } from "../scene/selection"; import { renderElement, renderElementToSvg } from "./renderElement"; import colors from "../colors"; +import { isLinearElement } from "../element/typeChecks"; +import { LinearElementEditor } from "../element/linearElementEditor"; import { isSelectedViaGroup, getSelectedGroupIds, @@ -83,6 +87,41 @@ const strokeCircle = ( context.stroke(); }; +const renderLinearPointHandles = ( + context: CanvasRenderingContext2D, + appState: AppState, + sceneState: SceneState, + element: NonDeleted, +) => { + context.translate(sceneState.scrollX, sceneState.scrollY); + const origStrokeStyle = context.strokeStyle; + const lineWidth = context.lineWidth; + context.lineWidth = 1 / sceneState.zoom; + + LinearElementEditor.getPointsGlobalCoordinates(element).forEach( + (point, idx) => { + context.strokeStyle = "red"; + context.setLineDash([]); + context.fillStyle = + appState.editingLinearElement?.activePointIndex === idx + ? "rgba(255, 127, 127, 0.9)" + : "rgba(255, 255, 255, 0.9)"; + const { POINT_HANDLE_SIZE } = LinearElementEditor; + strokeCircle( + context, + point[0] - POINT_HANDLE_SIZE / 2 / sceneState.zoom, + point[1] - POINT_HANDLE_SIZE / 2 / sceneState.zoom, + POINT_HANDLE_SIZE / sceneState.zoom, + POINT_HANDLE_SIZE / sceneState.zoom, + ); + }, + ); + context.setLineDash([]); + context.lineWidth = lineWidth; + context.translate(-sceneState.scrollX, -sceneState.scrollY); + context.strokeStyle = origStrokeStyle; +}; + export const renderScene = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, @@ -153,9 +192,16 @@ export const renderScene = ( visibleElements.forEach((element) => { renderElement(element, rc, context, renderOptimizations, sceneState); + if ( + isLinearElement(element) && + appState.editingLinearElement && + appState.editingLinearElement.elementId === element.id + ) { + renderLinearPointHandles(context, appState, sceneState, element); + } }); - // Pain selection element + // Paint selection element if (selectionElement) { renderElement( selectionElement, @@ -167,7 +213,11 @@ export const renderScene = ( } // Paint selected elements - if (renderSelection) { + if ( + renderSelection && + !appState.multiElement && + !appState.editingLinearElement + ) { context.translate(sceneState.scrollX, sceneState.scrollY); const selections = elements.reduce((acc, element) => { diff --git a/src/scene/globalScene.ts b/src/scene/globalScene.ts index 3be47430..441c0c0a 100644 --- a/src/scene/globalScene.ts +++ b/src/scene/globalScene.ts @@ -1,8 +1,13 @@ import { ExcalidrawElement, NonDeletedExcalidrawElement, + NonDeleted, } from "../element/types"; -import { getNonDeletedElements } from "../element"; +import { + getNonDeletedElements, + isNonDeletedElement, + getElementMap, +} from "../element"; export interface SceneStateCallback { (): void; @@ -13,22 +18,40 @@ export interface SceneStateCallbackRemover { } class GlobalScene { - private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = []; private callbacks: Set = new Set(); - constructor(private _elements: readonly ExcalidrawElement[] = []) {} + private nonDeletedElements: readonly NonDeletedExcalidrawElement[] = []; + private elements: readonly ExcalidrawElement[] = []; + private elementsMap: { + [id: string]: ExcalidrawElement; + } = {}; getElementsIncludingDeleted() { - return this._elements; + return this.elements; } getElements(): readonly NonDeletedExcalidrawElement[] { return this.nonDeletedElements; } + getElement(id: ExcalidrawElement["id"]): ExcalidrawElement | null { + return this.elementsMap[id] || null; + } + + getNonDeletedElement( + id: ExcalidrawElement["id"], + ): NonDeleted | null { + const element = this.getElement(id); + if (element && isNonDeletedElement(element)) { + return element; + } + return null; + } + replaceAllElements(nextElements: readonly ExcalidrawElement[]) { - this._elements = nextElements; - this.nonDeletedElements = getNonDeletedElements(this._elements); + this.elements = nextElements; + this.elementsMap = getElementMap(nextElements); + this.nonDeletedElements = getNonDeletedElements(this.elements); this.informMutation(); } diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 6a9e4fa5..873b01ad 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -19,6 +19,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -139,6 +140,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -172,6 +174,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id1": true, @@ -226,6 +229,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id2": true, @@ -301,6 +305,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -407,6 +412,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -495,6 +501,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -528,6 +535,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -607,6 +615,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -670,6 +679,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -727,6 +737,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -790,6 +801,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -823,6 +835,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -856,6 +869,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -889,6 +903,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -922,6 +937,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -979,6 +995,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -1043,6 +1060,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1076,6 +1094,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1134,6 +1153,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -1199,6 +1219,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1232,6 +1253,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1266,6 +1288,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1325,6 +1348,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -1413,6 +1437,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1446,6 +1471,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id1": true, @@ -1524,6 +1550,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -1637,6 +1664,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -1670,6 +1698,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id1": true, @@ -1724,6 +1753,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id2": true, @@ -1823,6 +1853,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": "id3", + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -1940,6 +1971,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -1973,6 +2005,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id1": true, @@ -2027,6 +2060,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id2": true, @@ -2102,6 +2136,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -2209,6 +2244,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -2509,6 +2545,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object {}, "viewBackgroundColor": "#ffffff", @@ -2518,6 +2555,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -2551,6 +2589,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id1": true, @@ -2605,6 +2644,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id2": true, @@ -2680,6 +2720,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id3": true, @@ -2787,6 +2828,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id4": true, @@ -2926,6 +2968,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id5": true, @@ -3100,6 +3143,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id5": true, @@ -3278,6 +3322,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id6": true, @@ -3491,6 +3536,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id6": true, @@ -3708,6 +3754,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id7": true, @@ -3981,6 +4028,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4044,6 +4092,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4101,6 +4150,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4164,6 +4214,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4221,6 +4272,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4284,6 +4336,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4341,6 +4394,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4415,6 +4469,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4483,6 +4538,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4557,6 +4613,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4625,6 +4682,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4699,6 +4757,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4767,6 +4826,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4841,6 +4901,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -4909,6 +4970,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -4972,6 +5034,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -5029,6 +5092,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -5092,6 +5156,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -5149,6 +5214,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -5223,6 +5289,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -5291,6 +5358,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -5354,6 +5422,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -5411,6 +5480,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -5485,6 +5555,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -5553,6 +5624,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -5754,6 +5826,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -5787,6 +5860,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id1": true, @@ -5841,6 +5915,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id2": true, @@ -5916,6 +5991,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -6000,6 +6076,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -6178,6 +6255,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -6266,6 +6344,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6299,6 +6378,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id1": true, @@ -6377,6 +6457,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -6439,6 +6520,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "rectangle", "errorMessage": null, @@ -6499,6 +6581,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -6578,6 +6661,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6611,6 +6695,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6645,6 +6730,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6680,6 +6766,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6716,6 +6803,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6753,6 +6841,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6791,6 +6880,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6830,6 +6920,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6870,6 +6961,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6911,6 +7003,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6953,6 +7046,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -6996,6 +7090,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7040,6 +7135,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7085,6 +7181,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7131,6 +7228,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7178,6 +7276,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7226,6 +7325,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7299,6 +7399,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -7369,6 +7470,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7402,6 +7504,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7436,6 +7539,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7471,6 +7575,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7507,6 +7612,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7544,6 +7650,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7582,6 +7689,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7621,6 +7729,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7685,6 +7794,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -7753,6 +7863,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7786,6 +7897,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7820,6 +7932,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7855,6 +7968,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7891,6 +8005,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7928,6 +8043,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -7990,6 +8106,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -8056,6 +8173,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8089,6 +8207,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8123,6 +8242,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8158,6 +8278,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8218,6 +8339,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -8282,6 +8404,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8315,6 +8438,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8373,6 +8497,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -8451,6 +8576,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8484,6 +8610,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8518,6 +8645,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8553,6 +8681,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8589,6 +8718,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8626,6 +8756,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8664,6 +8795,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8703,6 +8835,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8743,6 +8876,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8784,6 +8918,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8826,6 +8961,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8869,6 +9005,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8913,6 +9050,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -8958,6 +9096,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9004,6 +9143,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9051,6 +9191,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9123,6 +9264,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -9199,6 +9341,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9232,6 +9375,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9266,6 +9410,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9301,6 +9446,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9337,6 +9483,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9374,6 +9521,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9412,6 +9560,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9451,6 +9600,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9491,6 +9641,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9532,6 +9683,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9574,6 +9726,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9617,6 +9770,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9661,6 +9815,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9706,6 +9861,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9776,6 +9932,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -9850,6 +10007,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9883,6 +10041,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9917,6 +10076,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9952,6 +10112,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -9988,6 +10149,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10025,6 +10187,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10063,6 +10226,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10102,6 +10266,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10142,6 +10307,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10183,6 +10349,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10225,6 +10392,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10268,6 +10436,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10336,6 +10505,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -10408,6 +10578,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10441,6 +10612,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10475,6 +10647,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10510,6 +10683,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10546,6 +10720,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10583,6 +10758,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10621,6 +10797,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10660,6 +10837,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10700,6 +10878,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10741,6 +10920,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10807,6 +10987,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -10878,6 +11059,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10911,6 +11093,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10945,6 +11128,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -10980,6 +11164,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11016,6 +11201,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11053,6 +11239,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11091,6 +11278,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11130,6 +11318,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11170,6 +11359,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11235,6 +11425,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -11304,6 +11495,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11337,6 +11529,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11371,6 +11564,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11406,6 +11600,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11442,6 +11637,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11479,6 +11675,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11517,6 +11714,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11580,6 +11778,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -11647,6 +11846,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11680,6 +11880,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11714,6 +11915,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11749,6 +11951,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11785,6 +11988,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11846,6 +12050,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -11911,6 +12116,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11944,6 +12150,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -11978,6 +12185,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12037,6 +12245,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -12116,6 +12325,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12149,6 +12359,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12183,6 +12394,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12218,6 +12430,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12254,6 +12467,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12291,6 +12505,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12329,6 +12544,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12368,6 +12584,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12408,6 +12625,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12449,6 +12667,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12491,6 +12710,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12534,6 +12754,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12578,6 +12799,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12623,6 +12845,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12669,6 +12892,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12716,6 +12940,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12764,6 +12989,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12837,6 +13063,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -12914,6 +13141,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12947,6 +13175,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -12981,6 +13210,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13016,6 +13246,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13052,6 +13283,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13089,6 +13321,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13127,6 +13360,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13166,6 +13400,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13206,6 +13441,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13247,6 +13483,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13289,6 +13526,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13332,6 +13570,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13376,6 +13615,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13421,6 +13661,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13467,6 +13708,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13538,6 +13780,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -13613,6 +13856,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13646,6 +13890,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13680,6 +13925,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13715,6 +13961,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13751,6 +13998,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13788,6 +14036,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13826,6 +14075,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13865,6 +14115,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13905,6 +14156,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13946,6 +14198,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -13988,6 +14241,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14031,6 +14285,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14075,6 +14330,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14144,6 +14400,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -14217,6 +14474,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14250,6 +14508,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14284,6 +14543,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14319,6 +14579,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14355,6 +14616,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14392,6 +14654,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14430,6 +14693,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14469,6 +14733,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14509,6 +14774,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14550,6 +14816,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14592,6 +14859,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14659,6 +14927,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -14750,6 +15019,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14783,6 +15053,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id1": true, @@ -14837,6 +15108,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -14919,6 +15191,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -14979,6 +15252,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": "id3", + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -15098,6 +15372,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -15131,6 +15406,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id1": true, @@ -15185,6 +15461,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id2": true, @@ -15260,6 +15537,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -15343,6 +15621,7 @@ Object { Object { "appState": Object { "editingGroupId": "id3", + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -15426,6 +15705,7 @@ Object { Object { "appState": Object { "editingGroupId": "id3", + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -15511,6 +15791,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object { "id0": true, @@ -15621,6 +15902,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -15683,6 +15965,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, @@ -15807,6 +16090,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id2": true, @@ -15900,6 +16184,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id2": true, @@ -15991,6 +16276,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id0": true, @@ -16024,6 +16310,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Untitled-201933152653", "selectedElementIds": Object { "id1": true, @@ -16102,6 +16389,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "text", "errorMessage": null, @@ -16139,6 +16427,7 @@ Object { Object { "appState": Object { "editingGroupId": null, + "editingLinearElement": null, "name": "Unbenannt-201933152653", "selectedElementIds": Object {}, "viewBackgroundColor": "#ffffff", @@ -16172,6 +16461,7 @@ Object { "draggingElement": null, "editingElement": null, "editingGroupId": null, + "editingLinearElement": null, "elementLocked": false, "elementType": "selection", "errorMessage": null, diff --git a/src/types.ts b/src/types.ts index 72d41a02..a0846f23 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ import { import { SHAPES } from "./shapes"; import { Point as RoughPoint } from "roughjs/bin/geometry"; import { SocketUpdateDataSource } from "./data"; +import { LinearElementEditor } from "./element/linearElementEditor"; export type FlooredNumber = number & { _brand: "FlooredNumber" }; export type Point = Readonly; @@ -25,6 +26,7 @@ export type AppState = { // element being edited, but not necessarily added to elements array yet // (e.g. text element when typing into the input) editingElement: NonDeletedExcalidrawElement | null; + editingLinearElement: LinearElementEditor | null; elementType: typeof SHAPES[number]["value"]; elementLocked: boolean; exportBackground: boolean; diff --git a/src/utils.ts b/src/utils.ts index 9fc511a4..b0224a59 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -168,12 +168,12 @@ export const getShortcutKey = (shortcut: string): string => { const isMac = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform); if (isMac) { return `${shortcut - .replace(/CtrlOrCmd/i, "Cmd") - .replace(/Alt/i, "Option") - .replace(/Del/i, "Delete") - .replace(/Enter|Return/i, "Enter")}`; + .replace(/\bCtrlOrCmd\b/i, "Cmd") + .replace(/\bAlt\b/i, "Option") + .replace(/\bDel\b/i, "Delete") + .replace(/\b(Enter|Return)\b/i, "Enter")}`; } - return `${shortcut.replace(/CtrlOrCmd/i, "Ctrl")}`; + return `${shortcut.replace(/\bCtrlOrCmd\b/i, "Ctrl")}`; }; export const viewportCoordsToSceneCoords = ( { clientX, clientY }: { clientX: number; clientY: number },