拆分图片画布历史与持久化协调器
新增画布历史 hook 承接撤销重做快照逻辑 新增项目持久化 hook 承接加载资源创建与自动保存时序 补充 hook 单测并更新图片画布拆分跟踪文档
This commit is contained in:
169
src/components/image-editor/useCanvasHistory.ts
Normal file
169
src/components/image-editor/useCanvasHistory.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { type RefObject, useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { MAX_HISTORY_STEPS } from './ImageCanvasEditorModel';
|
||||
import type {
|
||||
CanvasGenerationDialogState,
|
||||
CanvasHistorySnapshot,
|
||||
CanvasLayer,
|
||||
CanvasMarqueeState,
|
||||
CanvasContextMenuState,
|
||||
CanvasViewport,
|
||||
GenerateDialogState,
|
||||
ImageContextMenuState,
|
||||
SnapGuide,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
|
||||
type CanvasHistoryRefs = {
|
||||
layersRef: RefObject<CanvasLayer[]>;
|
||||
viewportRef: RefObject<CanvasViewport>;
|
||||
generateDialogRef: RefObject<GenerateDialogState | null>;
|
||||
inactiveGenerateDialogsRef: RefObject<CanvasGenerationDialogState[]>;
|
||||
selectedLayerIdRef: RefObject<string | null>;
|
||||
selectedLayerIdsRef: RefObject<string[]>;
|
||||
};
|
||||
|
||||
type CanvasHistorySetters = {
|
||||
setLayers: (layers: CanvasLayer[]) => void;
|
||||
setViewport: (viewport: CanvasViewport) => void;
|
||||
setGenerateDialog: (dialog: GenerateDialogState | null) => void;
|
||||
setInactiveGenerateDialogs: (
|
||||
dialogs: CanvasGenerationDialogState[],
|
||||
) => void;
|
||||
setSelectedLayerId: (layerId: string | null) => void;
|
||||
setSelectedLayerIds: (layerIds: string[]) => void;
|
||||
};
|
||||
|
||||
type CanvasHistoryResetters = {
|
||||
setHoveredLayerId: (layerId: string | null) => void;
|
||||
setMetadataLayer: (layer: CanvasLayer | null) => void;
|
||||
setCanvasMarquee: (marquee: CanvasMarqueeState | null) => void;
|
||||
setSnapGuide: (guide: SnapGuide | null) => void;
|
||||
setImageContextMenu: (menu: ImageContextMenuState | null) => void;
|
||||
setContextMenu: (menu: CanvasContextMenuState | null) => void;
|
||||
setIsPanning: (isPanning: boolean) => void;
|
||||
clearDragState: () => void;
|
||||
};
|
||||
|
||||
function cloneGenerateDialog(dialog: GenerateDialogState): GenerateDialogState {
|
||||
return {
|
||||
...dialog,
|
||||
placeholder: dialog.placeholder ? { ...dialog.placeholder } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function cloneCanvasGenerationDialog(
|
||||
dialog: CanvasGenerationDialogState,
|
||||
): CanvasGenerationDialogState {
|
||||
return {
|
||||
...dialog,
|
||||
placeholder: dialog.placeholder ? { ...dialog.placeholder } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCanvasHistory({
|
||||
refs,
|
||||
setters,
|
||||
resetters,
|
||||
}: {
|
||||
refs: CanvasHistoryRefs;
|
||||
setters: CanvasHistorySetters;
|
||||
resetters: CanvasHistoryResetters;
|
||||
}) {
|
||||
const undoStackRef = useRef<CanvasHistorySnapshot[]>([]);
|
||||
const redoStackRef = useRef<CanvasHistorySnapshot[]>([]);
|
||||
const [historyVersion, setHistoryVersion] = useState(0);
|
||||
|
||||
const getCanvasHistorySnapshot = useCallback(
|
||||
(): CanvasHistorySnapshot => ({
|
||||
layers: refs.layersRef.current.map((layer) => ({ ...layer })),
|
||||
viewport: { ...refs.viewportRef.current },
|
||||
generateDialog: refs.generateDialogRef.current
|
||||
? cloneGenerateDialog(refs.generateDialogRef.current)
|
||||
: null,
|
||||
inactiveGenerateDialogs:
|
||||
refs.inactiveGenerateDialogsRef.current.map(cloneCanvasGenerationDialog),
|
||||
selectedLayerId: refs.selectedLayerIdRef.current,
|
||||
selectedLayerIds: [...refs.selectedLayerIdsRef.current],
|
||||
}),
|
||||
[refs],
|
||||
);
|
||||
|
||||
const restoreCanvasHistorySnapshot = useCallback(
|
||||
(snapshot: CanvasHistorySnapshot) => {
|
||||
setters.setLayers(snapshot.layers.map((layer) => ({ ...layer })));
|
||||
setters.setViewport({ ...snapshot.viewport });
|
||||
setters.setGenerateDialog(
|
||||
snapshot.generateDialog
|
||||
? cloneGenerateDialog(snapshot.generateDialog)
|
||||
: null,
|
||||
);
|
||||
setters.setInactiveGenerateDialogs(
|
||||
snapshot.inactiveGenerateDialogs.map(cloneCanvasGenerationDialog),
|
||||
);
|
||||
setters.setSelectedLayerId(snapshot.selectedLayerId);
|
||||
setters.setSelectedLayerIds([...snapshot.selectedLayerIds]);
|
||||
resetters.setHoveredLayerId(null);
|
||||
resetters.setMetadataLayer(null);
|
||||
resetters.setCanvasMarquee(null);
|
||||
resetters.setSnapGuide(null);
|
||||
resetters.setImageContextMenu(null);
|
||||
resetters.setContextMenu(null);
|
||||
resetters.setIsPanning(false);
|
||||
resetters.clearDragState();
|
||||
},
|
||||
[resetters, setters],
|
||||
);
|
||||
|
||||
const captureCanvasHistory = useCallback(
|
||||
(options: { clearRedo?: boolean } = {}) => {
|
||||
undoStackRef.current = [
|
||||
...undoStackRef.current.slice(-(MAX_HISTORY_STEPS - 1)),
|
||||
getCanvasHistorySnapshot(),
|
||||
];
|
||||
if (options.clearRedo !== false) {
|
||||
redoStackRef.current = [];
|
||||
}
|
||||
setHistoryVersion((version) => version + 1);
|
||||
},
|
||||
[getCanvasHistorySnapshot],
|
||||
);
|
||||
|
||||
const undoCanvasChange = useCallback(() => {
|
||||
const previousSnapshot = undoStackRef.current.at(-1);
|
||||
if (!previousSnapshot) {
|
||||
return;
|
||||
}
|
||||
undoStackRef.current = undoStackRef.current.slice(0, -1);
|
||||
redoStackRef.current = [
|
||||
...redoStackRef.current.slice(-(MAX_HISTORY_STEPS - 1)),
|
||||
getCanvasHistorySnapshot(),
|
||||
];
|
||||
restoreCanvasHistorySnapshot(previousSnapshot);
|
||||
setHistoryVersion((version) => version + 1);
|
||||
}, [getCanvasHistorySnapshot, restoreCanvasHistorySnapshot]);
|
||||
|
||||
const redoCanvasChange = useCallback(() => {
|
||||
const nextSnapshot = redoStackRef.current.at(-1);
|
||||
if (!nextSnapshot) {
|
||||
return;
|
||||
}
|
||||
redoStackRef.current = redoStackRef.current.slice(0, -1);
|
||||
undoStackRef.current = [
|
||||
...undoStackRef.current.slice(-(MAX_HISTORY_STEPS - 1)),
|
||||
getCanvasHistorySnapshot(),
|
||||
];
|
||||
restoreCanvasHistorySnapshot(nextSnapshot);
|
||||
setHistoryVersion((version) => version + 1);
|
||||
}, [getCanvasHistorySnapshot, restoreCanvasHistorySnapshot]);
|
||||
|
||||
return {
|
||||
canUndo: undoStackRef.current.length > 0,
|
||||
canRedo: redoStackRef.current.length > 0,
|
||||
historyVersion,
|
||||
getCanvasHistorySnapshot,
|
||||
restoreCanvasHistorySnapshot,
|
||||
captureCanvasHistory,
|
||||
undoCanvasChange,
|
||||
redoCanvasChange,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user