// Undo/Redo history hook for the tour/transit tool. // // localStorage is origin-scoped (amix-design.com), NOT path-scoped. Other tools // on the same origin use 'sankaku_saved_data' (triangle gen) and // 'combo_saved_data' (card combo), so we use a dedicated 'tour_' prefix here // to avoid mutual data loss. const LS_KEY = 'tour_saved_data'; const { useState, useRef, useCallback, useEffect } = React; function useHistory(initial){ const [state, setState] = useState(() => { try { const saved = localStorage.getItem(LS_KEY); if (saved) { return { ...initial, ...JSON.parse(saved) }; } } catch(e) { console.warn('Failed to parse saved data', e); } return initial; }); const pastRef = useRef([]); const futureRef = useRef([]); const [, force] = useState(0); useEffect(() => { const tm = setTimeout(() => { try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch(e) {} }, 500); return () => clearTimeout(tm); }, [state]); const set = useCallback((updater, opts = {}) => { setState(prev => { const next = typeof updater === 'function' ? updater(prev) : updater; if (next === prev) return prev; if (!opts.skipHistory){ pastRef.current.push(prev); if (pastRef.current.length > 80) pastRef.current.shift(); futureRef.current = []; force(x=>x+1); } return next; }); }, []); const undo = useCallback(()=>{ setState(prev => { if (pastRef.current.length === 0) return prev; const last = pastRef.current.pop(); futureRef.current.push(prev); force(x=>x+1); return last; }); },[]); const redo = useCallback(()=>{ setState(prev => { if (futureRef.current.length === 0) return prev; const next = futureRef.current.pop(); pastRef.current.push(prev); force(x=>x+1); return next; }); },[]); const reset = useCallback((value)=>{ pastRef.current = []; futureRef.current = []; setState(value); force(x=>x+1); },[]); return { state, set, undo, redo, reset, canUndo: pastRef.current.length>0, canRedo: futureRef.current.length>0, }; } window.useHistory = useHistory;