xpaintdev / hooks /useCanvasHistory.ts
suisuyy
Initialize xpaintai project with core files and basic structure
763be49
import { useState, useEffect, useCallback } from 'react';
import { saveCanvasState as dbSaveCanvasState, loadCanvasState as dbLoadCanvasState, LoadedCanvasState } from '../services/dbService';
import { MAX_HISTORY_STEPS, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT } from '../constants';
import { getBlankCanvasDataURL } from '../utils/canvasUtils';
export interface CanvasHistoryHook {
historyStack: string[];
currentHistoryIndex: number;
canvasWidth: number;
canvasHeight: number;
isLoading: boolean;
currentDataURL: string | null;
handleDrawEnd: (dataURL: string, newWidth?: number, newHeight?: number) => void;
handleUndo: () => void;
handleRedo: () => void;
updateCanvasState: (dataURL: string, width: number, height: number) => void;
}
export const useCanvasHistory = (): CanvasHistoryHook => {
const [historyStack, setHistoryStack] = useState<string[]>([]);
const [currentHistoryIndex, setCurrentHistoryIndex] = useState<number>(-1);
const [canvasWidth, setCanvasWidth] = useState<number>(DEFAULT_CANVAS_WIDTH);
const [canvasHeight, setCanvasHeight] = useState<number>(DEFAULT_CANVAS_HEIGHT);
const [isLoading, setIsLoading] = useState<boolean>(true);
const currentDataURL = historyStack.length > 0 && currentHistoryIndex >= 0 ? historyStack[currentHistoryIndex] : null;
const loadInitialCanvas = useCallback(async () => {
setIsLoading(true);
const savedState: LoadedCanvasState | null = await dbLoadCanvasState();
if (savedState && savedState.dataURL) { // Ensure dataURL exists
setCanvasWidth(savedState.width);
setCanvasHeight(savedState.height);
setHistoryStack([savedState.dataURL]);
setCurrentHistoryIndex(0);
} else {
const initialWidth = DEFAULT_CANVAS_WIDTH;
const initialHeight = DEFAULT_CANVAS_HEIGHT;
setCanvasWidth(initialWidth);
setCanvasHeight(initialHeight);
const blankCanvas = getBlankCanvasDataURL(initialWidth, initialHeight);
setHistoryStack([blankCanvas]);
setCurrentHistoryIndex(0);
await dbSaveCanvasState(blankCanvas, initialWidth, initialHeight);
}
setIsLoading(false);
}, []);
useEffect(() => {
loadInitialCanvas();
}, [loadInitialCanvas]);
const handleDrawEnd = useCallback((dataURL: string, newWidth?: number, newHeight?: number) => {
const effectiveWidth = newWidth ?? canvasWidth;
const effectiveHeight = newHeight ?? canvasHeight;
if (newWidth && newWidth !== canvasWidth) setCanvasWidth(newWidth);
if (newHeight && newHeight !== canvasHeight) setCanvasHeight(newHeight);
setHistoryStack(prevStack => {
// If currentHistoryIndex is not at the end, it means we've undone some steps.
// New drawing should overwrite the "redo" history.
const newStackBase = prevStack.slice(0, currentHistoryIndex + 1);
let newStack = [...newStackBase, dataURL];
if (newStack.length > MAX_HISTORY_STEPS) {
newStack = newStack.slice(newStack.length - MAX_HISTORY_STEPS);
}
// Update currentHistoryIndex to point to the new state (end of the new stack)
setCurrentHistoryIndex(newStack.length - 1);
return newStack;
});
dbSaveCanvasState(dataURL, effectiveWidth, effectiveHeight);
}, [currentHistoryIndex, canvasWidth, canvasHeight]); // Added canvasWidth, canvasHeight as they are used for effective dimensions.
const updateCanvasState = useCallback((dataURL: string, width: number, height: number) => {
// This function is for direct updates, like loading an image or AI result
handleDrawEnd(dataURL, width, height);
}, [handleDrawEnd]);
const handleUndo = () => {
if (currentHistoryIndex > 0) {
setCurrentHistoryIndex(prevIndex => prevIndex - 1);
// Autosave current state when undoing to ensure persistence of the "undone to" state.
// This is implicit as currentDataURL will update, and if the app were to reload,
// it should load the state at currentHistoryIndex.
// The currentDataURL used by CanvasComponent will be historyStack[newIndex].
// dbSaveCanvasState(historyStack[currentHistoryIndex - 1], canvasWidth, canvasHeight); // This might be too aggressive, rely on drawEnd
}
};
const handleRedo = () => {
if (currentHistoryIndex < historyStack.length - 1) {
setCurrentHistoryIndex(prevIndex => prevIndex + 1);
// dbSaveCanvasState(historyStack[currentHistoryIndex + 1], canvasWidth, canvasHeight); // Same as undo, might be too aggressive
}
};
return {
historyStack,
currentHistoryIndex,
canvasWidth,
canvasHeight,
isLoading,
currentDataURL,
handleDrawEnd,
handleUndo,
handleRedo,
updateCanvasState,
};
};