|
|
|
|
|
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) { |
|
|
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 => { |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
setCurrentHistoryIndex(newStack.length - 1); |
|
|
return newStack; |
|
|
}); |
|
|
dbSaveCanvasState(dataURL, effectiveWidth, effectiveHeight); |
|
|
}, [currentHistoryIndex, canvasWidth, canvasHeight]); |
|
|
|
|
|
const updateCanvasState = useCallback((dataURL: string, width: number, height: number) => { |
|
|
|
|
|
handleDrawEnd(dataURL, width, height); |
|
|
}, [handleDrawEnd]); |
|
|
|
|
|
|
|
|
const handleUndo = () => { |
|
|
if (currentHistoryIndex > 0) { |
|
|
setCurrentHistoryIndex(prevIndex => prevIndex - 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
}; |
|
|
|
|
|
const handleRedo = () => { |
|
|
if (currentHistoryIndex < historyStack.length - 1) { |
|
|
setCurrentHistoryIndex(prevIndex => prevIndex + 1); |
|
|
|
|
|
} |
|
|
}; |
|
|
|
|
|
return { |
|
|
historyStack, |
|
|
currentHistoryIndex, |
|
|
canvasWidth, |
|
|
canvasHeight, |
|
|
isLoading, |
|
|
currentDataURL, |
|
|
handleDrawEnd, |
|
|
handleUndo, |
|
|
handleRedo, |
|
|
updateCanvasState, |
|
|
}; |
|
|
}; |
|
|
|