perf(history): batch transform drag undo
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import type { AppState, HistorySnapshot } from "@editor/state";
|
||||
import type { CommandContext } from "./command";
|
||||
import { commandIds } from "./ids";
|
||||
import type { CommandContext, CommandHistoryPolicy } from "./command";
|
||||
import type { CommandId, CommandPayloads } from "./payloads";
|
||||
import type { CommandRegistry } from "./registry";
|
||||
|
||||
@@ -15,6 +14,8 @@ export function createCommandDispatcher(options: {
|
||||
getState: () => AppState;
|
||||
setState: (state: AppState) => void;
|
||||
}): CommandDispatcher {
|
||||
let deferredHistory: { snapshot: HistorySnapshot; changed: boolean } | undefined;
|
||||
|
||||
return {
|
||||
dispatch(commandId, payload) {
|
||||
const command = options.registry.get(commandId);
|
||||
@@ -23,28 +24,89 @@ export function createCommandDispatcher(options: {
|
||||
}
|
||||
|
||||
const currentState = options.getState();
|
||||
const historyPolicy = command.history ?? defaultHistoryPolicy;
|
||||
const deferredSnapshot = historyPolicy.mode === "deferred" && historyPolicy.phase === "begin" ? snapshot(currentState) : undefined;
|
||||
const context: CommandContext = { state: currentState };
|
||||
const executedState = command.execute(context, payload);
|
||||
if (executedState === currentState) {
|
||||
if (historyPolicy.mode === "deferred" && historyPolicy.phase === "commit") deferredHistory = undefined;
|
||||
return currentState;
|
||||
}
|
||||
const nextState = shouldRecordHistory(commandId, currentState, executedState) ? recordHistory(currentState, executedState) : executedState;
|
||||
|
||||
const nextState = applyHistoryPolicy({
|
||||
currentState,
|
||||
nextState: executedState,
|
||||
historyPolicy,
|
||||
deferredSnapshot,
|
||||
getDeferredHistory: () => deferredHistory,
|
||||
setDeferredHistory: (nextDeferredHistory) => {
|
||||
deferredHistory = nextDeferredHistory;
|
||||
},
|
||||
});
|
||||
options.setState(nextState);
|
||||
return nextState;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function shouldRecordHistory(commandId: CommandId, currentState: AppState, nextState: AppState) {
|
||||
if (commandId === commandIds.historyUndo || commandId === commandIds.historyRedo) return false;
|
||||
const defaultHistoryPolicy: CommandHistoryPolicy = { mode: "auto" };
|
||||
|
||||
function applyHistoryPolicy(options: {
|
||||
currentState: AppState;
|
||||
nextState: AppState;
|
||||
historyPolicy: CommandHistoryPolicy;
|
||||
deferredSnapshot?: HistorySnapshot;
|
||||
getDeferredHistory: () => { snapshot: HistorySnapshot; changed: boolean } | undefined;
|
||||
setDeferredHistory: (nextDeferredHistory: { snapshot: HistorySnapshot; changed: boolean } | undefined) => void;
|
||||
}): AppState {
|
||||
if (options.historyPolicy.mode === "ignore") {
|
||||
options.setDeferredHistory(undefined);
|
||||
return options.nextState;
|
||||
}
|
||||
|
||||
if (options.historyPolicy.mode === "deferred") {
|
||||
return applyDeferredHistoryPolicy(options);
|
||||
}
|
||||
|
||||
return shouldRecordHistory(options.currentState, options.nextState) ? recordHistory(snapshot(options.currentState), options.nextState) : options.nextState;
|
||||
}
|
||||
|
||||
function applyDeferredHistoryPolicy(options: {
|
||||
currentState: AppState;
|
||||
nextState: AppState;
|
||||
historyPolicy: Extract<CommandHistoryPolicy, { mode: "deferred" }>;
|
||||
deferredSnapshot?: HistorySnapshot;
|
||||
getDeferredHistory: () => { snapshot: HistorySnapshot; changed: boolean } | undefined;
|
||||
setDeferredHistory: (nextDeferredHistory: { snapshot: HistorySnapshot; changed: boolean } | undefined) => void;
|
||||
}): AppState {
|
||||
switch (options.historyPolicy.phase) {
|
||||
case "begin":
|
||||
options.setDeferredHistory(options.deferredSnapshot ? { snapshot: options.deferredSnapshot, changed: false } : undefined);
|
||||
return options.nextState;
|
||||
case "update": {
|
||||
const deferredHistory = options.getDeferredHistory();
|
||||
if (deferredHistory && options.currentState.document !== options.nextState.document) {
|
||||
options.setDeferredHistory({ ...deferredHistory, changed: true });
|
||||
}
|
||||
return options.nextState;
|
||||
}
|
||||
case "commit": {
|
||||
const deferredHistory = options.getDeferredHistory();
|
||||
options.setDeferredHistory(undefined);
|
||||
return deferredHistory?.changed ? recordHistory(deferredHistory.snapshot, options.nextState) : options.nextState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shouldRecordHistory(currentState: AppState, nextState: AppState) {
|
||||
return currentState.document !== nextState.document;
|
||||
}
|
||||
|
||||
function recordHistory(currentState: AppState, nextState: AppState): AppState {
|
||||
function recordHistory(historySnapshot: HistorySnapshot, nextState: AppState): AppState {
|
||||
return {
|
||||
...nextState,
|
||||
history: {
|
||||
past: [...currentState.history.past, snapshot(currentState)].slice(-100),
|
||||
past: [...nextState.history.past, historySnapshot].slice(-100),
|
||||
future: [],
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user