Add version and versionNonce into delta
This commit is contained in:
parent
14d512f321
commit
bc1a71e772
@ -18,7 +18,12 @@ import type {
|
|||||||
SceneElementsMap,
|
SceneElementsMap,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type { DTO, SubtypeOf, ValueOf } from "@excalidraw/common/utility-types";
|
import type {
|
||||||
|
DTO,
|
||||||
|
Mutable,
|
||||||
|
SubtypeOf,
|
||||||
|
ValueOf,
|
||||||
|
} from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AppState,
|
AppState,
|
||||||
@ -51,6 +56,8 @@ import { orderByFractionalIndex, syncMovedIndices } from "./fractionalIndex";
|
|||||||
|
|
||||||
import { Scene } from "./Scene";
|
import { Scene } from "./Scene";
|
||||||
|
|
||||||
|
import { StoreSnapshot } from "./store";
|
||||||
|
|
||||||
import type { BindableProp, BindingProp } from "./binding";
|
import type { BindableProp, BindingProp } from "./binding";
|
||||||
|
|
||||||
import type { ElementUpdate } from "./mutateElement";
|
import type { ElementUpdate } from "./mutateElement";
|
||||||
@ -858,10 +865,17 @@ export class AppStateDelta implements DeltaContainer<AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ElementPartial<T extends ExcalidrawElement = ExcalidrawElement> = Omit<
|
type ElementPartial<TElement extends ExcalidrawElement = ExcalidrawElement> =
|
||||||
ElementUpdate<Ordered<T>>,
|
Omit<Partial<Ordered<TElement>>, "id" | "updated" | "seed">;
|
||||||
"seed"
|
|
||||||
>;
|
export type ApplyToOptions = {
|
||||||
|
excludedProperties: Set<keyof ElementPartial>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ApplyToFlags = {
|
||||||
|
containsVisibleDifference: boolean;
|
||||||
|
containsZindexDifference: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elements change is a low level primitive to capture a change between two sets of elements.
|
* Elements change is a low level primitive to capture a change between two sets of elements.
|
||||||
@ -1101,8 +1115,11 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
|
|
||||||
for (const key of Object.keys(partial) as Array<keyof typeof partial>) {
|
for (const key of Object.keys(partial) as Array<keyof typeof partial>) {
|
||||||
// do not update following props:
|
// do not update following props:
|
||||||
|
// - `version` and `versionNonce`, as they should keep it's original value
|
||||||
// - `boundElements`, as it is a reference value which is postprocessed to contain only deleted/inserted keys
|
// - `boundElements`, as it is a reference value which is postprocessed to contain only deleted/inserted keys
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
case "version":
|
||||||
|
case "versionNonce":
|
||||||
case "boundElements":
|
case "boundElements":
|
||||||
latestPartial[key] = partial[key];
|
latestPartial[key] = partial[key];
|
||||||
break;
|
break;
|
||||||
@ -1150,12 +1167,15 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
|
|
||||||
public applyTo(
|
public applyTo(
|
||||||
elements: SceneElementsMap,
|
elements: SceneElementsMap,
|
||||||
elementsSnapshot: Map<string, OrderedExcalidrawElement>,
|
snapshot: StoreSnapshot["elements"] = StoreSnapshot.empty().elements,
|
||||||
|
options: ApplyToOptions = {
|
||||||
|
excludedProperties: new Set(),
|
||||||
|
},
|
||||||
): [SceneElementsMap, boolean] {
|
): [SceneElementsMap, boolean] {
|
||||||
let nextElements = new Map(elements) as SceneElementsMap;
|
let nextElements = new Map(elements) as SceneElementsMap;
|
||||||
let changedElements: Map<string, OrderedExcalidrawElement>;
|
let changedElements: Map<string, OrderedExcalidrawElement>;
|
||||||
|
|
||||||
const flags = {
|
const flags: ApplyToFlags = {
|
||||||
containsVisibleDifference: false,
|
containsVisibleDifference: false,
|
||||||
containsZindexDifference: false,
|
containsZindexDifference: false,
|
||||||
};
|
};
|
||||||
@ -1164,13 +1184,14 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
try {
|
try {
|
||||||
const applyDeltas = ElementsDelta.createApplier(
|
const applyDeltas = ElementsDelta.createApplier(
|
||||||
nextElements,
|
nextElements,
|
||||||
elementsSnapshot,
|
snapshot,
|
||||||
|
options,
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
const addedElements = applyDeltas("added", this.added);
|
const addedElements = applyDeltas(this.added);
|
||||||
const removedElements = applyDeltas("removed", this.removed);
|
const removedElements = applyDeltas(this.removed);
|
||||||
const updatedElements = applyDeltas("updated", this.updated);
|
const updatedElements = applyDeltas(this.updated);
|
||||||
|
|
||||||
const affectedElements = this.resolveConflicts(elements, nextElements);
|
const affectedElements = this.resolveConflicts(elements, nextElements);
|
||||||
|
|
||||||
@ -1229,18 +1250,12 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
private static createApplier =
|
private static createApplier =
|
||||||
(
|
(
|
||||||
nextElements: SceneElementsMap,
|
nextElements: SceneElementsMap,
|
||||||
snapshot: Map<string, OrderedExcalidrawElement>,
|
snapshot: StoreSnapshot["elements"],
|
||||||
flags: {
|
options: ApplyToOptions,
|
||||||
containsVisibleDifference: boolean;
|
flags: ApplyToFlags,
|
||||||
containsZindexDifference: boolean;
|
|
||||||
},
|
|
||||||
) =>
|
) =>
|
||||||
(
|
(deltas: Record<string, Delta<ElementPartial>>) => {
|
||||||
type: "added" | "removed" | "updated",
|
|
||||||
deltas: Record<string, Delta<ElementPartial>>,
|
|
||||||
) => {
|
|
||||||
const getElement = ElementsDelta.createGetter(
|
const getElement = ElementsDelta.createGetter(
|
||||||
type,
|
|
||||||
nextElements,
|
nextElements,
|
||||||
snapshot,
|
snapshot,
|
||||||
flags,
|
flags,
|
||||||
@ -1250,7 +1265,13 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
const element = getElement(id, delta.inserted);
|
const element = getElement(id, delta.inserted);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
const newElement = ElementsDelta.applyDelta(element, delta, flags);
|
const newElement = ElementsDelta.applyDelta(
|
||||||
|
element,
|
||||||
|
delta,
|
||||||
|
options,
|
||||||
|
flags,
|
||||||
|
);
|
||||||
|
|
||||||
nextElements.set(newElement.id, newElement);
|
nextElements.set(newElement.id, newElement);
|
||||||
acc.set(newElement.id, newElement);
|
acc.set(newElement.id, newElement);
|
||||||
}
|
}
|
||||||
@ -1261,13 +1282,9 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
|
|
||||||
private static createGetter =
|
private static createGetter =
|
||||||
(
|
(
|
||||||
type: "added" | "removed" | "updated",
|
|
||||||
elements: SceneElementsMap,
|
elements: SceneElementsMap,
|
||||||
snapshot: Map<string, OrderedExcalidrawElement>,
|
snapshot: StoreSnapshot["elements"],
|
||||||
flags: {
|
flags: ApplyToFlags,
|
||||||
containsVisibleDifference: boolean;
|
|
||||||
containsZindexDifference: boolean;
|
|
||||||
},
|
|
||||||
) =>
|
) =>
|
||||||
(id: string, partial: ElementPartial) => {
|
(id: string, partial: ElementPartial) => {
|
||||||
let element = elements.get(id);
|
let element = elements.get(id);
|
||||||
@ -1281,10 +1298,7 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
flags.containsZindexDifference = true;
|
flags.containsZindexDifference = true;
|
||||||
|
|
||||||
// as the element was force deleted, we need to check if adding it back results in a visible change
|
// as the element was force deleted, we need to check if adding it back results in a visible change
|
||||||
if (
|
if (!partial.isDeleted || (partial.isDeleted && !element.isDeleted)) {
|
||||||
partial.isDeleted === false ||
|
|
||||||
(partial.isDeleted !== true && element.isDeleted === false)
|
|
||||||
) {
|
|
||||||
flags.containsVisibleDifference = true;
|
flags.containsVisibleDifference = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1304,16 +1318,25 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
private static applyDelta(
|
private static applyDelta(
|
||||||
element: OrderedExcalidrawElement,
|
element: OrderedExcalidrawElement,
|
||||||
delta: Delta<ElementPartial>,
|
delta: Delta<ElementPartial>,
|
||||||
flags: {
|
options: ApplyToOptions,
|
||||||
containsVisibleDifference: boolean;
|
flags: ApplyToFlags,
|
||||||
containsZindexDifference: boolean;
|
|
||||||
} = {
|
|
||||||
// by default we don't care about about the flags
|
|
||||||
containsVisibleDifference: true,
|
|
||||||
containsZindexDifference: true,
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
const { boundElements, ...directlyApplicablePartial } = delta.inserted;
|
const directlyApplicablePartial: Mutable<ElementPartial> = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(delta.inserted) as Array<
|
||||||
|
keyof typeof delta.inserted
|
||||||
|
>) {
|
||||||
|
if (key === "boundElements") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.excludedProperties.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = delta.inserted[key];
|
||||||
|
Reflect.set(directlyApplicablePartial, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
delta.deleted.boundElements?.length ||
|
delta.deleted.boundElements?.length ||
|
||||||
@ -1665,7 +1688,7 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||||||
private static stripIrrelevantProps(
|
private static stripIrrelevantProps(
|
||||||
partial: Partial<OrderedExcalidrawElement>,
|
partial: Partial<OrderedExcalidrawElement>,
|
||||||
): ElementPartial {
|
): ElementPartial {
|
||||||
const { id, updated, version, versionNonce, ...strippedPartial } = partial;
|
const { id, updated, ...strippedPartial } = partial;
|
||||||
|
|
||||||
return strippedPartial;
|
return strippedPartial;
|
||||||
}
|
}
|
||||||
|
@ -534,7 +534,7 @@ export class StoreDelta {
|
|||||||
/**
|
/**
|
||||||
* Inverse store delta, creates new instance of `StoreDelta`.
|
* Inverse store delta, creates new instance of `StoreDelta`.
|
||||||
*/
|
*/
|
||||||
public static inverse(delta: StoreDelta): StoreDelta {
|
public static inverse(delta: StoreDelta) {
|
||||||
return this.create(delta.elements.inverse(), delta.appState.inverse());
|
return this.create(delta.elements.inverse(), delta.appState.inverse());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +545,7 @@ export class StoreDelta {
|
|||||||
delta: StoreDelta,
|
delta: StoreDelta,
|
||||||
elements: SceneElementsMap,
|
elements: SceneElementsMap,
|
||||||
modifierOptions: "deleted" | "inserted",
|
modifierOptions: "deleted" | "inserted",
|
||||||
): StoreDelta {
|
) {
|
||||||
return this.create(
|
return this.create(
|
||||||
delta.elements.applyLatestChanges(elements, modifierOptions),
|
delta.elements.applyLatestChanges(elements, modifierOptions),
|
||||||
delta.appState,
|
delta.appState,
|
||||||
@ -562,12 +562,9 @@ export class StoreDelta {
|
|||||||
delta: StoreDelta,
|
delta: StoreDelta,
|
||||||
elements: SceneElementsMap,
|
elements: SceneElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
prevSnapshot: StoreSnapshot = StoreSnapshot.empty(),
|
|
||||||
): [SceneElementsMap, AppState, boolean] {
|
): [SceneElementsMap, AppState, boolean] {
|
||||||
const [nextElements, elementsContainVisibleChange] = delta.elements.applyTo(
|
const [nextElements, elementsContainVisibleChange] =
|
||||||
elements,
|
delta.elements.applyTo(elements);
|
||||||
prevSnapshot.elements,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [nextAppState, appStateContainsVisibleChange] =
|
const [nextAppState, appStateContainsVisibleChange] =
|
||||||
delta.appState.applyTo(appState, nextElements);
|
delta.appState.applyTo(appState, nextElements);
|
||||||
|
@ -7,11 +7,65 @@ import {
|
|||||||
type Store,
|
type Store,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
|
import type { StoreSnapshot } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { SceneElementsMap } from "@excalidraw/element/types";
|
import type { SceneElementsMap } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type { AppState } from "./types";
|
import type { AppState } from "./types";
|
||||||
|
|
||||||
class HistoryEntry extends StoreDelta {}
|
class HistoryEntry extends StoreDelta {
|
||||||
|
/**
|
||||||
|
* Apply the delta to the passed elements and appState, does not modify the snapshot.
|
||||||
|
*/
|
||||||
|
public applyTo(
|
||||||
|
elements: SceneElementsMap,
|
||||||
|
appState: AppState,
|
||||||
|
snapshot: StoreSnapshot,
|
||||||
|
): [SceneElementsMap, AppState, boolean] {
|
||||||
|
const [nextElements, elementsContainVisibleChange] = this.elements.applyTo(
|
||||||
|
elements,
|
||||||
|
// used to fallback into local snapshot in case we couldn't apply the delta
|
||||||
|
// due to a missing elements in the scene (force deleted)
|
||||||
|
snapshot.elements,
|
||||||
|
// we don't want to apply the version and versionNonce properties for history
|
||||||
|
{
|
||||||
|
excludedProperties: new Set(["version", "versionNonce"]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [nextAppState, appStateContainsVisibleChange] = this.appState.applyTo(
|
||||||
|
appState,
|
||||||
|
nextElements,
|
||||||
|
);
|
||||||
|
|
||||||
|
const appliedVisibleChanges =
|
||||||
|
elementsContainVisibleChange || appStateContainsVisibleChange;
|
||||||
|
|
||||||
|
return [nextElements, nextAppState, appliedVisibleChanges];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overriding once to avoid type casting everywhere.
|
||||||
|
*/
|
||||||
|
public static override inverse(delta: StoreDelta): HistoryEntry {
|
||||||
|
return super.inverse(delta) as HistoryEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overriding once to avoid type casting everywhere.
|
||||||
|
*/
|
||||||
|
public static override applyLatestChanges(
|
||||||
|
delta: StoreDelta,
|
||||||
|
elements: SceneElementsMap,
|
||||||
|
modifierOptions: "deleted" | "inserted",
|
||||||
|
): HistoryEntry {
|
||||||
|
return super.applyLatestChanges(
|
||||||
|
delta,
|
||||||
|
elements,
|
||||||
|
modifierOptions,
|
||||||
|
) as HistoryEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class HistoryChangedEvent {
|
export class HistoryChangedEvent {
|
||||||
constructor(
|
constructor(
|
||||||
@ -112,12 +166,7 @@ export class History {
|
|||||||
while (historyEntry) {
|
while (historyEntry) {
|
||||||
try {
|
try {
|
||||||
[nextElements, nextAppState, containsVisibleChange] =
|
[nextElements, nextAppState, containsVisibleChange] =
|
||||||
StoreDelta.applyTo(
|
historyEntry.applyTo(nextElements, nextAppState, prevSnapshot);
|
||||||
historyEntry,
|
|
||||||
nextElements,
|
|
||||||
nextAppState,
|
|
||||||
prevSnapshot,
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextSnapshot = prevSnapshot.maybeClone(
|
const nextSnapshot = prevSnapshot.maybeClone(
|
||||||
action,
|
action,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user