-
-
Notifications
You must be signed in to change notification settings - Fork 265
/
proxyWithHistory.ts
94 lines (90 loc) · 2.8 KB
/
proxyWithHistory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { proxy, ref, snapshot, subscribe } from 'valtio/vanilla'
import type { INTERNAL_Snapshot as Snapshot } from 'valtio/vanilla'
type SnapshotOrUndefined<T> = Snapshot<T> | undefined
type Snapshots<T> = Snapshot<T>[]
const isObject = (x: unknown): x is object =>
typeof x === 'object' && x !== null
const deepClone = <T>(obj: T): T => {
if (!isObject(obj)) {
return obj
}
const baseObject: T = Array.isArray(obj)
? []
: Object.create(Object.getPrototypeOf(obj))
Reflect.ownKeys(obj).forEach((key) => {
baseObject[key as keyof T] = deepClone(obj[key as keyof T])
})
return baseObject
}
/**
* proxyWithHistory
*
* This creates a new proxy with history support.
* It includes following properties:
* - value: any value (does not have to be an object)
* - history: an array holding the history of snapshots
* - historyIndex: the history index to the current snapshot
* - canUndo: a function to return true if undo is available
* - undo: a function to go back history
* - canRedo: a function to return true if redo is available
* - redo: a function to go forward history
* - saveHistory: a function to save history
*
* [Notes]
* Suspense/promise is not supported.
*
* @example
* import { proxyWithHistory } from 'valtio/utils'
* const state = proxyWithHistory({
* count: 1,
* })
*/
export function proxyWithHistory<V>(initialValue: V, skipSubscribe = false) {
const proxyObject = proxy({
value: initialValue,
history: ref({
wip: undefined as SnapshotOrUndefined<V>, // to avoid infinite loop
snapshots: [] as Snapshots<V>,
index: -1,
}),
canUndo: () => proxyObject.history.index > 0,
undo: () => {
if (proxyObject.canUndo()) {
proxyObject.value = (proxyObject.history.wip = deepClone(
proxyObject.history.snapshots[--proxyObject.history.index]
) as Snapshot<V>) as V
}
},
canRedo: () =>
proxyObject.history.index < proxyObject.history.snapshots.length - 1,
redo: () => {
if (proxyObject.canRedo()) {
proxyObject.value = (proxyObject.history.wip = deepClone(
proxyObject.history.snapshots[++proxyObject.history.index]
) as Snapshot<V>) as V
}
},
saveHistory: () => {
proxyObject.history.snapshots.splice(proxyObject.history.index + 1)
proxyObject.history.snapshots.push(snapshot(proxyObject).value)
++proxyObject.history.index
},
subscribe: () =>
subscribe(proxyObject, (ops) => {
if (
ops.every(
(op) =>
op[1][0] === 'value' &&
(op[0] !== 'set' || op[2] !== proxyObject.history.wip)
)
) {
proxyObject.saveHistory()
}
}),
})
proxyObject.saveHistory()
if (!skipSubscribe) {
proxyObject.subscribe()
}
return proxyObject
}