mirror of
https://github.com/excalidraw/excalidraw.git
synced 2026-10-19 08:14:59 +00:00
feat(editor): track element creation timestamps (#12060)
feat(editor): element creation timestamp tracking Co-authored-by: Márk Tolmács <mark@lazycat.hu>
This commit is contained in:
co-authored by
Márk Tolmács
parent
0361bdd9cb
commit
854d00c31b
@@ -1,3 +1,4 @@
|
||||
# Guidelines
|
||||
|
||||
- For new DOM/browser API usage, use `app.ownerDocument` and `app.ownerWindow` instead of globals; without `app`, derive them from the mounted node's `ownerDocument` and its `defaultView`.
|
||||
- When overriding properties of an existing type, prefer `Merge<Base, Overrides>` from `@excalidraw/common/utility-types` over `Omit<Base, keyof Overrides> & Overrides`.
|
||||
|
||||
@@ -62,6 +62,18 @@ When saving an Excalidraw scene locally to a file, the JSON file (`.excalidraw`)
|
||||
}
|
||||
```
|
||||
|
||||
### Element creation time
|
||||
|
||||
Every normalized element has a `created: number | null` field. A number is the client's wall-clock time in epoch milliseconds when this element instance was created. `null` means the creation time is unknown; restoring an older file does not substitute its load time or last-update time.
|
||||
|
||||
| Operation | `created` |
|
||||
| --- | --- |
|
||||
| Construct a new element | Current time, unless explicitly supplied (including `null`) |
|
||||
| Duplicate, paste, or insert from a library | New time for the new instance |
|
||||
| Convert element skeletons with ID regeneration | New time for each resulting instance |
|
||||
| Restore/open/import an existing scene | Preserve the carried value; missing values become `null` |
|
||||
| Edit, change type while retaining the ID, capture, undo, or redo | Preserve the value |
|
||||
|
||||
## Excalidraw clipboard format
|
||||
|
||||
When copying selected excalidraw elements to clipboard, the JSON schema is similar to `.excalidraw` format, except it differs in attributes.
|
||||
|
||||
@@ -14,6 +14,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -51,6 +52,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -83,6 +85,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -144,6 +147,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -210,6 +214,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -249,6 +254,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -296,6 +302,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -341,6 +348,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -404,6 +412,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id50",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -449,6 +458,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -512,6 +522,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id39",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -557,6 +568,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -594,6 +606,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -631,6 +644,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -694,6 +708,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id43",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -741,6 +756,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -788,6 +804,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
},
|
||||
],
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -828,6 +845,7 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -860,6 +878,7 @@ exports[`Test Transform > should transform linear elements 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -907,6 +926,7 @@ exports[`Test Transform > should transform linear elements 2`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "triangle",
|
||||
@@ -954,6 +974,7 @@ exports[`Test Transform > should transform linear elements 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -1001,6 +1022,7 @@ exports[`Test Transform > should transform linear elements 4`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -1048,6 +1070,7 @@ exports[`Test Transform > should transform regular shapes 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1080,6 +1103,7 @@ exports[`Test Transform > should transform regular shapes 2`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1112,6 +1136,7 @@ exports[`Test Transform > should transform regular shapes 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1144,6 +1169,7 @@ exports[`Test Transform > should transform regular shapes 4`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "#c0eb75",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1176,6 +1202,7 @@ exports[`Test Transform > should transform regular shapes 5`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1208,6 +1235,7 @@ exports[`Test Transform > should transform regular shapes 6`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
@@ -1242,6 +1270,7 @@ exports[`Test Transform > should transform text element 1`] = `
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1284,6 +1313,7 @@ exports[`Test Transform > should transform text element 2`] = `
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1333,6 +1363,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1372,6 +1403,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1415,6 +1447,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1462,6 +1495,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1501,6 +1535,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -1569,6 +1604,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -1630,6 +1666,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "B",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1674,6 +1711,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "A",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1718,6 +1756,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "Alice",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1762,6 +1801,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "Bob",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1806,6 +1846,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "Bob_Alice",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1848,6 +1889,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "Bob_B",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -1893,6 +1935,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -1945,6 +1988,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -1997,6 +2041,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -2049,6 +2094,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -2098,6 +2144,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id25",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2140,6 +2187,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id26",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2182,6 +2230,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id27",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2225,6 +2274,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id28",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2271,6 +2321,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2308,6 +2359,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2345,6 +2397,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2382,6 +2435,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2419,6 +2473,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2456,6 +2511,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2490,6 +2546,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id13",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2532,6 +2589,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id14",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2575,6 +2633,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id15",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2620,6 +2679,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id16",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2663,6 +2723,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id17",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
@@ -2707,6 +2768,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"containerId": "id18",
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 5,
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { pointFrom } from "@excalidraw/math";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import { getUpdatedTimestamp } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
newElement,
|
||||
newEmbeddableElement,
|
||||
newFreeDrawElement,
|
||||
newIframeElement,
|
||||
} from "../newElement";
|
||||
|
||||
import {
|
||||
convertToExcalidrawElements,
|
||||
type ExcalidrawElementSkeleton,
|
||||
@@ -965,4 +974,88 @@ describe("Test Transform", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("creation timestamps", () => {
|
||||
const createElements = () => [
|
||||
{
|
||||
...newElement({ type: "rectangle", x: 0, y: 0 }),
|
||||
type: "rectangle" as const,
|
||||
},
|
||||
newFreeDrawElement({
|
||||
type: "freedraw",
|
||||
x: 0,
|
||||
y: 0,
|
||||
simulatePressure: false,
|
||||
}),
|
||||
newIframeElement({ type: "iframe", x: 0, y: 0 }),
|
||||
newEmbeddableElement({ type: "embeddable", x: 0, y: 0 }),
|
||||
];
|
||||
|
||||
it("assigns fresh timestamps when regenerating IDs without mutating the input", () => {
|
||||
const elements = createElements().map((element) => ({
|
||||
...element,
|
||||
created: 123,
|
||||
}));
|
||||
const converted = convertToExcalidrawElements(elements);
|
||||
|
||||
expect(converted).toHaveLength(elements.length);
|
||||
converted.forEach((element, index) => {
|
||||
expect(element.id).not.toBe(elements[index].id);
|
||||
expect(element.created).toBe(getUpdatedTimestamp());
|
||||
expect(elements[index].created).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([123, 0, null])(
|
||||
"preserves an explicit timestamp of %s when retaining IDs",
|
||||
(created) => {
|
||||
const elements = createElements().map((element) => ({
|
||||
...element,
|
||||
created,
|
||||
}));
|
||||
const converted = convertToExcalidrawElements(elements, opts);
|
||||
|
||||
converted.forEach((element, index) => {
|
||||
expect(element.id).toBe(elements[index].id);
|
||||
expect(element.created).toBe(created);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("defaults missing timestamps when retaining IDs, as every type goes through a constructor", () => {
|
||||
// models skeletons persisted before `created` existed; the declared
|
||||
// skeleton type is a complete element
|
||||
const elements = createElements().map(
|
||||
({ created, ...element }) => element,
|
||||
) as unknown as ExcalidrawElementSkeleton[];
|
||||
const converted = convertToExcalidrawElements(elements, opts);
|
||||
|
||||
converted.forEach((element, index) => {
|
||||
expect(element.id).toBe(elements[index].id);
|
||||
expect(element.created).toBe(getUpdatedTimestamp());
|
||||
expect(elements[index]).not.toHaveProperty("created");
|
||||
});
|
||||
});
|
||||
|
||||
it("assigns fresh timestamps to generated labels and binding endpoints", () => {
|
||||
const elements: ExcalidrawElementSkeleton[] = [
|
||||
{
|
||||
type: "arrow",
|
||||
x: 100,
|
||||
y: 100,
|
||||
created: 123,
|
||||
// fragments cannot carry a creation time; they are always new
|
||||
label: { text: "label" },
|
||||
start: { type: "rectangle" },
|
||||
end: { type: "text", text: "end" },
|
||||
},
|
||||
];
|
||||
const converted = convertToExcalidrawElements(elements);
|
||||
|
||||
expect(converted).toHaveLength(4);
|
||||
expect(converted.map((element) => element.created)).toEqual(
|
||||
Array(4).fill(getUpdatedTimestamp()),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -797,6 +797,10 @@ export const convertToShapeHandlePointerMoveFromPointerDown = (
|
||||
prevPreview?.type === shapePreview.type
|
||||
? prevPreview.id
|
||||
: shapePreview.id,
|
||||
created:
|
||||
prevPreview?.type === shapePreview.type
|
||||
? prevPreview.created
|
||||
: shapePreview.created,
|
||||
seed: 1,
|
||||
opacity: ELEMENT_PENDING_DRAW_SHAPE_OPACITY,
|
||||
isDeleted: false as const,
|
||||
|
||||
@@ -1688,8 +1688,9 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
||||
}
|
||||
|
||||
if (!flags.containsVisibleDifference) {
|
||||
// strip away fractional index, as even if it would be different, it doesn't have to result in visible change
|
||||
const { index, ...rest } = directlyApplicablePartial;
|
||||
// Creation metadata is not visible; a different fractional index does
|
||||
// not necessarily change the visible order either.
|
||||
const { index, created, ...rest } = directlyApplicablePartial;
|
||||
const containsVisibleDifference = ElementsDelta.checkForVisibleDifference(
|
||||
element,
|
||||
rest,
|
||||
|
||||
@@ -75,6 +75,7 @@ export const duplicateElement = <TElement extends ExcalidrawElement>(
|
||||
|
||||
copy.id = randomId();
|
||||
copy.updated = getUpdatedTimestamp();
|
||||
copy.created = copy.updated;
|
||||
if (randomizeSeed) {
|
||||
copy.seed = randomInteger();
|
||||
bumpVersion(copy);
|
||||
|
||||
@@ -21,9 +21,12 @@ import type {
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "./types";
|
||||
|
||||
// `created` is lifetime metadata: it is set once when an element instance is
|
||||
// constructed (or duplicated) and never updated, so it is not part of an
|
||||
// element update. An explicit repair must construct a new element object.
|
||||
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
||||
Partial<TElement>,
|
||||
"id" | "updated"
|
||||
"id" | "updated" | "created"
|
||||
>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,7 @@ export type ElementConstructorOpts = MarkOptional<
|
||||
| "locked"
|
||||
| "opacity"
|
||||
| "customData"
|
||||
| "created"
|
||||
>;
|
||||
|
||||
const _newElementBase = <T extends ExcalidrawElement>(
|
||||
@@ -123,6 +124,8 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
});
|
||||
}
|
||||
|
||||
const timestamp = getUpdatedTimestamp();
|
||||
|
||||
// assign type to guard against excess properties
|
||||
const element: Merge<
|
||||
ExcalidrawGenericElement,
|
||||
@@ -151,7 +154,9 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
versionNonce: rest.versionNonce ?? 0,
|
||||
isDeleted: false as false,
|
||||
boundElements,
|
||||
updated: getUpdatedTimestamp(),
|
||||
updated: timestamp,
|
||||
// Preserve explicit null when reconstructing a legacy element with its id.
|
||||
created: rest.created === undefined ? timestamp : rest.created,
|
||||
link,
|
||||
locked,
|
||||
customData: rest.customData,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
assertNever,
|
||||
cloneJSON,
|
||||
getFontString,
|
||||
getUpdatedTimestamp,
|
||||
isDevEnv,
|
||||
toBrandedType,
|
||||
getLineHeight,
|
||||
@@ -22,7 +23,10 @@ import { bindBindingElement } from "./binding";
|
||||
import {
|
||||
newArrowElement,
|
||||
newElement,
|
||||
newEmbeddableElement,
|
||||
newFrameElement,
|
||||
newFreeDrawElement,
|
||||
newIframeElement,
|
||||
newImageElement,
|
||||
newLinearElement,
|
||||
newMagicFrameElement,
|
||||
@@ -68,6 +72,16 @@ import type {
|
||||
VerticalAlign,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Options for elements generated from a skeleton fragment (bound labels and
|
||||
* binding endpoints). Such elements are always constructed fresh, hence they
|
||||
* never carry a creation time of their own.
|
||||
*/
|
||||
type FragmentConstructorOpts = MarkOptional<
|
||||
Omit<ElementConstructorOpts, "created">,
|
||||
"x" | "y"
|
||||
>;
|
||||
|
||||
export type ValidLinearElement = {
|
||||
type: "arrow" | "line";
|
||||
x: number;
|
||||
@@ -78,7 +92,7 @@ export type ValidLinearElement = {
|
||||
fontFamily?: FontFamilyValues;
|
||||
textAlign?: TextAlign;
|
||||
verticalAlign?: VerticalAlign;
|
||||
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
|
||||
} & FragmentConstructorOpts;
|
||||
end?:
|
||||
| (
|
||||
| (
|
||||
@@ -118,9 +132,9 @@ export type ValidLinearElement = {
|
||||
text: string;
|
||||
}
|
||||
) &
|
||||
Partial<ExcalidrawTextElement>)
|
||||
Partial<Omit<ExcalidrawTextElement, "created">>)
|
||||
) &
|
||||
MarkOptional<ElementConstructorOpts, "x" | "y">;
|
||||
FragmentConstructorOpts;
|
||||
start?:
|
||||
| (
|
||||
| (
|
||||
@@ -160,9 +174,9 @@ export type ValidLinearElement = {
|
||||
text: string;
|
||||
}
|
||||
) &
|
||||
Partial<ExcalidrawTextElement>)
|
||||
Partial<Omit<ExcalidrawTextElement, "created">>)
|
||||
) &
|
||||
MarkOptional<ElementConstructorOpts, "x" | "y">;
|
||||
FragmentConstructorOpts;
|
||||
} & Partial<ExcalidrawLinearElement>;
|
||||
|
||||
export type ValidContainer =
|
||||
@@ -175,7 +189,7 @@ export type ValidContainer =
|
||||
fontFamily?: FontFamilyValues;
|
||||
textAlign?: TextAlign;
|
||||
verticalAlign?: VerticalAlign;
|
||||
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
|
||||
} & FragmentConstructorOpts;
|
||||
} & ElementConstructorOpts;
|
||||
|
||||
export type ExcalidrawElementSkeleton =
|
||||
@@ -223,7 +237,7 @@ const DEFAULT_DIMENSION = 100;
|
||||
|
||||
const bindTextToContainer = (
|
||||
container: ExcalidrawElement,
|
||||
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||
textProps: { text: string } & FragmentConstructorOpts,
|
||||
scene: Scene,
|
||||
) => {
|
||||
const textElement: ExcalidrawTextElement = newTextElement({
|
||||
@@ -531,11 +545,14 @@ export const convertToExcalidrawElements = (
|
||||
const oldToNewElementIdMap = new Map<string, string>();
|
||||
|
||||
// Create individual elements
|
||||
// regenerated ids mean new instances, hence a fresh creation time as well
|
||||
const created = getUpdatedTimestamp();
|
||||
|
||||
for (const element of elements) {
|
||||
let excalidrawElement: ExcalidrawElement;
|
||||
const originalId = element.id;
|
||||
if (opts?.regenerateIds !== false) {
|
||||
Object.assign(element, { id: randomId() });
|
||||
Object.assign(element, { id: randomId(), created });
|
||||
}
|
||||
|
||||
switch (element.type) {
|
||||
@@ -634,10 +651,16 @@ export const convertToExcalidrawElements = (
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "freedraw":
|
||||
case "iframe":
|
||||
case "freedraw": {
|
||||
excalidrawElement = newFreeDrawElement({ ...element });
|
||||
break;
|
||||
}
|
||||
case "iframe": {
|
||||
excalidrawElement = newIframeElement({ ...element });
|
||||
break;
|
||||
}
|
||||
case "embeddable": {
|
||||
excalidrawElement = element;
|
||||
excalidrawElement = newEmbeddableElement({ ...element });
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,11 @@ type _ExcalidrawElementBase = Readonly<{
|
||||
boundElements: readonly BoundElement[] | null;
|
||||
/** epoch (ms) timestamp of last element update */
|
||||
updated: number;
|
||||
/** Client wall-clock creation time in epoch milliseconds; null if unknown.
|
||||
Preserved for this element's lifetime, including edits and undo/redo,
|
||||
and excluded from `ElementUpdate` (mutateElement / newElementWith).
|
||||
Duplicating an element starts a new lifetime. Not an ordering clock. */
|
||||
created: number | null;
|
||||
link: string | null;
|
||||
locked: boolean;
|
||||
customData?: Record<string, any>;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ORIG_ID,
|
||||
ROUNDNESS,
|
||||
isPrimitive,
|
||||
getUpdatedTimestamp,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { Excalidraw, mutateElement } from "@excalidraw/excalidraw";
|
||||
@@ -24,7 +25,11 @@ import {
|
||||
|
||||
import type { LocalPoint } from "@excalidraw/math";
|
||||
|
||||
import { duplicateElement, duplicateElements } from "../src/duplicate";
|
||||
import {
|
||||
deepCopyElement,
|
||||
duplicateElement,
|
||||
duplicateElements,
|
||||
} from "../src/duplicate";
|
||||
|
||||
import type { ExcalidrawLinearElement } from "../src/types";
|
||||
|
||||
@@ -43,6 +48,24 @@ const assertCloneObjects = (source: any, clone: any) => {
|
||||
};
|
||||
|
||||
describe("duplicating single elements", () => {
|
||||
it.each([123, 0, null])(
|
||||
"preserves created=%s on a deep copy and resets it for a new instance",
|
||||
(created) => {
|
||||
const element = API.createElement({ type: "rectangle", created });
|
||||
const cloned = deepCopyElement(element);
|
||||
const duplicate = duplicateElement(null, new Map(), element);
|
||||
|
||||
expect(cloned).toEqual(element);
|
||||
expect(duplicate).toMatchObject({
|
||||
created: getUpdatedTimestamp(),
|
||||
version: element.version,
|
||||
versionNonce: element.versionNonce,
|
||||
});
|
||||
expect(duplicate.id).not.toBe(element.id);
|
||||
expect(element.created).toBe(created);
|
||||
},
|
||||
);
|
||||
|
||||
it("clones arrow element", () => {
|
||||
const element = API.createElement({
|
||||
type: "arrow",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as common from "@excalidraw/common";
|
||||
|
||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||
|
||||
import { newElement, newLinearElement } from "../src/newElement";
|
||||
import { mutateElement, newElementWith } from "../src/mutateElement";
|
||||
|
||||
describe("element creation time", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it.each([
|
||||
"rectangle",
|
||||
"diamond",
|
||||
"ellipse",
|
||||
"text",
|
||||
"line",
|
||||
"arrow",
|
||||
"freedraw",
|
||||
"image",
|
||||
"frame",
|
||||
"magicframe",
|
||||
"embeddable",
|
||||
"iframe",
|
||||
] as const)("initializes a new %s before any capture", (type) => {
|
||||
vi.spyOn(common, "getUpdatedTimestamp").mockReturnValue(123);
|
||||
const element = API.createElement({ type });
|
||||
expect(element.created).toBe(123);
|
||||
|
||||
vi.mocked(common.getUpdatedTimestamp).mockReturnValue(456);
|
||||
const edited = newElementWith(element, { x: 50 });
|
||||
mutateElement(edited, new Map(), { y: 50 });
|
||||
|
||||
expect(edited).toMatchObject({ created: 123, updated: 456 });
|
||||
});
|
||||
|
||||
it.each([123, 0, null])(
|
||||
"preserves explicit created=%s when reconstructing the same element",
|
||||
(created) => {
|
||||
const original = newElement({
|
||||
type: "rectangle",
|
||||
x: 0,
|
||||
y: 0,
|
||||
created,
|
||||
});
|
||||
const converted = newLinearElement({ ...original, type: "line" });
|
||||
|
||||
expect(converted).toMatchObject({ id: original.id, created });
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -158,20 +158,21 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||
throw error;
|
||||
}
|
||||
if (isValidExcalidrawData(data)) {
|
||||
const elements = restoreElements(data.elements, localElements, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
});
|
||||
return {
|
||||
type: MIME_TYPES.excalidraw,
|
||||
data: {
|
||||
elements: restoreElements(data.elements, localElements, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
}),
|
||||
elements,
|
||||
appState: restoreAppState(
|
||||
{
|
||||
theme: localAppState?.theme,
|
||||
fileHandle: fileHandle || blob.handle || null,
|
||||
...cleanAppStateForExport(data.appState || {}),
|
||||
...(localAppState
|
||||
? getScrollToContentState(data.elements || [], localAppState)
|
||||
? getScrollToContentState(elements, localAppState)
|
||||
: {}),
|
||||
},
|
||||
localAppState,
|
||||
|
||||
@@ -477,6 +477,7 @@ const restoreElementWithProperties = <
|
||||
? element.boundElementIds.map((id) => ({ type: "arrow", id }))
|
||||
: element.boundElements ?? [],
|
||||
updated: element.updated ?? getUpdatedTimestamp(),
|
||||
created: element.created ?? null,
|
||||
link: element.link ? normalizeLink(element.link) : null,
|
||||
locked: element.locked ?? false,
|
||||
};
|
||||
|
||||
@@ -1008,6 +1008,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1042,6 +1043,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1209,6 +1211,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1265,6 +1268,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1427,6 +1431,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1459,6 +1464,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1515,6 +1521,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1569,6 +1576,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1764,6 +1772,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1796,6 +1805,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1852,6 +1862,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1906,6 +1917,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2103,6 +2115,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2159,6 +2172,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2319,6 +2333,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2375,6 +2390,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2568,6 +2584,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2600,6 +2617,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2656,6 +2674,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2710,6 +2729,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2877,6 +2897,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2911,6 +2932,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2969,6 +2991,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3023,6 +3046,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3252,6 +3276,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
@@ -3284,6 +3309,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "#a5d8ff",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
@@ -3340,6 +3366,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3394,6 +3421,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3776,6 +3804,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3808,6 +3837,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3864,6 +3894,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3918,6 +3949,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4105,6 +4137,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4137,6 +4170,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4193,6 +4227,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4247,6 +4282,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4437,6 +4473,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4469,6 +4506,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4525,6 +4563,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4579,6 +4618,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5728,6 +5768,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5760,6 +5801,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5816,6 +5858,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5870,6 +5913,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6953,6 +6997,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6987,6 +7032,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7045,6 +7091,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7099,6 +7146,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -9917,6 +9965,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -9949,6 +9998,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -9981,6 +10031,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10043,6 +10094,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] un
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
||||
@@ -7,6 +7,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -58,6 +59,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -92,6 +94,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -124,6 +127,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -173,6 +177,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`export > export svg-embedded scene > svg-embdedded scene export output 1`] = `
|
||||
"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="36" height="36"><!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTwY7TMFx1MDAxML3zXHUwMDE1UfaK2LRcYlx1MDAwZb0tsCv2sFxiUSRcdTAwMGWIg1x1MDAxYk+TUV3bsifblqpcdTAwMTKfwY1f5Fx1MDAxM1x1MDAxODshrtPdVorkNzN+b96Mjy+KoqSDhXJRlLCvhULpxK58XHUwMDE58EdwXHUwMDFljebQPJ696VxcXHUwMDFkM1tcIru4vlaGXHUwMDBiWuNp8bqqqr5cYlx1MDAxNGxBk+e073wuimP8clx1MDAwNGUovYlpXHUwMDEx+M9LsKeE7lx1MDAxOarG0yE77VBSy8js7VxitYBNSzkmdKMgK/TkzFx1MDAwNt5cdTAwMWJlXFxgvJpB+CfSlag3jTOdlmNcdTAwMGU5ob1cdTAwMTWOm0l5a1RqSYd4O/vBbpVcdTAwMTOOb4PE+Vx1MDAwNH+uikmbVoNcdTAwMGaGzUbUWFEjheZnVepcIii09zJ6+yNpcmJcdTAwMGL3wVxc3Sk1wqgl7KdgbHFgy1x1MDAwMlx1MDAxZUBmXG7S8C+wT0bXub3oP/DcKV6xXHUwMDE2ykMyNlx1MDAxMN6mnchIOytcdTAwMDVNeFx1MDAxNerNNI/3bPPE3XFv2NG/f37/Olx1MDAxYpHRtMSfQeC8ytA7sUVcdTAwMTU8fZNdcaOwXHR9llxu1mej5mZcdHm/xzBcdTAwMTmbojXfJ1CDuzTeOGxQXHUwMDBi9fVJeaIj81x1MDAwNXwvkFxcXHUwMDA3553Dx3GbX82TSiVWoD5cdTAwMWKP1E8k0MXYib9xXHUwMDExSmHtkthMjvZcdTAwMGaOl1x1MDAwNeXEh1x1MDAxZSOwyYNcYj1cdTAwMThcdLdarNTU5PJcdTAwMTFh9+7ybVxcreNv6GqYz0OnXGKXvFx1MDAwN3WQXHUwMDE5hn1cZvpOUVx1MDAxZj9cdTAwMWNcdTAwMTig0z+sMjD+In0=<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="36" height="36"><!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTwY7TMFx1MDAxML3zXHUwMDE1UfaK2LRcYlx1MDAwZb0tsCv2sFxiUSRcdTAwMGWIg1x1MDAxYk+TUV3bsifblqpcdTAwMTKfwY1f5Fx1MDAxM1x1MDAxODshrtPdVorkNzN+b96Mjy+KoqSDhXJRlLCvhULpxK58XHUwMDE58EdwXHUwMDFljebQPJ696VxcXHUwMDFkM1tcIru4vlaGXHUwMDBiWuNp8bqqqr5cYlx1MDAxNGxBk+e073wuimP8clx1MDAwNGUovYlpXHUwMDEx+M9LsKeE7lx1MDAxOarG0yE77VBSy8js7VxitYBNSzkmdKMgK/TkzFx1MDAwNt5cdTAwMWJlXFxgvJpB+CfSlag3jTOdlmNcdTAwMGU5ob1cdTAwMTWOm0l5a1RqSYd4O/vBbpVcdTAwMTOOb4PE+Vx1MDAwNH+uikmbVoNcdTAwMGaGzUbUWFEjheZnVepcIii09zJ6+yNpcmJcdTAwMGL3wVxc3Sk1wqgl7KdgbHFgy1x1MDAwMlx1MDAxZUBmXG7S8C+wT0bXub3oP/DcKV6xXHUwMDE2ykMyNlx1MDAxMN6mnchIOytcdTAwMDVNeGtcdTAwMDdcdTAwMTeYQr2Z1vLubZ7gi7vELv/98/vX2diMpiX+XGai51WG3oktquDzm+yKXHUwMDFihU3ovVSwPlx1MDAxYj9cdTAwMWJAyDs/hsnYXHUwMDE0rfk+gVx1MDAxYdzlMIzDXHUwMDA2tVBfn5QnOjJfwPdcdTAwMDLJdXDeOXxcdTAwMWM3/NU8qVRiXHUwMDA16rPxSP2UXHUwMDAyXYyd+Fx1MDAxYpejXHUwMDE01i6JzeRo/1xieYFQTnzoMVx1MDAwMps8iNCDkXCrxUpNTS5cdTAwMWZcdTAwMTF27y7fy9U6/oauhvk8dIpwybtRXHUwMDA3mWFcdTAwMDGOQd8p6uPHXHUwMDA0XHUwMDAzdPpcdTAwMDfOLTWbIn0=<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
</style></defs><rect x="0" y="0" width="36" height="36" fill="#ffffff"></rect><g transform="translate(10 10) rotate(0 8 8)" data-id="A"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, sans-serif, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">😀</text></g></svg>"
|
||||
`;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -37,6 +38,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -69,6 +71,7 @@ exports[`move element > rectangle 5`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -106,6 +109,7 @@ exports[`move element > rectangles with binding arrow 5`] = `
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -143,6 +147,7 @@ exports[`move element > rectangles with binding arrow 6`] = `
|
||||
"type": "arrow",
|
||||
},
|
||||
],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -175,6 +180,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
|
||||
@@ -5,6 +5,7 @@ exports[`multi point mode in linear elements > arrow 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -58,6 +59,7 @@ exports[`multi point mode in linear elements > line 3`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
|
||||
@@ -159,6 +159,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -213,6 +214,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -267,6 +269,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -593,6 +596,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -647,6 +651,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -701,6 +706,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1006,6 +1012,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1060,6 +1067,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1229,6 +1237,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1578,6 +1587,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1796,6 +1806,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1850,6 +1861,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -1904,6 +1916,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2183,6 +2196,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] undo
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2237,6 +2251,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] undo
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2432,6 +2447,7 @@ exports[`regression tests > arrow keys > [end of test] undo stack 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2620,6 +2636,7 @@ exports[`regression tests > can drag element that covers another element, while
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2674,6 +2691,7 @@ exports[`regression tests > can drag element that covers another element, while
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2728,6 +2746,7 @@ exports[`regression tests > can drag element that covers another element, while
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -2949,6 +2968,7 @@ exports[`regression tests > change the properties of a shape > [end of test] und
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3188,6 +3208,7 @@ exports[`regression tests > click on an element and drag it > [dragged] element
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3244,6 +3265,7 @@ exports[`regression tests > click on an element and drag it > [dragged] undo sta
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3459,6 +3481,7 @@ exports[`regression tests > click on an element and drag it > [end of test] undo
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3701,6 +3724,7 @@ exports[`regression tests > click to select a shape > [end of test] undo stack 1
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3755,6 +3779,7 @@ exports[`regression tests > click to select a shape > [end of test] undo stack 1
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -3966,6 +3991,7 @@ exports[`regression tests > click-drag to select a group > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4020,6 +4046,7 @@ exports[`regression tests > click-drag to select a group > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4074,6 +4101,7 @@ exports[`regression tests > click-drag to select a group > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4285,6 +4313,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4339,6 +4368,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4679,6 +4709,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4754,6 +4785,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -4808,6 +4840,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5015,6 +5048,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5069,6 +5103,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5249,6 +5284,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5324,6 +5360,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5510,6 +5547,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5716,6 +5754,7 @@ exports[`regression tests > double click to edit a group > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5770,6 +5809,7 @@ exports[`regression tests > double click to edit a group > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -5824,6 +5864,7 @@ exports[`regression tests > double click to edit a group > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6119,6 +6160,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6173,6 +6215,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6416,6 +6459,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6470,6 +6514,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6524,6 +6569,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -6583,6 +6629,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -6662,6 +6709,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -6739,6 +6787,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -6869,6 +6918,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -7006,6 +7056,7 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7219,6 +7270,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7273,6 +7325,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7327,6 +7380,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7559,6 +7613,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7613,6 +7668,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
||||
"angle": 0,
|
||||
"backgroundColor": "#ffc9c9",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7843,6 +7899,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -7874,6 +7931,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -8084,6 +8142,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -8115,6 +8174,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
||||
"angle": 0,
|
||||
"backgroundColor": "red",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -8328,6 +8388,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -8514,6 +8575,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] undo stac
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -8700,6 +8762,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] undo stac
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -8922,6 +8985,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -9161,6 +9225,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] undo stack 1
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -9354,6 +9419,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] undo sta
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -9600,6 +9666,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] undo stack
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -9803,6 +9870,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] undo stac
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10025,6 +10093,7 @@ exports[`regression tests > key l selects line tool > [end of test] undo stack 1
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -10226,6 +10295,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] undo stac
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10404,6 +10474,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] undo sta
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10614,6 +10685,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10808,6 +10880,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10862,6 +10935,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -10916,6 +10990,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11058,6 +11133,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11091,6 +11167,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11124,6 +11201,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] undo s
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11339,6 +11417,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11393,6 +11472,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11752,6 +11832,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -11962,6 +12043,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12016,6 +12098,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12289,6 +12372,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12343,6 +12427,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12397,6 +12482,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12728,6 +12814,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12782,6 +12869,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12928,6 +13016,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -12982,6 +13071,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -13498,6 +13588,7 @@ exports[`regression tests > supports nested groups > [end of test] undo stack 1`
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -13552,6 +13643,7 @@ exports[`regression tests > supports nested groups > [end of test] undo stack 1`
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -13606,6 +13698,7 @@ exports[`regression tests > supports nested groups > [end of test] undo stack 1`
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14089,6 +14182,7 @@ exports[`regression tests > switches from group of selected elements to another
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14164,6 +14258,7 @@ exports[`regression tests > switches from group of selected elements to another
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14218,6 +14313,7 @@ exports[`regression tests > switches from group of selected elements to another
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14272,6 +14368,7 @@ exports[`regression tests > switches from group of selected elements to another
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14433,6 +14530,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14508,6 +14606,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14562,6 +14661,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -14936,6 +15036,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] redo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -15007,6 +15108,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -15061,6 +15163,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] undo st
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
||||
@@ -5,6 +5,7 @@ exports[`select single element on the scene > arrow 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": "arrow",
|
||||
@@ -54,6 +55,7 @@ exports[`select single element on the scene > arrow escape 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -101,6 +103,7 @@ exports[`select single element on the scene > diamond 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -133,6 +136,7 @@ exports[`select single element on the scene > ellipse 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -165,6 +169,7 @@ exports[`select single element on the scene > rectangle 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { randomInteger } from "@excalidraw/common";
|
||||
import { CaptureUpdateAction, newElementWith } from "@excalidraw/element";
|
||||
|
||||
import type { FractionalIndex } from "@excalidraw/element/types";
|
||||
|
||||
import { reconcileElements } from "../data/reconcile";
|
||||
import { Excalidraw } from "../index";
|
||||
|
||||
import { API } from "./helpers/api";
|
||||
import { Keyboard } from "./helpers/ui";
|
||||
import { act, render } from "./test-utils";
|
||||
|
||||
import type { RemoteExcalidrawElement } from "../data/reconcile";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
describe("element creation timestamps", () => {
|
||||
beforeEach(async () => {
|
||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||
});
|
||||
|
||||
it.each([123, null])(
|
||||
"preserves created=%s through capture, edits, and creation undo/redo",
|
||||
(created) => {
|
||||
const element = API.createElement({
|
||||
type: "rectangle",
|
||||
x: 10,
|
||||
created,
|
||||
index: "a0" as FractionalIndex,
|
||||
});
|
||||
|
||||
API.updateScene({
|
||||
elements: [element],
|
||||
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
||||
});
|
||||
expect(API.getUndoStack()).toHaveLength(0);
|
||||
|
||||
act(() => {
|
||||
h.app.scheduleCapture();
|
||||
h.setState({});
|
||||
});
|
||||
expect(API.getUndoStack()).toHaveLength(1);
|
||||
expect(h.elements[0]).toMatchObject({
|
||||
created,
|
||||
version: element.version,
|
||||
versionNonce: element.versionNonce,
|
||||
});
|
||||
|
||||
const edited = newElementWith(h.elements[0], { x: 50 });
|
||||
API.updateScene({
|
||||
elements: [edited],
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
expect(API.getUndoStack()).toHaveLength(2);
|
||||
expect(h.elements[0]).toMatchObject({
|
||||
created,
|
||||
x: 50,
|
||||
version: edited.version,
|
||||
versionNonce: edited.versionNonce,
|
||||
});
|
||||
expect(h.store.snapshot.elements.get(element.id)?.created).toBe(created);
|
||||
|
||||
Keyboard.undo();
|
||||
expect(h.elements[0]).toMatchObject({
|
||||
created,
|
||||
x: 10,
|
||||
isDeleted: false,
|
||||
});
|
||||
expect(h.elements[0].version).toBeGreaterThan(edited.version);
|
||||
|
||||
Keyboard.undo();
|
||||
expect(h.elements[0]).toMatchObject({ created, isDeleted: true });
|
||||
expect(API.getUndoStack()).toHaveLength(0);
|
||||
|
||||
Keyboard.redo();
|
||||
expect(h.elements[0]).toMatchObject({
|
||||
created,
|
||||
x: 10,
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
Keyboard.redo();
|
||||
expect(h.elements[0]).toMatchObject({
|
||||
created,
|
||||
x: 50,
|
||||
isDeleted: false,
|
||||
});
|
||||
expect(API.getUndoStack()).toHaveLength(2);
|
||||
expect(API.getRedoStack()).toHaveLength(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("continues to a visible undo change after replaying creation metadata", () => {
|
||||
const element = API.createElement({
|
||||
type: "rectangle",
|
||||
x: 10,
|
||||
created: null,
|
||||
});
|
||||
API.updateScene({
|
||||
elements: [element],
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
|
||||
API.updateScene({
|
||||
elements: [newElementWith(h.elements[0], { x: 50 })],
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
// An explicit metadata repair must round-trip without consuming a
|
||||
// separate undo keypress before the user's visible edit. `created` is not
|
||||
// an element update, so a repair constructs a new element revision.
|
||||
API.updateScene({
|
||||
elements: [
|
||||
{
|
||||
...h.elements[0],
|
||||
created: 123,
|
||||
version: h.elements[0].version + 1,
|
||||
versionNonce: randomInteger(),
|
||||
},
|
||||
],
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
expect(API.getUndoStack()).toHaveLength(2);
|
||||
|
||||
Keyboard.undo();
|
||||
|
||||
expect(h.elements[0]).toMatchObject({ x: 10, created: null });
|
||||
expect(API.getUndoStack()).toHaveLength(0);
|
||||
expect(API.getRedoStack()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("preserves the remote creation timestamp and revision during reconciliation", () => {
|
||||
const element = API.createElement({ type: "rectangle", created: 123 });
|
||||
API.updateScene({
|
||||
elements: [element],
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
const remoteElement = newElementWith(h.elements[0], { x: 50 });
|
||||
const reconciled = reconcileElements(
|
||||
h.app.scene.getElementsIncludingDeleted(),
|
||||
[remoteElement as RemoteExcalidrawElement],
|
||||
h.state,
|
||||
);
|
||||
|
||||
API.updateScene({
|
||||
elements: reconciled,
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
|
||||
expect(h.elements[0]).toMatchObject({
|
||||
x: 50,
|
||||
created: 123,
|
||||
version: remoteElement.version,
|
||||
versionNonce: remoteElement.versionNonce,
|
||||
});
|
||||
expect(h.store.snapshot.elements.get(element.id)).toMatchObject({
|
||||
created: 123,
|
||||
version: remoteElement.version,
|
||||
versionNonce: remoteElement.versionNonce,
|
||||
});
|
||||
expect(API.getUndoStack()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ exports[`repairing bindings > should strip arrow binding if repair throws 1`] =
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": null,
|
||||
@@ -52,6 +53,7 @@ exports[`restoreElements > should restore arrow element correctly 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": null,
|
||||
@@ -99,6 +101,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"angle": 0,
|
||||
"backgroundColor": "blue",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
@@ -137,6 +140,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"angle": 0,
|
||||
"backgroundColor": "blue",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
@@ -175,6 +179,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"angle": 0,
|
||||
"backgroundColor": "blue",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "cross-hatch",
|
||||
"frameId": null,
|
||||
@@ -213,6 +218,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
@@ -261,6 +267,7 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] =
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -308,6 +315,7 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] =
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
@@ -357,6 +365,7 @@ exports[`restoreElements > should restore text element correctly passing value f
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"fontFamily": 1,
|
||||
@@ -399,6 +408,7 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"containerId": null,
|
||||
"created": 1,
|
||||
"customData": undefined,
|
||||
"fillStyle": "solid",
|
||||
"font": "10 unknown",
|
||||
|
||||
@@ -14,14 +14,17 @@ import type {
|
||||
ExcalidrawFreeDrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
FractionalIndex,
|
||||
} from "@excalidraw/element/types";
|
||||
import type { NormalizedZoomValue } from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { API } from "../helpers/api";
|
||||
import * as restore from "../../data/restore";
|
||||
import { getDefaultAppState } from "../../appState";
|
||||
import { serializeAsJSON } from "../../data/json";
|
||||
|
||||
import type { ImportedDataState } from "../../data/types";
|
||||
import type { LibraryItem, LibraryItem_v1, LibraryItems } from "../../types";
|
||||
|
||||
describe("restoreElements", () => {
|
||||
const mockSizeHelper = vi.spyOn(sizeHelpers, "isInvisiblySmallElement");
|
||||
@@ -43,6 +46,50 @@ describe("restoreElements", () => {
|
||||
expect(restoredElements.length).toBe(elements.length);
|
||||
});
|
||||
|
||||
it.each([123, 0, null, undefined])(
|
||||
"restores created=%s without substituting load or last-update time",
|
||||
(created) => {
|
||||
const element = {
|
||||
...API.createElement({
|
||||
type: "rectangle",
|
||||
index: "a0" as FractionalIndex,
|
||||
}),
|
||||
created,
|
||||
updated: 456,
|
||||
};
|
||||
// JSON removes undefined, matching files from clients predating created.
|
||||
const input: ImportedDataState = JSON.parse(
|
||||
JSON.stringify({ elements: [element] }),
|
||||
);
|
||||
const restored = restore.restoreElements(input.elements, null);
|
||||
|
||||
expect(restored[0]).toMatchObject({
|
||||
id: element.id,
|
||||
created: created ?? null,
|
||||
updated: 456,
|
||||
});
|
||||
|
||||
const exported: ImportedDataState = JSON.parse(
|
||||
serializeAsJSON(restored, getDefaultAppState(), {}, "local"),
|
||||
);
|
||||
expect(exported.elements?.[0]).toHaveProperty("created", created ?? null);
|
||||
expect(restore.restoreElements(exported.elements, null)).toEqual(
|
||||
restored,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves creation metadata when repairing duplicate element IDs", () => {
|
||||
const element = API.createElement({ type: "rectangle", created: 123 });
|
||||
const restored = restore.restoreElements(
|
||||
[element, { ...element, created: null }],
|
||||
null,
|
||||
);
|
||||
|
||||
expect(restored.map(({ created }) => created)).toEqual([123, null]);
|
||||
expect(restored[0].id).not.toBe(restored[1].id);
|
||||
});
|
||||
|
||||
it("when imported data state is null it should return an empty array of elements", () => {
|
||||
const restoredElements = restore.restoreElements(null, null);
|
||||
expect(restoredElements.length).toBe(0);
|
||||
@@ -1301,3 +1348,48 @@ describe("repairing bindings", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("restoreLibraryItems creation timestamps", () => {
|
||||
it.each([1, 2])(
|
||||
"accepts v%s input with missing creation metadata",
|
||||
(version) => {
|
||||
const { created, ...legacyElement } = API.createElement({
|
||||
type: "rectangle",
|
||||
});
|
||||
// models library data persisted before `created` existed; the declared
|
||||
// input type is a complete element, restore fills the field at runtime
|
||||
const elements = [
|
||||
legacyElement,
|
||||
API.createElement({ type: "rectangle", created: 123 }),
|
||||
API.createElement({ type: "rectangle", created: null }),
|
||||
] as unknown as LibraryItem["elements"];
|
||||
const legacyItem: LibraryItem_v1 = elements;
|
||||
const currentItem: LibraryItem = {
|
||||
id: "library-item",
|
||||
status: "unpublished",
|
||||
created: 456,
|
||||
elements,
|
||||
};
|
||||
const imported: ImportedDataState = {
|
||||
libraryItems: version === 1 ? [legacyItem] : [currentItem],
|
||||
};
|
||||
|
||||
const restoredItems: LibraryItems = restore.restoreLibraryItems(
|
||||
imported.libraryItems,
|
||||
"unpublished",
|
||||
);
|
||||
|
||||
expect(restoredItems).toHaveLength(1);
|
||||
expect(
|
||||
restoredItems[0].elements.map((element) => element.created),
|
||||
).toEqual([null, 123, null]);
|
||||
expect(restoredItems[0].elements.map((element) => element.id)).toEqual(
|
||||
elements.map((element) => element.id),
|
||||
);
|
||||
expect(legacyElement).not.toHaveProperty("created");
|
||||
if (version === 2) {
|
||||
expect(restoredItems[0].created).toBe(456);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KEYS } from "@excalidraw/common";
|
||||
import * as common from "@excalidraw/common";
|
||||
|
||||
import { getTargetElements, isArrowElement } from "@excalidraw/element";
|
||||
|
||||
@@ -574,7 +574,7 @@ describe("autoshape finalize funnel", () => {
|
||||
expect(h.state.newElement).toBeNull();
|
||||
|
||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||
Keyboard.keyPress(KEYS.Z);
|
||||
Keyboard.keyPress(common.KEYS.Z);
|
||||
});
|
||||
|
||||
// undo was blocked — the previously committed shape is still there
|
||||
@@ -778,14 +778,14 @@ describe("autoshape tool activation", () => {
|
||||
expect(h.state.activeTool.type).toBe("selection");
|
||||
|
||||
Keyboard.withModifierKeys({ shift: true }, () => {
|
||||
Keyboard.keyPress(KEYS.X);
|
||||
Keyboard.keyPress(common.KEYS.X);
|
||||
});
|
||||
|
||||
expect(h.state.activeTool.type).toBe("autoshape");
|
||||
});
|
||||
|
||||
it("is not activated by an unmodified X (freedraw's shortcut)", () => {
|
||||
Keyboard.keyPress(KEYS.X);
|
||||
Keyboard.keyPress(common.KEYS.X);
|
||||
|
||||
expect(h.state.activeTool.type).toBe("freedraw");
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
|
||||
isDeleted: false,
|
||||
boundElements: null,
|
||||
updated: 1,
|
||||
created: null,
|
||||
link: null,
|
||||
locked: false,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ROUNDNESS,
|
||||
assertNever,
|
||||
getStrokeWidthByKey,
|
||||
getUpdatedTimestamp,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
@@ -185,6 +186,7 @@ export class API {
|
||||
frameId?: ExcalidrawElement["id"] | null;
|
||||
index?: ExcalidrawElement["index"];
|
||||
groupIds?: ExcalidrawElement["groupIds"];
|
||||
created?: ExcalidrawElement["created"];
|
||||
// generic element props
|
||||
strokeColor?: ExcalidrawGenericElement["strokeColor"];
|
||||
backgroundColor?: ExcalidrawGenericElement["backgroundColor"];
|
||||
@@ -290,6 +292,7 @@ export class API {
|
||||
opacity: rest.opacity ?? appState.currentItemOpacity,
|
||||
boundElements: rest.boundElements ?? null,
|
||||
locked: rest.locked ?? false,
|
||||
created: rest.created === undefined ? getUpdatedTimestamp() : rest.created,
|
||||
};
|
||||
switch (type) {
|
||||
case "rectangle":
|
||||
|
||||
@@ -362,6 +362,7 @@ describe("Basic lasso selection tests", () => {
|
||||
...e,
|
||||
angle: e.angle as Radians,
|
||||
index: null,
|
||||
created: null,
|
||||
} as ExcalidrawElement),
|
||||
);
|
||||
|
||||
@@ -1047,6 +1048,7 @@ describe("Special cases", () => {
|
||||
].map((e) => ({
|
||||
...e,
|
||||
index: null,
|
||||
created: null,
|
||||
angle: e.angle as Radians,
|
||||
})) as ExcalidrawElement[];
|
||||
|
||||
@@ -1767,6 +1769,7 @@ describe("Special cases", () => {
|
||||
].map((e) => ({
|
||||
...e,
|
||||
index: null,
|
||||
created: null,
|
||||
angle: e.angle as Radians,
|
||||
})) as ExcalidrawElement[];
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ exports[`exportToSvg > with elements that have a link 1`] = `
|
||||
`;
|
||||
|
||||
exports[`exportToSvg > with exportEmbedScene 1`] = `
|
||||
"<!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1WW2vbMFx1MDAxOH3vrzDaa1llJ2nSvGXrLoWxwTIorOxBtT7bwrLkSnIuXHUwMDBi+e+T5MVyvLLnNYtcdTAwMDOG7350dD6c3UVcdTAwMTQhs61cdTAwMDHNI1x1MDAwNJuUcEZcdTAwMTVZo0vnX4HSTFxuXHUwMDFiSrytZaNSn1lcdTAwMThTz6+uuLRcdTAwMDWF1GY+wlx1MDAxOLdFwKFcdTAwMDJhtE17sHZcdTAwMTTt/NtGXHUwMDE4daWre/X0ZZGVTNDkKa2mn25cdTAwMTdcdTAwMWa++1KftLE543jc2Vs3fTTt7DWjprC+XHUwMDE4485XXHUwMDAwy1x1MDAwYjNwXHUwMDEykXOHNXi0UbKEt5JL5YC8wv5cdKNcdTAwMWZJWuZKNoKGnHhCyGNcdTAwMTZyMsb50mx5y1x1MDAwMkmLRlx1MDAwMVx1MDAxYUy4P0BcdTAwMWP4uzotLcuhyo7MXHUwMDBiXHUwMDAxWlx1MDAxZtXImqTMbFx1MDAwN6dy+Oo76tn9XHUwMDExUClSwZ2jVzSc91x1MDAxYlx1MDAwYvq78VHAclx1MDAwZo5oRHrH11x1MDAwMNRPXHUwMDFix9eT6VxynnWRoIM4wUPvZym8JuJ4NsN4nEyvw1x1MDAxOH1r1WB824xwXHKBaofsXVDKXHUwMDExuqampC1cbmxwJsphnlVf+Uzvg5opI5VcdTAwMTRcdTAwMTR5//7yrMV/XYvx6WpcdTAwMTE4Z7WGl6HFXHUwMDE43O//1mJyulo0sDG9i5PCLNlPXHUwMDE36Z3Bed+TinHH8yS0cKW2hVQsZ4Lw6LjXwf3t72nOWnCWO+JcdTAwMTCHrFx1MDAxN7LcXHUwMDE5Zv9TdGEj61x1MDAxME0tKsJcdTAwMDSoP6/U8lx1MDAwMFx1MDAxZju5v05cdTAwMDJm0lx1MDAxOPlcdTAwMTV0e0TPyHlcdF/IXHUwMDEyjs5L2C1hXHUwMDAwfkpLaN9eKIjU9dJYYm24XUm0YrB+84zoM/+4L6lfYSd6cLe021/sf1x1MDAwMVSoRdMifQ==<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
"<!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1WW2vbMFx1MDAxNH7vrzDea1llJ2myvGXrLoWxwTIobPRBtY5tYVlyJTmXhfz3SnJj2W7pXtclXHUwMDBlXHUwMDE4zneu+vRcdTAwMWSc3VlcdTAwMTCEeltBOFx1MDAwZkLYJJhRXCLxOjy3+FxupKKCXHUwMDFiV+xsJWqZuMhcXOtqfnHBhEnIhdLzXHUwMDExQqhJXHUwMDAyXHUwMDA2JXCtTNhvY1x1MDAwN8HOvY2HXHUwMDEym7q6kfffXHUwMDE3aUE5ie+Tcvr1avH5l0t1QVx1MDAxYlx1MDAxMzOOxq29td1H09ZeU6Jzg0VcYrVYXHUwMDBlNMv1XHUwMDAwxDxjdlaPKC1FXHUwMDAxXHUwMDFmXHUwMDA0XHUwMDEz0lx1MDAwZfJcdTAwMDa5x7e+w0mRSVFz4mOiXHTGd6mPSSljS71lXHJcdTAwMGI4yWtcdOGgw81hxFx1MDAwMd7mKWFY9lmmZZZzUKqXIyqcUL1cdTAwMWScys5XXVx1MDAxM8furZ9K4lx1MDAxMq4tvbxmrFuYk8fCPYfhXHUwMDFlLNEh7lx1MDAxY19cdTAwMDFcdTAwMTDXbVx1MDAxY11Opu/QrPV4XHUwMDFkRDFcdTAwMWGi31x1MDAwNHeaiKLZXGahcTy99G3UlVGDdmVTzFx1MDAxNHiq7WRcdTAwMWa9UnrT1Vx1MDAxNcFNkmcjkfCI9UJcdTAwMTnlxVx1MDAxM0wkxTMtXHUwMDBmXCInXHUwMDE0l4KT0OH785NE/3WJRkcnUWCMVlxuXodEI7C/45ZofHRcdTAwMTLVsNGd+1x1MDAxNFxcL+lcdTAwMWbr6Vx1MDAxY82in3BJmaV/4kvYVFNCSJpRjlnQr3WAf75cdTAwMWNmrVx1MDAwNaOZ5TNkkHZcXIZSTc1cdTAwMWaT1q1F5b2JmVxuU1x1MDAwZfLpTVx1MDAxYlx1MDAxZeBLu1x1MDAwNW9jPzOutfhcdTAwMDGqOaJj5LSbr2Q3R6fd/Ntu+vP8T7tp3k4/Ia6qpTbcXHUwMDFhd7Op4YrC+v0zu5C6x3533WbbXVx1MDAwMHt5u/3Z/lx1MDAwMaRcbl5vIn0=<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
@font-face { font-family: Excalifont; src: url(data:font/woff2;base64,d09GMgABAAAAAAf0AA4AAAAADbQAAAegAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgjgcNAZgAHwRCAqPYItcCxoAATYCJAMwBCAFgxgHIBujClGUblKX7Edh3Pg8OJbt5MlDFPwWk6FmJ08terdayeDx9O8Fa5eunTJ1ykAUGuAvgOF5bPfuH0nXrvQrNkFoNlERBLIQDd/mj2kP7fdg8avL4M/sm4cbu4L2i1VQVjShz/+90/YE3IJFmOC+f0Z1Xhs3bVxiJZZGa1IgY5sHCJ2EM2iBndkLWsZvFxCAhRBGCknKEOQenHCpmE2fVQTl1atzWyjvzs3aQPnWd20PJQZeG3mQp1lnLimFBAhOKFIQSrGa+vOB69gLCRkjLr4NIjd5rXiz8hhu+LIe9DZCgkJjmZEy34MkhXNBJ8GhNMMnJUgmSYA0AowAwT2zRor05iZAeN/80c4UkYlgSTOaKgOQCUOSE5F+z6+DXqndDTk4CEHP2v3/aKw7lXQXgAUAQP5+kgIpTYMHT9MEjIyWxJ5V5jAzgnrNtNBKWx101tXee86rA5O1f1fiIzEtJsS4GBMjYlikIhEvwwXDjYgiApUFdjgCFAA1m2GorwgdA5a6fzxxX0ei+Eqw6MspHfbmte7eLSh2FSEREgnySedVN+VfsbKAVQygCrGALXgDQoSA0kZqfB2OiyyaZolc/i3tnXswySlmifL9eo1ReyWQcYadAK/DD9c80uaI4pamykJM2hp/MF2uj+teZm9LiRlrltgBlcVYlWVWf+WPTpVJj40Kz9Gvi5p21Zcov8bKEL+5Uirz+j2Hjzr+99zosad2XtbpfsbKgyIQ3RJHxrTH1ysQQAjTUaii13B+OTtxrFoLubjpMYtit2uul6DdSPvKaQl/9FkppthYCXEvjoEdStVVjti2fUQf34q9YjychmIsxCAjKkj0GW4mIhlX1snPOEVRVq4UMRQwkzJNhK143IsBOlghz9meJAGf4apbAmYRMj2hsj5X1OGDPuIKXoOyN8yIMYbeusXTa9f4a+1UvYG9FgQxzoQBUoSPD5/ezAuQIP8s8IyVrUGrwSxC95WPAdlx3EkkQq5yNjcaKAAZ4LEHheIMvgHV3fe6X41/6rzCiMBwSxrnvl9RcR0gK5fy4jDVigBySy99iwlppT9aTlWIMSMKBx9WkbupSbZXNC/CKUC6z53jA0mS7340zruutV7xYguW6p/01tYHOQZQ7ptAagFiLAzFWqyWUwN8/eNGSyn2elPguvF9At0XUNlNv4t202nq+EnkKvdW6aLVlJUxhpXYUqMsn5svp9eoTpvKKf6BY8QwAkaJWYSggEuximEYiCVE0TjN5ojkVoznAP0GytsVT1SesrOn/s8LsMpVpXtSc6To4sT6XIl1q0jH96rd5/g5vmi35+y6eeYPNqjBGHtX0nKFGywfPr19aO9Gh/Q1sWZGuqckOoOJmlK2pSPTSgm/46h389ap+VmOnxsPdspP82Xyy2l7u6i2908pVjRkDbEddOKOprOPJ/94eJ59fi6jDtB6VbzuwXb5puJcqcKnUeM72fFnIlssAv/Z6uMq2s1Jd8qpd7DZo538w5PG7cHm2TU2TD41PjYiaUSikYspa/kjJ84AdgqXsT226/L+fW6j99QmJZlWN99tfcdbUPO17tLu8+gVw73h3OfmurdS9BCa5twNfokJhx7/agK3fHC9rXVewbT0kZG9vXqRBOtS6xGOZ5dC3pwleZIyidL1le+xC3btOlwJkXqGESosoDUG2TqcuE/6OcolnOykkpKdD+5l7+JODWBpAycPke+STDMRWidsWCLvfQJbKM7JpnkLG1WV73qjg6eZ1EMVF1NgG5rTaZF2r7tJNs2VzabNrTQfhSOtgxRxRLP5mldJB6/b6R5LWk9ss9nNUGyTTx8iytdCS9xlAlvtN/1hTrYObLVe5T8zLWNk2oko/bq0dv1cvjXrGC5k+pfYm+L1MtXCFsSzKDTY/dhu66JJWqlfkIxw7T5ufjDBa4pCy7ObPC0cojzlZMlCh2VjKwfX8qy5cpyRt1hp6DeKs/UZjc6zfGxC0rJHl1IUtIm2p7Tg+6VKn8vbfWR9fHljiebwTXPXTQ13HmoSbMssDsa32NnSfmmoH92xuU/E7U2qS8vWYFD9D73VJodqvWU8l7tB+b9ZdPiXvC2qoboerfoamG78+oOX7uWn1/WJy87rJPSHcVmUO7sN0ypzcG5zdfzU8k/NSh5OCbHvUSJlOk2xGrkmubA+nV7YtUCdoJk2vZ/dNSvrAkFnE0toKlC7rHllSrrOzt4Yb91LaLga9TtY+etg/uCpE8rVr604yv7YaE2x450wAACIfUvVNvFrrWXyNylLvwSAR70CtQDwePGbPuLQ/32Y10wCAClKi8AXKzralCj+/e0Dwri9RW2ArkwEqBW+cYJzghDkKyuPEB0FElKG8DIXEQkASS4s/2PSN2DGs1Bi6eh9EhVoZDAH0NblyEbw9tsoChtttEDDbQxvDTZWKm+YcYcDOj01qtdWK8110F5XIQq+nMJuwnpSopmb7KJVWbUgQqhwkyQxlOd76aglFYEKoxAJ8OeFkYPjBPTuQejsbXStQgY5klp1cr1LoQHSUa8StKqVbKnrXAqNAgg8wm1EB6RBL7ejLWnSmSI9hGJQZdAW2trTXRJoBsJm6M5VNQlFM3yJ/7EAAAA=); }
|
||||
@font-face { font-family: Excalifont; src: url(data:font/woff2;base64,d09GMgABAAAAAAHcAA0AAAAAA9gAAAGMAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx4cNAZgAAQRCAoAKgsEAAE2AiQDBAQgBYMYByAbHQPIrgp4MjSeIQLqpl4jnPFQwveqm205gmrZevZ2/8kKGaJ6ictBuCwMMnsKo8hC4RwaLBrJ7dXaFrFk0Re1e/Dk3t9wCUVrYAiZkgiZ5rGDVFdY04wBF4APOCzXHr/ljrNIDqLAY/slkQf0SyjxLNBaAq8Z1AJqYW5j0GjbckRLQhoJCgwYoyfY3q33QS5DrSAwYB4hFruNve4WoN2On4P29GEFWsC+nVM/UvSlVwIcsWycI/5gbqfUhE/9Q2P7UppTOAVeIR4TYIocELVKIEACZJgXCJ5GXl0o+esbFP3g+763DcDPx6/60MrnBaBPAsGPUDRC/8+FGIqGUSBA+NIJOwTWe13HRCIrgEvfRhj1RjLiE42eG7I5DIpVNuqkNNhxwaItTI2srRz4dfHGjhZoO0O8nb2pilFYQKhenFaycLUxsYcoAQRyoRBEnPvcgfysPq+npClt8UxLyvHWjaudqbGJA+TCckNECBGBGFforuTs0M4CUMbCAvp+P4in4o864XECREBtFQAAAAA=); }
|
||||
@font-face { font-family: Nunito; src: url(data:font/woff2;base64,d09GMgABAAAAAAIsAA4AAAAABLQAAAHYAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx4cLgZgP1NUQVREAAQRCAoAKgsEAAE2AiQDBAQgBYQkByAb5wPIrgp4Mt6IIcJZFNCfWmZY6KzqazTt6AiVHCFawzJ7V0CQBKoSQQEpFKrCVxiSFbqqqsaTevKs/q7s6uBYmujMyA9wxb6a7XnEOcNnDLgG4nW8PoPzHzYpS2uqp51pL3eB/xZoxIllCQc80B9o4j/4xMbxQB+j+SC3hsm6JmI8RMaHj+aJApW6ZbkXlg4vXSE5FECg0og6LzxP9pOarug4tF1RLpbHeZqLX0pIt2mfy3pNG6eyGaRIjrnrr/gv2c//yGdjpJ/7DuJLin5eIZRLaObBMM/NpYpuXJ8z3SE088mEFANcCARESfwChCioAwESsVxBgeyxp6+vZ3Xzv8uz7Ae8tRk+p6RUTPOR7BmlEgh+STsAimNuKibilyluhBNe5/wCACSBKrcykVfgyb+RYcK/TGq9yMyCt3bOskSnR1FqTLMSVBsMGLQVltDKKw2IYA+wcGxHJCINY9mDOCaN4IZEo1ChY4xNg8DaB0RxDqnbMNjXJJRzF9iInqahtm67M8dOHFvXaYbn+wrGxKG3YZI+2V4CW58ovdfV1tFHXFJJiPH7T1FAJxEgYo5BKkA5iDIVQluOqZYWhQapGF6TAFhaFAAoTBIZsCFHi/0oV3gpVKwbAA==); }
|
||||
|
||||
@@ -1017,7 +1017,8 @@ export interface ExcalidrawProps {
|
||||
}
|
||||
|
||||
export type SceneData = {
|
||||
elements?: ImportedDataState["elements"];
|
||||
/** Expects normalized elements; restore imported data before updating the scene. */
|
||||
elements?: readonly ExcalidrawElement[] | null;
|
||||
appState?: ImportedDataState["appState"];
|
||||
collaborators?: Map<SocketId, Collaborator>;
|
||||
captureUpdate?: CaptureUpdateActionType;
|
||||
|
||||
@@ -27,6 +27,7 @@ import type { AppState, BinaryFiles } from "@excalidraw/excalidraw/types";
|
||||
export { MIME_TYPES };
|
||||
|
||||
type ExportOpts = {
|
||||
/** restored before exporting */
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
appState?: Partial<Omit<AppState, "offsetTop" | "offsetLeft">>;
|
||||
files: BinaryFiles | null;
|
||||
@@ -150,8 +151,8 @@ export const exportToBlob = async (
|
||||
metadata: serializeAsJSON(
|
||||
// NOTE as long as we're using the Scene hack, we need to ensure
|
||||
// we pass the original, uncloned elements when serializing
|
||||
// so that we keep ids stable
|
||||
opts.elements,
|
||||
// so that we keep ids stable (restoring keeps the ids)
|
||||
restoreElements(opts.elements, null),
|
||||
opts.appState,
|
||||
opts.files || {},
|
||||
"local",
|
||||
@@ -214,7 +215,10 @@ export const exportToClipboard = async (
|
||||
} else if (opts.type === "png") {
|
||||
await copyBlobToClipboardAsPng(exportToBlob(opts));
|
||||
} else if (opts.type === "json") {
|
||||
await copyToClipboard(opts.elements, opts.files);
|
||||
await copyToClipboard(
|
||||
getNonDeletedElements(restoreElements(opts.elements, null)),
|
||||
opts.files,
|
||||
);
|
||||
} else {
|
||||
throw new Error("Invalid export type");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { MIME_TYPES } from "@excalidraw/common";
|
||||
import * as clipboardModule from "@excalidraw/excalidraw/clipboard";
|
||||
import * as imageModule from "@excalidraw/excalidraw/data/image";
|
||||
import * as jsonModule from "@excalidraw/excalidraw/data/json";
|
||||
import * as mockedSceneExportUtils from "@excalidraw/excalidraw/scene/export";
|
||||
import { diagramFactory } from "@excalidraw/excalidraw/tests/fixtures/diagramFixture";
|
||||
import { vi } from "vitest";
|
||||
@@ -132,3 +135,85 @@ describe("exportToSvg", () => {
|
||||
expect(passedOptions().exportEmbedScene).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// Elements persisted before `created` existed omit it (modeled with the cast
|
||||
// below, as the declared input type is a complete element). Every public
|
||||
// export entry point restores its input, so such elements must reach the
|
||||
// lower-level code restored, with `created: null`.
|
||||
describe("elements lacking creation metadata", () => {
|
||||
const legacyDiagram = () => {
|
||||
const { elements, ...rest } = diagramFactory({
|
||||
overrides: { appState: void 0 },
|
||||
});
|
||||
return {
|
||||
...rest,
|
||||
elements: elements.map(
|
||||
({ created, ...element }) => element,
|
||||
) as unknown as typeof elements,
|
||||
};
|
||||
};
|
||||
const createdOf = (elements: readonly { created: number | null }[]) =>
|
||||
elements.map((element) => element.created);
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("exportToSvg and exportToCanvas restore the elements", async () => {
|
||||
const exportToSvgSpy = vi.spyOn(mockedSceneExportUtils, "exportToSvg");
|
||||
const diagram = legacyDiagram();
|
||||
|
||||
await utils.exportToSvg(diagram);
|
||||
|
||||
expect(createdOf(exportToSvgSpy.mock.calls[0][0])).toEqual([
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
expect(diagram.elements[0]).not.toHaveProperty("created");
|
||||
|
||||
const exportToCanvasSpy = vi.spyOn(
|
||||
mockedSceneExportUtils,
|
||||
"exportToCanvas",
|
||||
);
|
||||
const canvas = await utils.exportToCanvas(diagram);
|
||||
|
||||
expect(createdOf(exportToCanvasSpy.mock.calls[0][0])).toEqual([
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
expect(canvas.width).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("exportToBlob embeds the restored scene into png metadata", async () => {
|
||||
const serializeSpy = vi.spyOn(jsonModule, "serializeAsJSON");
|
||||
vi.spyOn(imageModule, "encodePngMetadata").mockImplementation(
|
||||
async ({ blob }) => blob,
|
||||
);
|
||||
|
||||
await utils.exportToBlob({
|
||||
...legacyDiagram(),
|
||||
mimeType: MIME_TYPES.png,
|
||||
appState: { exportEmbedScene: true },
|
||||
});
|
||||
|
||||
expect(serializeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(createdOf(serializeSpy.mock.calls[0][0])).toEqual([
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
});
|
||||
|
||||
it("exportToClipboard copies the restored elements as json", async () => {
|
||||
const copySpy = vi
|
||||
.spyOn(clipboardModule, "copyToClipboard")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await utils.exportToClipboard({ ...legacyDiagram(), type: "json" });
|
||||
|
||||
expect(copySpy).toHaveBeenCalledTimes(1);
|
||||
expect(createdOf(copySpy.mock.calls[0][0])).toEqual([null, null, null]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user