diff --git a/src/data/restore.ts b/src/data/restore.ts index a8f968c9..51ef0597 100644 --- a/src/data/restore.ts +++ b/src/data/restore.ts @@ -15,6 +15,7 @@ import { DEFAULT_VERTICAL_ALIGN, } from "../constants"; import { getDefaultAppState } from "../appState"; +import { LinearElementEditor } from "../element/linearElementEditor"; type RestoredAppState = Omit< AppState, @@ -49,14 +50,18 @@ const getFontFamilyByName = (fontFamilyName: string): FontFamily => { return DEFAULT_FONT_FAMILY; }; -const restoreElementWithProperties = ( +const restoreElementWithProperties = < + T extends ExcalidrawElement, + K extends keyof Omit< + Required, + Exclude + > +>( element: Required, - extra: Omit, keyof ExcalidrawElement> & { - type?: ExcalidrawElement["type"]; - }, + extra: Pick, ): T => { const base: Pick = { - type: extra.type || element.type, + type: (extra as Partial).type || element.type, // all elements must have version > 0 so getSceneVersion() will pick up // newly added elements version: element.version || 1, @@ -69,8 +74,8 @@ const restoreElementWithProperties = ( roughness: element.roughness ?? 1, opacity: element.opacity == null ? 100 : element.opacity, angle: element.angle || 0, - x: element.x || 0, - y: element.y || 0, + x: (extra as Partial).x ?? element.x ?? 0, + y: (extra as Partial).y ?? element.y ?? 0, strokeColor: element.strokeColor, backgroundColor: element.backgroundColor, width: element.width || 0, @@ -131,6 +136,20 @@ const restoreElement = ( endArrowhead = element.type === "arrow" ? "arrow" : null, } = element; + let x = element.x; + let y = element.y; + let points = // migrate old arrow model to new one + !Array.isArray(element.points) || element.points.length < 2 + ? [ + [0, 0], + [element.width, element.height], + ] + : element.points; + + if (points[0][0] !== 0 || points[0][1] !== 0) { + ({ points, x, y } = LinearElementEditor.getNormalizedPoints(element)); + } + return restoreElementWithProperties(element, { type: (element.type as ExcalidrawElement["type"] | "draw") === "draw" @@ -138,17 +157,12 @@ const restoreElement = ( : element.type, startBinding: element.startBinding, endBinding: element.endBinding, - points: - // migrate old arrow model to new one - !Array.isArray(element.points) || element.points.length < 2 - ? [ - [0, 0], - [element.width, element.height], - ] - : element.points, lastCommittedPoint: null, startArrowhead, endArrowhead, + points, + x, + y, }); } // generic elements diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts index c4f38da4..7ea34218 100644 --- a/src/element/linearElementEditor.ts +++ b/src/element/linearElementEditor.ts @@ -415,26 +415,31 @@ export class LinearElementEditor { 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. + * expected in various parts of the codebase. Also returns new x/y to account + * for the potential normalization. */ - static normalizePoints(element: NonDeleted) { + static getNormalizedPoints(element: ExcalidrawLinearElement) { const { points } = element; const offsetX = points[0][0]; const offsetY = points[0][1]; - mutateElement(element, { + return { points: points.map((point, _idx) => { return [point[0] - offsetX, point[1] - offsetY] as const; }), x: element.x + offsetX, y: element.y + offsetY, - }); + }; + } + + // element-mutating methods + // --------------------------------------------------------------------------- + + static normalizePoints(element: NonDeleted) { + mutateElement(element, LinearElementEditor.getNormalizedPoints(element)); } static movePointByOffset(