12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- import {ReducerAction, ReducerState, useReducer} from 'react';
- export type UndoableNode<S> = {
- current: S;
- next: UndoableNode<S> | undefined;
- previous: UndoableNode<S> | undefined;
- };
- type UndoAction = {
- type: 'undo';
- };
- type RedoAction = {
- type: 'redo';
- };
- export type UndoableReducerAction<A> = UndoAction | RedoAction | A;
- export type UndoableReducer<R extends React.Reducer<any, any>> = React.Reducer<
- UndoableNode<ReducerState<R>>,
- UndoableReducerAction<ReducerAction<R>>
- >;
- function isUndoOrRedoAction(
- action: UndoableReducerAction<any>
- ): action is UndoAction | RedoAction {
- if (action.type) {
- return action.type === 'undo' || action.type === 'redo';
- }
- return false;
- }
- function undoableReducer<S>(
- state: UndoableNode<S>,
- action: UndoAction | RedoAction
- ): UndoableNode<S> {
- if (action.type === 'undo') {
- return state.previous === undefined ? state : state.previous;
- }
- if (action.type === 'redo') {
- return state.next === undefined ? state : state.next;
- }
- throw new Error('Unreachable case');
- }
- export function makeUndoableReducer<R extends React.Reducer<any, any>>(
- reducer: R
- ): UndoableReducer<R> {
- return (
- state: UndoableNode<ReducerState<R>>,
- action: UndoableReducerAction<ReducerAction<R>>
- ) => {
- if (isUndoOrRedoAction(action)) {
- return undoableReducer(state, action);
- }
- const newState: UndoableNode<ReducerState<R>> = {
- next: undefined,
- previous: state,
- current: reducer(state.current, action),
- };
- state.next = newState;
- return newState;
- };
- }
- export function useUndoableReducer<
- R extends React.Reducer<ReducerState<R>, ReducerAction<R>>
- >(
- reducer: R,
- initialState: ReducerState<R>
- ): [
- ReducerState<R>,
- React.Dispatch<UndoableReducerAction<ReducerAction<R>>>,
- {
- nextState: ReducerState<R> | undefined;
- previousState: ReducerState<R> | undefined;
- }
- ] {
- const [state, dispatch] = useReducer(makeUndoableReducer(reducer), {
- current: initialState,
- previous: undefined,
- next: undefined,
- });
- return [
- state.current,
- dispatch,
- {previousState: state.previous?.current, nextState: state.next?.current},
- ];
- }
|