73 lines
3.4 KiB
JavaScript
73 lines
3.4 KiB
JavaScript
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
|
|
const MAX_HISTORY = 50;
|
|
const DEBOUNCE_DELAY_MS = 300;
|
|
|
|
export function useDesignEditor() {
|
|
const [elements, setElements] = useState([]);
|
|
const [selectedId, setSelectedId] = useState(null);
|
|
const historyRef = useRef([]);
|
|
const historyIndexRef = useRef(-1);
|
|
const historyTimerRef = useRef(null);
|
|
const pendingChangesRef = useRef(null);
|
|
|
|
const saveToHistory = useCallback((newElements) => {
|
|
if (historyIndexRef.current < historyRef.current.length - 1) {
|
|
historyRef.current = historyRef.current.slice(0, historyIndexRef.current + 1);
|
|
}
|
|
historyRef.current.push(JSON.stringify(newElements));
|
|
if (historyRef.current.length > MAX_HISTORY) { historyRef.current.shift(); }
|
|
else { historyIndexRef.current++; }
|
|
}, []);
|
|
|
|
const flushPendingChanges = useCallback(() => {
|
|
if (pendingChangesRef.current) { saveToHistory(pendingChangesRef.current); pendingChangesRef.current = null; }
|
|
if (historyTimerRef.current) { clearTimeout(historyTimerRef.current); historyTimerRef.current = null; }
|
|
}, [saveToHistory]);
|
|
|
|
useEffect(() => { return () => { if (historyTimerRef.current) clearTimeout(historyTimerRef.current); }; }, []);
|
|
|
|
const canUndo = historyIndexRef.current > 0;
|
|
const canRedo = historyIndexRef.current < historyRef.current.length - 1;
|
|
|
|
const addElement = useCallback((element) => {
|
|
flushPendingChanges();
|
|
const newElement = { ...element, id: `element-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` };
|
|
setElements((prev) => { const newElements = [...prev, newElement]; saveToHistory(newElements); return newElements; });
|
|
setSelectedId(newElement.id);
|
|
return newElement.id;
|
|
}, [flushPendingChanges, saveToHistory]);
|
|
|
|
const updateElement = useCallback((id, attrs) => {
|
|
setElements((prev) => {
|
|
const newElements = prev.map((el) => (el.id === id ? { ...el, ...attrs } : el));
|
|
pendingChangesRef.current = newElements;
|
|
if (historyTimerRef.current) clearTimeout(historyTimerRef.current);
|
|
historyTimerRef.current = setTimeout(() => { flushPendingChanges(); }, DEBOUNCE_DELAY_MS);
|
|
return newElements;
|
|
});
|
|
}, [flushPendingChanges]);
|
|
|
|
const deleteElement = useCallback((id) => {
|
|
flushPendingChanges();
|
|
setElements((prev) => { const newElements = prev.filter((el) => el.id !== id); saveToHistory(newElements); return newElements; });
|
|
if (selectedId === id) setSelectedId(null);
|
|
}, [selectedId, flushPendingChanges, saveToHistory]);
|
|
|
|
const selectElement = useCallback((id) => setSelectedId(id), []);
|
|
const deselectAll = useCallback(() => setSelectedId(null), []);
|
|
const commitHistory = useCallback(() => flushPendingChanges(), [flushPendingChanges]);
|
|
|
|
const undo = useCallback(() => {
|
|
if (historyIndexRef.current > 0) { historyIndexRef.current--; setElements(JSON.parse(historyRef.current[historyIndexRef.current])); setSelectedId(null); }
|
|
}, []);
|
|
|
|
const redo = useCallback(() => {
|
|
if (historyIndexRef.current < historyRef.current.length - 1) { historyIndexRef.current++; setElements(JSON.parse(historyRef.current[historyIndexRef.current])); setSelectedId(null); }
|
|
}, []);
|
|
|
|
const initializeHistory = useCallback(() => { historyRef.current = [JSON.stringify([])]; historyIndexRef.current = 0; }, []);
|
|
|
|
return { elements, selectedId, addElement, updateElement, deleteElement, selectElement, deselectAll, commitHistory, undo, redo, canUndo, canRedo, initializeHistory };
|
|
}
|