feat: Make repair and refreshDimensions configurable in restoreElements (#6238)
* fix: don't repair during reconcilation * Add opts to restoreElement and enable refreshDimensions and repair via config * remove * update changelog * fix tests * rename to repairBindings
This commit is contained in:
parent
71fb60394a
commit
0d7ee891e0
@ -836,7 +836,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const scene = restore(initialData, null, null);
|
const scene = restore(initialData, null, null, { repairBindings: true });
|
||||||
scene.appState = {
|
scene.appState = {
|
||||||
...scene.appState,
|
...scene.appState,
|
||||||
theme: this.props.theme || scene.appState.theme,
|
theme: this.props.theme || scene.appState.theme,
|
||||||
|
@ -156,6 +156,7 @@ export const loadSceneOrLibraryFromBlob = async (
|
|||||||
},
|
},
|
||||||
localAppState,
|
localAppState,
|
||||||
localElements,
|
localElements,
|
||||||
|
{ repairBindings: true },
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
} else if (isValidLibrary(data)) {
|
} else if (isValidLibrary(data)) {
|
||||||
|
@ -339,7 +339,7 @@ export const restoreElements = (
|
|||||||
elements: ImportedDataState["elements"],
|
elements: ImportedDataState["elements"],
|
||||||
/** NOTE doesn't serve for reconciliation */
|
/** NOTE doesn't serve for reconciliation */
|
||||||
localElements: readonly ExcalidrawElement[] | null | undefined,
|
localElements: readonly ExcalidrawElement[] | null | undefined,
|
||||||
refreshDimensions = false,
|
opts?: { refreshDimensions?: boolean; repairBindings?: boolean } | undefined,
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
||||||
const restoredElements = (elements || []).reduce((elements, element) => {
|
const restoredElements = (elements || []).reduce((elements, element) => {
|
||||||
@ -348,7 +348,7 @@ export const restoreElements = (
|
|||||||
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
||||||
let migratedElement: ExcalidrawElement | null = restoreElement(
|
let migratedElement: ExcalidrawElement | null = restoreElement(
|
||||||
element,
|
element,
|
||||||
refreshDimensions,
|
opts?.refreshDimensions,
|
||||||
);
|
);
|
||||||
if (migratedElement) {
|
if (migratedElement) {
|
||||||
const localElement = localElementsMap?.get(element.id);
|
const localElement = localElementsMap?.get(element.id);
|
||||||
@ -361,6 +361,10 @@ export const restoreElements = (
|
|||||||
return elements;
|
return elements;
|
||||||
}, [] as ExcalidrawElement[]);
|
}, [] as ExcalidrawElement[]);
|
||||||
|
|
||||||
|
if (!opts?.repairBindings) {
|
||||||
|
return restoredElements;
|
||||||
|
}
|
||||||
|
|
||||||
// repair binding. Mutates elements.
|
// repair binding. Mutates elements.
|
||||||
const restoredElementsMap = arrayToMap(restoredElements);
|
const restoredElementsMap = arrayToMap(restoredElements);
|
||||||
for (const element of restoredElements) {
|
for (const element of restoredElements) {
|
||||||
@ -497,9 +501,10 @@ export const restore = (
|
|||||||
*/
|
*/
|
||||||
localAppState: Partial<AppState> | null | undefined,
|
localAppState: Partial<AppState> | null | undefined,
|
||||||
localElements: readonly ExcalidrawElement[] | null | undefined,
|
localElements: readonly ExcalidrawElement[] | null | undefined,
|
||||||
|
elementsConfig?: { refreshDimensions?: boolean; repairBindings?: boolean },
|
||||||
): RestoredDataState => {
|
): RestoredDataState => {
|
||||||
return {
|
return {
|
||||||
elements: restoreElements(data?.elements, localElements),
|
elements: restoreElements(data?.elements, localElements, elementsConfig),
|
||||||
appState: restoreAppState(data?.appState, localAppState || null),
|
appState: restoreAppState(data?.appState, localAppState || null),
|
||||||
files: data?.files || {},
|
files: data?.files || {},
|
||||||
};
|
};
|
||||||
|
@ -610,7 +610,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
const localElements = this.getSceneElementsIncludingDeleted();
|
const localElements = this.getSceneElementsIncludingDeleted();
|
||||||
const appState = this.excalidrawAPI.getAppState();
|
const appState = this.excalidrawAPI.getAppState();
|
||||||
|
|
||||||
remoteElements = restoreElements(remoteElements, null, false);
|
remoteElements = restoreElements(remoteElements, null);
|
||||||
|
|
||||||
const reconciledElements = _reconcileElements(
|
const reconciledElements = _reconcileElements(
|
||||||
localElements,
|
localElements,
|
||||||
|
@ -263,9 +263,12 @@ export const loadScene = async (
|
|||||||
await importFromBackend(id, privateKey),
|
await importFromBackend(id, privateKey),
|
||||||
localDataState?.appState,
|
localDataState?.appState,
|
||||||
localDataState?.elements,
|
localDataState?.elements,
|
||||||
|
{ repairBindings: true },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
data = restore(localDataState || null, null, null);
|
data = restore(localDataState || null, null, null, {
|
||||||
|
repairBindings: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -362,7 +362,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
if (data.scene) {
|
if (data.scene) {
|
||||||
excalidrawAPI.updateScene({
|
excalidrawAPI.updateScene({
|
||||||
...data.scene,
|
...data.scene,
|
||||||
...restore(data.scene, null, null),
|
...restore(data.scene, null, null, { repairBindings: true }),
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,24 @@ The change should be grouped under one of the below section and must contain PR
|
|||||||
Please add the latest change on the top under the correct section.
|
Please add the latest change on the top under the correct section.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) API now takes an optional parameter `opts` which currently supports the below attributes
|
||||||
|
|
||||||
|
```js
|
||||||
|
{ refreshDimensions?: boolean, repair?: boolean }
|
||||||
|
```
|
||||||
|
|
||||||
|
The same `opts` param has been added to [`restore`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restore) API as well.
|
||||||
|
|
||||||
|
For more details refer to the [docs](https://docs.excalidraw.com)
|
||||||
|
|
||||||
|
#### BREAKING CHANGE
|
||||||
|
|
||||||
|
- The optional parameter `refreshDimensions` in [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) has been removed and can be enabled via `opts`
|
||||||
|
|
||||||
## 0.14.2 (2023-02-01)
|
## 0.14.2 (2023-02-01)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
@ -534,7 +534,7 @@ describe("restore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("repairing bindings", () => {
|
describe("repairing bindings", () => {
|
||||||
it("should repair container boundElements", () => {
|
it("should repair container boundElements when repair is true", () => {
|
||||||
const container = API.createElement({
|
const container = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
boundElements: [],
|
boundElements: [],
|
||||||
@ -546,11 +546,28 @@ describe("repairing bindings", () => {
|
|||||||
|
|
||||||
expect(container.boundElements).toEqual([]);
|
expect(container.boundElements).toEqual([]);
|
||||||
|
|
||||||
const restoredElements = restore.restoreElements(
|
let restoredElements = restore.restoreElements(
|
||||||
[container, boundElement],
|
[container, boundElement],
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(restoredElements).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: container.id,
|
||||||
|
boundElements: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: boundElement.id,
|
||||||
|
containerId: container.id,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
restoredElements = restore.restoreElements(
|
||||||
|
[container, boundElement],
|
||||||
|
null,
|
||||||
|
{ repairBindings: true },
|
||||||
|
);
|
||||||
|
|
||||||
expect(restoredElements).toEqual([
|
expect(restoredElements).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: container.id,
|
id: container.id,
|
||||||
@ -563,7 +580,7 @@ describe("repairing bindings", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should repair containerId of boundElements", () => {
|
it("should repair containerId of boundElements when repair is true", () => {
|
||||||
const boundElement = API.createElement({
|
const boundElement = API.createElement({
|
||||||
type: "text",
|
type: "text",
|
||||||
containerId: null,
|
containerId: null,
|
||||||
@ -573,11 +590,28 @@ describe("repairing bindings", () => {
|
|||||||
boundElements: [{ type: boundElement.type, id: boundElement.id }],
|
boundElements: [{ type: boundElement.type, id: boundElement.id }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const restoredElements = restore.restoreElements(
|
let restoredElements = restore.restoreElements(
|
||||||
[container, boundElement],
|
[container, boundElement],
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(restoredElements).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: container.id,
|
||||||
|
boundElements: [{ type: boundElement.type, id: boundElement.id }],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: boundElement.id,
|
||||||
|
containerId: null,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
restoredElements = restore.restoreElements(
|
||||||
|
[container, boundElement],
|
||||||
|
null,
|
||||||
|
{ repairBindings: true },
|
||||||
|
);
|
||||||
|
|
||||||
expect(restoredElements).toEqual([
|
expect(restoredElements).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: container.id,
|
id: container.id,
|
||||||
@ -620,7 +654,7 @@ describe("repairing bindings", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove bindings of deleted elements from boundElements", () => {
|
it("should remove bindings of deleted elements from boundElements when repair is true", () => {
|
||||||
const container = API.createElement({
|
const container = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
boundElements: [],
|
boundElements: [],
|
||||||
@ -642,6 +676,8 @@ describe("repairing bindings", () => {
|
|||||||
type: invisibleBoundElement.type,
|
type: invisibleBoundElement.type,
|
||||||
id: invisibleBoundElement.id,
|
id: invisibleBoundElement.id,
|
||||||
};
|
};
|
||||||
|
expect(container.boundElements).toEqual([]);
|
||||||
|
|
||||||
const nonExistentBinding = { type: "text", id: "non-existent" };
|
const nonExistentBinding = { type: "text", id: "non-existent" };
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
container.boundElements = [
|
container.boundElements = [
|
||||||
@ -650,17 +686,28 @@ describe("repairing bindings", () => {
|
|||||||
nonExistentBinding,
|
nonExistentBinding,
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(container.boundElements).toEqual([
|
let restoredElements = restore.restoreElements(
|
||||||
obsoleteBinding,
|
|
||||||
invisibleBinding,
|
|
||||||
nonExistentBinding,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const restoredElements = restore.restoreElements(
|
|
||||||
[container, invisibleBoundElement, boundElement],
|
[container, invisibleBoundElement, boundElement],
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(restoredElements).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: container.id,
|
||||||
|
boundElements: [obsoleteBinding, invisibleBinding, nonExistentBinding],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: boundElement.id,
|
||||||
|
containerId: container.id,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
restoredElements = restore.restoreElements(
|
||||||
|
[container, invisibleBoundElement, boundElement],
|
||||||
|
null,
|
||||||
|
{ repairBindings: true },
|
||||||
|
);
|
||||||
|
|
||||||
expect(restoredElements).toEqual([
|
expect(restoredElements).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: container.id,
|
id: container.id,
|
||||||
@ -673,7 +720,7 @@ describe("repairing bindings", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove containerId if container not exists", () => {
|
it("should remove containerId if container not exists when repair is true", () => {
|
||||||
const boundElement = API.createElement({
|
const boundElement = API.createElement({
|
||||||
type: "text",
|
type: "text",
|
||||||
containerId: "non-existent",
|
containerId: "non-existent",
|
||||||
@ -684,11 +731,28 @@ describe("repairing bindings", () => {
|
|||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const restoredElements = restore.restoreElements(
|
let restoredElements = restore.restoreElements(
|
||||||
[boundElement, boundElementDeleted],
|
[boundElement, boundElementDeleted],
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(restoredElements).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: boundElement.id,
|
||||||
|
containerId: "non-existent",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: boundElementDeleted.id,
|
||||||
|
containerId: "non-existent",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
restoredElements = restore.restoreElements(
|
||||||
|
[boundElement, boundElementDeleted],
|
||||||
|
null,
|
||||||
|
{ repairBindings: true },
|
||||||
|
);
|
||||||
|
|
||||||
expect(restoredElements).toEqual([
|
expect(restoredElements).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: boundElement.id,
|
id: boundElement.id,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user