snapshot of https://github.com/excalidraw/excalidraw.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Tests / test (push) Failing after 15s
Some checks failed
Tests / test (push) Failing after 15s
This commit is contained in:
118
packages/excalidraw/data/reconcile.ts
Normal file
118
packages/excalidraw/data/reconcile.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
import { arrayToMap, isDevEnv, isTestEnv } from "@excalidraw/common";
|
||||
|
||||
import {
|
||||
orderByFractionalIndex,
|
||||
syncInvalidIndices,
|
||||
validateFractionalIndices,
|
||||
} from "@excalidraw/element";
|
||||
|
||||
import type { OrderedExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type { MakeBrand } from "@excalidraw/common/utility-types";
|
||||
|
||||
import type { AppState } from "../types";
|
||||
|
||||
export type ReconciledExcalidrawElement = OrderedExcalidrawElement &
|
||||
MakeBrand<"ReconciledElement">;
|
||||
|
||||
export type RemoteExcalidrawElement = OrderedExcalidrawElement &
|
||||
MakeBrand<"RemoteExcalidrawElement">;
|
||||
|
||||
export const shouldDiscardRemoteElement = (
|
||||
localAppState: AppState,
|
||||
local: OrderedExcalidrawElement | undefined,
|
||||
remote: RemoteExcalidrawElement,
|
||||
): boolean => {
|
||||
if (
|
||||
local &&
|
||||
// local element is being edited
|
||||
(local.id === localAppState.editingTextElement?.id ||
|
||||
local.id === localAppState.resizingElement?.id ||
|
||||
local.id === localAppState.newElement?.id ||
|
||||
// local element is newer
|
||||
local.version > remote.version ||
|
||||
// resolve conflicting edits deterministically by taking the one with
|
||||
// the lowest versionNonce
|
||||
(local.version === remote.version &&
|
||||
local.versionNonce <= remote.versionNonce))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const validateIndicesThrottled = throttle(
|
||||
(
|
||||
orderedElements: readonly OrderedExcalidrawElement[],
|
||||
localElements: readonly OrderedExcalidrawElement[],
|
||||
remoteElements: readonly RemoteExcalidrawElement[],
|
||||
) => {
|
||||
if (isDevEnv() || isTestEnv() || window?.DEBUG_FRACTIONAL_INDICES) {
|
||||
// create new instances due to the mutation
|
||||
const elements = syncInvalidIndices(
|
||||
orderedElements.map((x) => ({ ...x })),
|
||||
);
|
||||
|
||||
validateFractionalIndices(elements, {
|
||||
// throw in dev & test only, to remain functional on `DEBUG_FRACTIONAL_INDICES`
|
||||
shouldThrow: isTestEnv() || isDevEnv(),
|
||||
includeBoundTextValidation: true,
|
||||
reconciliationContext: {
|
||||
localElements,
|
||||
remoteElements,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
1000 * 60,
|
||||
{ leading: true, trailing: false },
|
||||
);
|
||||
|
||||
export const reconcileElements = (
|
||||
localElements: readonly OrderedExcalidrawElement[],
|
||||
remoteElements: readonly RemoteExcalidrawElement[],
|
||||
localAppState: AppState,
|
||||
): ReconciledExcalidrawElement[] => {
|
||||
const localElementsMap = arrayToMap(localElements);
|
||||
const reconciledElements: OrderedExcalidrawElement[] = [];
|
||||
const added = new Set<string>();
|
||||
|
||||
// process remote elements
|
||||
for (const remoteElement of remoteElements) {
|
||||
if (!added.has(remoteElement.id)) {
|
||||
const localElement = localElementsMap.get(remoteElement.id);
|
||||
const discardRemoteElement = shouldDiscardRemoteElement(
|
||||
localAppState,
|
||||
localElement,
|
||||
remoteElement,
|
||||
);
|
||||
|
||||
if (localElement && discardRemoteElement) {
|
||||
reconciledElements.push(localElement);
|
||||
added.add(localElement.id);
|
||||
} else {
|
||||
reconciledElements.push(remoteElement);
|
||||
added.add(remoteElement.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process remaining local elements
|
||||
for (const localElement of localElements) {
|
||||
if (!added.has(localElement.id)) {
|
||||
reconciledElements.push(localElement);
|
||||
added.add(localElement.id);
|
||||
}
|
||||
}
|
||||
|
||||
const orderedElements = orderByFractionalIndex(reconciledElements);
|
||||
|
||||
validateIndicesThrottled(orderedElements, localElements, remoteElements);
|
||||
|
||||
// de-duplicate indices
|
||||
syncInvalidIndices(orderedElements);
|
||||
|
||||
return orderedElements as ReconciledExcalidrawElement[];
|
||||
};
|
||||
Reference in New Issue
Block a user