Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4060682c57 |
@@ -266,7 +266,7 @@ const initializeScene = async (opts: {
|
|||||||
repairBindings: true,
|
repairBindings: true,
|
||||||
deleteInvisibleElements: true,
|
deleteInvisibleElements: true,
|
||||||
}),
|
}),
|
||||||
scene.elements,
|
localDataState?.elements,
|
||||||
),
|
),
|
||||||
appState: restoreAppState(
|
appState: restoreAppState(
|
||||||
imported.appState,
|
imported.appState,
|
||||||
@@ -551,11 +551,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
const username = importUsernameFromLocalStorage();
|
const username = importUsernameFromLocalStorage();
|
||||||
setLangCode(getPreferredLanguage());
|
setLangCode(getPreferredLanguage());
|
||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
elements: restoreElements(localDataState?.elements, null, {
|
...localDataState,
|
||||||
repairBindings: true,
|
|
||||||
deleteInvisibleElements: true,
|
|
||||||
}),
|
|
||||||
appState: restoreAppState(localDataState?.appState, null),
|
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
captureUpdate: CaptureUpdateAction.NEVER,
|
||||||
});
|
});
|
||||||
LibraryIndexedDBAdapter.load().then((data) => {
|
LibraryIndexedDBAdapter.load().then((data) => {
|
||||||
|
|||||||
@@ -86,11 +86,9 @@ const saveDataStateToLocalStorage = (
|
|||||||
_appState.openSidebar = null;
|
_appState.openSidebar = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const persistedElements = getNonDeletedElements(elements);
|
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
||||||
JSON.stringify(persistedElements),
|
JSON.stringify(getNonDeletedElements(elements)),
|
||||||
);
|
);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
||||||
|
|||||||
@@ -69,114 +69,6 @@ vi.mock("socket.io-client", () => {
|
|||||||
* i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
|
* i.e. multiplayer history tests could be a good first candidate, as we could test both history stacks simultaneously.
|
||||||
*/
|
*/
|
||||||
describe("collaboration", () => {
|
describe("collaboration", () => {
|
||||||
it("should preserve future element fields across collab reconciliation", async () => {
|
|
||||||
await render(<ExcalidrawApp />);
|
|
||||||
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
id: "A",
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
backgroundColor: "#ff0000",
|
|
||||||
});
|
|
||||||
|
|
||||||
const frameWithFutureFields = {
|
|
||||||
...frame,
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
...frame.schemaState.tracks,
|
|
||||||
"host.myapp.frame": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
futureField: "keep-me",
|
|
||||||
} as typeof frame & {
|
|
||||||
schemaState: { tracks: Record<string, number> };
|
|
||||||
futureField: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
API.updateScene({
|
|
||||||
elements: [frameWithFutureFields],
|
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect((h.elements[0] as any).futureField).toBe("keep-me");
|
|
||||||
expect((h.elements[0] as any).schemaState).toEqual(
|
|
||||||
frameWithFutureFields.schemaState,
|
|
||||||
);
|
|
||||||
expect(h.elements[0].backgroundColor).toBe("#ff0000");
|
|
||||||
});
|
|
||||||
|
|
||||||
const remoteMovedFrame = newElementWith(h.elements[0] as any, {
|
|
||||||
x: 120,
|
|
||||||
y: 80,
|
|
||||||
});
|
|
||||||
|
|
||||||
const reconciled = (window.collab as any)._reconcileElements([
|
|
||||||
remoteMovedFrame,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(reconciled[0]).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
x: 120,
|
|
||||||
y: 80,
|
|
||||||
backgroundColor: "#ff0000",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect((reconciled[0] as any).futureField).toBe("keep-me");
|
|
||||||
expect((reconciled[0] as any).schemaState).toEqual(
|
|
||||||
frameWithFutureFields.schemaState,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should preserve future element fields on local edits before broadcast", async () => {
|
|
||||||
await render(<ExcalidrawApp />);
|
|
||||||
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
id: "A",
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rectWithFutureFields = {
|
|
||||||
...rect,
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
...rect.schemaState.tracks,
|
|
||||||
"host.myapp.rect": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
futureField: { value: "keep-me" },
|
|
||||||
} as typeof rect & {
|
|
||||||
schemaState: { tracks: Record<string, number> };
|
|
||||||
futureField: { value: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
API.updateScene({
|
|
||||||
elements: [rectWithFutureFields],
|
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
|
||||||
});
|
|
||||||
|
|
||||||
const locallyEdited = newElementWith(h.elements[0] as any, { x: 200 });
|
|
||||||
API.updateScene({
|
|
||||||
elements: [locallyEdited],
|
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect((h.elements[0] as any).futureField).toEqual({ value: "keep-me" });
|
|
||||||
expect((h.elements[0] as any).schemaState).toEqual(
|
|
||||||
rectWithFutureFields.schemaState,
|
|
||||||
);
|
|
||||||
expect(h.elements[0]).toEqual(expect.objectContaining({ x: 200 }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should emit two ephemeral increments even though updates get batched", async () => {
|
it("should emit two ephemeral increments even though updates get batched", async () => {
|
||||||
const durableIncrements: DurableIncrement[] = [];
|
const durableIncrements: DurableIncrement[] = [];
|
||||||
const ephemeralIncrements: EphemeralIncrement[] = [];
|
const ephemeralIncrements: EphemeralIncrement[] = [];
|
||||||
@@ -191,18 +83,14 @@ describe("collaboration", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure this test starts from a deterministic scene regardless of previous
|
// eslint-disable-next-line dot-notation
|
||||||
// test state restored from persistence.
|
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
||||||
API.updateScene({
|
expect(durableIncrements.length).toBe(0);
|
||||||
elements: [],
|
expect(ephemeralIncrements.length).toBe(0);
|
||||||
captureUpdate: CaptureUpdateAction.NEVER,
|
|
||||||
});
|
|
||||||
|
|
||||||
const durableBaseline = durableIncrements.length;
|
|
||||||
const ephemeralBaseline = ephemeralIncrements.length;
|
|
||||||
|
|
||||||
const rectProps = {
|
const rectProps = {
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
|
id: "A",
|
||||||
height: 200,
|
height: 200,
|
||||||
width: 100,
|
width: 100,
|
||||||
x: 0,
|
x: 0,
|
||||||
@@ -217,7 +105,8 @@ describe("collaboration", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(durableIncrements.length).toBe(durableBaseline + 1);
|
// expect(commitSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(durableIncrements.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// simulate two batched remote updates
|
// simulate two batched remote updates
|
||||||
@@ -241,13 +130,13 @@ describe("collaboration", () => {
|
|||||||
// altough the updates get batched,
|
// altough the updates get batched,
|
||||||
// we expect two ephemeral increments for each update,
|
// we expect two ephemeral increments for each update,
|
||||||
// and each such update should have the expected change
|
// and each such update should have the expected change
|
||||||
expect(ephemeralIncrements.length).toBe(ephemeralBaseline + 2);
|
expect(ephemeralIncrements.length).toBe(2);
|
||||||
expect(
|
expect(ephemeralIncrements[0].change.elements.A).toEqual(
|
||||||
ephemeralIncrements[ephemeralBaseline].change.elements[rect.id],
|
expect.objectContaining({ x: 100 }),
|
||||||
).toEqual(expect.objectContaining({ x: 100 }));
|
);
|
||||||
expect(
|
expect(ephemeralIncrements[1].change.elements.A).toEqual(
|
||||||
ephemeralIncrements[ephemeralBaseline + 1].change.elements[rect.id],
|
expect.objectContaining({ x: 200 }),
|
||||||
).toEqual(expect.objectContaining({ x: 200 }));
|
);
|
||||||
// eslint-disable-next-line dot-notation
|
// eslint-disable-next-line dot-notation
|
||||||
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
expect(h.store["scheduledMicroActions"].length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#66a80f",
|
"strokeColor": "#66a80f",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -67,9 +64,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#9c36b5",
|
"strokeColor": "#9c36b5",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -122,9 +116,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -186,9 +177,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -235,9 +223,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -281,9 +266,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"originalText": "HEYYYYY",
|
"originalText": "HEYYYYY",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#c2255c",
|
"strokeColor": "#c2255c",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -330,9 +312,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"originalText": "Whats up ?",
|
"originalText": "Whats up ?",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -393,9 +372,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -443,9 +419,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"originalText": "HELLO WORLD!!",
|
"originalText": "HELLO WORLD!!",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -506,9 +479,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -556,9 +526,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"originalText": "HELLO WORLD!!",
|
"originalText": "HELLO WORLD!!",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -599,9 +566,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -639,9 +603,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -699,9 +660,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -749,9 +707,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"originalText": "HELLO WORLD!!",
|
"originalText": "HELLO WORLD!!",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -798,9 +753,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"originalText": "HEYYYYY",
|
"originalText": "HEYYYYY",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -847,9 +799,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"originalText": "WHATS UP ?",
|
"originalText": "WHATS UP ?",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -885,9 +834,6 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -933,9 +879,6 @@ exports[`Test Transform > should transform linear elements 1`] = `
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -983,9 +926,6 @@ exports[`Test Transform > should transform linear elements 2`] = `
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": "dot",
|
"startArrowhead": "dot",
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -1033,9 +973,6 @@ exports[`Test Transform > should transform linear elements 3`] = `
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -1083,9 +1020,6 @@ exports[`Test Transform > should transform linear elements 4`] = `
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -1120,9 +1054,6 @@ exports[`Test Transform > should transform regular shapes 1`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1155,9 +1086,6 @@ exports[`Test Transform > should transform regular shapes 2`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1190,9 +1118,6 @@ exports[`Test Transform > should transform regular shapes 3`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1225,9 +1150,6 @@ exports[`Test Transform > should transform regular shapes 4`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1260,9 +1182,6 @@ exports[`Test Transform > should transform regular shapes 5`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
@@ -1295,9 +1214,6 @@ exports[`Test Transform > should transform regular shapes 6`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1971c2",
|
"strokeColor": "#1971c2",
|
||||||
"strokeStyle": "dashed",
|
"strokeStyle": "dashed",
|
||||||
@@ -1336,9 +1252,6 @@ exports[`Test Transform > should transform text element 1`] = `
|
|||||||
"originalText": "HELLO WORLD!",
|
"originalText": "HELLO WORLD!",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1380,9 +1293,6 @@ exports[`Test Transform > should transform text element 2`] = `
|
|||||||
"originalText": "STYLED HELLO WORLD!",
|
"originalText": "STYLED HELLO WORLD!",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#5f3dc4",
|
"strokeColor": "#5f3dc4",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1429,9 +1339,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1471,9 +1378,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1517,9 +1421,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1567,9 +1468,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1629,9 +1527,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -1696,9 +1591,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
@@ -1748,9 +1640,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"originalText": "B",
|
"originalText": "B",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1794,9 +1683,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"originalText": "A",
|
"originalText": "A",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1840,9 +1726,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"originalText": "Alice",
|
"originalText": "Alice",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1886,9 +1769,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"originalText": "Bob",
|
"originalText": "Bob",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1930,9 +1810,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"originalText": "How are you?",
|
"originalText": "How are you?",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1974,9 +1851,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
|||||||
"originalText": "Friendship",
|
"originalText": "Friendship",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2030,9 +1904,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -2085,9 +1956,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -2140,9 +2008,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -2195,9 +2060,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -2238,9 +2100,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"originalText": "LABELED ARROW",
|
"originalText": "LABELED ARROW",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2282,9 +2141,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"originalText": "STYLED LABELED ARROW",
|
"originalText": "STYLED LABELED ARROW",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#099268",
|
"strokeColor": "#099268",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2326,9 +2182,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"originalText": "ANOTHER STYLED LABELLED ARROW",
|
"originalText": "ANOTHER STYLED LABELLED ARROW",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1098ad",
|
"strokeColor": "#1098ad",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2371,9 +2224,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"originalText": "ANOTHER STYLED LABELLED ARROW",
|
"originalText": "ANOTHER STYLED LABELLED ARROW",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#099268",
|
"strokeColor": "#099268",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2415,9 +2265,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2455,9 +2302,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2495,9 +2339,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2535,9 +2376,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2575,9 +2413,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#c2255c",
|
"strokeColor": "#c2255c",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2615,9 +2450,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#f08c00",
|
"strokeColor": "#f08c00",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2656,9 +2488,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"originalText": "RECTANGLE TEXT CONTAINER",
|
"originalText": "RECTANGLE TEXT CONTAINER",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2700,9 +2529,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"originalText": "ELLIPSE TEXT CONTAINER",
|
"originalText": "ELLIPSE TEXT CONTAINER",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2746,9 +2572,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
TEXT CONTAINER",
|
TEXT CONTAINER",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2792,9 +2615,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"originalText": "STYLED DIAMOND TEXT CONTAINER",
|
"originalText": "STYLED DIAMOND TEXT CONTAINER",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#099268",
|
"strokeColor": "#099268",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2837,9 +2657,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"originalText": "TOP LEFT ALIGNED RECTANGLE TEXT CONTAINER",
|
"originalText": "TOP LEFT ALIGNED RECTANGLE TEXT CONTAINER",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#c2255c",
|
"strokeColor": "#c2255c",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2883,9 +2700,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"originalText": "STYLED ELLIPSE TEXT CONTAINER",
|
"originalText": "STYLED ELLIPSE TEXT CONTAINER",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#c2255c",
|
"strokeColor": "#c2255c",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
|||||||
@@ -78,12 +78,7 @@ import type {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export const shouldTestInside = (element: ExcalidrawElement) => {
|
export const shouldTestInside = (element: ExcalidrawElement) => {
|
||||||
if (
|
if (element.type === "arrow") {
|
||||||
element.type === "arrow" ||
|
|
||||||
// frame elements should ignore inside hit test even if background is not
|
|
||||||
// transparent, so we can select children easily
|
|
||||||
isFrameLikeElement(element)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export * from "./positionElementsOnGrid";
|
|||||||
export * from "./renderElement";
|
export * from "./renderElement";
|
||||||
export * from "./resizeElements";
|
export * from "./resizeElements";
|
||||||
export * from "./resizeTest";
|
export * from "./resizeTest";
|
||||||
export * from "./schema";
|
|
||||||
export * from "./Scene";
|
export * from "./Scene";
|
||||||
export * from "./selection";
|
export * from "./selection";
|
||||||
export * from "./shape";
|
export * from "./shape";
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import type { Mutable } from "@excalidraw/common/utility-types";
|
|||||||
import { ShapeCache } from "./shape";
|
import { ShapeCache } from "./shape";
|
||||||
|
|
||||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||||
import { ensureSchemaStateForElementType } from "./schema";
|
|
||||||
|
|
||||||
import { isElbowArrow } from "./typeChecks";
|
import { isElbowArrow } from "./typeChecks";
|
||||||
|
|
||||||
@@ -138,10 +137,6 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
element.version = updates.version ?? element.version + 1;
|
element.version = updates.version ?? element.version + 1;
|
||||||
element.versionNonce = updates.versionNonce ?? randomInteger();
|
element.versionNonce = updates.versionNonce ?? randomInteger();
|
||||||
element.updated = getUpdatedTimestamp();
|
element.updated = getUpdatedTimestamp();
|
||||||
element.schemaState = ensureSchemaStateForElementType(
|
|
||||||
element.schemaState,
|
|
||||||
element.type,
|
|
||||||
) as TElement["schemaState"];
|
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
@@ -171,21 +166,13 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedElement = {
|
return {
|
||||||
...element,
|
...element,
|
||||||
...updates,
|
...updates,
|
||||||
version: updates.version ?? element.version + 1,
|
version: updates.version ?? element.version + 1,
|
||||||
versionNonce: updates.versionNonce ?? randomInteger(),
|
versionNonce: updates.versionNonce ?? randomInteger(),
|
||||||
updated: getUpdatedTimestamp(),
|
updated: getUpdatedTimestamp(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
...updatedElement,
|
|
||||||
schemaState: ensureSchemaStateForElementType(
|
|
||||||
updatedElement.schemaState,
|
|
||||||
updatedElement.type,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import { ensureSchemaStateForElementType } from "./schema";
|
|
||||||
import { newElementWith } from "./mutateElement";
|
import { newElementWith } from "./mutateElement";
|
||||||
import { getBoundTextMaxWidth } from "./textElement";
|
import { getBoundTextMaxWidth } from "./textElement";
|
||||||
import { normalizeText, measureText } from "./textMeasurements";
|
import { normalizeText, measureText } from "./textMeasurements";
|
||||||
@@ -71,7 +70,6 @@ export type ElementConstructorOpts = MarkOptional<
|
|||||||
| "roughness"
|
| "roughness"
|
||||||
| "strokeWidth"
|
| "strokeWidth"
|
||||||
| "roundness"
|
| "roundness"
|
||||||
| "schemaState"
|
|
||||||
| "locked"
|
| "locked"
|
||||||
| "opacity"
|
| "opacity"
|
||||||
| "customData"
|
| "customData"
|
||||||
@@ -146,7 +144,6 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||||||
roundness,
|
roundness,
|
||||||
seed: rest.seed ?? randomInteger(),
|
seed: rest.seed ?? randomInteger(),
|
||||||
version: rest.version || 1,
|
version: rest.version || 1,
|
||||||
schemaState: ensureSchemaStateForElementType(rest.schemaState, type),
|
|
||||||
versionNonce: rest.versionNonce ?? 0,
|
versionNonce: rest.versionNonce ?? 0,
|
||||||
isDeleted: false as false,
|
isDeleted: false as false,
|
||||||
boundElements,
|
boundElements,
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
isRTL,
|
isRTL,
|
||||||
getVerticalOffset,
|
getVerticalOffset,
|
||||||
invariant,
|
invariant,
|
||||||
isTransparent,
|
|
||||||
applyDarkModeFilter,
|
applyDarkModeFilter,
|
||||||
isSafari,
|
isSafari,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
@@ -79,7 +78,6 @@ import type {
|
|||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawFrameElement,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
@@ -779,45 +777,6 @@ export const renderSelectionElement = (
|
|||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderFrameBackground = (
|
|
||||||
frame: ExcalidrawFrameElement,
|
|
||||||
context: CanvasRenderingContext2D,
|
|
||||||
appState: StaticCanvasAppState | InteractiveCanvasAppState,
|
|
||||||
opts?: {
|
|
||||||
roundCorners?: boolean;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if (isTransparent(frame.backgroundColor)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.save();
|
|
||||||
context.translate(frame.x + appState.scrollX, frame.y + appState.scrollY);
|
|
||||||
context.fillStyle =
|
|
||||||
appState.theme === THEME.DARK
|
|
||||||
? applyDarkModeFilter(frame.backgroundColor)
|
|
||||||
: frame.backgroundColor;
|
|
||||||
|
|
||||||
const shouldRoundCorners = opts?.roundCorners ?? true;
|
|
||||||
|
|
||||||
if (shouldRoundCorners && FRAME_STYLE.radius && context.roundRect) {
|
|
||||||
context.beginPath();
|
|
||||||
context.roundRect(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
frame.width,
|
|
||||||
frame.height,
|
|
||||||
FRAME_STYLE.radius / appState.zoom.value,
|
|
||||||
);
|
|
||||||
context.fill();
|
|
||||||
context.closePath();
|
|
||||||
} else {
|
|
||||||
context.fillRect(0, 0, frame.width, frame.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.restore();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderElement = (
|
export const renderElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
elementsMap: RenderableElementsMap,
|
elementsMap: RenderableElementsMap,
|
||||||
@@ -849,6 +808,7 @@ export const renderElement = (
|
|||||||
element.x + appState.scrollX,
|
element.x + appState.scrollX,
|
||||||
element.y + appState.scrollY,
|
element.y + appState.scrollY,
|
||||||
);
|
);
|
||||||
|
context.fillStyle = "rgba(0, 0, 200, 0.04)";
|
||||||
|
|
||||||
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
|
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
|
||||||
context.strokeStyle =
|
context.strokeStyle =
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* Shared schema primitives used by element types and higher-level migrations.
|
|
||||||
*/
|
|
||||||
export const SCHEMA_INITIAL_TRACK_VERSION = 1 as const;
|
|
||||||
|
|
||||||
/** Core namespace reserved for built-in Excalidraw migrations. */
|
|
||||||
export const SCHEMA_CORE_NAMESPACE = "core" as const;
|
|
||||||
export type SchemaNamespace = typeof SCHEMA_CORE_NAMESPACE | `host.${string}`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A schema track is an independent version line:
|
|
||||||
* - core tracks: "excalidraw.*"
|
|
||||||
* - host tracks: "host.<appId>.<track>"
|
|
||||||
*/
|
|
||||||
export type SchemaTrack = `excalidraw.${string}` | `host.${string}.${string}`;
|
|
||||||
export type ElementSchemaState = Readonly<{
|
|
||||||
tracks: Readonly<Record<string, number>>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/** Core frame track id used by the frame background migration. */
|
|
||||||
export const CORE_FRAME_SCHEMA_TRACK = "excalidraw.shape.frame" as const;
|
|
||||||
|
|
||||||
/** Latest core track versions supported by this build. */
|
|
||||||
export const CORE_SUPPORTED_TRACKS = {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]: 2,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const getRequiredCoreTracksForElementType = (type: string) => {
|
|
||||||
if (type === "frame") {
|
|
||||||
return {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]: CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {} as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isValidTrackVersion = (version: unknown): version is number =>
|
|
||||||
typeof version === "number" &&
|
|
||||||
Number.isInteger(version) &&
|
|
||||||
version >= SCHEMA_INITIAL_TRACK_VERSION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures an element schema state is normalized and satisfies type defaults.
|
|
||||||
* Required core tracks are only ever bumped forward (never downgraded).
|
|
||||||
*/
|
|
||||||
export const ensureSchemaStateForElementType = (
|
|
||||||
schemaState: ElementSchemaState | undefined,
|
|
||||||
type: string,
|
|
||||||
): ElementSchemaState => {
|
|
||||||
const requiredTracks = getRequiredCoreTracksForElementType(type);
|
|
||||||
const currentTracks = schemaState?.tracks || {};
|
|
||||||
const nextTracks: Record<string, number> = {};
|
|
||||||
let didChange = !schemaState;
|
|
||||||
|
|
||||||
for (const [track, version] of Object.entries(
|
|
||||||
currentTracks as Record<string, unknown>,
|
|
||||||
)) {
|
|
||||||
if (isValidTrackVersion(version)) {
|
|
||||||
nextTracks[track] = version;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
nextTracks[track] = SCHEMA_INITIAL_TRACK_VERSION;
|
|
||||||
didChange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [track, requiredVersion] of Object.entries(requiredTracks)) {
|
|
||||||
const currentVersion = nextTracks[track];
|
|
||||||
if (
|
|
||||||
!isValidTrackVersion(currentVersion) ||
|
|
||||||
currentVersion < requiredVersion
|
|
||||||
) {
|
|
||||||
nextTracks[track] = requiredVersion;
|
|
||||||
didChange = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!didChange) {
|
|
||||||
return schemaState!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { tracks: nextTracks };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default schema state for newly created elements.
|
|
||||||
* New frames are created at the latest supported frame track version.
|
|
||||||
*/
|
|
||||||
export const getDefaultSchemaStateForElementType = (
|
|
||||||
type: string,
|
|
||||||
): ElementSchemaState => ensureSchemaStateForElementType(undefined, type);
|
|
||||||
@@ -15,8 +15,6 @@ import type {
|
|||||||
ValueOf,
|
ValueOf,
|
||||||
} from "@excalidraw/common/utility-types";
|
} from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import type { ElementSchemaState } from "./schema";
|
|
||||||
|
|
||||||
export type ChartType = "bar" | "line";
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
|
||||||
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
export type FontFamilyKeys = keyof typeof FONT_FAMILY;
|
||||||
@@ -60,8 +58,6 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
/** Integer that is sequentially incremented on each change. Used to reconcile
|
/** Integer that is sequentially incremented on each change. Used to reconcile
|
||||||
elements during collaboration or when saving to server. */
|
elements during collaboration or when saving to server. */
|
||||||
version: number;
|
version: number;
|
||||||
/** Per-track schema state used by migrations during restore. */
|
|
||||||
schemaState: ElementSchemaState;
|
|
||||||
/** Random integer that is regenerated on each change.
|
/** Random integer that is regenerated on each change.
|
||||||
Used for deterministic reconciliation of updates during collaboration,
|
Used for deterministic reconciliation of updates during collaboration,
|
||||||
in case the versions (see above) are identical. */
|
in case the versions (see above) are identical. */
|
||||||
|
|||||||
@@ -38,53 +38,6 @@ describe("check rotated elements can be hit:", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("frame hit testing", () => {
|
|
||||||
it.each(["transparent", "#ffffff"])(
|
|
||||||
"does not hit frame inside regardless of background color (%s)",
|
|
||||||
(backgroundColor) => {
|
|
||||||
const element = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
backgroundColor,
|
|
||||||
});
|
|
||||||
const elementsMap = arrayToMap([element]);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
hitElementItself({
|
|
||||||
point: pointFrom<GlobalPoint>(50, 50),
|
|
||||||
element,
|
|
||||||
threshold: 10,
|
|
||||||
elementsMap,
|
|
||||||
}),
|
|
||||||
).toBe(false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
it("hits frame outline", () => {
|
|
||||||
const element = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
backgroundColor: "#ffffff",
|
|
||||||
});
|
|
||||||
const elementsMap = arrayToMap([element]);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
hitElementItself({
|
|
||||||
point: pointFrom<GlobalPoint>(0, 50),
|
|
||||||
element,
|
|
||||||
threshold: 1,
|
|
||||||
elementsMap,
|
|
||||||
}),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hitElementItself cache", () => {
|
describe("hitElementItself cache", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// reset cache
|
// reset cache
|
||||||
|
|||||||
@@ -271,11 +271,7 @@ export const actionLoadScene = register({
|
|||||||
elements: loadedElements,
|
elements: loadedElements,
|
||||||
appState: loadedAppState,
|
appState: loadedAppState,
|
||||||
files,
|
files,
|
||||||
} = await loadFromJSON(
|
} = await loadFromJSON(appState, elements);
|
||||||
appState,
|
|
||||||
elements,
|
|
||||||
app.getSchemaMigrationRegistry(),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
elements: loadedElements,
|
elements: loadedElements,
|
||||||
appState: loadedAppState,
|
appState: loadedAppState,
|
||||||
|
|||||||
@@ -6,23 +6,11 @@ import {
|
|||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
STROKE_WIDTH,
|
STROKE_WIDTH,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import {
|
|
||||||
CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
CORE_SUPPORTED_TRACKS,
|
|
||||||
} from "@excalidraw/element";
|
|
||||||
|
|
||||||
import { Excalidraw } from "../index";
|
import { Excalidraw } from "../index";
|
||||||
import { API } from "../tests/helpers/api";
|
import { API } from "../tests/helpers/api";
|
||||||
import { UI } from "../tests/helpers/ui";
|
import { UI } from "../tests/helpers/ui";
|
||||||
import { act, render } from "../tests/test-utils";
|
import { render } from "../tests/test-utils";
|
||||||
|
|
||||||
import {
|
|
||||||
actionChangeBackgroundColor,
|
|
||||||
actionChangeRoundness,
|
|
||||||
actionChangeStrokeWidth,
|
|
||||||
} from "./actionProperties";
|
|
||||||
|
|
||||||
const { h } = window;
|
|
||||||
|
|
||||||
describe("element locking", () => {
|
describe("element locking", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -121,21 +109,6 @@ describe("element locking", () => {
|
|||||||
expect(crossHatchButton).toBe(null);
|
expect(crossHatchButton).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show background color picker for selected frame", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
});
|
|
||||||
API.setElements([frame]);
|
|
||||||
API.setSelectedElements([frame]);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByTestId(
|
|
||||||
document.body,
|
|
||||||
`color-top-pick-${DEFAULT_ELEMENT_BACKGROUND_PICKS[0]}`,
|
|
||||||
),
|
|
||||||
).not.toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should highlight common stroke width of selected elements", () => {
|
it("should highlight common stroke width of selected elements", () => {
|
||||||
const rect1 = API.createElement({
|
const rect1 = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
@@ -196,77 +169,5 @@ describe("element locking", () => {
|
|||||||
"active",
|
"active",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not update text background when changing background in mixed frame selection", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
schemaState: { tracks: {} },
|
|
||||||
});
|
|
||||||
const text = API.createElement({
|
|
||||||
type: "text",
|
|
||||||
backgroundColor: COLOR_PALETTE.transparent,
|
|
||||||
});
|
|
||||||
API.setElements([text, frame]);
|
|
||||||
API.setSelectedElements([text, frame]);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.app.actionManager.executeAction(actionChangeBackgroundColor, "ui", {
|
|
||||||
viewBackgroundColor: h.state.viewBackgroundColor,
|
|
||||||
currentItemBackgroundColor: "#ffc9c9",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(API.getElement(frame).backgroundColor).toBe("#ffc9c9");
|
|
||||||
expect(API.getElement(text).backgroundColor).toBe(
|
|
||||||
COLOR_PALETTE.transparent,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
API.getElement(frame).schemaState.tracks[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
).toBe(CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not update frame stroke width when changing stroke width in mixed selection", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
});
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
strokeWidth: STROKE_WIDTH.thin,
|
|
||||||
});
|
|
||||||
API.setElements([rect, frame]);
|
|
||||||
API.setSelectedElements([rect, frame]);
|
|
||||||
|
|
||||||
const originalFrameStrokeWidth = API.getElement(frame).strokeWidth;
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.app.actionManager.executeAction(
|
|
||||||
actionChangeStrokeWidth,
|
|
||||||
"ui",
|
|
||||||
STROKE_WIDTH.extraBold,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(API.getElement(rect).strokeWidth).toBe(STROKE_WIDTH.extraBold);
|
|
||||||
expect(API.getElement(frame).strokeWidth).toBe(originalFrameStrokeWidth);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not update frame roundness when changing roundness in mixed selection", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
});
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
roundness: null,
|
|
||||||
});
|
|
||||||
API.setElements([rect, frame]);
|
|
||||||
API.setSelectedElements([rect, frame]);
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
h.app.actionManager.executeAction(actionChangeRoundness, "ui", "round");
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(API.getElement(rect).roundness).not.toBe(null);
|
|
||||||
expect(API.getElement(frame).roundness).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isFrameElement,
|
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isLineElement,
|
isLineElement,
|
||||||
@@ -53,13 +52,7 @@ import {
|
|||||||
isUsingAdaptiveRadius,
|
isUsingAdaptiveRadius,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import {
|
import { hasStrokeColor } from "@excalidraw/element";
|
||||||
canChangeRoundness,
|
|
||||||
hasBackground,
|
|
||||||
hasStrokeColor,
|
|
||||||
hasStrokeStyle,
|
|
||||||
hasStrokeWidth,
|
|
||||||
} from "@excalidraw/element";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
updateElbowArrowPoints,
|
updateElbowArrowPoints,
|
||||||
@@ -416,18 +409,11 @@ export const actionChangeBackgroundColor = register<
|
|||||||
return el;
|
return el;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
nextElements = changeProperty(elements, appState, (el) => {
|
nextElements = changeProperty(elements, appState, (el) =>
|
||||||
if (isFrameElement(el)) {
|
newElementWith(el, {
|
||||||
return newElementWith(el, {
|
backgroundColor: value.currentItemBackgroundColor,
|
||||||
backgroundColor: value.currentItemBackgroundColor,
|
}),
|
||||||
});
|
);
|
||||||
}
|
|
||||||
return hasBackground(el.type)
|
|
||||||
? newElementWith(el, {
|
|
||||||
backgroundColor: value.currentItemBackgroundColor,
|
|
||||||
})
|
|
||||||
: el;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -458,12 +444,7 @@ export const actionChangeBackgroundColor = register<
|
|||||||
(element) => element.backgroundColor,
|
(element) => element.backgroundColor,
|
||||||
true,
|
true,
|
||||||
(hasSelection) =>
|
(hasSelection) =>
|
||||||
!hasSelection
|
!hasSelection ? appState.currentItemBackgroundColor : null,
|
||||||
? appState.activeTool.type === "frame"
|
|
||||||
? // background default shouldn't apply to new frames
|
|
||||||
"transparent"
|
|
||||||
: appState.currentItemBackgroundColor
|
|
||||||
: null,
|
|
||||||
)}
|
)}
|
||||||
onChange={(color) =>
|
onChange={(color) =>
|
||||||
updateData({ currentItemBackgroundColor: color })
|
updateData({ currentItemBackgroundColor: color })
|
||||||
@@ -490,13 +471,11 @@ export const actionChangeFillStyle = register<ExcalidrawElement["fillStyle"]>({
|
|||||||
})`,
|
})`,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
return hasBackground(el.type)
|
newElementWith(el, {
|
||||||
? newElementWith(el, {
|
fillStyle: value,
|
||||||
fillStyle: value,
|
}),
|
||||||
})
|
),
|
||||||
: el;
|
|
||||||
}),
|
|
||||||
appState: { ...appState, currentItemFillStyle: value },
|
appState: { ...appState, currentItemFillStyle: value },
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
@@ -569,13 +548,11 @@ export const actionChangeStrokeWidth = register<
|
|||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
return hasStrokeWidth(el.type)
|
newElementWith(el, {
|
||||||
? newElementWith(el, {
|
strokeWidth: value,
|
||||||
strokeWidth: value,
|
}),
|
||||||
})
|
),
|
||||||
: el;
|
|
||||||
}),
|
|
||||||
appState: { ...appState, currentItemStrokeWidth: value },
|
appState: { ...appState, currentItemStrokeWidth: value },
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
@@ -627,14 +604,12 @@ export const actionChangeSloppiness = register<ExcalidrawElement["roughness"]>({
|
|||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
return hasStrokeStyle(el.type)
|
newElementWith(el, {
|
||||||
? newElementWith(el, {
|
seed: randomInteger(),
|
||||||
seed: randomInteger(),
|
roughness: value,
|
||||||
roughness: value,
|
}),
|
||||||
})
|
),
|
||||||
: el;
|
|
||||||
}),
|
|
||||||
appState: { ...appState, currentItemRoughness: value },
|
appState: { ...appState, currentItemRoughness: value },
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
@@ -685,13 +660,11 @@ export const actionChangeStrokeStyle = register<
|
|||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
return hasStrokeStyle(el.type)
|
newElementWith(el, {
|
||||||
? newElementWith(el, {
|
strokeStyle: value,
|
||||||
strokeStyle: value,
|
}),
|
||||||
})
|
),
|
||||||
: el;
|
|
||||||
}),
|
|
||||||
appState: { ...appState, currentItemStrokeStyle: value },
|
appState: { ...appState, currentItemStrokeStyle: value },
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
@@ -1503,7 +1476,7 @@ export const actionChangeRoundness = register<"sharp" | "round">({
|
|||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
elements: changeProperty(elements, appState, (el) => {
|
||||||
if (isElbowArrow(el) || !canChangeRoundness(el.type)) {
|
if (isElbowArrow(el)) {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,13 +54,7 @@ describe("parseClipboard()", () => {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(clipboardData.elements).toEqual([
|
expect(clipboardData.elements).toEqual([rect]);
|
||||||
expect.objectContaining({
|
|
||||||
id: rect.id,
|
|
||||||
type: rect.type,
|
|
||||||
schemaState: rect.schemaState,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse valid excalidraw JSON if inside text/html", async () => {
|
it("should parse valid excalidraw JSON if inside text/html", async () => {
|
||||||
@@ -79,13 +73,7 @@ describe("parseClipboard()", () => {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(clipboardData.elements).toEqual([
|
expect(clipboardData.elements).toEqual([rect]);
|
||||||
expect.objectContaining({
|
|
||||||
id: rect.id,
|
|
||||||
type: rect.type,
|
|
||||||
schemaState: rect.schemaState,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
json = serializeAsClipboardJSON({ elements: [rect], files: null });
|
json = serializeAsClipboardJSON({ elements: [rect], files: null });
|
||||||
clipboardData = await parseClipboard(
|
clipboardData = await parseClipboard(
|
||||||
@@ -97,66 +85,10 @@ describe("parseClipboard()", () => {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(clipboardData.elements).toEqual([
|
expect(clipboardData.elements).toEqual([rect]);
|
||||||
expect.objectContaining({
|
|
||||||
id: rect.id,
|
|
||||||
type: rect.type,
|
|
||||||
schemaState: rect.schemaState,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should preserve per-element schema on clipboard payload", async () => {
|
|
||||||
const rect = API.createElement({ type: "rectangle" });
|
|
||||||
const clipboardPayload = JSON.parse(
|
|
||||||
serializeAsClipboardJSON({ elements: [rect], files: null }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const clipboardData = await parseClipboard(
|
|
||||||
await parseDataTransferEvent(
|
|
||||||
createPasteEvent({
|
|
||||||
types: {
|
|
||||||
"text/plain": JSON.stringify(clipboardPayload),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(clipboardData.elements?.[0]).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: rect.id,
|
|
||||||
schemaState: rect.schemaState,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not upcast legacy elements to latest schema on clipboard serialize", async () => {
|
|
||||||
const rect = API.createElement({ type: "rectangle" });
|
|
||||||
const legacyRect = { ...(rect as any) };
|
|
||||||
delete legacyRect.schemaState;
|
|
||||||
|
|
||||||
const clipboardPayload = JSON.parse(
|
|
||||||
serializeAsClipboardJSON({
|
|
||||||
elements: [legacyRect as typeof rect],
|
|
||||||
files: null,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(clipboardPayload.elements[0]).not.toHaveProperty("schemaState");
|
|
||||||
|
|
||||||
const clipboardData = await parseClipboard(
|
|
||||||
await parseDataTransferEvent(
|
|
||||||
createPasteEvent({
|
|
||||||
types: {
|
|
||||||
"text/plain": JSON.stringify(clipboardPayload),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(clipboardData.elements?.[0]).not.toHaveProperty("schemaState");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse <image> `src` urls out of text/html", async () => {
|
it("should parse <image> `src` urls out of text/html", async () => {
|
||||||
let clipboardData;
|
let clipboardData;
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -79,10 +79,7 @@ export const probablySupportsClipboardBlob =
|
|||||||
|
|
||||||
const clipboardContainsElements = (
|
const clipboardContainsElements = (
|
||||||
contents: any,
|
contents: any,
|
||||||
): contents is {
|
): contents is { elements: ExcalidrawElement[]; files?: BinaryFiles } => {
|
||||||
elements: ExcalidrawElement[];
|
|
||||||
files?: BinaryFiles;
|
|
||||||
} => {
|
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
EXPORT_DATA_TYPES.excalidraw,
|
EXPORT_DATA_TYPES.excalidraw,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
isArrowElement,
|
isArrowElement,
|
||||||
hasStrokeColor,
|
hasStrokeColor,
|
||||||
toolIsArrow,
|
toolIsArrow,
|
||||||
isFrameElement,
|
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -130,11 +129,8 @@ export const canChangeBackgroundColor = (
|
|||||||
targetElements: ExcalidrawElement[],
|
targetElements: ExcalidrawElement[],
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
// frame tool shouldn't allow to set background until frame is created
|
|
||||||
hasBackground(appState.activeTool.type) ||
|
hasBackground(appState.activeTool.type) ||
|
||||||
targetElements.some(
|
targetElements.some((element) => hasBackground(element.type))
|
||||||
(element) => hasBackground(element.type) || isFrameElement(element),
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -256,7 +256,6 @@ import {
|
|||||||
handleFocusPointPointerUp,
|
handleFocusPointPointerUp,
|
||||||
maybeHandleArrowPointlikeDrag,
|
maybeHandleArrowPointlikeDrag,
|
||||||
getUncroppedWidthAndHeight,
|
getUncroppedWidthAndHeight,
|
||||||
isFrameElement,
|
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
@@ -354,7 +353,6 @@ import {
|
|||||||
import { exportCanvas, loadFromBlob } from "../data";
|
import { exportCanvas, loadFromBlob } from "../data";
|
||||||
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
|
||||||
import { restoreAppState, restoreElements } from "../data/restore";
|
import { restoreAppState, restoreElements } from "../data/restore";
|
||||||
import { createSchemaMigrationRegistry } from "../data/schema";
|
|
||||||
import { getCenter, getDistance } from "../gesture";
|
import { getCenter, getDistance } from "../gesture";
|
||||||
import { History } from "../history";
|
import { History } from "../history";
|
||||||
import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
|
import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
|
||||||
@@ -461,7 +459,6 @@ import type {
|
|||||||
|
|
||||||
import type { ClipboardData, PastedMixedContent } from "../clipboard";
|
import type { ClipboardData, PastedMixedContent } from "../clipboard";
|
||||||
import type { ExportedElements } from "../data";
|
import type { ExportedElements } from "../data";
|
||||||
import type { SchemaMigrationRegistry } from "../data/schema";
|
|
||||||
import type { ContextMenuItems } from "./ContextMenu";
|
import type { ContextMenuItems } from "./ContextMenu";
|
||||||
import type { FileSystemHandle } from "../data/filesystem";
|
import type { FileSystemHandle } from "../data/filesystem";
|
||||||
|
|
||||||
@@ -608,7 +605,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
public fonts: Fonts;
|
public fonts: Fonts;
|
||||||
public renderer: Renderer;
|
public renderer: Renderer;
|
||||||
public visibleElements: readonly NonDeletedExcalidrawElement[];
|
public visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
private schemaMigrationRegistry: SchemaMigrationRegistry;
|
|
||||||
private resizeObserver: ResizeObserver | undefined;
|
private resizeObserver: ResizeObserver | undefined;
|
||||||
public library: AppClassProperties["library"];
|
public library: AppClassProperties["library"];
|
||||||
public libraryItemsFromStorage: LibraryItems | undefined;
|
public libraryItemsFromStorage: LibraryItems | undefined;
|
||||||
@@ -725,9 +721,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.stylesPanelMode = deriveStylesPanelMode(this.editorInterface);
|
this.stylesPanelMode = deriveStylesPanelMode(this.editorInterface);
|
||||||
|
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
this.schemaMigrationRegistry = createSchemaMigrationRegistry(
|
|
||||||
props.schemaPlugins,
|
|
||||||
);
|
|
||||||
this.library = new Library(this);
|
this.library = new Library(this);
|
||||||
this.actionManager = new ActionManager(
|
this.actionManager = new ActionManager(
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
@@ -774,7 +767,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
setCursor: this.setCursor,
|
setCursor: this.setCursor,
|
||||||
resetCursor: this.resetCursor,
|
resetCursor: this.resetCursor,
|
||||||
getEditorInterface: () => this.editorInterface,
|
getEditorInterface: () => this.editorInterface,
|
||||||
getSchemaMigrationRegistry: this.getSchemaMigrationRegistry,
|
|
||||||
updateFrameRendering: this.updateFrameRendering,
|
updateFrameRendering: this.updateFrameRendering,
|
||||||
toggleSidebar: this.toggleSidebar,
|
toggleSidebar: this.toggleSidebar,
|
||||||
onChange: (cb) => this.onChangeEmitter.on(cb),
|
onChange: (cb) => this.onChangeEmitter.on(cb),
|
||||||
@@ -2327,10 +2319,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return this.scene.getNonDeletedElements();
|
return this.scene.getNonDeletedElements();
|
||||||
};
|
};
|
||||||
|
|
||||||
public getSchemaMigrationRegistry = () => {
|
|
||||||
return this.schemaMigrationRegistry;
|
|
||||||
};
|
|
||||||
|
|
||||||
public onInsertElements = (elements: readonly ExcalidrawElement[]) => {
|
public onInsertElements = (elements: readonly ExcalidrawElement[]) => {
|
||||||
this.addElementsFromPasteOrLibrary({
|
this.addElementsFromPasteOrLibrary({
|
||||||
elements,
|
elements,
|
||||||
@@ -2819,7 +2807,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const restoredElements = restoreElements(initialData?.elements, null, {
|
const restoredElements = restoreElements(initialData?.elements, null, {
|
||||||
repairBindings: true,
|
repairBindings: true,
|
||||||
deleteInvisibleElements: true,
|
deleteInvisibleElements: true,
|
||||||
schemaMigrationRegistry: this.schemaMigrationRegistry,
|
|
||||||
});
|
});
|
||||||
let restoredAppState = restoreAppState(initialData?.appState, null);
|
let restoredAppState = restoreAppState(initialData?.appState, null);
|
||||||
const activeTool = restoredAppState.activeTool;
|
const activeTool = restoredAppState.activeTool;
|
||||||
@@ -3231,12 +3218,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
if (prevProps.schemaPlugins !== this.props.schemaPlugins) {
|
|
||||||
this.schemaMigrationRegistry = createSchemaMigrationRegistry(
|
|
||||||
this.props.schemaPlugins,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateEmbeddables();
|
this.updateEmbeddables();
|
||||||
const elements = this.scene.getElementsIncludingDeleted();
|
const elements = this.scene.getElementsIncludingDeleted();
|
||||||
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
||||||
@@ -3740,7 +3721,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}) => {
|
}) => {
|
||||||
const elements = restoreElements(opts.elements, null, {
|
const elements = restoreElements(opts.elements, null, {
|
||||||
deleteInvisibleElements: true,
|
deleteInvisibleElements: true,
|
||||||
schemaMigrationRegistry: this.schemaMigrationRegistry,
|
|
||||||
});
|
});
|
||||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||||
|
|
||||||
@@ -5057,8 +5037,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (
|
if (
|
||||||
event.key === KEYS.G &&
|
event.key === KEYS.G &&
|
||||||
(hasBackground(this.state.activeTool.type) ||
|
(hasBackground(this.state.activeTool.type) ||
|
||||||
this.state.activeTool.type === "frame" ||
|
|
||||||
selectedElements.some((element) => isFrameElement(element)) ||
|
|
||||||
selectedElements.some((element) => hasBackground(element.type)))
|
selectedElements.some((element) => hasBackground(element.type)))
|
||||||
) {
|
) {
|
||||||
this.setState({ openPopup: "elementBackground" });
|
this.setState({ openPopup: "elementBackground" });
|
||||||
@@ -11447,7 +11425,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
fileHandle,
|
fileHandle,
|
||||||
this.schemaMigrationRegistry,
|
|
||||||
);
|
);
|
||||||
this.syncActionResult({
|
this.syncActionResult({
|
||||||
...scene,
|
...scene,
|
||||||
@@ -11494,11 +11471,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
);
|
);
|
||||||
// legacy library dataTransfer format
|
// legacy library dataTransfer format
|
||||||
} else if (excalidrawLibrary_data) {
|
} else if (excalidrawLibrary_data) {
|
||||||
libraryItems = parseLibraryJSON(
|
libraryItems = parseLibraryJSON(excalidrawLibrary_data);
|
||||||
excalidrawLibrary_data,
|
|
||||||
"unpublished",
|
|
||||||
this.schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (libraryItems?.length) {
|
if (libraryItems?.length) {
|
||||||
libraryItems = libraryItems.map((item) => ({
|
libraryItems = libraryItems.map((item) => ({
|
||||||
@@ -11568,7 +11541,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
elements,
|
elements,
|
||||||
fileHandle,
|
fileHandle,
|
||||||
this.schemaMigrationRegistry,
|
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const imageSceneDataError = error instanceof ImageSceneDataError;
|
const imageSceneDataError = error instanceof ImageSceneDataError;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
import type { AppState, DataURL, LibraryItem } from "../types";
|
import type { AppState, DataURL, LibraryItem } from "../types";
|
||||||
|
|
||||||
import type { FileSystemHandle } from "browser-fs-access";
|
import type { FileSystemHandle } from "browser-fs-access";
|
||||||
import type { SchemaMigrationRegistry } from "./schema";
|
|
||||||
import type { ImportedLibraryData } from "./types";
|
import type { ImportedLibraryData } from "./types";
|
||||||
|
|
||||||
const parseFileContents = async (blob: Blob | File): Promise<string> => {
|
const parseFileContents = async (blob: Blob | File): Promise<string> => {
|
||||||
@@ -142,7 +141,6 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||||||
localElements: readonly ExcalidrawElement[] | null,
|
localElements: readonly ExcalidrawElement[] | null,
|
||||||
/** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */
|
/** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */
|
||||||
fileHandle?: FileSystemHandle | null,
|
fileHandle?: FileSystemHandle | null,
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry,
|
|
||||||
) => {
|
) => {
|
||||||
const contents = await parseFileContents(blob);
|
const contents = await parseFileContents(blob);
|
||||||
let data;
|
let data;
|
||||||
@@ -165,7 +163,6 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||||||
elements: restoreElements(data.elements, localElements, {
|
elements: restoreElements(data.elements, localElements, {
|
||||||
repairBindings: true,
|
repairBindings: true,
|
||||||
deleteInvisibleElements: true,
|
deleteInvisibleElements: true,
|
||||||
schemaMigrationRegistry,
|
|
||||||
}),
|
}),
|
||||||
appState: restoreAppState(
|
appState: restoreAppState(
|
||||||
{
|
{
|
||||||
@@ -203,14 +200,12 @@ export const loadFromBlob = async (
|
|||||||
localElements: readonly ExcalidrawElement[] | null,
|
localElements: readonly ExcalidrawElement[] | null,
|
||||||
/** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */
|
/** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */
|
||||||
fileHandle?: FileSystemHandle | null,
|
fileHandle?: FileSystemHandle | null,
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry,
|
|
||||||
) => {
|
) => {
|
||||||
const ret = await loadSceneOrLibraryFromBlob(
|
const ret = await loadSceneOrLibraryFromBlob(
|
||||||
blob,
|
blob,
|
||||||
localAppState,
|
localAppState,
|
||||||
localElements,
|
localElements,
|
||||||
fileHandle,
|
fileHandle,
|
||||||
schemaMigrationRegistry,
|
|
||||||
);
|
);
|
||||||
if (ret.type !== MIME_TYPES.excalidraw) {
|
if (ret.type !== MIME_TYPES.excalidraw) {
|
||||||
throw new Error("Error: invalid file");
|
throw new Error("Error: invalid file");
|
||||||
@@ -221,28 +216,20 @@ export const loadFromBlob = async (
|
|||||||
export const parseLibraryJSON = (
|
export const parseLibraryJSON = (
|
||||||
json: string,
|
json: string,
|
||||||
defaultStatus: LibraryItem["status"] = "unpublished",
|
defaultStatus: LibraryItem["status"] = "unpublished",
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry,
|
|
||||||
) => {
|
) => {
|
||||||
const data: ImportedLibraryData | undefined = JSON.parse(json);
|
const data: ImportedLibraryData | undefined = JSON.parse(json);
|
||||||
if (!isValidLibrary(data)) {
|
if (!isValidLibrary(data)) {
|
||||||
throw new Error("Invalid library");
|
throw new Error("Invalid library");
|
||||||
}
|
}
|
||||||
const libraryItems = data.libraryItems || data.library;
|
const libraryItems = data.libraryItems || data.library;
|
||||||
return restoreLibraryItems(libraryItems, defaultStatus, {
|
return restoreLibraryItems(libraryItems, defaultStatus);
|
||||||
schemaMigrationRegistry,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadLibraryFromBlob = async (
|
export const loadLibraryFromBlob = async (
|
||||||
blob: Blob,
|
blob: Blob,
|
||||||
defaultStatus: LibraryItem["status"] = "unpublished",
|
defaultStatus: LibraryItem["status"] = "unpublished",
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry,
|
|
||||||
) => {
|
) => {
|
||||||
return parseLibraryJSON(
|
return parseLibraryJSON(await parseFileContents(blob), defaultStatus);
|
||||||
await parseFileContents(blob),
|
|
||||||
defaultStatus,
|
|
||||||
schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canvasToBlob = async (
|
export const canvasToBlob = async (
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { isImageFileHandle, loadFromBlob } from "./blob";
|
|||||||
import { fileOpen, fileSave } from "./filesystem";
|
import { fileOpen, fileSave } from "./filesystem";
|
||||||
|
|
||||||
import type { AppState, BinaryFiles, LibraryItems } from "../types";
|
import type { AppState, BinaryFiles, LibraryItems } from "../types";
|
||||||
import type { SchemaMigrationRegistry } from "./schema";
|
|
||||||
import type {
|
import type {
|
||||||
ExportedDataState,
|
ExportedDataState,
|
||||||
ImportedDataState,
|
ImportedDataState,
|
||||||
@@ -94,7 +93,6 @@ export const saveAsJSON = async (
|
|||||||
export const loadFromJSON = async (
|
export const loadFromJSON = async (
|
||||||
localAppState: AppState,
|
localAppState: AppState,
|
||||||
localElements: readonly ExcalidrawElement[] | null,
|
localElements: readonly ExcalidrawElement[] | null,
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry,
|
|
||||||
) => {
|
) => {
|
||||||
const file = await fileOpen({
|
const file = await fileOpen({
|
||||||
description: "Excalidraw files",
|
description: "Excalidraw files",
|
||||||
@@ -102,13 +100,7 @@ export const loadFromJSON = async (
|
|||||||
// gets resolved. Else, iOS users cannot open `.excalidraw` files.
|
// gets resolved. Else, iOS users cannot open `.excalidraw` files.
|
||||||
// extensions: ["json", "excalidraw", "png", "svg"],
|
// extensions: ["json", "excalidraw", "png", "svg"],
|
||||||
});
|
});
|
||||||
return loadFromBlob(
|
return loadFromBlob(file, localAppState, localElements, file.handle);
|
||||||
file,
|
|
||||||
localAppState,
|
|
||||||
localElements,
|
|
||||||
file.handle,
|
|
||||||
schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidExcalidrawData = (data?: {
|
export const isValidExcalidrawData = (data?: {
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import { loadLibraryFromBlob } from "./blob";
|
|||||||
import { restoreLibraryItems } from "./restore";
|
import { restoreLibraryItems } from "./restore";
|
||||||
|
|
||||||
import type App from "../components/App";
|
import type App from "../components/App";
|
||||||
import type { SchemaMigrationRegistry } from "./schema";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
LibraryItems,
|
LibraryItems,
|
||||||
@@ -66,9 +65,9 @@ type LibraryUpdate = {
|
|||||||
updatedItems: Map<LibraryItem["id"], LibraryItem>;
|
updatedItems: Map<LibraryItem["id"], LibraryItem>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LibraryPersistedData = {
|
// an object so that we can later add more properties to it without breaking,
|
||||||
libraryItems: LibraryItems;
|
// such as schema version
|
||||||
};
|
export type LibraryPersistedData = { libraryItems: LibraryItems };
|
||||||
|
|
||||||
const onLibraryUpdateEmitter = new Emitter<
|
const onLibraryUpdateEmitter = new Emitter<
|
||||||
[update: LibraryUpdate, libraryItems: LibraryItems]
|
[update: LibraryUpdate, libraryItems: LibraryItems]
|
||||||
@@ -100,9 +99,7 @@ export interface LibraryMigrationAdapter {
|
|||||||
* loads data from legacy data source. Returns `null` if no data is
|
* loads data from legacy data source. Returns `null` if no data is
|
||||||
* to be migrated.
|
* to be migrated.
|
||||||
*/
|
*/
|
||||||
load(): MaybePromise<{
|
load(): MaybePromise<{ libraryItems: LibraryItems_anyVersion } | null>;
|
||||||
libraryItems: LibraryItems_anyVersion;
|
|
||||||
} | null>;
|
|
||||||
|
|
||||||
/** clears entire storage afterwards */
|
/** clears entire storage afterwards */
|
||||||
clear(): MaybePromise<void>;
|
clear(): MaybePromise<void>;
|
||||||
@@ -317,15 +314,9 @@ class Library {
|
|||||||
let nextItems;
|
let nextItems;
|
||||||
|
|
||||||
if (source instanceof Blob) {
|
if (source instanceof Blob) {
|
||||||
nextItems = await loadLibraryFromBlob(
|
nextItems = await loadLibraryFromBlob(source, defaultStatus);
|
||||||
source,
|
|
||||||
defaultStatus,
|
|
||||||
this.app.getSchemaMigrationRegistry(),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
nextItems = restoreLibraryItems(source, defaultStatus, {
|
nextItems = restoreLibraryItems(source, defaultStatus);
|
||||||
schemaMigrationRegistry: this.app.getSchemaMigrationRegistry(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!prompt ||
|
!prompt ||
|
||||||
@@ -558,17 +549,12 @@ class AdapterTransaction {
|
|||||||
adapter: LibraryPersistenceAdapter,
|
adapter: LibraryPersistenceAdapter,
|
||||||
source: LibraryAdatapterSource,
|
source: LibraryAdatapterSource,
|
||||||
_queue = true,
|
_queue = true,
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry,
|
|
||||||
): Promise<LibraryItems> {
|
): Promise<LibraryItems> {
|
||||||
const task = () =>
|
const task = () =>
|
||||||
new Promise<LibraryItems>(async (resolve, reject) => {
|
new Promise<LibraryItems>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const data = await adapter.load({ source });
|
const data = await adapter.load({ source });
|
||||||
resolve(
|
resolve(restoreLibraryItems(data?.libraryItems || [], "published"));
|
||||||
restoreLibraryItems(data?.libraryItems || [], "published", {
|
|
||||||
schemaMigrationRegistry,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
@@ -583,36 +569,22 @@ class AdapterTransaction {
|
|||||||
|
|
||||||
static run = async <T>(
|
static run = async <T>(
|
||||||
adapter: LibraryPersistenceAdapter,
|
adapter: LibraryPersistenceAdapter,
|
||||||
schemaMigrationRegistry: SchemaMigrationRegistry | undefined,
|
|
||||||
fn: (transaction: AdapterTransaction) => Promise<T>,
|
fn: (transaction: AdapterTransaction) => Promise<T>,
|
||||||
) => {
|
) => {
|
||||||
const transaction = new AdapterTransaction(
|
const transaction = new AdapterTransaction(adapter);
|
||||||
adapter,
|
|
||||||
schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
return AdapterTransaction.queue.push(() => fn(transaction));
|
return AdapterTransaction.queue.push(() => fn(transaction));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
private adapter: LibraryPersistenceAdapter;
|
private adapter: LibraryPersistenceAdapter;
|
||||||
private schemaMigrationRegistry: SchemaMigrationRegistry | undefined;
|
|
||||||
|
|
||||||
constructor(
|
constructor(adapter: LibraryPersistenceAdapter) {
|
||||||
adapter: LibraryPersistenceAdapter,
|
|
||||||
schemaMigrationRegistry: SchemaMigrationRegistry | undefined,
|
|
||||||
) {
|
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.schemaMigrationRegistry = schemaMigrationRegistry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLibraryItems(source: LibraryAdatapterSource) {
|
getLibraryItems(source: LibraryAdatapterSource) {
|
||||||
return AdapterTransaction.getLibraryItems(
|
return AdapterTransaction.getLibraryItems(this.adapter, source, false);
|
||||||
this.adapter,
|
|
||||||
source,
|
|
||||||
false,
|
|
||||||
this.schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,73 +607,68 @@ export const getLibraryItemsHash = (items: LibraryItems) => {
|
|||||||
const persistLibraryUpdate = async (
|
const persistLibraryUpdate = async (
|
||||||
adapter: LibraryPersistenceAdapter,
|
adapter: LibraryPersistenceAdapter,
|
||||||
update: LibraryUpdate,
|
update: LibraryUpdate,
|
||||||
schemaMigrationRegistry: SchemaMigrationRegistry | undefined,
|
|
||||||
): Promise<LibraryItems> => {
|
): Promise<LibraryItems> => {
|
||||||
try {
|
try {
|
||||||
librarySaveCounter++;
|
librarySaveCounter++;
|
||||||
|
|
||||||
return await AdapterTransaction.run(
|
return await AdapterTransaction.run(adapter, async (transaction) => {
|
||||||
adapter,
|
const nextLibraryItemsMap = arrayToMap(
|
||||||
schemaMigrationRegistry,
|
await transaction.getLibraryItems("save"),
|
||||||
async (transaction) => {
|
);
|
||||||
const nextLibraryItemsMap = arrayToMap(
|
|
||||||
await transaction.getLibraryItems("save"),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const [id] of update.deletedItems) {
|
for (const [id] of update.deletedItems) {
|
||||||
nextLibraryItemsMap.delete(id);
|
nextLibraryItemsMap.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addedItems: LibraryItem[] = [];
|
||||||
|
|
||||||
|
// we want to merge current library items with the ones stored in the
|
||||||
|
// DB so that we don't lose any elements that for some reason aren't
|
||||||
|
// in the current editor library, which could happen when:
|
||||||
|
//
|
||||||
|
// 1. we haven't received an update deleting some elements
|
||||||
|
// (in which case it's still better to keep them in the DB lest
|
||||||
|
// it was due to a different reason)
|
||||||
|
// 2. we keep a single DB for all active editors, but the editors'
|
||||||
|
// libraries aren't synced or there's a race conditions during
|
||||||
|
// syncing
|
||||||
|
// 3. some other race condition, e.g. during init where emit updates
|
||||||
|
// for partial updates (e.g. you install a 3rd party library and
|
||||||
|
// init from DB only after — we emit events for both updates)
|
||||||
|
for (const [id, item] of update.addedItems) {
|
||||||
|
if (nextLibraryItemsMap.has(id)) {
|
||||||
|
// replace item with latest version
|
||||||
|
// TODO we could prefer the newer item instead
|
||||||
|
nextLibraryItemsMap.set(id, item);
|
||||||
|
} else {
|
||||||
|
// we want to prepend the new items with the ones that are already
|
||||||
|
// in DB to preserve the ordering we do in editor (newly added
|
||||||
|
// items are added to the beginning)
|
||||||
|
addedItems.push(item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const addedItems: LibraryItem[] = [];
|
// replace existing items with their updated versions
|
||||||
|
if (update.updatedItems) {
|
||||||
// we want to merge current library items with the ones stored in the
|
for (const [id, item] of update.updatedItems) {
|
||||||
// DB so that we don't lose any elements that for some reason aren't
|
nextLibraryItemsMap.set(id, item);
|
||||||
// in the current editor library, which could happen when:
|
|
||||||
//
|
|
||||||
// 1. we haven't received an update deleting some elements
|
|
||||||
// (in which case it's still better to keep them in the DB lest
|
|
||||||
// it was due to a different reason)
|
|
||||||
// 2. we keep a single DB for all active editors, but the editors'
|
|
||||||
// libraries aren't synced or there's a race conditions during
|
|
||||||
// syncing
|
|
||||||
// 3. some other race condition, e.g. during init where emit updates
|
|
||||||
// for partial updates (e.g. you install a 3rd party library and
|
|
||||||
// init from DB only after — we emit events for both updates)
|
|
||||||
for (const [id, item] of update.addedItems) {
|
|
||||||
if (nextLibraryItemsMap.has(id)) {
|
|
||||||
// replace item with latest version
|
|
||||||
// TODO we could prefer the newer item instead
|
|
||||||
nextLibraryItemsMap.set(id, item);
|
|
||||||
} else {
|
|
||||||
// we want to prepend the new items with the ones that are already
|
|
||||||
// in DB to preserve the ordering we do in editor (newly added
|
|
||||||
// items are added to the beginning)
|
|
||||||
addedItems.push(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// replace existing items with their updated versions
|
const nextLibraryItems = addedItems.concat(
|
||||||
if (update.updatedItems) {
|
Array.from(nextLibraryItemsMap.values()),
|
||||||
for (const [id, item] of update.updatedItems) {
|
);
|
||||||
nextLibraryItemsMap.set(id, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextLibraryItems = addedItems.concat(
|
const version = getLibraryItemsHash(nextLibraryItems);
|
||||||
Array.from(nextLibraryItemsMap.values()),
|
|
||||||
);
|
|
||||||
|
|
||||||
const version = getLibraryItemsHash(nextLibraryItems);
|
if (version !== lastSavedLibraryItemsHash) {
|
||||||
|
await adapter.save({ libraryItems: nextLibraryItems });
|
||||||
|
}
|
||||||
|
|
||||||
if (version !== lastSavedLibraryItemsHash) {
|
lastSavedLibraryItemsHash = version;
|
||||||
await adapter.save({ libraryItems: nextLibraryItems });
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSavedLibraryItemsHash = version;
|
return nextLibraryItems;
|
||||||
|
});
|
||||||
return nextLibraryItems;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
librarySaveCounter--;
|
librarySaveCounter--;
|
||||||
}
|
}
|
||||||
@@ -885,24 +852,16 @@ export const useHandleLibrary = (
|
|||||||
.then(async (libraryData) => {
|
.then(async (libraryData) => {
|
||||||
let restoredData: LibraryItems | null = null;
|
let restoredData: LibraryItems | null = null;
|
||||||
try {
|
try {
|
||||||
const schemaMigrationRegistry =
|
|
||||||
optsRef.current.excalidrawAPI?.getSchemaMigrationRegistry();
|
|
||||||
// if no library data to migrate, assume no migration needed
|
// if no library data to migrate, assume no migration needed
|
||||||
// and skip persisting to new data store, as well as well
|
// and skip persisting to new data store, as well as well
|
||||||
// clearing the old store via `migrationAdapter.clear()`
|
// clearing the old store via `migrationAdapter.clear()`
|
||||||
if (!libraryData) {
|
if (!libraryData) {
|
||||||
return AdapterTransaction.getLibraryItems(
|
return AdapterTransaction.getLibraryItems(adapter, "load");
|
||||||
adapter,
|
|
||||||
"load",
|
|
||||||
true,
|
|
||||||
schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restoredData = restoreLibraryItems(
|
restoredData = restoreLibraryItems(
|
||||||
libraryData.libraryItems || [],
|
libraryData.libraryItems || [],
|
||||||
"published",
|
"published",
|
||||||
{ schemaMigrationRegistry },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// we don't queue this operation because it's running inside
|
// we don't queue this operation because it's running inside
|
||||||
@@ -910,7 +869,6 @@ export const useHandleLibrary = (
|
|||||||
const nextItems = await persistLibraryUpdate(
|
const nextItems = await persistLibraryUpdate(
|
||||||
adapter,
|
adapter,
|
||||||
createLibraryUpdate([], restoredData),
|
createLibraryUpdate([], restoredData),
|
||||||
schemaMigrationRegistry,
|
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await migrationAdapter.clear();
|
await migrationAdapter.clear();
|
||||||
@@ -933,23 +891,12 @@ export const useHandleLibrary = (
|
|||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
console.error(`error during library migration: ${error.message}`);
|
console.error(`error during library migration: ${error.message}`);
|
||||||
// as a default, load latest library from current data source
|
// as a default, load latest library from current data source
|
||||||
return AdapterTransaction.getLibraryItems(
|
return AdapterTransaction.getLibraryItems(adapter, "load");
|
||||||
adapter,
|
|
||||||
"load",
|
|
||||||
true,
|
|
||||||
optsRef.current.excalidrawAPI?.getSchemaMigrationRegistry(),
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
initDataPromise.resolve(
|
initDataPromise.resolve(
|
||||||
promiseTry(
|
promiseTry(AdapterTransaction.getLibraryItems, adapter, "load"),
|
||||||
AdapterTransaction.getLibraryItems,
|
|
||||||
adapter,
|
|
||||||
"load",
|
|
||||||
true,
|
|
||||||
optsRef.current.excalidrawAPI?.getSchemaMigrationRegistry(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,11 +958,7 @@ export const useHandleLibrary = (
|
|||||||
lastSavedLibraryItemsHash !==
|
lastSavedLibraryItemsHash !==
|
||||||
getLibraryItemsHash(nextLibraryItems)
|
getLibraryItemsHash(nextLibraryItems)
|
||||||
) {
|
) {
|
||||||
await persistLibraryUpdate(
|
await persistLibraryUpdate(adapter, update);
|
||||||
adapter,
|
|
||||||
update,
|
|
||||||
optsRef.current.excalidrawAPI?.getSchemaMigrationRegistry(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -82,10 +82,6 @@ import {
|
|||||||
getNormalizedZoom,
|
getNormalizedZoom,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
|
|
||||||
import { migrateElements } from "./schema";
|
|
||||||
|
|
||||||
import type { SchemaMigrationRegistry } from "./schema";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
@@ -247,7 +243,7 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restoreElementWithProperties = <
|
const restoreElementWithProperties = <
|
||||||
T extends Omit<ExcalidrawElement, "customData"> & {
|
T extends Required<Omit<ExcalidrawElement, "customData">> & {
|
||||||
customData?: ExcalidrawElement["customData"];
|
customData?: ExcalidrawElement["customData"];
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
boundElementIds?: readonly ExcalidrawElement["id"][];
|
boundElementIds?: readonly ExcalidrawElement["id"][];
|
||||||
@@ -289,10 +285,6 @@ const restoreElementWithProperties = <
|
|||||||
width: element.width || 0,
|
width: element.width || 0,
|
||||||
height: element.height || 0,
|
height: element.height || 0,
|
||||||
seed: element.seed ?? 1,
|
seed: element.seed ?? 1,
|
||||||
schemaState:
|
|
||||||
element.schemaState && typeof element.schemaState === "object"
|
|
||||||
? element.schemaState
|
|
||||||
: { tracks: {} },
|
|
||||||
groupIds: element.groupIds ?? [],
|
groupIds: element.groupIds ?? [],
|
||||||
frameId: element.frameId ?? null,
|
frameId: element.frameId ?? null,
|
||||||
roundness: element.roundness
|
roundness: element.roundness
|
||||||
@@ -513,9 +505,6 @@ export const restoreElement = (
|
|||||||
case "embeddable":
|
case "embeddable":
|
||||||
return restoreElementWithProperties(element, {});
|
return restoreElementWithProperties(element, {});
|
||||||
case "magicframe":
|
case "magicframe":
|
||||||
return restoreElementWithProperties(element, {
|
|
||||||
name: element.name ?? null,
|
|
||||||
});
|
|
||||||
case "frame":
|
case "frame":
|
||||||
return restoreElementWithProperties(element, {
|
return restoreElementWithProperties(element, {
|
||||||
name: element.name ?? null,
|
name: element.name ?? null,
|
||||||
@@ -644,25 +633,17 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
|||||||
refreshDimensions?: boolean;
|
refreshDimensions?: boolean;
|
||||||
repairBindings?: boolean;
|
repairBindings?: boolean;
|
||||||
deleteInvisibleElements?: boolean;
|
deleteInvisibleElements?: boolean;
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry;
|
|
||||||
}
|
}
|
||||||
| undefined,
|
| undefined,
|
||||||
): CombineBrandsIfNeeded<T, OrderedExcalidrawElement> => {
|
): CombineBrandsIfNeeded<T, OrderedExcalidrawElement> => {
|
||||||
const migratedTargetElements = migrateElements(
|
|
||||||
targetElements as readonly ExcalidrawElement[] | undefined | null,
|
|
||||||
{
|
|
||||||
schemaMigrationRegistry: opts?.schemaMigrationRegistry,
|
|
||||||
},
|
|
||||||
) as readonly T[] | undefined | null;
|
|
||||||
|
|
||||||
// used to detect duplicate top-level element ids
|
// used to detect duplicate top-level element ids
|
||||||
const existingIds = new Set<string>();
|
const existingIds = new Set<string>();
|
||||||
const targetElementsMap = arrayToMap(migratedTargetElements || []);
|
const targetElementsMap = arrayToMap(targetElements || []);
|
||||||
const existingElementsMap = existingElements
|
const existingElementsMap = existingElements
|
||||||
? arrayToMap(existingElements)
|
? arrayToMap(existingElements)
|
||||||
: null;
|
: null;
|
||||||
const restoredElements = syncInvalidIndices(
|
const restoredElements = syncInvalidIndices(
|
||||||
(migratedTargetElements || []).reduce((elements, element) => {
|
(targetElements || []).reduce((elements, element) => {
|
||||||
// filtering out selection, which is legacy, no longer kept in elements,
|
// filtering out selection, which is legacy, no longer kept in elements,
|
||||||
// and causing issues if retained
|
// and causing issues if retained
|
||||||
if (element.type === "selection") {
|
if (element.type === "selection") {
|
||||||
@@ -972,14 +953,10 @@ export const restoreAppState = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreLibraryItem = (
|
const restoreLibraryItem = (libraryItem: LibraryItem) => {
|
||||||
libraryItem: LibraryItem,
|
|
||||||
opts?: { schemaMigrationRegistry?: SchemaMigrationRegistry },
|
|
||||||
) => {
|
|
||||||
const elements = restoreElements(
|
const elements = restoreElements(
|
||||||
getNonDeletedElements(libraryItem.elements),
|
getNonDeletedElements(libraryItem.elements),
|
||||||
null,
|
null,
|
||||||
{ schemaMigrationRegistry: opts?.schemaMigrationRegistry },
|
|
||||||
);
|
);
|
||||||
return elements.length ? { ...libraryItem, elements } : null;
|
return elements.length ? { ...libraryItem, elements } : null;
|
||||||
};
|
};
|
||||||
@@ -987,21 +964,17 @@ const restoreLibraryItem = (
|
|||||||
export const restoreLibraryItems = (
|
export const restoreLibraryItems = (
|
||||||
libraryItems: ImportedDataState["libraryItems"] = [],
|
libraryItems: ImportedDataState["libraryItems"] = [],
|
||||||
defaultStatus: LibraryItem["status"],
|
defaultStatus: LibraryItem["status"],
|
||||||
opts?: { schemaMigrationRegistry?: SchemaMigrationRegistry },
|
|
||||||
) => {
|
) => {
|
||||||
const restoredItems: LibraryItem[] = [];
|
const restoredItems: LibraryItem[] = [];
|
||||||
for (const item of libraryItems) {
|
for (const item of libraryItems) {
|
||||||
// migrate older libraries
|
// migrate older libraries
|
||||||
if (Array.isArray(item)) {
|
if (Array.isArray(item)) {
|
||||||
const restoredItem = restoreLibraryItem(
|
const restoredItem = restoreLibraryItem({
|
||||||
{
|
status: defaultStatus,
|
||||||
status: defaultStatus,
|
elements: item,
|
||||||
elements: item,
|
id: randomId(),
|
||||||
id: randomId(),
|
created: Date.now(),
|
||||||
created: Date.now(),
|
});
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
if (restoredItem) {
|
if (restoredItem) {
|
||||||
restoredItems.push(restoredItem);
|
restoredItems.push(restoredItem);
|
||||||
}
|
}
|
||||||
@@ -1010,15 +983,12 @@ export const restoreLibraryItems = (
|
|||||||
LibraryItem,
|
LibraryItem,
|
||||||
"id" | "status" | "created"
|
"id" | "status" | "created"
|
||||||
>;
|
>;
|
||||||
const restoredItem = restoreLibraryItem(
|
const restoredItem = restoreLibraryItem({
|
||||||
{
|
..._item,
|
||||||
..._item,
|
id: _item.id || randomId(),
|
||||||
id: _item.id || randomId(),
|
status: _item.status || defaultStatus,
|
||||||
status: _item.status || defaultStatus,
|
created: _item.created || Date.now(),
|
||||||
created: _item.created || Date.now(),
|
});
|
||||||
},
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
if (restoredItem) {
|
if (restoredItem) {
|
||||||
restoredItems.push(restoredItem);
|
restoredItems.push(restoredItem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,408 +0,0 @@
|
|||||||
import { DEFAULT_ELEMENT_PROPS } from "@excalidraw/common";
|
|
||||||
|
|
||||||
import { API } from "../tests/helpers/api";
|
|
||||||
|
|
||||||
import {
|
|
||||||
CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
createSchemaMigrationRegistry,
|
|
||||||
type SchemaPlugin,
|
|
||||||
type SchemaMigration,
|
|
||||||
CORE_SUPPORTED_TRACKS,
|
|
||||||
migrateElements,
|
|
||||||
resolveTrackVersion,
|
|
||||||
SCHEMA_INITIAL_TRACK_VERSION,
|
|
||||||
SCHEMA_MIGRATIONS,
|
|
||||||
validateSchemaMigrations,
|
|
||||||
validateSchemaPlugins,
|
|
||||||
} from "./schema";
|
|
||||||
|
|
||||||
describe("schema migration", () => {
|
|
||||||
it("should migrate legacy frame backgrounds to transparent", () => {
|
|
||||||
const frame = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
}),
|
|
||||||
schemaState: { tracks: {} },
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([frame])!;
|
|
||||||
|
|
||||||
expect(migrated[0].backgroundColor).toBe(
|
|
||||||
DEFAULT_ELEMENT_PROPS.backgroundColor,
|
|
||||||
);
|
|
||||||
expect(migrated[0].schemaState.tracks[CORE_FRAME_SCHEMA_TRACK]).toBe(
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle elements without schemaState", () => {
|
|
||||||
const frameWithoutSchemaState = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
}),
|
|
||||||
schemaState: undefined,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const textWithoutSchemaState = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "text",
|
|
||||||
text: "",
|
|
||||||
}),
|
|
||||||
schemaState: undefined,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const migrated = migrateElements([
|
|
||||||
frameWithoutSchemaState,
|
|
||||||
textWithoutSchemaState,
|
|
||||||
])!;
|
|
||||||
|
|
||||||
expect(migrated[0].backgroundColor).toBe(
|
|
||||||
DEFAULT_ELEMENT_PROPS.backgroundColor,
|
|
||||||
);
|
|
||||||
expect(migrated[0].schemaState.tracks[CORE_FRAME_SCHEMA_TRACK]).toBe(
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
);
|
|
||||||
expect(migrated[1].schemaState).toEqual({ tracks: {} });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep latest-track frame backgrounds unchanged", () => {
|
|
||||||
const frame = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
}),
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]:
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([frame])!;
|
|
||||||
|
|
||||||
expect(migrated[0].backgroundColor).toBe("#ffc9c9");
|
|
||||||
expect(migrated[0].schemaState.tracks[CORE_FRAME_SCHEMA_TRACK]).toBe(
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should normalize legacy frame backgrounds", () => {
|
|
||||||
const frame = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#a5d8ff",
|
|
||||||
}),
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]: SCHEMA_INITIAL_TRACK_VERSION,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([frame])!;
|
|
||||||
expect(migrated[0].backgroundColor).toBe(
|
|
||||||
DEFAULT_ELEMENT_PROPS.backgroundColor,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should resolve invalid track versions to initial", () => {
|
|
||||||
expect(resolveTrackVersion(undefined)).toBe(SCHEMA_INITIAL_TRACK_VERSION);
|
|
||||||
expect(resolveTrackVersion(0)).toBe(SCHEMA_INITIAL_TRACK_VERSION);
|
|
||||||
expect(resolveTrackVersion(2)).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have a valid migration registry configuration", () => {
|
|
||||||
expect(validateSchemaMigrations(SCHEMA_MIGRATIONS)).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject invalid migration metadata", () => {
|
|
||||||
const invalidMigrations: SchemaMigration[] = [
|
|
||||||
{
|
|
||||||
id: "",
|
|
||||||
namespace: "core",
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: 2.1,
|
|
||||||
title: "",
|
|
||||||
description: " ",
|
|
||||||
targetTypes: [],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "dup",
|
|
||||||
namespace: "core",
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: 2.1,
|
|
||||||
title: "duplicate",
|
|
||||||
description: "duplicate version",
|
|
||||||
targetTypes: ["frame"],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "dup",
|
|
||||||
namespace: "core",
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: 3,
|
|
||||||
title: "duplicate id",
|
|
||||||
description: "duplicate id",
|
|
||||||
targetTypes: ["frame"],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const errors = validateSchemaMigrations(invalidMigrations);
|
|
||||||
|
|
||||||
expect(errors.length).toBeGreaterThan(0);
|
|
||||||
expect(errors.join("\n")).toContain("integer version");
|
|
||||||
expect(errors.join("\n")).toContain("title must be non-empty");
|
|
||||||
expect(errors.join("\n")).toContain("non-empty description");
|
|
||||||
expect(errors.join("\n")).toContain("Duplicate schema migration id");
|
|
||||||
expect(errors.join("\n")).toContain("at least one target type");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject versions at or below initial", () => {
|
|
||||||
const errors = validateSchemaMigrations([
|
|
||||||
{
|
|
||||||
id: "invalid-start",
|
|
||||||
namespace: "core",
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: SCHEMA_INITIAL_TRACK_VERSION,
|
|
||||||
title: "invalid start",
|
|
||||||
description: "bad version",
|
|
||||||
targetTypes: ["frame"],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(errors.join("\n")).toContain("must be greater than 1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject core track/version mismatch", () => {
|
|
||||||
const errors = validateSchemaMigrations([
|
|
||||||
{
|
|
||||||
id: "frame-v3",
|
|
||||||
namespace: "core",
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK] + 1,
|
|
||||||
title: "future migration",
|
|
||||||
description: "future migration for test",
|
|
||||||
targetTypes: ["frame"],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(errors.join("\n")).toContain(
|
|
||||||
`Core supported track "${CORE_FRAME_SCHEMA_TRACK}" (${CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK]}) must match last migration version`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject undeclared core tracks", () => {
|
|
||||||
const errors = validateSchemaMigrations([
|
|
||||||
{
|
|
||||||
id: "unknown-core-track",
|
|
||||||
namespace: "core",
|
|
||||||
track: "excalidraw.shape.unknown",
|
|
||||||
toVersion: 2,
|
|
||||||
title: "unknown core track",
|
|
||||||
description: "should require supported-track declaration",
|
|
||||||
targetTypes: ["rectangle"],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(errors.join("\n")).toContain(
|
|
||||||
"must be declared in CORE_SUPPORTED_TRACKS",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject invalid plugin metadata", () => {
|
|
||||||
const errors = validateSchemaPlugins([
|
|
||||||
{
|
|
||||||
id: "",
|
|
||||||
migrations: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "dup",
|
|
||||||
migrations: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "dup",
|
|
||||||
migrations: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "core-overwrite",
|
|
||||||
migrations: [
|
|
||||||
{
|
|
||||||
id: "bad.core.migration",
|
|
||||||
namespace: "core",
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: 2,
|
|
||||||
title: "bad",
|
|
||||||
description: "bad",
|
|
||||||
targetTypes: ["frame"],
|
|
||||||
apply: (element) => element,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(errors.join("\n")).toContain("Schema plugin id must be non-empty");
|
|
||||||
expect(errors.join("\n")).toContain("Duplicate schema plugin id found");
|
|
||||||
expect(errors.join("\n")).toContain("cannot declare core migrations");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not depend on temporary fields during migration", () => {
|
|
||||||
const frame = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#a5d8ff",
|
|
||||||
}),
|
|
||||||
schemaState: { tracks: {} },
|
|
||||||
};
|
|
||||||
const withTempField = {
|
|
||||||
...frame,
|
|
||||||
backgroundEnabled: false,
|
|
||||||
} as typeof frame & { backgroundEnabled: boolean };
|
|
||||||
|
|
||||||
const migratedBase = migrateElements([frame])!;
|
|
||||||
const migratedWithTempField = migrateElements([withTempField])!;
|
|
||||||
|
|
||||||
expect(migratedBase[0].backgroundColor).toBe(
|
|
||||||
DEFAULT_ELEMENT_PROPS.backgroundColor,
|
|
||||||
);
|
|
||||||
expect(migratedWithTempField[0].backgroundColor).toBe(
|
|
||||||
DEFAULT_ELEMENT_PROPS.backgroundColor,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should use per-element track hints", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ff0000",
|
|
||||||
});
|
|
||||||
const frameFromModernSource = {
|
|
||||||
...frame,
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]:
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([frameFromModernSource])!;
|
|
||||||
|
|
||||||
expect(migrated[0].backgroundColor).toBe("#ff0000");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should migrate mixed-hint elements individually", () => {
|
|
||||||
const legacyFrame = {
|
|
||||||
...API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ff0000",
|
|
||||||
}),
|
|
||||||
schemaState: { tracks: {} },
|
|
||||||
};
|
|
||||||
const modernFrame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#00ff00",
|
|
||||||
});
|
|
||||||
const modernFrameWithTrack = {
|
|
||||||
...modernFrame,
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]:
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([legacyFrame, modernFrameWithTrack])!;
|
|
||||||
|
|
||||||
expect(migrated[0].backgroundColor).toBe(
|
|
||||||
DEFAULT_ELEMENT_PROPS.backgroundColor,
|
|
||||||
);
|
|
||||||
expect(migrated[1].backgroundColor).toBe("#00ff00");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should preserve higher-than-supported track versions", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ff0000",
|
|
||||||
});
|
|
||||||
const futureFrame = {
|
|
||||||
...frame,
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]:
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK] + 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([futureFrame])!;
|
|
||||||
expect(migrated[0].schemaState.tracks[CORE_FRAME_SCHEMA_TRACK]).toBe(
|
|
||||||
CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK] + 1,
|
|
||||||
);
|
|
||||||
expect(migrated[0].backgroundColor).toBe("#ff0000");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should normalize invalid schema state and preserve unknown tracks", () => {
|
|
||||||
const rect = {
|
|
||||||
...API.createElement({ type: "rectangle" }),
|
|
||||||
schemaState: {
|
|
||||||
tracks: {
|
|
||||||
"host.myapp.card": 4,
|
|
||||||
[CORE_FRAME_SCHEMA_TRACK]: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrated = migrateElements([rect])!;
|
|
||||||
expect(migrated[0].schemaState.tracks[CORE_FRAME_SCHEMA_TRACK]).toBe(
|
|
||||||
SCHEMA_INITIAL_TRACK_VERSION,
|
|
||||||
);
|
|
||||||
expect(migrated[0].schemaState.tracks["host.myapp.card"]).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not run plugin migrations unless plugins are provided", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
backgroundColor: "#ffd8a8",
|
|
||||||
});
|
|
||||||
const plugin: SchemaPlugin = {
|
|
||||||
id: "myapp",
|
|
||||||
migrations: [
|
|
||||||
{
|
|
||||||
id: "host.myapp.rect.normalize.v2",
|
|
||||||
namespace: "host.myapp",
|
|
||||||
track: "host.myapp.rectangle",
|
|
||||||
toVersion: 2,
|
|
||||||
title: "normalize rect background",
|
|
||||||
description: "plugin migration for testing",
|
|
||||||
targetTypes: ["rectangle"],
|
|
||||||
apply: (element) =>
|
|
||||||
element.type === "rectangle"
|
|
||||||
? { ...element, backgroundColor: "#12b886" }
|
|
||||||
: element,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const migratedWithoutPlugin = migrateElements([rect])!;
|
|
||||||
const migratedWithPlugin = migrateElements([rect], {
|
|
||||||
schemaMigrationRegistry: createSchemaMigrationRegistry([plugin]),
|
|
||||||
})!;
|
|
||||||
|
|
||||||
expect(migratedWithoutPlugin[0].backgroundColor).toBe("#ffd8a8");
|
|
||||||
expect(
|
|
||||||
migratedWithoutPlugin[0].schemaState.tracks["host.myapp.rectangle"],
|
|
||||||
).toBe(undefined);
|
|
||||||
expect(migratedWithPlugin[0].backgroundColor).toBe("#12b886");
|
|
||||||
expect(
|
|
||||||
migratedWithPlugin[0].schemaState.tracks["host.myapp.rectangle"],
|
|
||||||
).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
import {
|
|
||||||
CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
SCHEMA_CORE_NAMESPACE,
|
|
||||||
SCHEMA_INITIAL_TRACK_VERSION,
|
|
||||||
CORE_SUPPORTED_TRACKS,
|
|
||||||
} from "@excalidraw/element/schema";
|
|
||||||
|
|
||||||
import type { SchemaNamespace, SchemaTrack } from "@excalidraw/element/schema";
|
|
||||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
|
||||||
|
|
||||||
export {
|
|
||||||
CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
CORE_SUPPORTED_TRACKS,
|
|
||||||
SCHEMA_CORE_NAMESPACE,
|
|
||||||
SCHEMA_INITIAL_TRACK_VERSION,
|
|
||||||
};
|
|
||||||
export type { SchemaNamespace, SchemaTrack };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schema migration flow:
|
|
||||||
* 0) Compile schema config from core migrations + optional host plugins.
|
|
||||||
* - validate plugin metadata
|
|
||||||
* - validate migration ordering/metadata
|
|
||||||
* - derive per-track supported versions for this registry
|
|
||||||
* 1) Normalize element.schemaState.tracks (invalid/missing -> initial track version).
|
|
||||||
* 2) Iterate compiled migrations in declaration order.
|
|
||||||
* 3) For matching element types, apply only forward migrations that are
|
|
||||||
* supported by the current registry config (never re-run, never downgrade).
|
|
||||||
* 4) Stamp migrated track versions back onto each element.
|
|
||||||
*/
|
|
||||||
/** One migration step for a single track version bump. */
|
|
||||||
export type SchemaMigration = {
|
|
||||||
/** Stable unique id for validation and debugging. */
|
|
||||||
id: string;
|
|
||||||
/** Owner of the migration: core or a host namespace. */
|
|
||||||
namespace: SchemaNamespace;
|
|
||||||
/** Version line this migration belongs to. */
|
|
||||||
track: SchemaTrack;
|
|
||||||
/** Target version reached after applying this migration. */
|
|
||||||
toVersion: number;
|
|
||||||
/** Human-readable metadata for maintainers/reviewers. */
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
/** Which element types this migration may transform ("*" = all). */
|
|
||||||
targetTypes: readonly ExcalidrawElement["type"][] | "*";
|
|
||||||
/** Pure transform for a single element. */
|
|
||||||
apply: (element: ExcalidrawElement) => ExcalidrawElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional host-provided migration bundle.
|
|
||||||
* Plugins are additive and may only declare host namespace migrations.
|
|
||||||
*/
|
|
||||||
export type SchemaPlugin = {
|
|
||||||
/** Stable plugin id for diagnostics. */
|
|
||||||
id: string;
|
|
||||||
/** Host migration steps merged with core migrations into one registry. */
|
|
||||||
migrations: readonly SchemaMigration[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Default plugin registry (intentionally empty in core). */
|
|
||||||
export const SCHEMA_PLUGINS: readonly SchemaPlugin[] = [];
|
|
||||||
|
|
||||||
export type SchemaMigrationRegistry = Readonly<{
|
|
||||||
/** Fully validated core + host migrations used for this run. */
|
|
||||||
migrations: readonly SchemaMigration[];
|
|
||||||
/** Latest supported version for each known track in this run. */
|
|
||||||
supportedTrackVersions: Readonly<Record<string, number>>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export const SCHEMA_MIGRATIONS: readonly SchemaMigration[] = [
|
|
||||||
{
|
|
||||||
id: "core.frame.background.normalize.v2",
|
|
||||||
namespace: SCHEMA_CORE_NAMESPACE,
|
|
||||||
track: CORE_FRAME_SCHEMA_TRACK,
|
|
||||||
toVersion: CORE_SUPPORTED_TRACKS[CORE_FRAME_SCHEMA_TRACK],
|
|
||||||
title: "Normalize legacy frame backgrounds",
|
|
||||||
description:
|
|
||||||
"Frames saved before frame track v2 must render without visible fill, so normalize backgroundColor to transparent on restore.",
|
|
||||||
targetTypes: ["frame"],
|
|
||||||
apply: (element) => {
|
|
||||||
if (element.type !== "frame") {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...element,
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const resolveTrackVersion = (trackVersion: unknown) => {
|
|
||||||
if (
|
|
||||||
Number.isInteger(trackVersion) &&
|
|
||||||
(trackVersion as number) >= SCHEMA_INITIAL_TRACK_VERSION
|
|
||||||
) {
|
|
||||||
return trackVersion as number;
|
|
||||||
}
|
|
||||||
return SCHEMA_INITIAL_TRACK_VERSION;
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeSchemaTracks = (tracks: unknown) => {
|
|
||||||
if (!tracks || typeof tracks !== "object") {
|
|
||||||
return {} as Record<string, number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(tracks as Record<string, unknown>).reduce<
|
|
||||||
Record<string, number>
|
|
||||||
>((acc, [track, version]) => {
|
|
||||||
const normalizedVersion = resolveTrackVersion(version);
|
|
||||||
if (normalizedVersion >= SCHEMA_INITIAL_TRACK_VERSION) {
|
|
||||||
acc[track] = normalizedVersion;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeElementSchemaState = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
): ExcalidrawElement["schemaState"] => {
|
|
||||||
const tracks = normalizeSchemaTracks(
|
|
||||||
(
|
|
||||||
element as ExcalidrawElement & {
|
|
||||||
schemaState?: ExcalidrawElement["schemaState"];
|
|
||||||
}
|
|
||||||
).schemaState?.tracks,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
tracks,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ensureElementSchemaState = (element: ExcalidrawElement) => {
|
|
||||||
const normalizedSchemaState = normalizeElementSchemaState(element);
|
|
||||||
|
|
||||||
// Fast path: avoid reallocating when element already has normalized state.
|
|
||||||
if (element.schemaState === normalizedSchemaState) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
element.schemaState &&
|
|
||||||
Object.keys(element.schemaState?.tracks || {}).length ===
|
|
||||||
Object.keys(normalizedSchemaState.tracks).length &&
|
|
||||||
Object.entries(normalizedSchemaState.tracks).every(
|
|
||||||
([track, version]) => element.schemaState?.tracks?.[track] === version,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...element,
|
|
||||||
schemaState: normalizedSchemaState,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTrackVersion = (element: ExcalidrawElement, track: SchemaTrack) => {
|
|
||||||
return resolveTrackVersion(element.schemaState.tracks[track]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const withTrackVersion = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
track: SchemaTrack,
|
|
||||||
version: number,
|
|
||||||
) => {
|
|
||||||
if (element.schemaState.tracks[track] === version) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...element,
|
|
||||||
schemaState: {
|
|
||||||
...element.schemaState,
|
|
||||||
tracks: {
|
|
||||||
...element.schemaState.tracks,
|
|
||||||
[track]: version,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrationMatchesElementType = (
|
|
||||||
migration: SchemaMigration,
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
migration.targetTypes === "*" ||
|
|
||||||
migration.targetTypes.includes(element.type)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateSchemaMigrations = (
|
|
||||||
migrations: readonly SchemaMigration[],
|
|
||||||
) => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const seenIds = new Set<string>();
|
|
||||||
const previousVersionByTrack = new Map<string, number>();
|
|
||||||
|
|
||||||
for (const migration of migrations) {
|
|
||||||
if (!migration.id.trim()) {
|
|
||||||
errors.push("Migration id must be non-empty.");
|
|
||||||
}
|
|
||||||
if (seenIds.has(migration.id)) {
|
|
||||||
errors.push(`Duplicate schema migration id found: ${migration.id}.`);
|
|
||||||
}
|
|
||||||
seenIds.add(migration.id);
|
|
||||||
|
|
||||||
if (!migration.title.trim()) {
|
|
||||||
errors.push(`Migration "${migration.id}" title must be non-empty.`);
|
|
||||||
}
|
|
||||||
if (!migration.description.trim()) {
|
|
||||||
errors.push(
|
|
||||||
`Migration "${migration.id}" must include a non-empty description.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number.isInteger(migration.toVersion)) {
|
|
||||||
errors.push(`Migration "${migration.id}" must use an integer version.`);
|
|
||||||
}
|
|
||||||
if (migration.toVersion <= SCHEMA_INITIAL_TRACK_VERSION) {
|
|
||||||
errors.push(
|
|
||||||
`Migration "${migration.id}" version must be greater than ${SCHEMA_INITIAL_TRACK_VERSION}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
migration.targetTypes !== "*" &&
|
|
||||||
(!migration.targetTypes.length ||
|
|
||||||
migration.targetTypes.some((type) => !type))
|
|
||||||
) {
|
|
||||||
errors.push(
|
|
||||||
`Migration "${migration.id}" must declare at least one target type.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackKey = `${migration.namespace}|${migration.track}`;
|
|
||||||
const previousVersion =
|
|
||||||
previousVersionByTrack.get(trackKey) ?? SCHEMA_INITIAL_TRACK_VERSION;
|
|
||||||
if (migration.toVersion <= previousVersion) {
|
|
||||||
errors.push(
|
|
||||||
`Migration "${migration.id}" must be ordered by increasing version within ${trackKey}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
previousVersionByTrack.set(trackKey, migration.toVersion);
|
|
||||||
|
|
||||||
if (
|
|
||||||
migration.namespace === SCHEMA_CORE_NAMESPACE &&
|
|
||||||
!migration.track.startsWith("excalidraw.")
|
|
||||||
) {
|
|
||||||
errors.push(
|
|
||||||
`Core migration "${migration.id}" must use an excalidraw.* track.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
migration.namespace === SCHEMA_CORE_NAMESPACE &&
|
|
||||||
!(migration.track in CORE_SUPPORTED_TRACKS)
|
|
||||||
) {
|
|
||||||
errors.push(
|
|
||||||
`Core migration "${migration.id}" track "${migration.track}" must be declared in CORE_SUPPORTED_TRACKS.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
migration.namespace !== SCHEMA_CORE_NAMESPACE &&
|
|
||||||
!migration.track.startsWith(`${migration.namespace}.`)
|
|
||||||
) {
|
|
||||||
errors.push(
|
|
||||||
`Host migration "${migration.id}" track must use namespace prefix ${migration.namespace}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [track, supportedVersion] of Object.entries(
|
|
||||||
CORE_SUPPORTED_TRACKS,
|
|
||||||
)) {
|
|
||||||
const migrationTrackKey = `${SCHEMA_CORE_NAMESPACE}|${track}`;
|
|
||||||
const lastDeclaredVersion =
|
|
||||||
previousVersionByTrack.get(migrationTrackKey) ??
|
|
||||||
SCHEMA_INITIAL_TRACK_VERSION;
|
|
||||||
|
|
||||||
if (lastDeclaredVersion !== supportedVersion) {
|
|
||||||
errors.push(
|
|
||||||
`Core supported track "${track}" (${supportedVersion}) must match last migration version (${lastDeclaredVersion}).`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateSchemaPlugins = (plugins: readonly SchemaPlugin[]) => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const seenIds = new Set<string>();
|
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
if (!plugin.id.trim()) {
|
|
||||||
errors.push("Schema plugin id must be non-empty.");
|
|
||||||
}
|
|
||||||
if (seenIds.has(plugin.id)) {
|
|
||||||
errors.push(`Duplicate schema plugin id found: ${plugin.id}.`);
|
|
||||||
}
|
|
||||||
seenIds.add(plugin.id);
|
|
||||||
|
|
||||||
for (const migration of plugin.migrations) {
|
|
||||||
if (migration.namespace === SCHEMA_CORE_NAMESPACE) {
|
|
||||||
errors.push(
|
|
||||||
`Schema plugin "${plugin.id}" cannot declare core migrations ("${migration.id}").`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const collectPluginMigrations = (plugins: readonly SchemaPlugin[]) =>
|
|
||||||
plugins.flatMap((plugin) => plugin.migrations);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the registry "latest version" map:
|
|
||||||
* - core tracks come from CORE_SUPPORTED_TRACKS
|
|
||||||
* - host tracks are inferred from provided plugin migrations
|
|
||||||
*/
|
|
||||||
const getSupportedTrackVersions = (
|
|
||||||
migrations: readonly SchemaMigration[],
|
|
||||||
): Readonly<Record<string, number>> => {
|
|
||||||
const supportedTrackVersions: Record<string, number> = {
|
|
||||||
...CORE_SUPPORTED_TRACKS,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const migration of migrations) {
|
|
||||||
if (migration.namespace === SCHEMA_CORE_NAMESPACE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentSupportedVersion =
|
|
||||||
supportedTrackVersions[migration.track] ?? SCHEMA_INITIAL_TRACK_VERSION;
|
|
||||||
if (migration.toVersion > currentSupportedVersion) {
|
|
||||||
supportedTrackVersions[migration.track] = migration.toVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return supportedTrackVersions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createSchemaMigrationRegistry = (
|
|
||||||
plugins: readonly SchemaPlugin[] = SCHEMA_PLUGINS,
|
|
||||||
): SchemaMigrationRegistry => {
|
|
||||||
const pluginErrors = validateSchemaPlugins(plugins);
|
|
||||||
if (pluginErrors.length) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid schema plugin configuration:\n${pluginErrors.join("\n")}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const migrations = [
|
|
||||||
...SCHEMA_MIGRATIONS,
|
|
||||||
...collectPluginMigrations(plugins),
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const migrationErrors = validateSchemaMigrations(migrations);
|
|
||||||
if (migrationErrors.length) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid schema migration configuration:\n${migrationErrors.join("\n")}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
migrations,
|
|
||||||
supportedTrackVersions: getSupportedTrackVersions(migrations),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const CORE_SCHEMA_MIGRATION_REGISTRY = createSchemaMigrationRegistry();
|
|
||||||
|
|
||||||
/** Uses cached core config by default, recompiles when plugins are provided. */
|
|
||||||
const resolveSchemaMigrationRegistry = (
|
|
||||||
schemaMigrationRegistry: SchemaMigrationRegistry | undefined,
|
|
||||||
) => schemaMigrationRegistry || CORE_SCHEMA_MIGRATION_REGISTRY;
|
|
||||||
|
|
||||||
const migrateElement = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
schemaMigrationRegistry: SchemaMigrationRegistry,
|
|
||||||
) => {
|
|
||||||
// Always migrate from a normalized per-element schema state.
|
|
||||||
let migratedElement = ensureElementSchemaState(element);
|
|
||||||
|
|
||||||
for (const migration of schemaMigrationRegistry.migrations) {
|
|
||||||
if (!migrationMatchesElementType(migration, migratedElement)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTrackVersion = getTrackVersion(
|
|
||||||
migratedElement,
|
|
||||||
migration.track,
|
|
||||||
);
|
|
||||||
const supportedTrackVersion =
|
|
||||||
schemaMigrationRegistry.supportedTrackVersions[migration.track] ??
|
|
||||||
currentTrackVersion;
|
|
||||||
|
|
||||||
// Never re-run or downgrade.
|
|
||||||
if (currentTrackVersion >= migration.toVersion) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve future data: ignore migrations newer than what this app supports.
|
|
||||||
if (migration.toVersion > supportedTrackVersion) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply transform, then stamp the element's track version.
|
|
||||||
migratedElement = withTrackVersion(
|
|
||||||
migration.apply(migratedElement),
|
|
||||||
migration.track,
|
|
||||||
migration.toVersion,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return migratedElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const migrateElements = (
|
|
||||||
elements: readonly ExcalidrawElement[] | null | undefined,
|
|
||||||
opts?: {
|
|
||||||
schemaMigrationRegistry?: SchemaMigrationRegistry;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if (!elements) {
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemaMigrationRegistry = resolveSchemaMigrationRegistry(
|
|
||||||
opts?.schemaMigrationRegistry,
|
|
||||||
);
|
|
||||||
|
|
||||||
return elements.map((element) =>
|
|
||||||
migrateElement(element, schemaMigrationRegistry),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -30,7 +30,7 @@ export class HistoryDelta extends StoreDelta {
|
|||||||
// as we always need to end up with a new version due to collaboration,
|
// as we always need to end up with a new version due to collaboration,
|
||||||
// approaching each undo / redo as a new user action
|
// approaching each undo / redo as a new user action
|
||||||
{
|
{
|
||||||
excludedProperties: new Set(["version", "versionNonce", "schemaState"]),
|
excludedProperties: new Set(["version", "versionNonce"]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
aiEnabled,
|
aiEnabled,
|
||||||
showDeprecatedFonts,
|
showDeprecatedFonts,
|
||||||
renderScrollbars,
|
renderScrollbars,
|
||||||
schemaPlugins,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const canvasActions = props.UIOptions?.canvasActions;
|
const canvasActions = props.UIOptions?.canvasActions;
|
||||||
@@ -150,7 +149,6 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
aiEnabled={aiEnabled !== false}
|
aiEnabled={aiEnabled !== false}
|
||||||
showDeprecatedFonts={showDeprecatedFonts}
|
showDeprecatedFonts={showDeprecatedFonts}
|
||||||
renderScrollbars={renderScrollbars}
|
renderScrollbars={renderScrollbars}
|
||||||
schemaPlugins={schemaPlugins}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</App>
|
</App>
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import {
|
|||||||
getTargetFrame,
|
getTargetFrame,
|
||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
renderElement,
|
renderElement,
|
||||||
renderFrameBackground,
|
|
||||||
shouldApplyFrameClip,
|
shouldApplyFrameClip,
|
||||||
isFrameElement,
|
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import { bootstrapCanvas, getNormalizedCanvasDimensions } from "./helpers";
|
import { bootstrapCanvas, getNormalizedCanvasDimensions } from "./helpers";
|
||||||
@@ -69,14 +67,6 @@ const _renderNewElementScene = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
isFrameElement(newElement) &&
|
|
||||||
appState.frameRendering.enabled &&
|
|
||||||
appState.frameRendering.outline
|
|
||||||
) {
|
|
||||||
renderFrameBackground(newElement, context, appState);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderElement(
|
renderElement(
|
||||||
newElement,
|
newElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
|
|||||||
@@ -4,29 +4,26 @@ import {
|
|||||||
THEME,
|
THEME,
|
||||||
throttleRAF,
|
throttleRAF,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import { isElementLink, isFrameElement } from "@excalidraw/element";
|
import { isElementLink } from "@excalidraw/element";
|
||||||
import { createPlaceholderEmbeddableLabel } from "@excalidraw/element";
|
import { createPlaceholderEmbeddableLabel } from "@excalidraw/element";
|
||||||
import { getBoundTextElement } from "@excalidraw/element";
|
import { getBoundTextElement } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
isEmbeddableElement,
|
isEmbeddableElement,
|
||||||
isFrameLikeElement,
|
|
||||||
isIframeLikeElement,
|
isIframeLikeElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
elementOverlapsWithFrame,
|
elementOverlapsWithFrame,
|
||||||
getContainingFrame,
|
|
||||||
getTargetFrame,
|
getTargetFrame,
|
||||||
shouldApplyFrameClip,
|
shouldApplyFrameClip,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import { renderElement, renderFrameBackground } from "@excalidraw/element";
|
import { renderElement } from "@excalidraw/element";
|
||||||
|
|
||||||
import { getElementAbsoluteCoords } from "@excalidraw/element";
|
import { getElementAbsoluteCoords } from "@excalidraw/element";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawFrameElement,
|
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
@@ -279,15 +276,6 @@ const _renderStaticScene = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const groupsToBeAddedToFrame = new Set<string>();
|
const groupsToBeAddedToFrame = new Set<string>();
|
||||||
// iframe-like elements are rendered in a separate top-layer pass.
|
|
||||||
const nonIframeVisibleElements = visibleElements.filter(
|
|
||||||
(el) => !isIframeLikeElement(el),
|
|
||||||
);
|
|
||||||
// Frame background to render right before a given element id.
|
|
||||||
const frameBackgroundByElementId = new Map<
|
|
||||||
NonDeletedExcalidrawElement["id"],
|
|
||||||
ExcalidrawFrameElement
|
|
||||||
>();
|
|
||||||
|
|
||||||
visibleElements.forEach((element) => {
|
visibleElements.forEach((element) => {
|
||||||
if (
|
if (
|
||||||
@@ -309,135 +297,92 @@ const _renderStaticScene = ({
|
|||||||
|
|
||||||
const inFrameGroupsMap = new Map<string, boolean>();
|
const inFrameGroupsMap = new Map<string, boolean>();
|
||||||
|
|
||||||
if (
|
|
||||||
appState.frameRendering.enabled &&
|
|
||||||
(appState.frameRendering.outline || renderConfig.exportingFrame)
|
|
||||||
) {
|
|
||||||
// Precompute where each frame background should be emitted to avoid
|
|
||||||
// re-resolving containing frames during the paint loop.
|
|
||||||
const renderedFrameBackgrounds = new Set<string>();
|
|
||||||
if (
|
|
||||||
renderConfig.exportingFrame &&
|
|
||||||
isFrameElement(renderConfig.exportingFrame)
|
|
||||||
) {
|
|
||||||
renderFrameBackground(renderConfig.exportingFrame, context, appState, {
|
|
||||||
roundCorners: false,
|
|
||||||
});
|
|
||||||
renderedFrameBackgrounds.add(renderConfig.exportingFrame.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeQueueFrameBackground = (
|
|
||||||
element: NonDeletedExcalidrawElement | ExcalidrawFrameLikeElement,
|
|
||||||
) => {
|
|
||||||
const frame = isFrameLikeElement(element)
|
|
||||||
? element
|
|
||||||
: getContainingFrame(element, elementsMap);
|
|
||||||
|
|
||||||
if (!isFrameElement(frame) || renderedFrameBackgrounds.has(frame.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
frameBackgroundByElementId.set(element.id, frame);
|
|
||||||
renderedFrameBackgrounds.add(frame.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
nonIframeVisibleElements.forEach((element) => {
|
|
||||||
maybeQueueFrameBackground(element);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint visible elements
|
// Paint visible elements
|
||||||
nonIframeVisibleElements.forEach((element) => {
|
visibleElements
|
||||||
try {
|
.filter((el) => !isIframeLikeElement(el))
|
||||||
const frameBackground = frameBackgroundByElementId.get(element.id);
|
.forEach((element) => {
|
||||||
if (frameBackground) {
|
try {
|
||||||
renderFrameBackground(frameBackground, context, appState, {
|
const frameId = element.frameId || appState.frameToHighlight?.id;
|
||||||
roundCorners:
|
|
||||||
!renderConfig.exportingFrame ||
|
|
||||||
frameBackground.id !== renderConfig.exportingFrame.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const frameId = element.frameId || appState.frameToHighlight?.id;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isTextElement(element) &&
|
|
||||||
element.containerId &&
|
|
||||||
elementsMap.has(element.containerId)
|
|
||||||
) {
|
|
||||||
// will be rendered with the container
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.save();
|
|
||||||
|
|
||||||
if (
|
|
||||||
frameId &&
|
|
||||||
appState.frameRendering.enabled &&
|
|
||||||
appState.frameRendering.clip
|
|
||||||
) {
|
|
||||||
const frame = getTargetFrame(element, elementsMap, appState);
|
|
||||||
if (
|
if (
|
||||||
frame &&
|
isTextElement(element) &&
|
||||||
shouldApplyFrameClip(
|
element.containerId &&
|
||||||
element,
|
elementsMap.has(element.containerId)
|
||||||
frame,
|
|
||||||
appState,
|
|
||||||
elementsMap,
|
|
||||||
inFrameGroupsMap,
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
frameClip(frame, context, renderConfig, appState);
|
// will be rendered with the container
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
renderElement(
|
|
||||||
element,
|
context.save();
|
||||||
elementsMap,
|
|
||||||
allElementsMap,
|
if (
|
||||||
rc,
|
frameId &&
|
||||||
context,
|
appState.frameRendering.enabled &&
|
||||||
renderConfig,
|
appState.frameRendering.clip
|
||||||
appState,
|
) {
|
||||||
);
|
const frame = getTargetFrame(element, elementsMap, appState);
|
||||||
} else {
|
if (
|
||||||
renderElement(
|
frame &&
|
||||||
element,
|
shouldApplyFrameClip(
|
||||||
elementsMap,
|
element,
|
||||||
allElementsMap,
|
frame,
|
||||||
rc,
|
appState,
|
||||||
context,
|
elementsMap,
|
||||||
renderConfig,
|
inFrameGroupsMap,
|
||||||
appState,
|
)
|
||||||
|
) {
|
||||||
|
frameClip(frame, context, renderConfig, appState);
|
||||||
|
}
|
||||||
|
renderElement(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
allElementsMap,
|
||||||
|
rc,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
renderElement(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
allElementsMap,
|
||||||
|
rc,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
if (boundTextElement) {
|
||||||
|
renderElement(
|
||||||
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
allElementsMap,
|
||||||
|
rc,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
|
||||||
|
if (!isExporting) {
|
||||||
|
renderLinkIcon(element, context, appState, elementsMap);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(
|
||||||
|
error,
|
||||||
|
element.id,
|
||||||
|
element.x,
|
||||||
|
element.y,
|
||||||
|
element.width,
|
||||||
|
element.height,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
||||||
if (boundTextElement) {
|
|
||||||
renderElement(
|
|
||||||
boundTextElement,
|
|
||||||
elementsMap,
|
|
||||||
allElementsMap,
|
|
||||||
rc,
|
|
||||||
context,
|
|
||||||
renderConfig,
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.restore();
|
|
||||||
|
|
||||||
if (!isExporting) {
|
|
||||||
renderLinkIcon(element, context, appState, elementsMap);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(
|
|
||||||
error,
|
|
||||||
element.id,
|
|
||||||
element.x,
|
|
||||||
element.y,
|
|
||||||
element.width,
|
|
||||||
element.height,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// render embeddables on top
|
// render embeddables on top
|
||||||
visibleElements
|
visibleElements
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
isRTL,
|
isRTL,
|
||||||
isTestEnv,
|
isTestEnv,
|
||||||
getVerticalOffset,
|
getVerticalOffset,
|
||||||
isTransparent,
|
|
||||||
applyDarkModeFilter,
|
applyDarkModeFilter,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
@@ -24,8 +23,6 @@ import { getBoundTextElement, getContainerElement } from "@excalidraw/element";
|
|||||||
import { getLineHeightInPx } from "@excalidraw/element";
|
import { getLineHeightInPx } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
isFrameElement,
|
|
||||||
isFrameLikeElement,
|
|
||||||
isIframeLikeElement,
|
isIframeLikeElement,
|
||||||
isInitializedImageElement,
|
isInitializedImageElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
@@ -41,8 +38,6 @@ import { getElementAbsoluteCoords } from "@excalidraw/element";
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameElement,
|
|
||||||
ExcalidrawFrameLikeElement,
|
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
@@ -722,140 +717,50 @@ export const renderSceneToSvg = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonIframeElements = elements.filter((el) => !isIframeLikeElement(el));
|
|
||||||
const frameBackgroundByElementId = new Map<
|
|
||||||
ExcalidrawElement["id"],
|
|
||||||
ExcalidrawFrameElement
|
|
||||||
>();
|
|
||||||
|
|
||||||
const renderFrameBackgroundNode = (frame: ExcalidrawFrameElement) => {
|
|
||||||
if (
|
|
||||||
!frame ||
|
|
||||||
!frame.backgroundColor ||
|
|
||||||
isTransparent(frame.backgroundColor)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
|
|
||||||
const cx = (x2 - x1) / 2 - (frame.x - x1);
|
|
||||||
const cy = (y2 - y1) / 2 - (frame.y - y1);
|
|
||||||
const degree = (180 * frame.angle) / Math.PI;
|
|
||||||
|
|
||||||
const rect = svgRoot.ownerDocument.createElementNS(SVG_NS, "rect");
|
|
||||||
rect.setAttribute(
|
|
||||||
"transform",
|
|
||||||
`translate(${frame.x + renderConfig.offsetX} ${
|
|
||||||
frame.y + renderConfig.offsetY
|
|
||||||
}) rotate(${degree} ${cx} ${cy})`,
|
|
||||||
);
|
|
||||||
rect.setAttribute("width", `${frame.width}px`);
|
|
||||||
rect.setAttribute("height", `${frame.height}px`);
|
|
||||||
|
|
||||||
const isDirectlyExportedFrame =
|
|
||||||
!!renderConfig.exportingFrame &&
|
|
||||||
frame.id === renderConfig.exportingFrame.id;
|
|
||||||
if (!isDirectlyExportedFrame) {
|
|
||||||
rect.setAttribute("rx", FRAME_STYLE.radius.toString());
|
|
||||||
rect.setAttribute("ry", FRAME_STYLE.radius.toString());
|
|
||||||
}
|
|
||||||
rect.setAttribute(
|
|
||||||
"fill",
|
|
||||||
renderConfig.theme === THEME.DARK
|
|
||||||
? applyDarkModeFilter(frame.backgroundColor)
|
|
||||||
: frame.backgroundColor,
|
|
||||||
);
|
|
||||||
rect.setAttribute("stroke", "none");
|
|
||||||
|
|
||||||
svgRoot.appendChild(rect);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
renderConfig.frameRendering.enabled &&
|
|
||||||
(renderConfig.frameRendering.outline || renderConfig.exportingFrame)
|
|
||||||
) {
|
|
||||||
const renderedFrameBackgrounds = new Set<string>();
|
|
||||||
if (
|
|
||||||
renderConfig.exportingFrame &&
|
|
||||||
isFrameElement(renderConfig.exportingFrame)
|
|
||||||
) {
|
|
||||||
renderFrameBackgroundNode(renderConfig.exportingFrame);
|
|
||||||
renderedFrameBackgrounds.add(renderConfig.exportingFrame.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeQueueFrameBackground = (
|
|
||||||
element: NonDeletedExcalidrawElement | ExcalidrawFrameLikeElement,
|
|
||||||
) => {
|
|
||||||
const frame = isFrameLikeElement(element)
|
|
||||||
? element
|
|
||||||
: getContainingFrame(element, elementsMap);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isFrameElement(frame) ||
|
|
||||||
renderedFrameBackgrounds.has(frame.id) ||
|
|
||||||
!frame.backgroundColor ||
|
|
||||||
isTransparent(frame.backgroundColor)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
frameBackgroundByElementId.set(element.id, frame);
|
|
||||||
renderedFrameBackgrounds.add(frame.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
nonIframeElements.forEach((element) => {
|
|
||||||
if (!element.isDeleted) {
|
|
||||||
maybeQueueFrameBackground(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// render elements
|
// render elements
|
||||||
nonIframeElements.forEach((element) => {
|
elements
|
||||||
if (!element.isDeleted) {
|
.filter((el) => !isIframeLikeElement(el))
|
||||||
const frameBackground = frameBackgroundByElementId.get(element.id);
|
.forEach((element) => {
|
||||||
if (frameBackground) {
|
if (!element.isDeleted) {
|
||||||
renderFrameBackgroundNode(frameBackground);
|
if (
|
||||||
}
|
isTextElement(element) &&
|
||||||
if (
|
element.containerId &&
|
||||||
isTextElement(element) &&
|
elementsMap.has(element.containerId)
|
||||||
element.containerId &&
|
) {
|
||||||
elementsMap.has(element.containerId)
|
// will be rendered with the container
|
||||||
) {
|
return;
|
||||||
// will be rendered with the container
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
renderElementToSvg(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
rsvg,
|
|
||||||
svgRoot,
|
|
||||||
files,
|
|
||||||
element.x + renderConfig.offsetX,
|
|
||||||
element.y + renderConfig.offsetY,
|
|
||||||
renderConfig,
|
|
||||||
);
|
|
||||||
|
|
||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
|
||||||
if (boundTextElement) {
|
|
||||||
renderElementToSvg(
|
renderElementToSvg(
|
||||||
boundTextElement,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
rsvg,
|
rsvg,
|
||||||
svgRoot,
|
svgRoot,
|
||||||
files,
|
files,
|
||||||
boundTextElement.x + renderConfig.offsetX,
|
element.x + renderConfig.offsetX,
|
||||||
boundTextElement.y + renderConfig.offsetY,
|
element.y + renderConfig.offsetY,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
if (boundTextElement) {
|
||||||
|
renderElementToSvg(
|
||||||
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
rsvg,
|
||||||
|
svgRoot,
|
||||||
|
files,
|
||||||
|
boundTextElement.x + renderConfig.offsetX,
|
||||||
|
boundTextElement.y + renderConfig.offsetY,
|
||||||
|
renderConfig,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// render embeddables on top
|
// render embeddables on top
|
||||||
elements
|
elements
|
||||||
|
|||||||
@@ -169,20 +169,6 @@ const prepareElementsForRender = ({
|
|||||||
return nextElements;
|
return nextElements;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addExportingFrameToElements = (
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
exportingFrame?: ExcalidrawFrameLikeElement | null,
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
!exportingFrame ||
|
|
||||||
elements.some((element) => element.id === exportingFrame.id)
|
|
||||||
) {
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...elements, exportingFrame];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportToCanvas = async (
|
export const exportToCanvas = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@@ -230,14 +216,6 @@ export const exportToCanvas = async (
|
|||||||
exportWithDarkMode: appState.exportWithDarkMode,
|
exportWithDarkMode: appState.exportWithDarkMode,
|
||||||
frameRendering,
|
frameRendering,
|
||||||
});
|
});
|
||||||
const elementsForLookup = addExportingFrameToElements(
|
|
||||||
elementsForRender,
|
|
||||||
exportingFrame,
|
|
||||||
);
|
|
||||||
const allElementsForLookup = addExportingFrameToElements(
|
|
||||||
elements,
|
|
||||||
exportingFrame,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (exportingFrame) {
|
if (exportingFrame) {
|
||||||
exportPadding = 0;
|
exportPadding = 0;
|
||||||
@@ -264,10 +242,10 @@ export const exportToCanvas = async (
|
|||||||
canvas,
|
canvas,
|
||||||
rc: rough.canvas(canvas),
|
rc: rough.canvas(canvas),
|
||||||
elementsMap: toBrandedType<RenderableElementsMap>(
|
elementsMap: toBrandedType<RenderableElementsMap>(
|
||||||
arrayToMap(elementsForLookup),
|
arrayToMap(elementsForRender),
|
||||||
),
|
),
|
||||||
allElementsMap: toBrandedType<NonDeletedSceneElementsMap>(
|
allElementsMap: toBrandedType<NonDeletedSceneElementsMap>(
|
||||||
arrayToMap(syncInvalidIndices(allElementsForLookup)),
|
arrayToMap(syncInvalidIndices(elements)),
|
||||||
),
|
),
|
||||||
visibleElements: elementsForRender,
|
visibleElements: elementsForRender,
|
||||||
scale,
|
scale,
|
||||||
@@ -283,7 +261,6 @@ export const exportToCanvas = async (
|
|||||||
},
|
},
|
||||||
renderConfig: {
|
renderConfig: {
|
||||||
canvasBackgroundColor: viewBackgroundColor,
|
canvasBackgroundColor: viewBackgroundColor,
|
||||||
exportingFrame,
|
|
||||||
imageCache,
|
imageCache,
|
||||||
renderGrid: false,
|
renderGrid: false,
|
||||||
isExporting: true,
|
isExporting: true,
|
||||||
@@ -348,10 +325,6 @@ export const exportToSvg = async (
|
|||||||
exportWithDarkMode,
|
exportWithDarkMode,
|
||||||
frameRendering,
|
frameRendering,
|
||||||
});
|
});
|
||||||
const elementsForLookup = addExportingFrameToElements(
|
|
||||||
elementsForRender,
|
|
||||||
exportingFrame,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (exportingFrame) {
|
if (exportingFrame) {
|
||||||
exportPadding = 0;
|
exportPadding = 0;
|
||||||
@@ -413,10 +386,10 @@ export const exportToSvg = async (
|
|||||||
// frame clip paths
|
// frame clip paths
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const frameElements = getFrameLikeElements(elementsForLookup);
|
const frameElements = getFrameLikeElements(elements);
|
||||||
|
|
||||||
if (frameElements.length) {
|
if (frameElements.length) {
|
||||||
const elementsMap = arrayToMap(elementsForLookup);
|
const elementsMap = arrayToMap(elements);
|
||||||
|
|
||||||
for (const frame of frameElements) {
|
for (const frame of frameElements) {
|
||||||
const clipPath = svgRoot.ownerDocument.createElementNS(
|
const clipPath = svgRoot.ownerDocument.createElementNS(
|
||||||
@@ -499,7 +472,7 @@ export const exportToSvg = async (
|
|||||||
|
|
||||||
renderSceneToSvg(
|
renderSceneToSvg(
|
||||||
elementsForRender,
|
elementsForRender,
|
||||||
toBrandedType<RenderableElementsMap>(arrayToMap(elementsForLookup)),
|
toBrandedType<RenderableElementsMap>(arrayToMap(elementsForRender)),
|
||||||
rsvg,
|
rsvg,
|
||||||
svgRoot,
|
svgRoot,
|
||||||
files || {},
|
files || {},
|
||||||
@@ -510,7 +483,6 @@ export const exportToSvg = async (
|
|||||||
exportWithDarkMode,
|
exportWithDarkMode,
|
||||||
renderEmbeddables,
|
renderEmbeddables,
|
||||||
frameRendering,
|
frameRendering,
|
||||||
exportingFrame,
|
|
||||||
canvasBackgroundColor: viewBackgroundColor,
|
canvasBackgroundColor: viewBackgroundColor,
|
||||||
embedsValidationStatus: renderEmbeddables
|
embedsValidationStatus: renderEmbeddables
|
||||||
? new Map(
|
? new Map(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { UserIdleState, EditorInterface } from "@excalidraw/common";
|
import type { UserIdleState, EditorInterface } from "@excalidraw/common";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameLikeElement,
|
|
||||||
NonDeletedElementsMap,
|
NonDeletedElementsMap,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
@@ -27,7 +26,6 @@ export type RenderableElementsMap = NonDeletedElementsMap &
|
|||||||
|
|
||||||
export type StaticCanvasRenderConfig = {
|
export type StaticCanvasRenderConfig = {
|
||||||
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
||||||
exportingFrame?: ExcalidrawFrameLikeElement | null;
|
|
||||||
// extra options passed to the renderer
|
// extra options passed to the renderer
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
imageCache: AppClassProperties["imageCache"];
|
imageCache: AppClassProperties["imageCache"];
|
||||||
@@ -48,7 +46,6 @@ export type SVGRenderConfig = {
|
|||||||
exportWithDarkMode: boolean;
|
exportWithDarkMode: boolean;
|
||||||
renderEmbeddables: boolean;
|
renderEmbeddables: boolean;
|
||||||
frameRendering: AppState["frameRendering"];
|
frameRendering: AppState["frameRendering"];
|
||||||
exportingFrame?: ExcalidrawFrameLikeElement | null;
|
|
||||||
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
||||||
embedsValidationStatus: EmbedsValidationStatus;
|
embedsValidationStatus: EmbedsValidationStatus;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1020,9 +1020,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1,
|
"seed": 1,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1057,9 +1054,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1,
|
"seed": 1,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1222,9 +1216,6 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1280,9 +1271,6 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -1443,9 +1431,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1014066025,
|
"seed": 1014066025,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1478,9 +1463,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1536,9 +1518,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -1593,9 +1572,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -1637,16 +1613,10 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"id0": {
|
"id0": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1795,9 +1765,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1014066025,
|
"seed": 1014066025,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1830,9 +1797,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -1888,9 +1852,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -1945,9 +1906,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -1989,16 +1947,10 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"id0": {
|
"id0": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2149,9 +2101,6 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2207,9 +2156,6 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -2368,9 +2314,6 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2426,9 +2369,6 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -2466,16 +2406,10 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"id0": {
|
"id0": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2626,9 +2560,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2661,9 +2592,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1014066025,
|
"seed": 1014066025,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2719,9 +2647,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -2776,9 +2701,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -2946,9 +2868,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -2983,9 +2902,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1014066025,
|
"seed": 1014066025,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -3041,9 +2957,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -3098,9 +3011,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -3162,16 +3072,10 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id9",
|
"id9",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3180,16 +3084,10 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id9",
|
"id9",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3340,9 +3238,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
@@ -3375,9 +3270,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1898319239,
|
"seed": 1898319239,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
@@ -3433,9 +3325,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -3490,9 +3379,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -3525,16 +3411,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
@@ -3557,16 +3437,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 5,
|
"version": 5,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3588,16 +3462,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 6,
|
"version": 6,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 5,
|
"version": 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3618,16 +3486,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"version": 7,
|
"version": 7,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"version": 6,
|
"version": 6,
|
||||||
},
|
},
|
||||||
@@ -3650,16 +3512,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 8,
|
"version": 8,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 7,
|
"version": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3681,16 +3537,10 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 9,
|
"version": 9,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 8,
|
"version": 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3723,9 +3573,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"version": 4,
|
"version": 4,
|
||||||
@@ -3735,9 +3582,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"version": 3,
|
"version": 3,
|
||||||
@@ -3888,9 +3732,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 238820263,
|
"seed": 238820263,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -3923,9 +3764,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -3981,9 +3819,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -4038,9 +3873,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -4074,16 +3906,10 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"index": "Zz",
|
"index": "Zz",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4232,9 +4058,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1014066025,
|
"seed": 1014066025,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -4267,9 +4090,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -4325,9 +4145,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -4382,9 +4199,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -4418,16 +4232,10 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"index": "Zz",
|
"index": "Zz",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4579,9 +4387,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -4614,9 +4419,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 238820263,
|
"seed": 238820263,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -4672,9 +4474,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -4729,9 +4528,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -4793,16 +4589,10 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id9",
|
"id9",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4811,16 +4601,10 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id9",
|
"id9",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4848,36 +4632,24 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"id0": {
|
"id0": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 5,
|
"version": 5,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id9",
|
"id9",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 5,
|
"version": 5,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id9",
|
"id9",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -5903,9 +5675,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -5938,9 +5707,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 400692809,
|
"seed": 400692809,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -5996,9 +5762,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -6053,9 +5816,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -7139,9 +6899,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -7176,9 +6933,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 238820263,
|
"seed": 238820263,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -7234,9 +6988,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -7291,9 +7042,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
@@ -7377,16 +7125,10 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id12",
|
"id12",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -7395,16 +7137,10 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id12",
|
"id12",
|
||||||
],
|
],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -10094,9 +9830,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -10129,9 +9862,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1,
|
"seed": 1,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -10164,9 +9894,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1,
|
"seed": 1,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -10228,9 +9955,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] un
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -74,9 +71,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -111,9 +105,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -159,9 +150,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -198,9 +186,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`export > export svg-embedded scene > svg-embdedded scene export output 1`] = `
|
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 -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTwW7bMFxmve8rXGb3OrROhu6QW7t1WFx1MDAwZt1hXHUwMDE5sMOwg2oxNmFFXHUwMDEyJLpJXHUwMDE2XHUwMDA06Gfstl/sJ5RSXFwrcrtcdTAwMThcYqBHinzke9q/K4qSdlx1MDAxNspFUcK2XHUwMDE2XG6lXHUwMDEzm/J9wFx1MDAxZsB5NJpD83j2pnd1zGyJ7OLiQlx1MDAxOb7QXHUwMDFhT4tcdTAwMGZVVVx1MDAxZC+BgjVo8pz2i89FsY//XHUwMDFjQVx1MDAxOa5exbRcYrz0JdhSQrdcZlXjaZedNiipZWT2cYRawKalXHUwMDFjXHUwMDEzulGQXfTkTFx1MDAwN5+MMi50PJtB+FLTe1F3jTO9lmNcdTAwMGU5ob1cdTAwMTWOh0l5K1RqSbtYnffB2yonPX5cdTAwMGVcdTAwMTTnXHUwMDEz/H+3uGnTavBhYbNcdTAwMTE1VtRIYfhZlaZcYlxm7a2Mu/2dODmxhtuwXFzdKzXCqCVsp2BcdTAwMWNx6JZcdTAwMDU8gMxcdTAwMTgk8Vx1MDAxM+brXHUwMDE21mJJgsIkL8JcdTAwMTZxV3VcdTAwMTdq7lx1MDAwZlx1MDAwM3iYXHUwMDE2+mZ0nWuC/jObhWLflVBcdTAwMWWSXHUwMDFhgeVNMlLGtLdS0ISsQt1N89ic3Vx1MDAxYrWj2ViGp39/XHUwMDFmT3Q1mpb4J1x1MDAxMJxXXHUwMDE5+kWsUVx1MDAwNSEus1x1MDAxMldcbpuwnFLB6sRcdTAwMWY8LCE/ijFMxqZozfVcdTAwMDRqcK/VMlx1MDAwZVx1MDAxYtRC/XiTnujJfFx1MDAwN38kSK6H08nh6/hcdTAwMDTO55cxXHUwMDEwVIhcdTAwMDYphbW5XmxcIpSTUY9cdTAwMTiBTWNG6M5IuNHiXk33WD4gbK5fv5mzVfxccsRcdTAwMDdcdO56RbhkqWtiXHUwMDFmjC6JXHUwMDBlXHRcdTAwMGZcblx1MDAwNujwXGZOizVKIn0=<!-- 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 -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTS27bMFx1MDAxMN33XHUwMDE0grItXHUwMDEy2UW68C7NXHUwMDA3zVwiXdRcdTAwMDW6KLpgxLE0ME1cdTAwMTLkKLZrXHUwMDE4yDG661x1MDAxNXOEXGZpVTTlRFx1MDAwMlxi8M3vzZvh7kNRlLS1UM6KXHUwMDEyNrVQKJ1Yl1x1MDAxZlx1MDAwM/5cdTAwMDTOo9Fsmsa7N52ro2dLZGdcdTAwMTdcdTAwMTfKcEBrPM0+VVV1XGJcdTAwMDJcdTAwMDUr0OTZ7Vx1MDAxN9+LYlx1MDAxN0+2oFxmoVfRLVx1MDAwMv/rXHUwMDEybCihXHUwMDFihqrhts1ua5TUMjL5PEAtYNNSjlx03SjIXHUwMDAyPTmzhGujjFx1MDAwYlx1MDAxNc8mXHUwMDEw/lT0UdTLxplOy8GHnNDeXG7HzSS/XHUwMDA1KjWnbczOerBa5ajGz57idIS/XHUwMDE3xUWbVoNcdTAwMGaCTVx1MDAwNtRYUSOF5idV6lwiMLT3Mmr7O3FyYlx1MDAwNfdBXFzdKTXAqCVsxmBssa+WXHUwMDE5PIDMXHUwMDE4pOGfYN+MrnN50d/w3CmmWFxi5SFcdFx1MDAxYlxu3qadyIp2VlxuXHUwMDFh1VWol2M/3rPlXHUwMDFiuePesKIv//4+XHUwMDFmjchomuOfQHBaZeidWKFcbppeZimuXHUwMDE0NqHPUsHiaNTcLCHv92AmY5O15nxcdTAwMDI1uFPhjcNcdTAwMDa1UD/epCc6Mt/BXHUwMDFmXGKS6+C4c/g6bPP59DJcdTAwMWH2fMZZl8LaObFebD28Kd5cdTAwMDeUo1ZcdTAwMGZcdTAwMTiBTW1G6MFIuNXiUY11LJ9cdTAwMTDWX07X/2xcdTAwMTG/nng/godOXHUwMDExznnUNfFcdTAwMWWEee5cdTAwMDK/feTHb1x1MDAwM3po/1xua2IoWiJ9<!-- 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>"
|
</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
@@ -18,9 +18,6 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -53,9 +50,6 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1505387817,
|
"seed": 1505387817,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -88,9 +82,6 @@ exports[`move element > rectangle 5`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -128,9 +119,6 @@ exports[`move element > rectangles with binding arrow 5`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -168,9 +156,6 @@ exports[`move element > rectangles with binding arrow 6`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1116226695,
|
"seed": 1116226695,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -226,9 +211,6 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 23633383,
|
"seed": 23633383,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ exports[`multi point mode in linear elements > arrow 3`] = `
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -91,9 +88,6 @@ exports[`multi point mode in linear elements > line 3`] = `
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -33,9 +33,6 @@ exports[`select single element on the scene > arrow 1`] = `
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -83,9 +80,6 @@ exports[`select single element on the scene > arrow escape 1`] = `
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -120,9 +114,6 @@ exports[`select single element on the scene > diamond 1`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -155,9 +146,6 @@ exports[`select single element on the scene > ellipse 1`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -190,9 +178,6 @@ exports[`select single element on the scene > rectangle 1`] = `
|
|||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ exports[`repairing bindings > should strip arrow binding if repair throws 1`] =
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -81,9 +78,6 @@ exports[`restoreElements > should restore arrow element correctly 1`] = `
|
|||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -124,9 +118,6 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 3,
|
"type": 3,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "red",
|
"strokeColor": "red",
|
||||||
"strokeStyle": "dashed",
|
"strokeStyle": "dashed",
|
||||||
@@ -165,9 +156,6 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 3,
|
"type": 3,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "red",
|
"strokeColor": "red",
|
||||||
"strokeStyle": "dashed",
|
"strokeStyle": "dashed",
|
||||||
@@ -206,9 +194,6 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 3,
|
"type": 3,
|
||||||
},
|
},
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "red",
|
"strokeColor": "red",
|
||||||
"strokeStyle": "dashed",
|
"strokeStyle": "dashed",
|
||||||
@@ -252,9 +237,6 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
|
|||||||
"pressures": [],
|
"pressures": [],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"simulatePressure": true,
|
"simulatePressure": true,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
@@ -301,9 +283,6 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] =
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -351,9 +330,6 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] =
|
|||||||
"polygon": false,
|
"polygon": false,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -394,9 +370,6 @@ exports[`restoreElements > should restore text element correctly passing value f
|
|||||||
"originalText": "text",
|
"originalText": "text",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
@@ -439,9 +412,6 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
|||||||
"originalText": "",
|
"originalText": "",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"schemaState": {
|
|
||||||
"tracks": {},
|
|
||||||
},
|
|
||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { pointFrom } from "@excalidraw/math";
|
import { pointFrom } from "@excalidraw/math";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import {
|
import { DEFAULT_SIDEBAR, FONT_FAMILY, ROUNDNESS } from "@excalidraw/common";
|
||||||
DEFAULT_ELEMENT_PROPS,
|
|
||||||
DEFAULT_SIDEBAR,
|
|
||||||
FONT_FAMILY,
|
|
||||||
ROUNDNESS,
|
|
||||||
} from "@excalidraw/common";
|
|
||||||
|
|
||||||
import { newElementWith } from "@excalidraw/element";
|
import { newElementWith } from "@excalidraw/element";
|
||||||
import * as sizeHelpers from "@excalidraw/element";
|
import * as sizeHelpers from "@excalidraw/element";
|
||||||
@@ -24,10 +19,8 @@ import type { NormalizedZoomValue } from "@excalidraw/excalidraw/types";
|
|||||||
|
|
||||||
import { API } from "../helpers/api";
|
import { API } from "../helpers/api";
|
||||||
import * as restore from "../../data/restore";
|
import * as restore from "../../data/restore";
|
||||||
import { createSchemaMigrationRegistry } from "../../data/schema";
|
|
||||||
import { getDefaultAppState } from "../../appState";
|
import { getDefaultAppState } from "../../appState";
|
||||||
|
|
||||||
import type { SchemaPlugin } from "../../data/schema";
|
|
||||||
import type { ImportedDataState } from "../../data/types";
|
import type { ImportedDataState } from "../../data/types";
|
||||||
|
|
||||||
describe("restoreElements", () => {
|
describe("restoreElements", () => {
|
||||||
@@ -88,109 +81,6 @@ describe("restoreElements", () => {
|
|||||||
).toEqual([expect.objectContaining({ isDeleted: true })]);
|
).toEqual([expect.objectContaining({ isDeleted: true })]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should restore frame element", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
});
|
|
||||||
|
|
||||||
const restoredFrame = restore.restoreElements(
|
|
||||||
[frame],
|
|
||||||
null,
|
|
||||||
)[0] as ExcalidrawElement;
|
|
||||||
|
|
||||||
expect(restoredFrame.type).toBe("frame");
|
|
||||||
expect(restoredFrame.backgroundColor).toBe("#ffc9c9");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should restore library frame backgrounds by default", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#a5d8ff",
|
|
||||||
});
|
|
||||||
|
|
||||||
const restoredLibraryItems = restore.restoreLibraryItems(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: "library-item-1",
|
|
||||||
status: "published",
|
|
||||||
elements: [frame],
|
|
||||||
created: Date.now(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"published",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
(restoredLibraryItems[0].elements[0] as ExcalidrawElement)
|
|
||||||
.backgroundColor,
|
|
||||||
).toBe("#a5d8ff");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should normalize legacy library frame backgrounds when schema is old", () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
backgroundColor: "#a5d8ff",
|
|
||||||
});
|
|
||||||
const legacyFrame = {
|
|
||||||
...frame,
|
|
||||||
schemaState: { tracks: {} },
|
|
||||||
};
|
|
||||||
|
|
||||||
const restoredLibraryItems = restore.restoreLibraryItems(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: "library-item-1",
|
|
||||||
status: "published",
|
|
||||||
elements: [legacyFrame],
|
|
||||||
created: Date.now(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"published",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
(restoredLibraryItems[0].elements[0] as ExcalidrawElement)
|
|
||||||
.backgroundColor,
|
|
||||||
).toBe(DEFAULT_ELEMENT_PROPS.backgroundColor);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should apply schema plugins on restore boundaries when provided", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
backgroundColor: "#ffd8a8",
|
|
||||||
});
|
|
||||||
const plugin: SchemaPlugin = {
|
|
||||||
id: "myapp",
|
|
||||||
migrations: [
|
|
||||||
{
|
|
||||||
id: "host.myapp.rect.normalize.v2",
|
|
||||||
namespace: "host.myapp",
|
|
||||||
track: "host.myapp.rectangle",
|
|
||||||
toVersion: 2,
|
|
||||||
title: "normalize rectangle background",
|
|
||||||
description: "plugin migration for restore test",
|
|
||||||
targetTypes: ["rectangle"],
|
|
||||||
apply: (element) =>
|
|
||||||
element.type === "rectangle"
|
|
||||||
? { ...element, backgroundColor: "#12b886" }
|
|
||||||
: element,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const restoredWithoutPlugin = restore.restoreElements([rect], null);
|
|
||||||
const restoredWithPlugin = restore.restoreElements([rect], null, {
|
|
||||||
schemaMigrationRegistry: createSchemaMigrationRegistry([plugin]),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(restoredWithoutPlugin[0].backgroundColor).toBe("#ffd8a8");
|
|
||||||
expect(restoredWithPlugin[0].backgroundColor).toBe("#12b886");
|
|
||||||
expect(
|
|
||||||
restoredWithPlugin[0].schemaState.tracks["host.myapp.rectangle"],
|
|
||||||
).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should restore text element correctly passing value for each attribute", () => {
|
it("should restore text element correctly passing value for each attribute", () => {
|
||||||
const textElement = API.createElement({
|
const textElement = API.createElement({
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
|
|||||||
index: null,
|
index: null,
|
||||||
seed: 1041657908,
|
seed: 1041657908,
|
||||||
version: 120,
|
version: 120,
|
||||||
schemaState: {
|
|
||||||
tracks: {},
|
|
||||||
},
|
|
||||||
versionNonce: 1188004276,
|
versionNonce: 1188004276,
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
boundElements: null,
|
boundElements: null,
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ export class API {
|
|||||||
roundness?: ExcalidrawGenericElement["roundness"];
|
roundness?: ExcalidrawGenericElement["roundness"];
|
||||||
roughness?: ExcalidrawGenericElement["roughness"];
|
roughness?: ExcalidrawGenericElement["roughness"];
|
||||||
opacity?: ExcalidrawGenericElement["opacity"];
|
opacity?: ExcalidrawGenericElement["opacity"];
|
||||||
schemaState?: ExcalidrawGenericElement["schemaState"];
|
|
||||||
// text props
|
// text props
|
||||||
text?: T extends "text" ? ExcalidrawTextElement["text"] : never;
|
text?: T extends "text" ? ExcalidrawTextElement["text"] : never;
|
||||||
fontSize?: T extends "text" ? ExcalidrawTextElement["fontSize"] : never;
|
fontSize?: T extends "text" ? ExcalidrawTextElement["fontSize"] : never;
|
||||||
@@ -247,7 +246,6 @@ export class API {
|
|||||||
| "groupIds"
|
| "groupIds"
|
||||||
| "link"
|
| "link"
|
||||||
| "updated"
|
| "updated"
|
||||||
| "schemaState"
|
|
||||||
> = {
|
> = {
|
||||||
seed: 1,
|
seed: 1,
|
||||||
x,
|
x,
|
||||||
@@ -278,7 +276,6 @@ export class API {
|
|||||||
opacity: rest.opacity ?? appState.currentItemOpacity,
|
opacity: rest.opacity ?? appState.currentItemOpacity,
|
||||||
boundElements: rest.boundElements ?? null,
|
boundElements: rest.boundElements ?? null,
|
||||||
locked: rest.locked ?? false,
|
locked: rest.locked ?? false,
|
||||||
...(rest.schemaState ? { schemaState: rest.schemaState } : {}),
|
|
||||||
};
|
};
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
|
|||||||
@@ -359,7 +359,6 @@ describe("Basic lasso selection tests", () => {
|
|||||||
...e,
|
...e,
|
||||||
angle: e.angle as Radians,
|
angle: e.angle as Radians,
|
||||||
index: null,
|
index: null,
|
||||||
schemaState: { tracks: {} },
|
|
||||||
} as ExcalidrawElement),
|
} as ExcalidrawElement),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1045,7 +1044,6 @@ describe("Special cases", () => {
|
|||||||
...e,
|
...e,
|
||||||
index: null,
|
index: null,
|
||||||
angle: e.angle as Radians,
|
angle: e.angle as Radians,
|
||||||
schemaState: { tracks: {} },
|
|
||||||
})) as ExcalidrawElement[];
|
})) as ExcalidrawElement[];
|
||||||
|
|
||||||
h.elements = elements;
|
h.elements = elements;
|
||||||
@@ -1765,7 +1763,6 @@ describe("Special cases", () => {
|
|||||||
...e,
|
...e,
|
||||||
index: null,
|
index: null,
|
||||||
angle: e.angle as Radians,
|
angle: e.angle as Radians,
|
||||||
schemaState: { tracks: {} },
|
|
||||||
})) as ExcalidrawElement[];
|
})) as ExcalidrawElement[];
|
||||||
|
|
||||||
h.elements = elements;
|
h.elements = elements;
|
||||||
|
|||||||
@@ -167,12 +167,6 @@ describe("library", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should serialize library payload", () => {
|
|
||||||
const serialized = serializeLibraryAsJSON([]);
|
|
||||||
const parsed = JSON.parse(serialized);
|
|
||||||
expect(parsed.type).toBe("excalidrawlib");
|
|
||||||
});
|
|
||||||
|
|
||||||
// NOTE: mocked to test logic, not actual drag&drop via UI
|
// NOTE: mocked to test logic, not actual drag&drop via UI
|
||||||
it("drop library item onto canvas", async () => {
|
it("drop library item onto canvas", async () => {
|
||||||
expect(h.elements).toEqual([]);
|
expect(h.elements).toEqual([]);
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ exports[`exportToSvg > with elements that have a link 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`exportToSvg > with exportEmbedScene 1`] = `
|
exports[`exportToSvg > with exportEmbedScene 1`] = `
|
||||||
"<!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1WW2/TMFx1MDAxNH7fr4jCK2JJ2q6lb4VxmYRAokiTmPbgxSeJVcfObKdcdTAwMTeq/nd8nDZOwsRcdTAwMDNPXGaaSpF8rt/5/Fx1MDAxZKX7iyBcYs2ugnBcdTAwMWWEsE1cdGdUkU34XHUwMDEy7WtQmklhXYk7a1mr1EVcdTAwMTbGVPPLSy5tQiG1mY+iKGqSgENcdMJoXHUwMDFidmfPQbB3b+thXHUwMDE0U9e36vHLXCJbMUGTx7ScfrpefPjuUl3Q1saM43F73mH30bQ9b1x1MDAxODWFtcVR1NpcbmB5YVx1MDAwNkZcInKOWL1FXHUwMDFiJVfwVnKpXHUwMDEwyIvIPb71XHUwMDAzSVe5krWgPiaeXHUwMDEw8pD5mIxxvjQ73rBA0qJWXHUwMDEwXHUwMDBlOtyeIFx1MDAwZextnpaWZZ9lW+aFXHUwMDAwrXs5slwiKTO7wVSIr7qhjt17j0qRXHUwMDEybpBeUXPeLSzosXDPYblcdTAwMDckOiSd8TVcdTAwMDB13cbx1WT6Opq1XHUwMDFlr4M46dCZXHUwMDE2UJKlIVx1MDAwNoc63TLKSVlcIrHn/nA0XHUwMDFlhqU+S+GEXHUwMDE0x7NZXHUwMDE0jZPplcemr62EjMOSXHUwMDExrsHfXHUwMDBmjvPOy6s3Ul1R0iR5XG45XHUwMDEzq2GclezqidqnXHUwMDE1oIyUUtDwolx1MDAwM/ss4L9YwPFZwD1cdTAwMDFcdTAwMDPnrNLwPFx1MDAwNFx1MDAxY1x1MDAwM/7+b1x1MDAwMSdnXHUwMDAx91x1MDAwNGxgazq3LYVZslx1MDAxZujpXGaO1vekZFx1MDAxYy9n4ktgqi0hXHUwMDE1y5kgPOjXOpm//T5cZk9cdTAwMGLOcmQ75JB1XFyWO8PsX57WbWTlvalFRZhcdTAwMDD1q1x1MDAwZSxcdTAwMGbwsd2RV4nHTGojv4JuRnSMnDf3mWzu6Ly5XHLmP91cXFx1MDAwZvxf2lxc+3bqXG5JVfVvOFxcM9i8eWJTMvfgN9vdNm5cblx1MDAxY0Vw+Fx0stl5kyJ9<!-- payload-end --></metadata><defs><style class="style-fonts">
|
"<!-- 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">
|
||||||
@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,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: 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==); }
|
@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==); }
|
||||||
|
|||||||
@@ -279,7 +279,6 @@ describe("exporting frames", () => {
|
|||||||
height: 100,
|
height: 100,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
});
|
});
|
||||||
const frameChild = API.createElement({
|
const frameChild = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
@@ -312,58 +311,11 @@ describe("exporting frames", () => {
|
|||||||
expect(
|
expect(
|
||||||
svg.querySelector(`[data-id="${rectOverlapping.id}"]`),
|
svg.querySelector(`[data-id="${rectOverlapping.id}"]`),
|
||||||
).not.toBeNull();
|
).not.toBeNull();
|
||||||
// exporting frame background should still be rendered
|
|
||||||
const frameBackgroundNode = svg.querySelector('rect[fill="#ffc9c9"]');
|
|
||||||
expect(frameBackgroundNode).not.toBeNull();
|
|
||||||
expect(frameBackgroundNode).not.toHaveAttribute("rx");
|
|
||||||
expect(frameBackgroundNode).not.toHaveAttribute("ry");
|
|
||||||
expect(
|
|
||||||
frameBackgroundNode!.compareDocumentPosition(
|
|
||||||
svg.querySelector(`[data-id="${frameChild.id}"]`)!,
|
|
||||||
) & Node.DOCUMENT_POSITION_FOLLOWING,
|
|
||||||
).toBeTruthy();
|
|
||||||
|
|
||||||
expect(svg.getAttribute("width")).toBe(frame.width.toString());
|
expect(svg.getAttribute("width")).toBe(frame.width.toString());
|
||||||
expect(svg.getAttribute("height")).toBe(frame.height.toString());
|
expect(svg.getAttribute("height")).toBe(frame.height.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not render SVG frame background for empty/transparent exporting frame bg", async () => {
|
|
||||||
const transparentFrame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
});
|
|
||||||
const emptyBgFrame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
backgroundColor: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const transparentSvg = await exportToSvg({
|
|
||||||
elements: [transparentFrame],
|
|
||||||
files: null,
|
|
||||||
exportPadding: 0,
|
|
||||||
exportingFrame: transparentFrame,
|
|
||||||
});
|
|
||||||
const emptyBgSvg = await exportToSvg({
|
|
||||||
elements: [emptyBgFrame],
|
|
||||||
files: null,
|
|
||||||
exportPadding: 0,
|
|
||||||
exportingFrame: emptyBgFrame,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
transparentSvg.querySelector('rect[fill="transparent"]'),
|
|
||||||
).toBeNull();
|
|
||||||
expect(emptyBgSvg.querySelector('rect[fill=""]')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should filter non-overlapping elements when exporting a frame", async () => {
|
it("should filter non-overlapping elements when exporting a frame", async () => {
|
||||||
const frame = API.createElement({
|
const frame = API.createElement({
|
||||||
type: "frame",
|
type: "frame",
|
||||||
@@ -517,103 +469,6 @@ describe("exporting frames", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render frame with its background color", async () => {
|
|
||||||
const frame = API.createElement({
|
|
||||||
type: "frame",
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
});
|
|
||||||
const frameChild = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
x: 10,
|
|
||||||
y: 10,
|
|
||||||
frameId: frame.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { exportedElements, exportingFrame } = prepareElementsForExport(
|
|
||||||
[frameChild, frame],
|
|
||||||
{
|
|
||||||
selectedElementIds: {},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const svg = await exportToSvg({
|
|
||||||
elements: exportedElements,
|
|
||||||
files: null,
|
|
||||||
exportPadding: 0,
|
|
||||||
exportingFrame,
|
|
||||||
});
|
|
||||||
|
|
||||||
const frameSvgNode = svg.querySelector(`[data-id="${frame.id}"]`);
|
|
||||||
const childSvgNode = svg.querySelector(`[data-id="${frameChild.id}"]`);
|
|
||||||
const frameBackgroundNode = svg.querySelector('rect[fill="#ffc9c9"]');
|
|
||||||
|
|
||||||
expect(frameSvgNode).not.toBeNull();
|
|
||||||
expect(childSvgNode).not.toBeNull();
|
|
||||||
expect(frameBackgroundNode).not.toBeNull();
|
|
||||||
expect(frameSvgNode).toHaveAttribute("fill", "none");
|
|
||||||
expect(frameBackgroundNode).toHaveAttribute(
|
|
||||||
"rx",
|
|
||||||
FRAME_STYLE.radius.toString(),
|
|
||||||
);
|
|
||||||
expect(frameBackgroundNode).toHaveAttribute(
|
|
||||||
"ry",
|
|
||||||
FRAME_STYLE.radius.toString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
frameBackgroundNode!.compareDocumentPosition(childSvgNode!) &
|
|
||||||
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
childSvgNode!.compareDocumentPosition(frameSvgNode!) &
|
|
||||||
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not render magicframe background", async () => {
|
|
||||||
const magicframe = API.createElement({
|
|
||||||
type: "magicframe",
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
backgroundColor: "#ffc9c9",
|
|
||||||
});
|
|
||||||
const frameChild = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
x: 10,
|
|
||||||
y: 10,
|
|
||||||
frameId: magicframe.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { exportedElements, exportingFrame } = prepareElementsForExport(
|
|
||||||
[frameChild, magicframe],
|
|
||||||
{
|
|
||||||
selectedElementIds: {},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const svg = await exportToSvg({
|
|
||||||
elements: exportedElements,
|
|
||||||
files: null,
|
|
||||||
exportPadding: 0,
|
|
||||||
exportingFrame,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(svg.querySelector(`[data-id="${magicframe.id}"]`)).not.toBeNull();
|
|
||||||
expect(svg.querySelector('rect[fill="#ffc9c9"]')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not export frame-overlapping elements belonging to different frame", async () => {
|
it("should not export frame-overlapping elements belonging to different frame", async () => {
|
||||||
const frame1 = API.createElement({
|
const frame1 = API.createElement({
|
||||||
type: "frame",
|
type: "frame",
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import type { FileSystemHandle } from "./data/filesystem";
|
|||||||
import type { ContextMenuItems } from "./components/ContextMenu";
|
import type { ContextMenuItems } from "./components/ContextMenu";
|
||||||
import type { SnapLine } from "./snapping";
|
import type { SnapLine } from "./snapping";
|
||||||
import type { ImportedDataState } from "./data/types";
|
import type { ImportedDataState } from "./data/types";
|
||||||
import type { SchemaMigrationRegistry, SchemaPlugin } from "./data/schema";
|
|
||||||
|
|
||||||
import type { Language } from "./i18n";
|
import type { Language } from "./i18n";
|
||||||
import type { isOverScrollBars } from "./scene/scrollbars";
|
import type { isOverScrollBars } from "./scene/scrollbars";
|
||||||
@@ -641,11 +640,6 @@ export interface ExcalidrawProps {
|
|||||||
aiEnabled?: boolean;
|
aiEnabled?: boolean;
|
||||||
showDeprecatedFonts?: boolean;
|
showDeprecatedFonts?: boolean;
|
||||||
renderScrollbars?: boolean;
|
renderScrollbars?: boolean;
|
||||||
/**
|
|
||||||
* Optional host-provided schema migration plugins.
|
|
||||||
* Applied on restore/import boundaries when provided.
|
|
||||||
*/
|
|
||||||
schemaPlugins?: readonly SchemaPlugin[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SceneData = {
|
export type SceneData = {
|
||||||
@@ -764,7 +758,6 @@ export type AppClassProperties = {
|
|||||||
getEditorUIOffsets: App["getEditorUIOffsets"];
|
getEditorUIOffsets: App["getEditorUIOffsets"];
|
||||||
visibleElements: App["visibleElements"];
|
visibleElements: App["visibleElements"];
|
||||||
excalidrawContainerValue: App["excalidrawContainerValue"];
|
excalidrawContainerValue: App["excalidrawContainerValue"];
|
||||||
getSchemaMigrationRegistry: () => SchemaMigrationRegistry;
|
|
||||||
|
|
||||||
onPointerUpEmitter: App["onPointerUpEmitter"];
|
onPointerUpEmitter: App["onPointerUpEmitter"];
|
||||||
updateEditorAtom: App["updateEditorAtom"];
|
updateEditorAtom: App["updateEditorAtom"];
|
||||||
@@ -874,7 +867,6 @@ export interface ExcalidrawImperativeAPI {
|
|||||||
resetCursor: InstanceType<typeof App>["resetCursor"];
|
resetCursor: InstanceType<typeof App>["resetCursor"];
|
||||||
toggleSidebar: InstanceType<typeof App>["toggleSidebar"];
|
toggleSidebar: InstanceType<typeof App>["toggleSidebar"];
|
||||||
getEditorInterface: () => EditorInterface;
|
getEditorInterface: () => EditorInterface;
|
||||||
getSchemaMigrationRegistry: () => SchemaMigrationRegistry;
|
|
||||||
/**
|
/**
|
||||||
* Disables rendering of frames (including element clipping), but currently
|
* Disables rendering of frames (including element clipping), but currently
|
||||||
* the frames are still interactive in edit mode. As such, this API should be
|
* the frames are still interactive in edit mode. As such, this API should be
|
||||||
|
|||||||
Reference in New Issue
Block a user