♻️ (builder) Change to features-centric folder structure
This commit is contained in:
committed by
Baptiste Arnaud
parent
3686465a85
commit
643571fe7d
159
apps/builder/src/features/editor/hooks/useUndo.ts
Normal file
159
apps/builder/src/features/editor/hooks/useUndo.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { isDefined } from '@udecode/plate-core'
|
||||
import { dequal } from 'dequal'
|
||||
// import { diff } from 'deep-object-diff'
|
||||
import { useReducer, useCallback, useRef } from 'react'
|
||||
import { isNotDefined } from 'utils'
|
||||
|
||||
enum ActionType {
|
||||
Undo = 'UNDO',
|
||||
Redo = 'REDO',
|
||||
Set = 'SET',
|
||||
Flush = 'FLUSH',
|
||||
}
|
||||
|
||||
export interface Actions<T> {
|
||||
set: (
|
||||
newPresent: T | ((current: T) => T),
|
||||
options?: { updateDate: boolean }
|
||||
) => void
|
||||
undo: () => void
|
||||
redo: () => void
|
||||
flush: () => void
|
||||
canUndo: boolean
|
||||
canRedo: boolean
|
||||
presentRef: React.MutableRefObject<T>
|
||||
}
|
||||
|
||||
interface Action<T> {
|
||||
type: ActionType
|
||||
newPresent?: T
|
||||
updateDate?: boolean
|
||||
}
|
||||
|
||||
export interface State<T> {
|
||||
past: T[]
|
||||
present: T
|
||||
future: T[]
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
past: [],
|
||||
present: null,
|
||||
future: [],
|
||||
}
|
||||
|
||||
const reducer = <T>(state: State<T>, action: Action<T>) => {
|
||||
const { past, present, future } = state
|
||||
|
||||
switch (action.type) {
|
||||
case ActionType.Undo: {
|
||||
if (past.length === 0) {
|
||||
return state
|
||||
}
|
||||
|
||||
const previous = past[past.length - 1]
|
||||
const newPast = past.slice(0, past.length - 1)
|
||||
|
||||
return {
|
||||
past: newPast,
|
||||
present: previous,
|
||||
future: [present, ...future],
|
||||
}
|
||||
}
|
||||
|
||||
case ActionType.Redo: {
|
||||
if (future.length === 0) {
|
||||
return state
|
||||
}
|
||||
const next = future[0]
|
||||
const newFuture = future.slice(1)
|
||||
|
||||
return {
|
||||
past: [...past, present],
|
||||
present: next,
|
||||
future: newFuture,
|
||||
}
|
||||
}
|
||||
|
||||
case ActionType.Set: {
|
||||
const { newPresent, updateDate } = action
|
||||
if (
|
||||
isNotDefined(newPresent) ||
|
||||
(present &&
|
||||
dequal(
|
||||
JSON.parse(JSON.stringify(newPresent)),
|
||||
JSON.parse(JSON.stringify(present))
|
||||
))
|
||||
) {
|
||||
return state
|
||||
}
|
||||
// Uncomment to debug history ⬇️
|
||||
// console.log(
|
||||
// diff(
|
||||
// JSON.parse(JSON.stringify(newPresent)),
|
||||
// present ? JSON.parse(JSON.stringify(present)) : {}
|
||||
// )
|
||||
// )
|
||||
return {
|
||||
past: [...past, present].filter(isDefined),
|
||||
present: {
|
||||
...newPresent,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
updatedAt: updateDate ? new Date() : newPresent.updatedAt,
|
||||
},
|
||||
future: [],
|
||||
}
|
||||
}
|
||||
|
||||
case ActionType.Flush:
|
||||
return { ...initialState, present }
|
||||
}
|
||||
}
|
||||
|
||||
const useUndo = <T>(initialPresent: T): [State<T>, Actions<T>] => {
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
...initialState,
|
||||
present: initialPresent,
|
||||
}) as [State<T>, React.Dispatch<Action<T>>]
|
||||
const presentRef = useRef<T>(initialPresent)
|
||||
|
||||
const canUndo = state.past.length !== 0
|
||||
const canRedo = state.future.length !== 0
|
||||
|
||||
const undo = useCallback(() => {
|
||||
if (canUndo) {
|
||||
dispatch({ type: ActionType.Undo })
|
||||
}
|
||||
}, [canUndo])
|
||||
|
||||
const redo = useCallback(() => {
|
||||
if (canRedo) {
|
||||
dispatch({ type: ActionType.Redo })
|
||||
}
|
||||
}, [canRedo])
|
||||
|
||||
const set = useCallback(
|
||||
(newPresent: T | ((current: T) => T), options = { updateDate: true }) => {
|
||||
const updatedTypebot =
|
||||
'id' in newPresent
|
||||
? newPresent
|
||||
: (newPresent as (current: T) => T)(presentRef.current)
|
||||
presentRef.current = updatedTypebot
|
||||
dispatch({
|
||||
type: ActionType.Set,
|
||||
newPresent: updatedTypebot,
|
||||
updateDate: options.updateDate,
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const flush = useCallback(() => {
|
||||
dispatch({ type: ActionType.Flush })
|
||||
}, [])
|
||||
|
||||
return [state, { set, undo, redo, flush, canUndo, canRedo, presentRef }]
|
||||
}
|
||||
|
||||
export default useUndo
|
||||
Reference in New Issue
Block a user