fix: restoreElementWithProperties drops "parent" property (#5742)

Co-authored-by: Yosyp Buchma <yo@yosyp.co>
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Joseph Buchma 2022-10-08 21:42:05 +03:00 committed by GitHub
parent 76cf560914
commit e9a224a0de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 19 deletions

View File

@ -212,3 +212,7 @@ export const ELEMENT_READY_TO_ERASE_OPACITY = 20;
export const COOKIES = { export const COOKIES = {
AUTH_STATE_COOKIE: "excplus-auth", AUTH_STATE_COOKIE: "excplus-auth",
} as const; } as const;
/** key containt id of precedeing elemnt id we use in reconciliation during
* collaboration */
export const PRECEDING_ELEMENT_KEY = "__precedingElement__";

View File

@ -21,6 +21,7 @@ import {
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
DEFAULT_TEXT_ALIGN, DEFAULT_TEXT_ALIGN,
DEFAULT_VERTICAL_ALIGN, DEFAULT_VERTICAL_ALIGN,
PRECEDING_ELEMENT_KEY,
FONT_FAMILY, FONT_FAMILY,
} from "../constants"; } from "../constants";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
@ -71,6 +72,8 @@ const restoreElementWithProperties = <
customData?: ExcalidrawElement["customData"]; customData?: ExcalidrawElement["customData"];
/** @deprecated */ /** @deprecated */
boundElementIds?: readonly ExcalidrawElement["id"][]; boundElementIds?: readonly ExcalidrawElement["id"][];
/** metadata that may be present in elements during collaboration */
[PRECEDING_ELEMENT_KEY]?: string;
}, },
K extends Pick<T, keyof Omit<Required<T>, keyof ExcalidrawElement>>, K extends Pick<T, keyof Omit<Required<T>, keyof ExcalidrawElement>>,
>( >(
@ -83,7 +86,9 @@ const restoreElementWithProperties = <
> & > &
Partial<Pick<ExcalidrawElement, "type" | "x" | "y">>, Partial<Pick<ExcalidrawElement, "type" | "x" | "y">>,
): T => { ): T => {
const base: Pick<T, keyof ExcalidrawElement> = { const base: Pick<T, keyof ExcalidrawElement> & {
[PRECEDING_ELEMENT_KEY]?: string;
} = {
type: extra.type || element.type, type: extra.type || element.type,
// all elements must have version > 0 so getSceneVersion() will pick up // all elements must have version > 0 so getSceneVersion() will pick up
// newly added elements // newly added elements
@ -120,6 +125,10 @@ const restoreElementWithProperties = <
base.customData = element.customData; base.customData = element.customData;
} }
if (PRECEDING_ELEMENT_KEY in element) {
base[PRECEDING_ELEMENT_KEY] = element[PRECEDING_ELEMENT_KEY];
}
return { return {
...base, ...base,
...getNormalizedDimensions(base), ...getNormalizedDimensions(base),

View File

@ -18,6 +18,7 @@ import throttle from "lodash.throttle";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../element/mutateElement";
import { BroadcastedExcalidrawElement } from "./reconciliation"; import { BroadcastedExcalidrawElement } from "./reconciliation";
import { encryptData } from "../../data/encryption"; import { encryptData } from "../../data/encryption";
import { PRECEDING_ELEMENT_KEY } from "../../constants";
class Portal { class Portal {
collab: TCollabClass; collab: TCollabClass;
@ -152,7 +153,7 @@ class Portal {
acc.push({ acc.push({
...element, ...element,
// z-index info for the reconciler // z-index info for the reconciler
parent: idx === 0 ? "^" : elements[idx - 1]?.id, [PRECEDING_ELEMENT_KEY]: idx === 0 ? "^" : elements[idx - 1]?.id,
}); });
} }
return acc; return acc;

View File

@ -1,3 +1,4 @@
import { PRECEDING_ELEMENT_KEY } from "../../constants";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../element/types";
import { AppState } from "../../types"; import { AppState } from "../../types";
@ -6,7 +7,7 @@ export type ReconciledElements = readonly ExcalidrawElement[] & {
}; };
export type BroadcastedExcalidrawElement = ExcalidrawElement & { export type BroadcastedExcalidrawElement = ExcalidrawElement & {
parent?: string; [PRECEDING_ELEMENT_KEY]?: string;
}; };
const shouldDiscardRemoteElement = ( const shouldDiscardRemoteElement = (
@ -71,8 +72,8 @@ export const reconcileElements = (
const local = localElementsData[remoteElement.id]; const local = localElementsData[remoteElement.id];
if (shouldDiscardRemoteElement(localAppState, local?.[0], remoteElement)) { if (shouldDiscardRemoteElement(localAppState, local?.[0], remoteElement)) {
if (remoteElement.parent) { if (remoteElement[PRECEDING_ELEMENT_KEY]) {
delete remoteElement.parent; delete remoteElement[PRECEDING_ELEMENT_KEY];
} }
continue; continue;
@ -92,10 +93,12 @@ export const reconcileElements = (
// parent may not be defined in case the remote client is running an older // parent may not be defined in case the remote client is running an older
// excalidraw version // excalidraw version
const parent = const parent =
remoteElement.parent || remoteElements[remoteElementIdx - 1]?.id || null; remoteElement[PRECEDING_ELEMENT_KEY] ||
remoteElements[remoteElementIdx - 1]?.id ||
null;
if (parent != null) { if (parent != null) {
delete remoteElement.parent; delete remoteElement[PRECEDING_ELEMENT_KEY];
// ^ indicates the element is the first in elements array // ^ indicates the element is the first in elements array
if (parent === "^") { if (parent === "^") {

View File

@ -1,4 +1,5 @@
import { expect } from "chai"; import { expect } from "chai";
import { PRECEDING_ELEMENT_KEY } from "../constants";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { import {
BroadcastedExcalidrawElement, BroadcastedExcalidrawElement,
@ -13,7 +14,7 @@ type ElementLike = {
id: string; id: string;
version: number; version: number;
versionNonce: number; versionNonce: number;
parent?: string | null; [PRECEDING_ELEMENT_KEY]?: string | null;
}; };
type Cache = Record<string, ExcalidrawElement | undefined>; type Cache = Record<string, ExcalidrawElement | undefined>;
@ -42,7 +43,7 @@ const createElement = (opts: { uid: string } | ElementLike) => {
id, id,
version, version,
versionNonce: versionNonce || randomInteger(), versionNonce: versionNonce || randomInteger(),
parent: parent || null, [PRECEDING_ELEMENT_KEY]: parent || null,
}; };
}; };
@ -51,16 +52,20 @@ const idsToElements = (
cache: Cache = {}, cache: Cache = {},
): readonly ExcalidrawElement[] => { ): readonly ExcalidrawElement[] => {
return ids.reduce((acc, _uid, idx) => { return ids.reduce((acc, _uid, idx) => {
const { uid, id, version, parent, versionNonce } = createElement( const {
typeof _uid === "string" ? { uid: _uid } : _uid, uid,
); id,
version,
[PRECEDING_ELEMENT_KEY]: parent,
versionNonce,
} = createElement(typeof _uid === "string" ? { uid: _uid } : _uid);
const cached = cache[uid]; const cached = cache[uid];
const elem = { const elem = {
id, id,
version: version ?? 0, version: version ?? 0,
versionNonce, versionNonce,
...cached, ...cached,
parent, [PRECEDING_ELEMENT_KEY]: parent,
} as BroadcastedExcalidrawElement; } as BroadcastedExcalidrawElement;
// @ts-ignore // @ts-ignore
cache[uid] = elem; cache[uid] = elem;
@ -71,7 +76,7 @@ const idsToElements = (
const addParents = (elements: BroadcastedExcalidrawElement[]) => { const addParents = (elements: BroadcastedExcalidrawElement[]) => {
return elements.map((el, idx, els) => { return elements.map((el, idx, els) => {
el.parent = els[idx - 1]?.id || "^"; el[PRECEDING_ELEMENT_KEY] = els[idx - 1]?.id || "^";
return el; return el;
}); });
}; };
@ -79,7 +84,7 @@ const addParents = (elements: BroadcastedExcalidrawElement[]) => {
const cleanElements = (elements: ReconciledElements) => { const cleanElements = (elements: ReconciledElements) => {
return elements.map((el) => { return elements.map((el) => {
// @ts-ignore // @ts-ignore
delete el.parent; delete el[PRECEDING_ELEMENT_KEY];
// @ts-ignore // @ts-ignore
delete el.next; delete el.next;
// @ts-ignore // @ts-ignore
@ -385,13 +390,13 @@ describe("elements reconciliation", () => {
id: "A", id: "A",
version: 1, version: 1,
versionNonce: 1, versionNonce: 1,
parent: null, [PRECEDING_ELEMENT_KEY]: null,
}, },
{ {
id: "B", id: "B",
version: 1, version: 1,
versionNonce: 1, versionNonce: 1,
parent: null, [PRECEDING_ELEMENT_KEY]: null,
}, },
]; ];
@ -404,13 +409,13 @@ describe("elements reconciliation", () => {
id: "A", id: "A",
version: 1, version: 1,
versionNonce: 1, versionNonce: 1,
parent: null, [PRECEDING_ELEMENT_KEY]: null,
}; };
const el2 = { const el2 = {
id: "B", id: "B",
version: 1, version: 1,
versionNonce: 1, versionNonce: 1,
parent: null, [PRECEDING_ELEMENT_KEY]: null,
}; };
testIdentical([el1, el2], [el2, el1], ["A", "B"]); testIdentical([el1, el2], [el2, el1], ["A", "B"]);
}); });