|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
import {
|
|
|
|
|
ExcalidrawElement,
|
|
|
|
|
ExcalidrawSelectionElement,
|
|
|
|
|
ExcalidrawTextElement,
|
|
|
|
|
FontFamilyValues,
|
|
|
|
|
} from "../element/types";
|
|
|
|
|
import {
|
|
|
|
@ -16,7 +17,7 @@ import {
|
|
|
|
|
isInvisiblySmallElement,
|
|
|
|
|
refreshTextDimensions,
|
|
|
|
|
} from "../element";
|
|
|
|
|
import { isLinearElementType } from "../element/typeChecks";
|
|
|
|
|
import { isLinearElementType, isTextElement } from "../element/typeChecks";
|
|
|
|
|
import { randomId } from "../random";
|
|
|
|
|
import {
|
|
|
|
|
DEFAULT_FONT_FAMILY,
|
|
|
|
@ -235,6 +236,82 @@ const restoreElement = (
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Repairs contaienr element's boundElements array by removing duplicates and
|
|
|
|
|
* fixing containerId of bound elements if not present. Also removes any
|
|
|
|
|
* bound elements that do not exist in the elements array.
|
|
|
|
|
*
|
|
|
|
|
* NOTE mutates elements.
|
|
|
|
|
*/
|
|
|
|
|
const repairContainerElement = (
|
|
|
|
|
container: Mutable<ExcalidrawElement>,
|
|
|
|
|
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
|
|
|
|
) => {
|
|
|
|
|
if (container.boundElements) {
|
|
|
|
|
// copy because we're not cloning on restore, and we don't want to mutate upstream
|
|
|
|
|
const boundElements = container.boundElements.slice();
|
|
|
|
|
|
|
|
|
|
// dedupe bindings & fix boundElement.containerId if not set already
|
|
|
|
|
const boundIds = new Set<ExcalidrawElement["id"]>();
|
|
|
|
|
container.boundElements = boundElements.reduce(
|
|
|
|
|
(
|
|
|
|
|
acc: Mutable<NonNullable<ExcalidrawElement["boundElements"]>>,
|
|
|
|
|
binding,
|
|
|
|
|
) => {
|
|
|
|
|
const boundElement = elementsMap.get(binding.id);
|
|
|
|
|
if (boundElement && !boundIds.has(binding.id)) {
|
|
|
|
|
if (
|
|
|
|
|
isTextElement(boundElement) &&
|
|
|
|
|
// being slightly conservative here, preserving existing containerId
|
|
|
|
|
// if defined, lest boundElements is stale
|
|
|
|
|
!boundElement.containerId
|
|
|
|
|
) {
|
|
|
|
|
(boundElement as Mutable<ExcalidrawTextElement>).containerId =
|
|
|
|
|
container.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acc.push(binding);
|
|
|
|
|
boundIds.add(binding.id);
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Repairs target bound element's container's boundElements array,
|
|
|
|
|
* or removes contaienrId if container does not exist.
|
|
|
|
|
*
|
|
|
|
|
* NOTE mutates elements.
|
|
|
|
|
*/
|
|
|
|
|
const repairBoundElement = (
|
|
|
|
|
boundElement: Mutable<ExcalidrawTextElement>,
|
|
|
|
|
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
|
|
|
|
) => {
|
|
|
|
|
const container = boundElement.containerId
|
|
|
|
|
? elementsMap.get(boundElement.containerId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
if (!container) {
|
|
|
|
|
boundElement.containerId = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
container.boundElements &&
|
|
|
|
|
!container.boundElements.find((binding) => binding.id === boundElement.id)
|
|
|
|
|
) {
|
|
|
|
|
// copy because we're not cloning on restore, and we don't want to mutate upstream
|
|
|
|
|
const boundElements = (
|
|
|
|
|
container.boundElements || (container.boundElements = [])
|
|
|
|
|
).slice();
|
|
|
|
|
boundElements.push({ type: "text", id: boundElement.id });
|
|
|
|
|
container.boundElements = boundElements;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const restoreElements = (
|
|
|
|
|
elements: ImportedDataState["elements"],
|
|
|
|
|
/** NOTE doesn't serve for reconciliation */
|
|
|
|
@ -242,7 +319,7 @@ export const restoreElements = (
|
|
|
|
|
refreshDimensions = false,
|
|
|
|
|
): ExcalidrawElement[] => {
|
|
|
|
|
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
|
|
|
|
return (elements || []).reduce((elements, element) => {
|
|
|
|
|
const restoredElements = (elements || []).reduce((elements, element) => {
|
|
|
|
|
// filtering out selection, which is legacy, no longer kept in elements,
|
|
|
|
|
// and causing issues if retained
|
|
|
|
|
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
|
|
|
@ -260,6 +337,18 @@ export const restoreElements = (
|
|
|
|
|
}
|
|
|
|
|
return elements;
|
|
|
|
|
}, [] as ExcalidrawElement[]);
|
|
|
|
|
|
|
|
|
|
// repair binding. Mutates elements.
|
|
|
|
|
const restoredElementsMap = arrayToMap(restoredElements);
|
|
|
|
|
for (const element of restoredElements) {
|
|
|
|
|
if (isTextElement(element) && element.containerId) {
|
|
|
|
|
repairBoundElement(element, restoredElementsMap);
|
|
|
|
|
} else if (element.boundElements) {
|
|
|
|
|
repairContainerElement(element, restoredElementsMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return restoredElements;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const coalesceAppStateValue = <
|
|
|
|
|