From 899c6521474115ca075dc0a480de147db5b1f4ac Mon Sep 17 00:00:00 2001 From: sunub Date: Thu, 17 Apr 2025 13:22:59 +0900 Subject: [PATCH] refactor: Replace { x: number, y: number } type declarations with Point type - Updated type declarations written as { x: number, y: number } to use the Point type as per requirements. - Explicit names were used for variables by destructuring previously used object types. - For example, scenePointer.x, scenePointer.y are now destructured as scenePointerX and scenePointerY. - When a Point type was required as an argument, the `pointFrom` helper function was used to convert and pass the value. --- .../components/ExampleApp.tsx | 2 +- packages/common/src/utils.ts | 22 ++-- packages/element/src/binding.ts | 21 ++-- packages/element/src/bounds.ts | 5 +- packages/element/src/dragElements.ts | 11 +- packages/element/src/frame.ts | 15 +-- packages/element/src/linearElementEditor.ts | 119 +++++++++--------- packages/element/src/sizeHelpers.ts | 77 ++++++------ packages/excalidraw/actions/actionCanvas.tsx | 4 +- .../excalidraw/actions/actionFinalize.tsx | 2 +- packages/excalidraw/animated-trail.ts | 4 +- packages/excalidraw/appState.ts | 7 +- packages/excalidraw/clients.ts | 2 +- .../components/ElementCanvasButtons.tsx | 2 +- packages/excalidraw/components/EyeDropper.tsx | 9 +- .../components/hyperlink/Hyperlink.tsx | 10 +- packages/excalidraw/gesture.ts | 26 ++-- packages/excalidraw/scene/scroll.ts | 12 +- packages/excalidraw/scene/types.ts | 3 +- packages/excalidraw/snapping.ts | 20 ++- packages/excalidraw/types.ts | 25 ++-- 21 files changed, 210 insertions(+), 188 deletions(-) diff --git a/examples/with-script-in-browser/components/ExampleApp.tsx b/examples/with-script-in-browser/components/ExampleApp.tsx index 976004f04..cb3e79edc 100644 --- a/examples/with-script-in-browser/components/ExampleApp.tsx +++ b/examples/with-script-in-browser/components/ExampleApp.tsx @@ -422,7 +422,7 @@ export default function ExampleApp({ if (!excalidrawAPI) { return false; } - const { x, y } = viewportCoordsToSceneCoords( + const [x, y] = viewportCoordsToSceneCoords( { clientX: event.clientX - pointerDownState.hitElementOffsets.x, clientY: event.clientY - pointerDownState.hitElementOffsets.y, diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 54eaa67cc..a51278f09 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,4 +1,10 @@ -import { average, pointFrom, type GlobalPoint } from "@excalidraw/math"; +import { + average, + pointFrom, + type ViewportPoint, + type GlobalPoint, + type GenericPoint, +} from "@excalidraw/math"; import type { ExcalidrawBindableElement, @@ -448,11 +454,11 @@ export const viewportCoordsToSceneCoords = ( scrollX: number; scrollY: number; }, -) => { +): ViewportPoint => { const x = (clientX - offsetLeft) / zoom.value - scrollX; const y = (clientY - offsetTop) / zoom.value - scrollY; - return { x, y }; + return pointFrom(x, y); }; export const sceneCoordsToViewportCoords = ( @@ -470,10 +476,10 @@ export const sceneCoordsToViewportCoords = ( scrollX: number; scrollY: number; }, -) => { +): ViewportPoint => { const x = (sceneX + scrollX) * zoom.value + offsetLeft; const y = (sceneY + scrollY) * zoom.value + offsetTop; - return { x, y }; + return pointFrom(x, y); }; export const getGlobalCSSVariable = (name: string) => @@ -492,11 +498,11 @@ const RE_RTL_CHECK = new RegExp(`^[^${RS_LTR_CHARS}]*[${RS_RTL_CHARS}]`); */ export const isRTL = (text: string) => RE_RTL_CHECK.test(text); -export const tupleToCoors = ( +export const tupleToCoors = ( xyTuple: readonly [number, number], -): { x: number; y: number } => { +): Point => { const [x, y] = xyTuple; - return { x, y }; + return pointFrom(x, y); }; /** use as a rejectionHandler to mute filesystem Abort errors */ diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 5c32e8c81..d5db99e43 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -29,7 +29,7 @@ import { import { isPointOnShape } from "@excalidraw/utils/collision"; -import type { LocalPoint, Radians } from "@excalidraw/math"; +import type { GenericPoint, LocalPoint, Radians } from "@excalidraw/math"; import type Scene from "@excalidraw/excalidraw/scene/Scene"; @@ -426,10 +426,10 @@ export const getSuggestedBindingsForArrows = ( ); }; -export const maybeBindLinearElement = ( +export const maybeBindLinearElement = ( linearElement: NonDeleted, appState: AppState, - pointerCoords: { x: number; y: number }, + pointerCoords: Point, elementsMap: NonDeletedSceneElementsMap, elements: readonly NonDeletedExcalidrawElement[], ): void => { @@ -577,11 +577,8 @@ const unbindLinearElement = ( return binding.elementId; }; -export const getHoveredElementForBinding = ( - pointerCoords: { - x: number; - y: number; - }, +export const getHoveredElementForBinding = ( + pointerCoords: Point, elements: readonly NonDeletedExcalidrawElement[], elementsMap: NonDeletedSceneElementsMap, zoom?: AppState["zoom"], @@ -1393,11 +1390,11 @@ const getElligibleElementForBindingElement = ( ); }; -const getLinearElementEdgeCoors = ( +const getLinearElementEdgeCoors = ( linearElement: NonDeleted, startOrEnd: "start" | "end", elementsMap: NonDeletedSceneElementsMap, -): { x: number; y: number } => { +): Point => { const index = startOrEnd === "start" ? 0 : -1; return tupleToCoors( LinearElementEditor.getPointAtIndexGlobalCoordinates( @@ -1706,9 +1703,9 @@ const newBoundElements = ( return nextBoundElements; }; -export const bindingBorderTest = ( +export const bindingBorderTest = ( element: NonDeleted, - { x, y }: { x: number; y: number }, + [x, y]: Point, elementsMap: NonDeletedSceneElementsMap, zoom?: AppState["zoom"], fullShape?: boolean, diff --git a/packages/element/src/bounds.ts b/packages/element/src/bounds.ts index 9e723c214..71074b0ed 100644 --- a/packages/element/src/bounds.ts +++ b/packages/element/src/bounds.ts @@ -18,6 +18,7 @@ import { pointsOnBezierCurves } from "points-on-curve"; import type { Curve, Degrees, + GenericPoint, GlobalPoint, LineSegment, LocalPoint, @@ -1051,7 +1052,7 @@ export const getElementPointsCoords = ( export const getClosestElementBounds = ( elements: readonly ExcalidrawElement[], - from: { x: number; y: number }, + from: GenericPoint, ): Bounds => { if (!elements.length) { return [0, 0, 0, 0]; @@ -1064,7 +1065,7 @@ export const getClosestElementBounds = ( const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); const distance = pointDistance( pointFrom((x1 + x2) / 2, (y1 + y2) / 2), - pointFrom(from.x, from.y), + from, ); if (distance < minDistance) { diff --git a/packages/element/src/dragElements.ts b/packages/element/src/dragElements.ts index 669417a54..5d1efc986 100644 --- a/packages/element/src/dragElements.ts +++ b/packages/element/src/dragElements.ts @@ -12,6 +12,7 @@ import type { } from "@excalidraw/excalidraw/types"; import type Scene from "@excalidraw/excalidraw/scene/Scene"; +import type { GenericPoint } from "@excalidraw/math"; import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types"; @@ -208,10 +209,7 @@ export const dragNewElement = ({ /** whether to keep given aspect ratio when `isResizeWithSidesSameLength` is true */ widthAspectRatio?: number | null; - originOffset?: { - x: number; - y: number; - } | null; + originOffset?: GenericPoint | null; informMutation?: boolean; }) => { if (shouldMaintainAspectRatio && newElement.type !== "selection") { @@ -285,11 +283,12 @@ export const dragNewElement = ({ }; } + const [originOffsetX, originOffsetY] = originOffset ?? [0, 0]; mutateElement( newElement, { - x: newX + (originOffset?.x ?? 0), - y: newY + (originOffset?.y ?? 0), + x: newX + originOffsetX, + y: newY + originOffsetY, width, height, ...textAutoResize, diff --git a/packages/element/src/frame.ts b/packages/element/src/frame.ts index 7f40148b7..54208a4cc 100644 --- a/packages/element/src/frame.ts +++ b/packages/element/src/frame.ts @@ -1,5 +1,9 @@ import { arrayToMap } from "@excalidraw/common"; -import { isPointWithinBounds, pointFrom } from "@excalidraw/math"; +import { + type GenericPoint, + isPointWithinBounds, + pointFrom, +} from "@excalidraw/math"; import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox"; import { elementsOverlappingBBox } from "@excalidraw/utils/withinBounds"; @@ -154,11 +158,8 @@ export const elementOverlapsWithFrame = ( ); }; -export const isCursorInFrame = ( - cursorCoords: { - x: number; - y: number; - }, +export const isCursorInFrame = ( + cursorCoords: Point, frame: NonDeleted, elementsMap: ElementsMap, ) => { @@ -166,7 +167,7 @@ export const isCursorInFrame = ( return isPointWithinBounds( pointFrom(fx1, fy1), - pointFrom(cursorCoords.x, cursorCoords.y), + cursorCoords, pointFrom(fx2, fy2), ); }; diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 8a9117bf8..5f28801d0 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -3,8 +3,6 @@ import { pointFrom, pointRotateRads, pointsEqual, - type GlobalPoint, - type LocalPoint, pointDistance, vectorFromPoint, } from "@excalidraw/math"; @@ -26,7 +24,12 @@ import Scene from "@excalidraw/excalidraw/scene/Scene"; import type { Store } from "@excalidraw/excalidraw/store"; -import type { Radians } from "@excalidraw/math"; +import type { + GlobalPoint, + LocalPoint, + GenericPoint, + Radians, +} from "@excalidraw/math"; import type { AppState, @@ -106,7 +109,7 @@ export class LinearElementEditor { /** index */ lastClickedPoint: number; lastClickedIsEndPoint: boolean; - origin: Readonly<{ x: number; y: number }> | null; + origin: Readonly | null; segmentMidpoint: { value: GlobalPoint | null; index: number | null; @@ -117,7 +120,7 @@ export class LinearElementEditor { /** whether you're dragging a point */ public readonly isDragging: boolean; public readonly lastUncommittedPoint: LocalPoint | null; - public readonly pointerOffset: Readonly<{ x: number; y: number }>; + public readonly pointerOffset: Readonly; public readonly startBindingElement: | ExcalidrawBindableElement | null @@ -139,7 +142,7 @@ export class LinearElementEditor { this.selectedPointsIndices = null; this.lastUncommittedPoint = null; this.isDragging = false; - this.pointerOffset = { x: 0, y: 0 }; + this.pointerOffset = pointFrom(0, 0); this.startBindingElement = "keep"; this.endBindingElement = "keep"; this.pointerDownState = { @@ -242,14 +245,14 @@ export class LinearElementEditor { /** * @returns whether point was dragged */ - static handlePointDragging( + static handlePointDragging( event: PointerEvent, app: AppClassProperties, scenePointerX: number, scenePointerY: number, maybeSuggestBinding: ( element: NonDeleted, - pointSceneCoords: { x: number; y: number }[], + pointSceneCoords: Point[], ) => void, linearElementEditor: LinearElementEditor, scene: Scene, @@ -320,11 +323,13 @@ export class LinearElementEditor { }, ]); } else { + const [pointerOffsetX, pointerOffsetY] = + linearElementEditor.pointerOffset; const newDraggingPointPosition = LinearElementEditor.createPointAt( element, elementsMap, - scenePointerX - linearElementEditor.pointerOffset.x, - scenePointerY - linearElementEditor.pointerOffset.y, + scenePointerX - pointerOffsetX, + scenePointerY - pointerOffsetY, event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), ); @@ -339,8 +344,8 @@ export class LinearElementEditor { ? LinearElementEditor.createPointAt( element, elementsMap, - scenePointerX - linearElementEditor.pointerOffset.x, - scenePointerY - linearElementEditor.pointerOffset.y, + scenePointerX - pointerOffsetX, + scenePointerY - pointerOffsetY, event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), ) : pointFrom( @@ -363,7 +368,7 @@ export class LinearElementEditor { // suggest bindings for first and last point if selected if (isBindingElement(element, false)) { - const coords: { x: number; y: number }[] = []; + const coords: Point[] = []; const firstSelectedIndex = selectedPointsIndices[0]; if (firstSelectedIndex === 0) { @@ -511,7 +516,7 @@ export class LinearElementEditor { ? [pointerDownState.lastClickedPoint] : selectedPointsIndices, isDragging: false, - pointerOffset: { x: 0, y: 0 }, + pointerOffset: pointFrom(0, 0), }; } @@ -586,9 +591,9 @@ export class LinearElementEditor { editorMidPointsCache.zoom = appState.zoom.value; }; - static getSegmentMidpointHitCoords = ( + static getSegmentMidpointHitCoords = ( linearElementEditor: LinearElementEditor, - scenePointer: { x: number; y: number }, + [scenePointerX, scenePointerY]: Point, appState: AppState, elementsMap: ElementsMap, ): GlobalPoint | null => { @@ -601,8 +606,8 @@ export class LinearElementEditor { element, elementsMap, appState.zoom, - scenePointer.x, - scenePointer.y, + scenePointerX, + scenePointerY, ); if (!isElbowArrow(element) && clickedPointIndex >= 0) { return null; @@ -630,7 +635,7 @@ export class LinearElementEditor { existingSegmentMidpointHitCoords[0], existingSegmentMidpointHitCoords[1], ), - pointFrom(scenePointer.x, scenePointer.y), + pointFrom(scenePointerX, scenePointerY), ); if (distance <= threshold) { return existingSegmentMidpointHitCoords; @@ -644,7 +649,7 @@ export class LinearElementEditor { if (midPoints[index] !== null) { const distance = pointDistance( midPoints[index]!, - pointFrom(scenePointer.x, scenePointer.y), + pointFrom(scenePointerX, scenePointerY), ); if (distance <= threshold) { return midPoints[index]; @@ -656,7 +661,7 @@ export class LinearElementEditor { return null; }; - static isSegmentTooShort

( + static isSegmentTooShort

( element: NonDeleted, startPoint: P, endPoint: P, @@ -747,11 +752,11 @@ export class LinearElementEditor { return -1; } - static handlePointerDown( + static handlePointerDown( event: React.PointerEvent, app: AppClassProperties, store: Store, - scenePointer: { x: number; y: number }, + scenePointer: Point, linearElementEditor: LinearElementEditor, scene: Scene, ): { @@ -762,6 +767,7 @@ export class LinearElementEditor { const appState = app.state; const elementsMap = scene.getNonDeletedElementsMap(); const elements = scene.getNonDeletedElements(); + const [scenePointerX, scenePointerY] = scenePointer; const ret: ReturnType = { didAddPoint: false, @@ -801,8 +807,8 @@ export class LinearElementEditor { LinearElementEditor.createPointAt( element, elementsMap, - scenePointer.x, - scenePointer.y, + scenePointerX, + scenePointerY, event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(), ), ], @@ -816,7 +822,7 @@ export class LinearElementEditor { prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices, lastClickedPoint: -1, lastClickedIsEndPoint: false, - origin: { x: scenePointer.x, y: scenePointer.y }, + origin: pointFrom(scenePointerX, scenePointerY), segmentMidpoint: { value: segmentMidpoint, index: segmentMidpointIndex, @@ -842,8 +848,8 @@ export class LinearElementEditor { element, elementsMap, appState.zoom, - scenePointer.x, - scenePointer.y, + 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 @@ -897,7 +903,7 @@ export class LinearElementEditor { prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices, lastClickedPoint: clickedPointIndex, lastClickedIsEndPoint: clickedPointIndex === element.points.length - 1, - origin: { x: scenePointer.x, y: scenePointer.y }, + origin: pointFrom(scenePointerX, scenePointerY), segmentMidpoint: { value: segmentMidpoint, index: segmentMidpointIndex, @@ -906,17 +912,17 @@ export class LinearElementEditor { }, selectedPointsIndices: nextSelectedPointsIndices, pointerOffset: targetPoint - ? { - x: scenePointer.x - targetPoint[0], - y: scenePointer.y - targetPoint[1], - } - : { x: 0, y: 0 }, + ? pointFrom( + scenePointerX - targetPoint[0], + scenePointerY - targetPoint[1], + ) + : pointFrom(0, 0), }; return ret; } - static arePointsEqual( + static arePointsEqual( point1: Point | null, point2: Point | null, ) { @@ -977,11 +983,13 @@ export class LinearElementEditor { height + lastCommittedPoint[1], ); } else { + const [pointerOffsetX, pointerOffsetY] = + appState.editingLinearElement.pointerOffset; newPoint = LinearElementEditor.createPointAt( element, elementsMap, - scenePointerX - appState.editingLinearElement.pointerOffset.x, - scenePointerY - appState.editingLinearElement.pointerOffset.y, + scenePointerX - pointerOffsetX, + scenePointerY - pointerOffsetY, event[KEYS.CTRL_OR_CMD] || isElbowArrow(element) ? null : app.getEffectiveGridSize(), @@ -1376,10 +1384,7 @@ export class LinearElementEditor { } const origin = linearElementEditor.pointerDownState.origin!; - const dist = pointDistance( - pointFrom(origin.x, origin.y), - pointFrom(pointerCoords.x, pointerCoords.y), - ); + const dist = pointDistance(origin, pointerCoords); if ( !appState.editingLinearElement && dist < DRAGGING_THRESHOLD / appState.zoom.value @@ -1412,11 +1417,12 @@ export class LinearElementEditor { selectedPointsIndices: linearElementEditor.selectedPointsIndices, }; + const [pointerX, pointerY] = pointerCoords; const midpoint = LinearElementEditor.createPointAt( element, elementsMap, - pointerCoords.x, - pointerCoords.y, + pointerX, + pointerY, snapToGrid && !isElbowArrow(element) ? app.getEffectiveGridSize() : null, ); const points = [ @@ -1528,31 +1534,28 @@ export class LinearElementEditor { element: NonDeleted, elementsMap: ElementsMap, referencePoint: LocalPoint, - scenePointer: GlobalPoint, + [scenePointerX, scenePointerY]: GlobalPoint, gridSize: NullableGridSize, ) { - const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates( - element, - referencePoint, - elementsMap, - ); + const [referencePointCoordsX, referencePointCoordsY] = + LinearElementEditor.getPointGlobalCoordinates( + element, + referencePoint, + elementsMap, + ); if (isElbowArrow(element)) { return [ - scenePointer[0] - referencePointCoords[0], - scenePointer[1] - referencePointCoords[1], + scenePointerX - referencePointCoordsX, + scenePointerY - referencePointCoordsY, ]; } - const [gridX, gridY] = getGridPoint( - scenePointer[0], - scenePointer[1], - gridSize, - ); + const [gridX, gridY] = getGridPoint(scenePointerX, scenePointerY, gridSize); const { width, height } = getLockedLinearCursorAlignSize( - referencePointCoords[0], - referencePointCoords[1], + referencePointCoordsX, + referencePointCoordsY, gridX, gridY, ); diff --git a/packages/element/src/sizeHelpers.ts b/packages/element/src/sizeHelpers.ts index 7a84dadba..7a9e3ba51 100644 --- a/packages/element/src/sizeHelpers.ts +++ b/packages/element/src/sizeHelpers.ts @@ -37,26 +37,28 @@ export const isElementInViewport = ( elementsMap: ElementsMap, ) => { const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); // scene coordinates - const topLeftSceneCoords = viewportCoordsToSceneCoords( - { - clientX: viewTransformations.offsetLeft, - clientY: viewTransformations.offsetTop, - }, - viewTransformations, - ); - const bottomRightSceneCoords = viewportCoordsToSceneCoords( - { - clientX: viewTransformations.offsetLeft + width, - clientY: viewTransformations.offsetTop + height, - }, - viewTransformations, - ); + const [topLeftSceneCoordsX, topLeftSceneCoordsY] = + viewportCoordsToSceneCoords( + { + clientX: viewTransformations.offsetLeft, + clientY: viewTransformations.offsetTop, + }, + viewTransformations, + ); + const [bottomRightSceneCoordsX, bottomRightSceneCoordsY] = + viewportCoordsToSceneCoords( + { + clientX: viewTransformations.offsetLeft + width, + clientY: viewTransformations.offsetTop + height, + }, + viewTransformations, + ); return ( - topLeftSceneCoords.x <= x2 && - topLeftSceneCoords.y <= y2 && - bottomRightSceneCoords.x >= x1 && - bottomRightSceneCoords.y >= y1 + topLeftSceneCoordsX <= x2 && + topLeftSceneCoordsY <= y2 && + bottomRightSceneCoordsX >= x1 && + bottomRightSceneCoordsY >= y1 ); }; @@ -75,26 +77,29 @@ export const isElementCompletelyInViewport = ( padding?: Offsets, ) => { const [x1, y1, x2, y2] = getCommonBounds(elements, elementsMap); // scene coordinates - const topLeftSceneCoords = viewportCoordsToSceneCoords( - { - clientX: viewTransformations.offsetLeft + (padding?.left || 0), - clientY: viewTransformations.offsetTop + (padding?.top || 0), - }, - viewTransformations, - ); - const bottomRightSceneCoords = viewportCoordsToSceneCoords( - { - clientX: viewTransformations.offsetLeft + width - (padding?.right || 0), - clientY: viewTransformations.offsetTop + height - (padding?.bottom || 0), - }, - viewTransformations, - ); + const [topLeftSceneCoordsX, topLeftSceneCoordsY] = + viewportCoordsToSceneCoords( + { + clientX: viewTransformations.offsetLeft + (padding?.left || 0), + clientY: viewTransformations.offsetTop + (padding?.top || 0), + }, + viewTransformations, + ); + const [bottomRightSceneCoordsX, bottomRightSceneCoordsY] = + viewportCoordsToSceneCoords( + { + clientX: viewTransformations.offsetLeft + width - (padding?.right || 0), + clientY: + viewTransformations.offsetTop + height - (padding?.bottom || 0), + }, + viewTransformations, + ); return ( - x1 >= topLeftSceneCoords.x && - y1 >= topLeftSceneCoords.y && - x2 <= bottomRightSceneCoords.x && - y2 <= bottomRightSceneCoords.y + x1 >= topLeftSceneCoordsX && + y1 >= topLeftSceneCoordsY && + x2 <= bottomRightSceneCoordsX && + y2 <= bottomRightSceneCoordsY ); }; diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx index a8bd56e82..1caa9a473 100644 --- a/packages/excalidraw/actions/actionCanvas.tsx +++ b/packages/excalidraw/actions/actionCanvas.tsx @@ -1,4 +1,4 @@ -import { clamp, roundToStep } from "@excalidraw/math"; +import { clamp, pointFrom, roundToStep } from "@excalidraw/math"; import { DEFAULT_CANVAS_BACKGROUND_PICKS, @@ -333,7 +333,7 @@ export const zoomToFitBounds = ({ ); const centerScroll = centerScrollOn({ - scenePoint: { x: centerX, y: centerY }, + scenePoint: pointFrom(centerX, centerY), viewportDimensions: { width: appState.width, height: appState.height, diff --git a/packages/excalidraw/actions/actionFinalize.tsx b/packages/excalidraw/actions/actionFinalize.tsx index 984961656..b39cd0f11 100644 --- a/packages/excalidraw/actions/actionFinalize.tsx +++ b/packages/excalidraw/actions/actionFinalize.tsx @@ -143,7 +143,7 @@ export const actionFinalize = register({ maybeBindLinearElement( multiPointElement, appState, - { x, y }, + pointFrom(x, y), elementsMap, elements, ); diff --git a/packages/excalidraw/animated-trail.ts b/packages/excalidraw/animated-trail.ts index af6162e99..66fa54b98 100644 --- a/packages/excalidraw/animated-trail.ts +++ b/packages/excalidraw/animated-trail.ts @@ -182,12 +182,12 @@ export class AnimatedTrail implements Trail { const _stroke = trail .getStrokeOutline(trail.options.size / state.zoom.value) .map(([x, y]) => { - const result = sceneCoordsToViewportCoords( + const [resultX, resultY] = sceneCoordsToViewportCoords( { sceneX: x, sceneY: y }, state, ); - return [result.x, result.y]; + return [resultX, resultY]; }); const stroke = this.trailAnimation diff --git a/packages/excalidraw/appState.ts b/packages/excalidraw/appState.ts index a75745f2a..f2cf8600a 100644 --- a/packages/excalidraw/appState.ts +++ b/packages/excalidraw/appState.ts @@ -12,6 +12,8 @@ import { DEFAULT_GRID_STEP, } from "@excalidraw/common"; +import { pointFrom } from "@excalidraw/math"; + import type { AppState, NormalizedZoomValue } from "./types"; const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio) @@ -112,10 +114,7 @@ export const getDefaultAppState = (): Omit< showHyperlinkPopup: false, selectedLinearElement: null, snapLines: [], - originSnapOffset: { - x: 0, - y: 0, - }, + originSnapOffset: pointFrom(0, 0), objectsSnapModeEnabled: false, userToFollow: null, followedBy: new Set(), diff --git a/packages/excalidraw/clients.ts b/packages/excalidraw/clients.ts index 9467b1362..339a9d751 100644 --- a/packages/excalidraw/clients.ts +++ b/packages/excalidraw/clients.ts @@ -69,7 +69,7 @@ export const renderRemoteCursors = ({ }) => { // Paint remote pointers for (const [socketId, pointer] of renderConfig.remotePointerViewportCoords) { - let { x, y } = pointer; + let [x, y] = pointer; const collaborator = appState.collaborators.get(socketId); diff --git a/packages/excalidraw/components/ElementCanvasButtons.tsx b/packages/excalidraw/components/ElementCanvasButtons.tsx index 424c4f3b4..ddfdab961 100644 --- a/packages/excalidraw/components/ElementCanvasButtons.tsx +++ b/packages/excalidraw/components/ElementCanvasButtons.tsx @@ -20,7 +20,7 @@ const getContainerCoords = ( elementsMap: ElementsMap, ) => { const [x1, y1] = getElementAbsoluteCoords(element, elementsMap); - const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords( + const [viewportX, viewportY] = sceneCoordsToViewportCoords( { sceneX: x1 + element.width, sceneY: y1 }, appState, ); diff --git a/packages/excalidraw/components/EyeDropper.tsx b/packages/excalidraw/components/EyeDropper.tsx index f7f98123d..2d02aa96a 100644 --- a/packages/excalidraw/components/EyeDropper.tsx +++ b/packages/excalidraw/components/EyeDropper.tsx @@ -166,9 +166,14 @@ export const EyeDropper: React.FC<{ eyeDropperContainer.focus(); // init color preview else it would show only after the first mouse move + const [ + stablePropsAppLastViewportPositionX, + stablePropsAppLastViewportPositionY, + ] = stableProps.app.lastViewportPosition; + mouseMoveListener({ - clientX: stableProps.app.lastViewportPosition.x, - clientY: stableProps.app.lastViewportPosition.y, + clientX: stablePropsAppLastViewportPositionX, + clientY: stablePropsAppLastViewportPositionY, altKey: false, }); diff --git a/packages/excalidraw/components/hyperlink/Hyperlink.tsx b/packages/excalidraw/components/hyperlink/Hyperlink.tsx index 9a386a163..ce36ce5bc 100644 --- a/packages/excalidraw/components/hyperlink/Hyperlink.tsx +++ b/packages/excalidraw/components/hyperlink/Hyperlink.tsx @@ -360,7 +360,7 @@ const getCoordsForPopover = ( elementsMap: ElementsMap, ) => { const [x1, y1] = getElementAbsoluteCoords(element, elementsMap); - const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords( + const [viewportX, viewportY] = sceneCoordsToViewportCoords( { sceneX: x1 + element.width / 2, sceneY: y1 }, appState, ); @@ -422,7 +422,7 @@ const renderTooltip = ( appState, ); - const linkViewportCoords = sceneCoordsToViewportCoords( + const [linkViewportCoordX, linkViewportCoordY] = sceneCoordsToViewportCoords( { sceneX: linkX, sceneY: linkY }, appState, ); @@ -430,8 +430,8 @@ const renderTooltip = ( updateTooltipPosition( tooltipDiv, { - left: linkViewportCoords.x, - top: linkViewportCoords.y, + left: linkViewportCoordX, + top: linkViewportCoordY, width: linkWidth, height: linkHeight, }, @@ -457,7 +457,7 @@ const shouldHideLinkPopup = ( appState: AppState, [clientX, clientY]: GlobalPoint, ): Boolean => { - const { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords( + const [sceneX, sceneY] = viewportCoordsToSceneCoords( { clientX, clientY }, appState, ); diff --git a/packages/excalidraw/gesture.ts b/packages/excalidraw/gesture.ts index 8ffa6d2bb..f043de3bd 100644 --- a/packages/excalidraw/gesture.ts +++ b/packages/excalidraw/gesture.ts @@ -1,15 +1,25 @@ -import type { PointerCoords } from "./types"; +import { pointFrom, type GenericPoint } from "@excalidraw/math"; -export const getCenter = (pointers: Map) => { +export const getCenter = ( + pointers: Map, +): Point => { const allCoords = Array.from(pointers.values()); - return { - x: sum(allCoords, (coords) => coords.x) / allCoords.length, - y: sum(allCoords, (coords) => coords.y) / allCoords.length, - }; + return pointFrom( + sum(allCoords, ([coordsX, _]) => coordsX) / allCoords.length, + sum(allCoords, ([_, coordsY]) => coordsY) / allCoords.length, + ); }; -export const getDistance = ([a, b]: readonly PointerCoords[]) => - Math.hypot(a.x - b.x, a.y - b.y); +export const isTwoPointerCoords = ( + arr: Point[], +): arr is [Point, Point] => { + return arr.length === 2; +}; + +export const getDistance = ([ + [x1, y1], + [x2, y2], +]: readonly [Point, Point]) => Math.hypot(x1 - x2, y1 - y2); const sum = (array: readonly T[], mapper: (item: T) => number): number => array.reduce((acc, item) => acc + mapper(item), 0); diff --git a/packages/excalidraw/scene/scroll.ts b/packages/excalidraw/scene/scroll.ts index a99ad075f..8c3be05bf 100644 --- a/packages/excalidraw/scene/scroll.ts +++ b/packages/excalidraw/scene/scroll.ts @@ -3,6 +3,7 @@ import { sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, } from "@excalidraw/common"; +import { pointFrom } from "@excalidraw/math"; import { getClosestElementBounds } from "@excalidraw/element/bounds"; @@ -14,11 +15,11 @@ import type { AppState, Offsets, PointerCoords, Zoom } from "../types"; const isOutsideViewPort = (appState: AppState, cords: Array) => { const [x1, y1, x2, y2] = cords; - const { x: viewportX1, y: viewportY1 } = sceneCoordsToViewportCoords( + const [viewportX1, viewportY1] = sceneCoordsToViewportCoords( { sceneX: x1, sceneY: y1 }, appState, ); - const { x: viewportX2, y: viewportY2 } = sceneCoordsToViewportCoords( + const [viewportX2, viewportY2] = sceneCoordsToViewportCoords( { sceneX: x2, sceneY: y2 }, appState, ); @@ -39,15 +40,16 @@ export const centerScrollOn = ({ zoom: Zoom; offsets?: Offsets; }) => { + const [scenePointX, scenePointY] = scenePoint; let scrollX = (viewportDimensions.width - (offsets?.right ?? 0)) / 2 / zoom.value - - scenePoint.x; + scenePointX; scrollX += (offsets?.left ?? 0) / 2 / zoom.value; let scrollY = (viewportDimensions.height - (offsets?.bottom ?? 0)) / 2 / zoom.value - - scenePoint.y; + scenePointY; scrollY += (offsets?.top ?? 0) / 2 / zoom.value; @@ -85,7 +87,7 @@ export const calculateScrollCenter = ( const centerY = (y1 + y2) / 2; return centerScrollOn({ - scenePoint: { x: centerX, y: centerY }, + scenePoint: pointFrom(centerX, centerY), viewportDimensions: { width: appState.width, height: appState.height }, zoom: appState.zoom, }); diff --git a/packages/excalidraw/scene/types.ts b/packages/excalidraw/scene/types.ts index 08b05a57d..f352559be 100644 --- a/packages/excalidraw/scene/types.ts +++ b/packages/excalidraw/scene/types.ts @@ -7,6 +7,7 @@ import type { } from "@excalidraw/element/types"; import type { MakeBrand } from "@excalidraw/common/utility-types"; +import type { GenericPoint } from "@excalidraw/math"; import type { AppClassProperties, @@ -61,7 +62,7 @@ export type InteractiveCanvasRenderConfig = { // collab-related state // --------------------------------------------------------------------------- remoteSelectedElementIds: Map; - remotePointerViewportCoords: Map; + remotePointerViewportCoords: Map; remotePointerUserStates: Map; remotePointerUsernames: Map; remotePointerButton: Map; diff --git a/packages/excalidraw/snapping.ts b/packages/excalidraw/snapping.ts index 6ea23bd87..3204fee80 100644 --- a/packages/excalidraw/snapping.ts +++ b/packages/excalidraw/snapping.ts @@ -1327,7 +1327,7 @@ export const getSnapLinesAtPointer = ( ) => { if (!isSnappingEnabled({ event, selectedElements: [], app })) { return { - originOffset: { x: 0, y: 0 }, + originOffset: pointFrom(0, 0), snapLines: [], }; } @@ -1388,16 +1388,14 @@ export const getSnapLinesAtPointer = ( } return { - originOffset: { - x: - verticalSnapLines.length > 0 - ? verticalSnapLines[0].points[0][0] - pointer.x - : 0, - y: - horizontalSnapLines.length > 0 - ? horizontalSnapLines[0].points[0][1] - pointer.y - : 0, - }, + originOffset: pointFrom( + verticalSnapLines.length > 0 + ? verticalSnapLines[0].points[0][0] - pointer.x + : 0, + horizontalSnapLines.length > 0 + ? horizontalSnapLines[0].points[0][1] - pointer.y + : 0, + ), snapLines: [...verticalSnapLines, ...horizontalSnapLines], }; }; diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts index 717993b43..faa5af8c9 100644 --- a/packages/excalidraw/types.ts +++ b/packages/excalidraw/types.ts @@ -42,6 +42,7 @@ import type { ValueOf, MakeBrand, } from "@excalidraw/common/utility-types"; +import type { GenericPoint } from "@excalidraw/math"; import type { Action } from "./actions/types"; import type { Spreadsheet } from "./charts"; @@ -413,10 +414,7 @@ export interface AppState { showHyperlinkPopup: false | "info" | "editor"; selectedLinearElement: LinearElementEditor | null; snapLines: readonly SnapLine[]; - originSnapOffset: { - x: number; - y: number; - } | null; + originSnapOffset: GenericPoint | null; objectsSnapModeEnabled: boolean; /** the user's socket id & username who is being followed on the canvas */ userToFollow: UserToFollow | null; @@ -456,14 +454,11 @@ export type Zoom = Readonly<{ value: NormalizedZoomValue; }>; -export type PointerCoords = Readonly<{ - x: number; - y: number; -}>; +export type PointerCoords = Readonly; export type Gesture = { pointers: Map; - lastCenter: { x: number; y: number } | null; + lastCenter: PointerCoords | null; initialDistance: number | null; initialScale: number | null; }; @@ -717,13 +712,13 @@ export type AppClassProperties = { export type PointerDownState = Readonly<{ // The first position at which pointerDown happened - origin: Readonly<{ x: number; y: number }>; + origin: Readonly; // Same as "origin" but snapped to the grid, if grid is on - originInGrid: Readonly<{ x: number; y: number }>; + originInGrid: Readonly; // Scrollbar checks scrollbars: ReturnType; // The previous pointer position - lastCoords: { x: number; y: number }; + lastCoords: GenericPoint; // map of original elements data originalElements: Map>; resize: { @@ -732,11 +727,11 @@ export type PointerDownState = Readonly<{ // This is determined on the initial pointer down event isResizing: boolean; // This is determined on the initial pointer down event - offset: { x: number; y: number }; + offset: GenericPoint; // This is determined on the initial pointer down event arrowDirection: "origin" | "end"; // This is a center point of selected elements determined on the initial pointer down event (for rotation only) - center: { x: number; y: number }; + center: GenericPoint; }; hit: { // The element the pointer is "hitting", is determined on the initial @@ -757,7 +752,7 @@ export type PointerDownState = Readonly<{ // Might change during the pointer interaction hasOccurred: boolean; // Might change during the pointer interaction - offset: { x: number; y: number } | null; + offset: GenericPoint | null; }; // We need to have these in the state so that we can unsubscribe them eventListeners: {