mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-19 07:20:21 +02:00
feat: fix delta apply to issues (#9830)
This commit is contained in:
@@ -151,6 +151,16 @@ export class Delta<T> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two deltas into a new one.
|
||||
*/
|
||||
public static merge<T>(delta1: Delta<T>, delta2: Delta<T>) {
|
||||
return Delta.create(
|
||||
{ ...delta1.deleted, ...delta2.deleted },
|
||||
{ ...delta1.inserted, ...delta2.inserted },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges deleted and inserted object partials.
|
||||
*/
|
||||
@@ -497,6 +507,11 @@ export interface DeltaContainer<T> {
|
||||
*/
|
||||
applyTo(previous: T, ...options: unknown[]): [T, boolean];
|
||||
|
||||
/**
|
||||
* Squashes the current delta with the given one.
|
||||
*/
|
||||
squash(delta: DeltaContainer<T>): this;
|
||||
|
||||
/**
|
||||
* Checks whether all `Delta`s are empty.
|
||||
*/
|
||||
@@ -504,7 +519,7 @@ export interface DeltaContainer<T> {
|
||||
}
|
||||
|
||||
export class AppStateDelta implements DeltaContainer<AppState> {
|
||||
private constructor(public readonly delta: Delta<ObservedAppState>) {}
|
||||
private constructor(public delta: Delta<ObservedAppState>) {}
|
||||
|
||||
public static calculate<T extends ObservedAppState>(
|
||||
prevAppState: T,
|
||||
@@ -535,6 +550,11 @@ export class AppStateDelta implements DeltaContainer<AppState> {
|
||||
return new AppStateDelta(inversedDelta);
|
||||
}
|
||||
|
||||
public squash(delta: AppStateDelta): this {
|
||||
this.delta = Delta.merge(this.delta, delta.delta);
|
||||
return this;
|
||||
}
|
||||
|
||||
public applyTo(
|
||||
appState: AppState,
|
||||
nextElements: SceneElementsMap,
|
||||
@@ -1196,8 +1216,8 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
||||
const inverseInternal = (deltas: Record<string, Delta<ElementPartial>>) => {
|
||||
const inversedDeltas: Record<string, Delta<ElementPartial>> = {};
|
||||
|
||||
for (const [id, delta] of Object.entries(deltas)) {
|
||||
inversedDeltas[id] = Delta.create(delta.inserted, delta.deleted);
|
||||
for (const [id, { inserted, deleted }] of Object.entries(deltas)) {
|
||||
inversedDeltas[id] = Delta.create({ ...inserted }, { ...deleted });
|
||||
}
|
||||
|
||||
return inversedDeltas;
|
||||
@@ -1395,6 +1415,42 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
||||
}
|
||||
}
|
||||
|
||||
public squash(delta: ElementsDelta): this {
|
||||
const { added, removed, updated } = delta;
|
||||
|
||||
for (const [id, nextDelta] of Object.entries(added)) {
|
||||
const prevDelta = this.added[id];
|
||||
|
||||
if (!prevDelta) {
|
||||
this.added[id] = nextDelta;
|
||||
} else {
|
||||
this.added[id] = Delta.merge(prevDelta, nextDelta);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, nextDelta] of Object.entries(removed)) {
|
||||
const prevDelta = this.removed[id];
|
||||
|
||||
if (!prevDelta) {
|
||||
this.removed[id] = nextDelta;
|
||||
} else {
|
||||
this.removed[id] = Delta.merge(prevDelta, nextDelta);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, nextDelta] of Object.entries(updated)) {
|
||||
const prevDelta = this.updated[id];
|
||||
|
||||
if (!prevDelta) {
|
||||
this.updated[id] = nextDelta;
|
||||
} else {
|
||||
this.updated[id] = Delta.merge(prevDelta, nextDelta);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static createApplier =
|
||||
(
|
||||
nextElements: SceneElementsMap,
|
||||
@@ -1624,25 +1680,12 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
||||
Array.from(prevElements).filter(([id]) => nextAffectedElements.has(id)),
|
||||
);
|
||||
|
||||
// calculate complete deltas for affected elements, and assign them back to all the deltas
|
||||
// technically we could do better here if perf. would become an issue
|
||||
const { added, removed, updated } = ElementsDelta.calculate(
|
||||
prevAffectedElements,
|
||||
nextAffectedElements,
|
||||
// calculate complete deltas for affected elements, and squash them back to the current deltas
|
||||
this.squash(
|
||||
// technically we could do better here if perf. would become an issue
|
||||
ElementsDelta.calculate(prevAffectedElements, nextAffectedElements),
|
||||
);
|
||||
|
||||
for (const [id, delta] of Object.entries(added)) {
|
||||
this.added[id] = delta;
|
||||
}
|
||||
|
||||
for (const [id, delta] of Object.entries(removed)) {
|
||||
this.removed[id] = delta;
|
||||
}
|
||||
|
||||
for (const [id, delta] of Object.entries(updated)) {
|
||||
this.updated[id] = delta;
|
||||
}
|
||||
|
||||
return nextAffectedElements;
|
||||
}
|
||||
|
||||
|
@@ -76,8 +76,9 @@ type MicroActionsQueue = (() => void)[];
|
||||
* Store which captures the observed changes and emits them as `StoreIncrement` events.
|
||||
*/
|
||||
export class Store {
|
||||
// internally used by history
|
||||
// for internal use by history
|
||||
public readonly onDurableIncrementEmitter = new Emitter<[DurableIncrement]>();
|
||||
// for public use as part of onIncrement API
|
||||
public readonly onStoreIncrementEmitter = new Emitter<
|
||||
[DurableIncrement | EphemeralIncrement]
|
||||
>();
|
||||
@@ -239,7 +240,6 @@ export class Store {
|
||||
if (!storeDelta.isEmpty()) {
|
||||
const increment = new DurableIncrement(storeChange, storeDelta);
|
||||
|
||||
// Notify listeners with the increment
|
||||
this.onDurableIncrementEmitter.trigger(increment);
|
||||
this.onStoreIncrementEmitter.trigger(increment);
|
||||
}
|
||||
|
Reference in New Issue
Block a user