1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2025-02-18 13:29:36 +01:00

fix: throttle fractional indices validation (#8306)

This commit is contained in:
Marcel Mraz 2024-08-02 11:55:15 +02:00 committed by GitHub
parent e63dd025c9
commit 84d89b9a8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 71 additions and 40 deletions

@ -1,3 +1,4 @@
import throttle from "lodash.throttle";
import { ENV } from "../constants";
import type { OrderedExcalidrawElement } from "../element/types";
import {
@ -38,6 +39,37 @@ const shouldDiscardRemoteElement = (
return false;
};
const validateIndicesThrottled = throttle(
(
orderedElements: readonly OrderedExcalidrawElement[],
localElements: readonly OrderedExcalidrawElement[],
remoteElements: readonly RemoteExcalidrawElement[],
) => {
if (
import.meta.env.DEV ||
import.meta.env.MODE === ENV.TEST ||
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: import.meta.env.DEV || import.meta.env.MODE === ENV.TEST,
includeBoundTextValidation: true,
reconciliationContext: {
localElements,
remoteElements,
},
});
}
},
1000 * 60,
{ leading: true, trailing: false },
);
export const reconcileElements = (
localElements: readonly OrderedExcalidrawElement[],
remoteElements: readonly RemoteExcalidrawElement[],
@ -77,26 +109,7 @@ export const reconcileElements = (
const orderedElements = orderByFractionalIndex(reconciledElements);
if (
import.meta.env.DEV ||
import.meta.env.MODE === ENV.TEST ||
window?.DEBUG_FRACTIONAL_INDICES
) {
const elements = syncInvalidIndices(
// create new instances due to the mutation
orderedElements.map((x) => ({ ...x })),
);
validateFractionalIndices(elements, {
// throw in dev & test only, to remain functional on `DEBUG_FRACTIONAL_INDICES`
shouldThrow: import.meta.env.DEV || import.meta.env.MODE === ENV.TEST,
includeBoundTextValidation: true,
reconciliationContext: {
localElements,
remoteElements,
},
});
}
validateIndicesThrottled(orderedElements, localElements, remoteElements);
// de-duplicate indices
syncInvalidIndices(orderedElements);

@ -37,10 +37,12 @@ export const validateFractionalIndices = (
{
shouldThrow = false,
includeBoundTextValidation = false,
ignoreLogs,
reconciliationContext,
}: {
shouldThrow: boolean;
includeBoundTextValidation: boolean;
ignoreLogs?: true;
reconciliationContext?: {
localElements: ReadonlyArray<ExcalidrawElement>;
remoteElements: ReadonlyArray<ExcalidrawElement>;
@ -95,6 +97,7 @@ export const validateFractionalIndices = (
);
}
if (!ignoreLogs) {
// report just once and with the stacktrace
console.error(
errorMessages.join("\n\n"),
@ -102,6 +105,7 @@ export const validateFractionalIndices = (
elements.map((x) => stringifyElement(x)),
...additionalContext,
);
}
if (shouldThrow) {
// if enabled, gather all the errors first, throw once
@ -157,7 +161,11 @@ export const syncMovedIndices = (
validateFractionalIndices(
elementsCandidates,
// we don't autofix invalid bound text indices, hence don't include it in the validation
{ includeBoundTextValidation: false, shouldThrow: true },
{
includeBoundTextValidation: false,
shouldThrow: true,
ignoreLogs: true,
},
);
// split mutation so we don't end up in an incosistent state

@ -1,3 +1,4 @@
import throttle from "lodash.throttle";
import type {
ExcalidrawElement,
NonDeletedExcalidrawElement,
@ -50,6 +51,24 @@ const getNonDeletedElements = <T extends ExcalidrawElement>(
return { elementsMap, elements };
};
const validateIndicesThrottled = throttle(
(elements: readonly ExcalidrawElement[]) => {
if (
import.meta.env.DEV ||
import.meta.env.MODE === ENV.TEST ||
window?.DEBUG_FRACTIONAL_INDICES
) {
validateFractionalIndices(elements, {
// throw only in dev & test, to remain functional on `DEBUG_FRACTIONAL_INDICES`
shouldThrow: import.meta.env.DEV || import.meta.env.MODE === ENV.TEST,
includeBoundTextValidation: true,
});
}
},
1000 * 60,
{ leading: true, trailing: false },
);
const hashSelectionOpts = (
opts: Parameters<InstanceType<typeof Scene>["getSelectedElements"]>[0],
) => {
@ -274,18 +293,7 @@ class Scene {
: Array.from(nextElements.values());
const nextFrameLikes: ExcalidrawFrameLikeElement[] = [];
if (
import.meta.env.DEV ||
import.meta.env.MODE === ENV.TEST ||
window?.DEBUG_FRACTIONAL_INDICES
) {
validateFractionalIndices(_nextElements, {
// validate everything
includeBoundTextValidation: true,
// throw only in dev & test, to remain functional on `DEBUG_FRACTIONAL_INDICES`
shouldThrow: import.meta.env.DEV || import.meta.env.MODE === ENV.TEST,
});
}
validateIndicesThrottled(_nextElements);
this.elements = syncInvalidIndices(_nextElements);
this.elementsMap.clear();

@ -766,6 +766,7 @@ function test(
validateFractionalIndices(elements, {
shouldThrow: true,
includeBoundTextValidation: true,
ignoreLogs: true,
}),
).toThrowError(InvalidFractionalIndexError);
}
@ -783,6 +784,7 @@ function test(
validateFractionalIndices(syncedElements, {
shouldThrow: true,
includeBoundTextValidation: true,
ignoreLogs: true,
}),
).not.toThrowError(InvalidFractionalIndexError);