119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
/**
|
||
* Provider that wraps the builder and adds undo/redo functionality
|
||
* Automatically stores state snapshots when significant changes occur
|
||
*/
|
||
|
||
"use client";
|
||
|
||
import { createContext, useContext, useEffect, useRef, type ReactNode } from 'react';
|
||
import { useBuilderState, useBuilderDispatch } from '@/lib/admin/builder/context';
|
||
import { useSimpleUndoRedo } from '@/lib/admin/builder/useSimpleUndoRedo';
|
||
import type { BuilderState } from '@/lib/admin/builder/context';
|
||
|
||
interface UndoRedoContextValue {
|
||
canUndo: boolean;
|
||
canRedo: boolean;
|
||
undo: () => void;
|
||
redo: () => void;
|
||
store: () => void;
|
||
clear: () => void;
|
||
resetDirty: () => void; // Сброс isDirty флага
|
||
}
|
||
|
||
const UndoRedoContext = createContext<UndoRedoContextValue | undefined>(undefined);
|
||
|
||
interface BuilderUndoRedoProviderProps {
|
||
children: ReactNode;
|
||
}
|
||
|
||
export function BuilderUndoRedoProvider({ children }: BuilderUndoRedoProviderProps) {
|
||
const state = useBuilderState();
|
||
const dispatch = useBuilderDispatch();
|
||
const previousStateRef = useRef<BuilderState>(state);
|
||
const isRestoringRef = useRef(false);
|
||
|
||
// Функция для сброса isDirty
|
||
const resetDirty = () => {
|
||
dispatch({ type: 'reset', payload: { ...state, isDirty: false } });
|
||
};
|
||
|
||
const undoRedo = useSimpleUndoRedo(
|
||
state,
|
||
(newState) => {
|
||
isRestoringRef.current = true;
|
||
dispatch({ type: 'reset', payload: newState });
|
||
}
|
||
);
|
||
|
||
// Auto-store state when significant changes occur
|
||
useEffect(() => {
|
||
// Don't store if we're in the middle of restoring from undo/redo
|
||
if (isRestoringRef.current) {
|
||
isRestoringRef.current = false;
|
||
previousStateRef.current = state;
|
||
return;
|
||
}
|
||
|
||
const prev = previousStateRef.current;
|
||
|
||
// Check for significant changes that should trigger a store
|
||
const shouldStore = (
|
||
// Screen count changed
|
||
prev.screens.length !== state.screens.length ||
|
||
|
||
// Selected screen changed
|
||
prev.selectedScreenId !== state.selectedScreenId ||
|
||
|
||
// Meta data changed
|
||
JSON.stringify(prev.meta) !== JSON.stringify(state.meta) ||
|
||
|
||
// Screen structure changed (templates, navigation, etc.)
|
||
prev.screens.some((prevScreen, index) => {
|
||
const currentScreen = state.screens[index];
|
||
if (!currentScreen || prevScreen.id !== currentScreen.id) return true;
|
||
|
||
return (
|
||
prevScreen.template !== currentScreen.template ||
|
||
JSON.stringify(prevScreen.navigation) !== JSON.stringify(currentScreen.navigation) ||
|
||
JSON.stringify(prevScreen.title) !== JSON.stringify(currentScreen.title)
|
||
);
|
||
})
|
||
);
|
||
|
||
if (shouldStore) {
|
||
// Store the previous state (not current) so we can undo to it
|
||
undoRedo.store();
|
||
previousStateRef.current = state;
|
||
}
|
||
}, [state, undoRedo]);
|
||
|
||
// Store initial state
|
||
useEffect(() => {
|
||
undoRedo.store();
|
||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||
|
||
const contextValue: UndoRedoContextValue = {
|
||
canUndo: undoRedo.canUndo,
|
||
canRedo: undoRedo.canRedo,
|
||
undo: undoRedo.undo,
|
||
redo: undoRedo.redo,
|
||
store: undoRedo.store,
|
||
clear: undoRedo.clear,
|
||
resetDirty,
|
||
};
|
||
|
||
return (
|
||
<UndoRedoContext.Provider value={contextValue}>
|
||
{children}
|
||
</UndoRedoContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function useBuilderUndoRedo(): UndoRedoContextValue {
|
||
const context = useContext(UndoRedoContext);
|
||
if (!context) {
|
||
throw new Error('useBuilderUndoRedo must be used within BuilderUndoRedoProvider');
|
||
}
|
||
return context;
|
||
}
|