mobx-keystone 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +864 -853
- package/README.md +61 -61
- package/dist/mobx-keystone.esm.js +11336 -11148
- package/dist/mobx-keystone.esm.mjs +11336 -11148
- package/dist/mobx-keystone.umd.js +12229 -11738
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
- package/dist/types/modelShared/prop.d.ts +5 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
- package/dist/types/ref/Ref.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +1 -1
- package/dist/types/types/TypeChecker.d.ts +1 -1
- package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
- package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
- package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
- package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
- package/dist/types/types/objectBased/typesModel.d.ts +1 -1
- package/dist/types/types/objectBased/typesObject.d.ts +3 -3
- package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
- package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
- package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
- package/dist/types/types/types.d.ts +3 -3
- package/dist/types/types/utility/typesCodec.d.ts +1 -6
- package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
- package/dist/types/types/utility/typesOr.d.ts +1 -1
- package/dist/types/types/utility/typesRefinement.d.ts +1 -1
- package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
- package/dist/types/types/utility/typesTag.d.ts +1 -1
- package/dist/types/wrappers/ObjectMap.d.ts +2 -0
- package/dist/types/wrappers/asMap.d.ts +10 -6
- package/package.json +14 -12
- package/src/action/applyAction.ts +125 -114
- package/src/action/applyMethodCall.ts +37 -37
- package/src/action/isModelAction.ts +16 -16
- package/src/action/middleware.ts +137 -137
- package/src/action/modelFlow.ts +240 -240
- package/src/action/modelFlowPromiseGenerator.ts +127 -125
- package/src/action/pendingActions.ts +42 -42
- package/src/action/wrapInAction.ts +156 -156
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
- package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
- package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
- package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
- package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
- package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
- package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
- package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
- package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
- package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
- package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
- package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
- package/src/actionMiddlewares/undoMiddleware.ts +804 -796
- package/src/actionMiddlewares/utils.ts +2 -2
- package/src/computedTree/computedTree.ts +143 -143
- package/src/context/context.ts +1 -1
- package/src/dataModel/BaseDataModel.ts +194 -192
- package/src/dataModel/actions.ts +1 -1
- package/src/dataModel/newDataModel.ts +57 -57
- package/src/deepChange/index.ts +1 -1
- package/src/deepChange/onDeepChange.ts +244 -244
- package/src/frozen/Frozen.ts +146 -146
- package/src/globalConfig/globalConfig.ts +107 -107
- package/src/index.ts +24 -24
- package/src/model/BaseModel.ts +275 -273
- package/src/model/ModelConstructorOptions.ts +15 -15
- package/src/model/getModelMetadata.ts +51 -51
- package/src/model/index.ts +6 -6
- package/src/model/mixins.ts +288 -288
- package/src/model/newModel.ts +301 -301
- package/src/model/utils.ts +249 -249
- package/src/modelShared/modelDecorator.ts +260 -253
- package/src/modelShared/modelInfo.ts +50 -50
- package/src/modelShared/modelSymbols.ts +3 -3
- package/src/modelShared/newModel.ts +25 -25
- package/src/modelShared/prop.ts +713 -684
- package/src/modelShared/sharedInternalModel.ts +427 -417
- package/src/modelShared/utils.ts +24 -24
- package/src/parent/core.ts +65 -65
- package/src/parent/coreObjectChildren.ts +180 -180
- package/src/parent/detach.ts +47 -47
- package/src/parent/findParent.ts +1 -1
- package/src/parent/getChildrenObjects.ts +25 -25
- package/src/parent/path.ts +386 -381
- package/src/parent/setParent.ts +149 -149
- package/src/parent/walkTree.ts +1 -1
- package/src/patch/applyPatches.ts +187 -187
- package/src/patch/jsonPatch.ts +108 -108
- package/src/patch/patchRecorder.ts +6 -1
- package/src/redux/connectReduxDevTools.ts +39 -7
- package/src/redux/redux.ts +6 -2
- package/src/ref/Ref.ts +1 -1
- package/src/ref/core.ts +339 -345
- package/src/ref/customRef.ts +3 -3
- package/src/ref/rootRef.ts +100 -100
- package/src/rootStore/attachDetach.ts +89 -89
- package/src/rootStore/rootStore.ts +1 -1
- package/src/snapshot/SnapshotOf.ts +104 -104
- package/src/snapshot/SnapshotProcessingError.ts +45 -45
- package/src/snapshot/applySnapshot.ts +177 -181
- package/src/snapshot/cloneTreeValue.ts +30 -30
- package/src/snapshot/fromArraySnapshot.ts +27 -33
- package/src/snapshot/fromModelSnapshot.ts +70 -70
- package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
- package/src/snapshot/fromSnapshot.ts +198 -198
- package/src/snapshot/getSnapshot.ts +80 -79
- package/src/snapshot/index.ts +8 -8
- package/src/snapshot/internal.ts +230 -230
- package/src/snapshot/reconcileArraySnapshot.ts +70 -70
- package/src/snapshot/reconcileModelSnapshot.ts +155 -150
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
- package/src/snapshot/reconcileSnapshot.ts +88 -88
- package/src/standardActions/actions.ts +64 -64
- package/src/standardActions/objectActions.ts +60 -60
- package/src/transforms/ImmutableDate.ts +93 -93
- package/src/transforms/asMap.ts +50 -50
- package/src/transforms/asSet.ts +22 -22
- package/src/transforms/bigint.ts +20 -20
- package/src/transforms/date.ts +40 -40
- package/src/treeUtils/deepEquals.ts +55 -55
- package/src/treeUtils/sandbox.ts +6 -3
- package/src/tweaker/TweakerPriority.ts +9 -9
- package/src/tweaker/tweak.ts +4 -3
- package/src/tweaker/tweakArray.ts +530 -475
- package/src/tweaker/tweakFrozen.ts +42 -42
- package/src/tweaker/tweakModel.ts +1 -1
- package/src/tweaker/tweakPlainObject.ts +355 -355
- package/src/tweaker/typeChecking.ts +101 -101
- package/src/types/SnapshotTypeMismatchError.ts +46 -46
- package/src/types/TypeCheckError.ts +183 -184
- package/src/types/TypeChecker.ts +202 -185
- package/src/types/arrayBased/typesArray.ts +136 -137
- package/src/types/arrayBased/typesTuple.ts +138 -139
- package/src/types/createPerEntryCachedCheck.ts +166 -160
- package/src/types/index.ts +6 -6
- package/src/types/objectBased/typesArraySet.ts +133 -134
- package/src/types/objectBased/typesDataModelData.ts +203 -204
- package/src/types/objectBased/typesModel.ts +220 -221
- package/src/types/objectBased/typesObject.ts +308 -310
- package/src/types/objectBased/typesObjectMap.ts +142 -143
- package/src/types/objectBased/typesRecord.ts +156 -157
- package/src/types/objectBased/typesRef.ts +89 -89
- package/src/types/primitiveBased/typesEnum.ts +63 -63
- package/src/types/primitiveBased/typesPrimitive.ts +219 -220
- package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
- package/src/types/resolveTypeChecker.ts +78 -78
- package/src/types/schemas.ts +208 -208
- package/src/types/tProp.ts +277 -268
- package/src/types/typeCheck.ts +21 -21
- package/src/types/typeCheckErrorUtils.ts +23 -23
- package/src/types/types.ts +142 -142
- package/src/types/utility/typeToStoredData.ts +37 -37
- package/src/types/utility/typesBigInt.ts +15 -15
- package/src/types/utility/typesCodec.ts +41 -38
- package/src/types/utility/typesCodecCore.ts +178 -178
- package/src/types/utility/typesCodecSupport.ts +806 -806
- package/src/types/utility/typesDate.ts +27 -27
- package/src/types/utility/typesMap.ts +253 -222
- package/src/types/utility/typesOr.ts +263 -259
- package/src/types/utility/typesRefinement.ts +131 -125
- package/src/types/utility/typesSet.ts +110 -110
- package/src/types/utility/typesSkipCheck.ts +69 -70
- package/src/types/utility/typesTag.ts +88 -82
- package/src/types/utility/typesUnchecked.ts +40 -40
- package/src/utils/AnyFunction.ts +4 -4
- package/src/utils/ModelPool.ts +59 -59
- package/src/utils/chainFns.ts +18 -18
- package/src/utils/decorators.ts +274 -274
- package/src/utils/errorDiagnostics.ts +204 -204
- package/src/utils/index.ts +443 -372
- package/src/wrappers/ArraySet.ts +156 -156
- package/src/wrappers/ObjectMap.ts +155 -136
- package/src/wrappers/asMap.ts +65 -30
- package/src/wrappers/asSet.ts +3 -3
|
@@ -1,796 +1,804 @@
|
|
|
1
|
-
import { action, computed } from "mobx"
|
|
2
|
-
import type { ActionMiddlewareDisposer } from "../action/middleware"
|
|
3
|
-
import { modelAction } from "../action/modelAction"
|
|
4
|
-
import { Model } from "../model/Model"
|
|
5
|
-
import { model } from "../modelShared/modelDecorator"
|
|
6
|
-
import { fastGetRootPath } from "../parent/path"
|
|
7
|
-
import type { Path } from "../parent/pathTypes"
|
|
8
|
-
import { applyPatches, Patch, PatchRecorder, patchRecorder } from "../patch"
|
|
9
|
-
import { assertTweakedObject } from "../tweaker/core"
|
|
10
|
-
import { typesArray } from "../types/arrayBased/typesArray"
|
|
11
|
-
import { tProp } from "../types/tProp"
|
|
12
|
-
import { typesUnchecked } from "../types/utility/typesUnchecked"
|
|
13
|
-
import { failure, getMobxVersion, mobx6, namespace } from "../utils"
|
|
14
|
-
import { actionTrackingMiddleware, SimpleActionContext } from "./actionTrackingMiddleware"
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* An undo/redo event without attached state.
|
|
18
|
-
*/
|
|
19
|
-
export type UndoEventWithoutAttachedState = UndoSingleEvent | UndoEventGroup
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* An undo/redo event.
|
|
23
|
-
*/
|
|
24
|
-
export type UndoEvent = UndoEventWithoutAttachedState & {
|
|
25
|
-
/**
|
|
26
|
-
* The state saved before the event actions started / after the event actions finished.
|
|
27
|
-
*/
|
|
28
|
-
attachedState: {
|
|
29
|
-
/**
|
|
30
|
-
* The state saved before the event actions started.
|
|
31
|
-
*/
|
|
32
|
-
beforeEvent?: unknown
|
|
33
|
-
/**
|
|
34
|
-
* The state saved after the event actions finished.
|
|
35
|
-
*/
|
|
36
|
-
afterEvent?: unknown
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Undo event type.
|
|
42
|
-
*/
|
|
43
|
-
export enum UndoEventType {
|
|
44
|
-
Single = "single",
|
|
45
|
-
Group = "group",
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* An undo/redo single event.
|
|
50
|
-
*/
|
|
51
|
-
export interface UndoSingleEvent {
|
|
52
|
-
/**
|
|
53
|
-
* Expresses this is a single event.
|
|
54
|
-
*/
|
|
55
|
-
readonly type: UndoEventType.Single
|
|
56
|
-
/**
|
|
57
|
-
* Path to the object that invoked the action from its root.
|
|
58
|
-
*/
|
|
59
|
-
readonly targetPath: Path
|
|
60
|
-
/**
|
|
61
|
-
* Name of the action that was invoked.
|
|
62
|
-
*/
|
|
63
|
-
readonly actionName: string
|
|
64
|
-
/**
|
|
65
|
-
* Patches with changes done inside the action.
|
|
66
|
-
* Use `redo()` in the `UndoManager` to apply them.
|
|
67
|
-
*/
|
|
68
|
-
readonly patches: ReadonlyArray<Patch>
|
|
69
|
-
/**
|
|
70
|
-
* Patches to undo the changes done inside the action.
|
|
71
|
-
* Use `undo()` in the `UndoManager` to apply them.
|
|
72
|
-
*/
|
|
73
|
-
readonly inversePatches: ReadonlyArray<Patch>
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* An undo/redo event group.
|
|
78
|
-
*/
|
|
79
|
-
export interface UndoEventGroup {
|
|
80
|
-
/**
|
|
81
|
-
* Expresses this is an event group.
|
|
82
|
-
*/
|
|
83
|
-
readonly type: UndoEventType.Group
|
|
84
|
-
/**
|
|
85
|
-
* Name of the group (if any).
|
|
86
|
-
*/
|
|
87
|
-
readonly groupName?: string
|
|
88
|
-
/**
|
|
89
|
-
* Events that conform this group (might be single events or other nested groups).
|
|
90
|
-
*/
|
|
91
|
-
readonly events: ReadonlyArray<UndoEventWithoutAttachedState>
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function toSingleEvents(
|
|
95
|
-
event: UndoEventWithoutAttachedState,
|
|
96
|
-
reverse: boolean
|
|
97
|
-
): ReadonlyArray<UndoSingleEvent> {
|
|
98
|
-
if (event.type === UndoEventType.Single) {
|
|
99
|
-
return [event]
|
|
100
|
-
} else {
|
|
101
|
-
const array: UndoSingleEvent[] = []
|
|
102
|
-
for (const e of event.events) {
|
|
103
|
-
if (reverse) {
|
|
104
|
-
array.unshift(...toSingleEvents(e, true))
|
|
105
|
-
} else {
|
|
106
|
-
array.push(...toSingleEvents(e, false))
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return array
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Store model instance for undo/redo actions.
|
|
115
|
-
* Do not manipulate directly, other that creating it.
|
|
116
|
-
*/
|
|
117
|
-
@model(`${namespace}/UndoStore`)
|
|
118
|
-
export class UndoStore extends Model({
|
|
119
|
-
// TODO: add proper type checking to undo store
|
|
120
|
-
undoEvents: tProp(typesArray(typesUnchecked<UndoEvent>()), () => []),
|
|
121
|
-
redoEvents: tProp(typesArray(typesUnchecked<UndoEvent>()), () => []),
|
|
122
|
-
}) {
|
|
123
|
-
/**
|
|
124
|
-
* @ignore
|
|
125
|
-
*/
|
|
126
|
-
@modelAction
|
|
127
|
-
_clearUndo() {
|
|
128
|
-
withoutUndo(() => {
|
|
129
|
-
this.undoEvents.length = 0
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* @ignore
|
|
135
|
-
*/
|
|
136
|
-
@modelAction
|
|
137
|
-
_clearRedo() {
|
|
138
|
-
withoutUndo(() => {
|
|
139
|
-
this.redoEvents.length = 0
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @ignore
|
|
145
|
-
*/
|
|
146
|
-
enforceMaxLevels({
|
|
147
|
-
maxUndoLevels,
|
|
148
|
-
maxRedoLevels,
|
|
149
|
-
}: {
|
|
150
|
-
maxUndoLevels?: number
|
|
151
|
-
maxRedoLevels?: number
|
|
152
|
-
}) {
|
|
153
|
-
if (maxUndoLevels !== undefined) {
|
|
154
|
-
while (this.undoEvents.length > maxUndoLevels) {
|
|
155
|
-
this.undoEvents.shift()
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (maxRedoLevels !== undefined) {
|
|
159
|
-
while (this.redoEvents.length > maxRedoLevels) {
|
|
160
|
-
this.redoEvents.shift()
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* @ignore
|
|
167
|
-
*/
|
|
168
|
-
@modelAction
|
|
169
|
-
_undo({ maxRedoLevels }: { maxRedoLevels: number | undefined }) {
|
|
170
|
-
withoutUndo(() => {
|
|
171
|
-
const event = this.undoEvents.pop()!
|
|
172
|
-
this.redoEvents.push(event)
|
|
173
|
-
this.enforceMaxLevels({ maxRedoLevels })
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* @ignore
|
|
179
|
-
*/
|
|
180
|
-
@modelAction
|
|
181
|
-
_redo({ maxUndoLevels }: { maxUndoLevels: number | undefined }) {
|
|
182
|
-
withoutUndo(() => {
|
|
183
|
-
const event = this.redoEvents.pop()!
|
|
184
|
-
this.undoEvents.push(event)
|
|
185
|
-
this.enforceMaxLevels({ maxUndoLevels })
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* @ignore
|
|
191
|
-
*/
|
|
192
|
-
@modelAction
|
|
193
|
-
_addUndo({ event, maxUndoLevels }: { event: UndoEvent; maxUndoLevels: number | undefined }) {
|
|
194
|
-
withoutUndo(() => {
|
|
195
|
-
this.undoEvents.push(event)
|
|
196
|
-
// once an undo event is added redo queue is no longer valid
|
|
197
|
-
this.redoEvents.length = 0
|
|
198
|
-
this.enforceMaxLevels({ maxUndoLevels })
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private _groupStack: UndoEventGroup[] = []
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* @ignore
|
|
206
|
-
*/
|
|
207
|
-
_addUndoToParentGroup(parentGroup: UndoEventGroup, event: UndoEventWithoutAttachedState) {
|
|
208
|
-
;(parentGroup.events as UndoEventWithoutAttachedState[]).push(event)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* @ignore
|
|
213
|
-
*/
|
|
214
|
-
get _currentGroup(): UndoEventGroup | undefined {
|
|
215
|
-
return this._groupStack[this._groupStack.length - 1]
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* @ignore
|
|
220
|
-
*/
|
|
221
|
-
_startGroup(
|
|
222
|
-
groupName: string | undefined,
|
|
223
|
-
startRunning: boolean,
|
|
224
|
-
options: UndoMiddlewareOptions<unknown> | undefined
|
|
225
|
-
) {
|
|
226
|
-
let running = false
|
|
227
|
-
let ended = false
|
|
228
|
-
const parentGroup = this._currentGroup
|
|
229
|
-
|
|
230
|
-
const group: UndoEventGroup = {
|
|
231
|
-
type: UndoEventType.Group,
|
|
232
|
-
groupName,
|
|
233
|
-
events: [],
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const attachedStateBeforeEvent = parentGroup ? undefined : options?.attachedState?.save()
|
|
237
|
-
|
|
238
|
-
const api = {
|
|
239
|
-
pause: () => {
|
|
240
|
-
if (ended) {
|
|
241
|
-
throw failure("cannot pause a group when it is already ended")
|
|
242
|
-
}
|
|
243
|
-
if (!running) {
|
|
244
|
-
throw failure("cannot pause a group when it is not running")
|
|
245
|
-
}
|
|
246
|
-
if (this._currentGroup !== group) {
|
|
247
|
-
throw failure("group out of order")
|
|
248
|
-
}
|
|
249
|
-
this._groupStack.pop()
|
|
250
|
-
running = false
|
|
251
|
-
},
|
|
252
|
-
resume: () => {
|
|
253
|
-
if (ended) {
|
|
254
|
-
throw failure("cannot resume a group when it is already ended")
|
|
255
|
-
}
|
|
256
|
-
if (running) {
|
|
257
|
-
throw failure("cannot resume a group when it is already running")
|
|
258
|
-
}
|
|
259
|
-
this._groupStack.push(group)
|
|
260
|
-
running = true
|
|
261
|
-
},
|
|
262
|
-
end: () => {
|
|
263
|
-
if (running) {
|
|
264
|
-
api.pause()
|
|
265
|
-
}
|
|
266
|
-
ended = true
|
|
267
|
-
if (parentGroup) {
|
|
268
|
-
this._addUndoToParentGroup(parentGroup, group)
|
|
269
|
-
} else {
|
|
270
|
-
this._addUndo({
|
|
271
|
-
event: {
|
|
272
|
-
...group,
|
|
273
|
-
attachedState: {
|
|
274
|
-
beforeEvent: attachedStateBeforeEvent,
|
|
275
|
-
afterEvent: options?.attachedState?.save(),
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
maxUndoLevels: options?.maxUndoLevels,
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (startRunning) {
|
|
285
|
-
api.resume()
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return api
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Manager class returned by `undoMiddleware` that allows you to perform undo/redo actions.
|
|
294
|
-
*/
|
|
295
|
-
export class UndoManager {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
*
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
},
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
*
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
*
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
} else {
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
this.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
patches
|
|
739
|
-
inversePatches
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1
|
+
import { action, computed } from "mobx"
|
|
2
|
+
import type { ActionMiddlewareDisposer } from "../action/middleware"
|
|
3
|
+
import { modelAction } from "../action/modelAction"
|
|
4
|
+
import { Model } from "../model/Model"
|
|
5
|
+
import { model } from "../modelShared/modelDecorator"
|
|
6
|
+
import { fastGetRootPath } from "../parent/path"
|
|
7
|
+
import type { Path } from "../parent/pathTypes"
|
|
8
|
+
import { applyPatches, type Patch, type PatchRecorder, patchRecorder } from "../patch"
|
|
9
|
+
import { assertTweakedObject } from "../tweaker/core"
|
|
10
|
+
import { typesArray } from "../types/arrayBased/typesArray"
|
|
11
|
+
import { tProp } from "../types/tProp"
|
|
12
|
+
import { typesUnchecked } from "../types/utility/typesUnchecked"
|
|
13
|
+
import { failure, getMobxVersion, mobx6, namespace } from "../utils"
|
|
14
|
+
import { actionTrackingMiddleware, type SimpleActionContext } from "./actionTrackingMiddleware"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An undo/redo event without attached state.
|
|
18
|
+
*/
|
|
19
|
+
export type UndoEventWithoutAttachedState = UndoSingleEvent | UndoEventGroup
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An undo/redo event.
|
|
23
|
+
*/
|
|
24
|
+
export type UndoEvent = UndoEventWithoutAttachedState & {
|
|
25
|
+
/**
|
|
26
|
+
* The state saved before the event actions started / after the event actions finished.
|
|
27
|
+
*/
|
|
28
|
+
attachedState: {
|
|
29
|
+
/**
|
|
30
|
+
* The state saved before the event actions started.
|
|
31
|
+
*/
|
|
32
|
+
beforeEvent?: unknown
|
|
33
|
+
/**
|
|
34
|
+
* The state saved after the event actions finished.
|
|
35
|
+
*/
|
|
36
|
+
afterEvent?: unknown
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Undo event type.
|
|
42
|
+
*/
|
|
43
|
+
export enum UndoEventType {
|
|
44
|
+
Single = "single",
|
|
45
|
+
Group = "group",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* An undo/redo single event.
|
|
50
|
+
*/
|
|
51
|
+
export interface UndoSingleEvent {
|
|
52
|
+
/**
|
|
53
|
+
* Expresses this is a single event.
|
|
54
|
+
*/
|
|
55
|
+
readonly type: UndoEventType.Single
|
|
56
|
+
/**
|
|
57
|
+
* Path to the object that invoked the action from its root.
|
|
58
|
+
*/
|
|
59
|
+
readonly targetPath: Path
|
|
60
|
+
/**
|
|
61
|
+
* Name of the action that was invoked.
|
|
62
|
+
*/
|
|
63
|
+
readonly actionName: string
|
|
64
|
+
/**
|
|
65
|
+
* Patches with changes done inside the action.
|
|
66
|
+
* Use `redo()` in the `UndoManager` to apply them.
|
|
67
|
+
*/
|
|
68
|
+
readonly patches: ReadonlyArray<Patch>
|
|
69
|
+
/**
|
|
70
|
+
* Patches to undo the changes done inside the action.
|
|
71
|
+
* Use `undo()` in the `UndoManager` to apply them.
|
|
72
|
+
*/
|
|
73
|
+
readonly inversePatches: ReadonlyArray<Patch>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* An undo/redo event group.
|
|
78
|
+
*/
|
|
79
|
+
export interface UndoEventGroup {
|
|
80
|
+
/**
|
|
81
|
+
* Expresses this is an event group.
|
|
82
|
+
*/
|
|
83
|
+
readonly type: UndoEventType.Group
|
|
84
|
+
/**
|
|
85
|
+
* Name of the group (if any).
|
|
86
|
+
*/
|
|
87
|
+
readonly groupName?: string
|
|
88
|
+
/**
|
|
89
|
+
* Events that conform this group (might be single events or other nested groups).
|
|
90
|
+
*/
|
|
91
|
+
readonly events: ReadonlyArray<UndoEventWithoutAttachedState>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function toSingleEvents(
|
|
95
|
+
event: UndoEventWithoutAttachedState,
|
|
96
|
+
reverse: boolean
|
|
97
|
+
): ReadonlyArray<UndoSingleEvent> {
|
|
98
|
+
if (event.type === UndoEventType.Single) {
|
|
99
|
+
return [event]
|
|
100
|
+
} else {
|
|
101
|
+
const array: UndoSingleEvent[] = []
|
|
102
|
+
for (const e of event.events) {
|
|
103
|
+
if (reverse) {
|
|
104
|
+
array.unshift(...toSingleEvents(e, true))
|
|
105
|
+
} else {
|
|
106
|
+
array.push(...toSingleEvents(e, false))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return array
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Store model instance for undo/redo actions.
|
|
115
|
+
* Do not manipulate directly, other that creating it.
|
|
116
|
+
*/
|
|
117
|
+
@model(`${namespace}/UndoStore`)
|
|
118
|
+
export class UndoStore extends Model({
|
|
119
|
+
// TODO: add proper type checking to undo store
|
|
120
|
+
undoEvents: tProp(typesArray(typesUnchecked<UndoEvent>()), () => []),
|
|
121
|
+
redoEvents: tProp(typesArray(typesUnchecked<UndoEvent>()), () => []),
|
|
122
|
+
}) {
|
|
123
|
+
/**
|
|
124
|
+
* @ignore
|
|
125
|
+
*/
|
|
126
|
+
@modelAction
|
|
127
|
+
_clearUndo() {
|
|
128
|
+
withoutUndo(() => {
|
|
129
|
+
this.undoEvents.length = 0
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @ignore
|
|
135
|
+
*/
|
|
136
|
+
@modelAction
|
|
137
|
+
_clearRedo() {
|
|
138
|
+
withoutUndo(() => {
|
|
139
|
+
this.redoEvents.length = 0
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @ignore
|
|
145
|
+
*/
|
|
146
|
+
enforceMaxLevels({
|
|
147
|
+
maxUndoLevels,
|
|
148
|
+
maxRedoLevels,
|
|
149
|
+
}: {
|
|
150
|
+
maxUndoLevels?: number
|
|
151
|
+
maxRedoLevels?: number
|
|
152
|
+
}) {
|
|
153
|
+
if (maxUndoLevels !== undefined) {
|
|
154
|
+
while (this.undoEvents.length > maxUndoLevels) {
|
|
155
|
+
this.undoEvents.shift()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (maxRedoLevels !== undefined) {
|
|
159
|
+
while (this.redoEvents.length > maxRedoLevels) {
|
|
160
|
+
this.redoEvents.shift()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @ignore
|
|
167
|
+
*/
|
|
168
|
+
@modelAction
|
|
169
|
+
_undo({ maxRedoLevels }: { maxRedoLevels: number | undefined }) {
|
|
170
|
+
withoutUndo(() => {
|
|
171
|
+
const event = this.undoEvents.pop()!
|
|
172
|
+
this.redoEvents.push(event)
|
|
173
|
+
this.enforceMaxLevels({ maxRedoLevels })
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @ignore
|
|
179
|
+
*/
|
|
180
|
+
@modelAction
|
|
181
|
+
_redo({ maxUndoLevels }: { maxUndoLevels: number | undefined }) {
|
|
182
|
+
withoutUndo(() => {
|
|
183
|
+
const event = this.redoEvents.pop()!
|
|
184
|
+
this.undoEvents.push(event)
|
|
185
|
+
this.enforceMaxLevels({ maxUndoLevels })
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @ignore
|
|
191
|
+
*/
|
|
192
|
+
@modelAction
|
|
193
|
+
_addUndo({ event, maxUndoLevels }: { event: UndoEvent; maxUndoLevels: number | undefined }) {
|
|
194
|
+
withoutUndo(() => {
|
|
195
|
+
this.undoEvents.push(event)
|
|
196
|
+
// once an undo event is added redo queue is no longer valid
|
|
197
|
+
this.redoEvents.length = 0
|
|
198
|
+
this.enforceMaxLevels({ maxUndoLevels })
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private _groupStack: UndoEventGroup[] = []
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @ignore
|
|
206
|
+
*/
|
|
207
|
+
_addUndoToParentGroup(parentGroup: UndoEventGroup, event: UndoEventWithoutAttachedState) {
|
|
208
|
+
;(parentGroup.events as UndoEventWithoutAttachedState[]).push(event)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @ignore
|
|
213
|
+
*/
|
|
214
|
+
get _currentGroup(): UndoEventGroup | undefined {
|
|
215
|
+
return this._groupStack[this._groupStack.length - 1]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @ignore
|
|
220
|
+
*/
|
|
221
|
+
_startGroup(
|
|
222
|
+
groupName: string | undefined,
|
|
223
|
+
startRunning: boolean,
|
|
224
|
+
options: UndoMiddlewareOptions<unknown> | undefined
|
|
225
|
+
) {
|
|
226
|
+
let running = false
|
|
227
|
+
let ended = false
|
|
228
|
+
const parentGroup = this._currentGroup
|
|
229
|
+
|
|
230
|
+
const group: UndoEventGroup = {
|
|
231
|
+
type: UndoEventType.Group,
|
|
232
|
+
groupName,
|
|
233
|
+
events: [],
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const attachedStateBeforeEvent = parentGroup ? undefined : options?.attachedState?.save()
|
|
237
|
+
|
|
238
|
+
const api = {
|
|
239
|
+
pause: () => {
|
|
240
|
+
if (ended) {
|
|
241
|
+
throw failure("cannot pause a group when it is already ended")
|
|
242
|
+
}
|
|
243
|
+
if (!running) {
|
|
244
|
+
throw failure("cannot pause a group when it is not running")
|
|
245
|
+
}
|
|
246
|
+
if (this._currentGroup !== group) {
|
|
247
|
+
throw failure("group out of order")
|
|
248
|
+
}
|
|
249
|
+
this._groupStack.pop()
|
|
250
|
+
running = false
|
|
251
|
+
},
|
|
252
|
+
resume: () => {
|
|
253
|
+
if (ended) {
|
|
254
|
+
throw failure("cannot resume a group when it is already ended")
|
|
255
|
+
}
|
|
256
|
+
if (running) {
|
|
257
|
+
throw failure("cannot resume a group when it is already running")
|
|
258
|
+
}
|
|
259
|
+
this._groupStack.push(group)
|
|
260
|
+
running = true
|
|
261
|
+
},
|
|
262
|
+
end: () => {
|
|
263
|
+
if (running) {
|
|
264
|
+
api.pause()
|
|
265
|
+
}
|
|
266
|
+
ended = true
|
|
267
|
+
if (parentGroup) {
|
|
268
|
+
this._addUndoToParentGroup(parentGroup, group)
|
|
269
|
+
} else {
|
|
270
|
+
this._addUndo({
|
|
271
|
+
event: {
|
|
272
|
+
...group,
|
|
273
|
+
attachedState: {
|
|
274
|
+
beforeEvent: attachedStateBeforeEvent,
|
|
275
|
+
afterEvent: options?.attachedState?.save(),
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
maxUndoLevels: options?.maxUndoLevels,
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (startRunning) {
|
|
285
|
+
api.resume()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return api
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Manager class returned by `undoMiddleware` that allows you to perform undo/redo actions.
|
|
294
|
+
*/
|
|
295
|
+
export class UndoManager {
|
|
296
|
+
private readonly disposer: ActionMiddlewareDisposer
|
|
297
|
+
private readonly subtreeRoot: object
|
|
298
|
+
private readonly options: UndoMiddlewareOptions<unknown> | undefined
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The store currently being used to store undo/redo action events.
|
|
302
|
+
*/
|
|
303
|
+
readonly store: UndoStore
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The undo stack, where the first operation to undo will be the last of the array.
|
|
307
|
+
* Do not manipulate this array directly.
|
|
308
|
+
*/
|
|
309
|
+
@computed
|
|
310
|
+
get undoQueue(): ReadonlyArray<UndoEvent> {
|
|
311
|
+
return this.store.undoEvents
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The redo stack, where the first operation to redo will be the last of the array.
|
|
316
|
+
* Do not manipulate this array directly.
|
|
317
|
+
*/
|
|
318
|
+
@computed
|
|
319
|
+
get redoQueue(): ReadonlyArray<UndoEvent> {
|
|
320
|
+
return this.store.redoEvents
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The number of undo actions available.
|
|
325
|
+
*/
|
|
326
|
+
@computed
|
|
327
|
+
get undoLevels() {
|
|
328
|
+
return this.undoQueue.length
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* If undo can be performed (if there is at least one undo action available).
|
|
333
|
+
*/
|
|
334
|
+
@computed
|
|
335
|
+
get canUndo() {
|
|
336
|
+
return this.undoLevels > 0
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Clears the undo queue.
|
|
341
|
+
*/
|
|
342
|
+
@action
|
|
343
|
+
clearUndo() {
|
|
344
|
+
this.store._clearUndo()
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* The number of redo actions available.
|
|
349
|
+
*/
|
|
350
|
+
@computed
|
|
351
|
+
get redoLevels() {
|
|
352
|
+
return this.redoQueue.length
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* If redo can be performed (if there is at least one redo action available)
|
|
357
|
+
*/
|
|
358
|
+
@computed
|
|
359
|
+
get canRedo() {
|
|
360
|
+
return this.redoLevels > 0
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Clears the redo queue.
|
|
365
|
+
*/
|
|
366
|
+
@action
|
|
367
|
+
clearRedo() {
|
|
368
|
+
this.store._clearRedo()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Undoes the last action.
|
|
373
|
+
* Will throw if there is no action to undo.
|
|
374
|
+
*/
|
|
375
|
+
@action
|
|
376
|
+
undo() {
|
|
377
|
+
if (!this.canUndo) {
|
|
378
|
+
throw failure("nothing to undo")
|
|
379
|
+
}
|
|
380
|
+
const event = this.undoQueue[this.undoQueue.length - 1]
|
|
381
|
+
|
|
382
|
+
withoutUndo(() => {
|
|
383
|
+
toSingleEvents(event, true).forEach((e) => {
|
|
384
|
+
applyPatches(this.subtreeRoot, e.inversePatches, true)
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
// restore the attached state before the operation was made
|
|
388
|
+
if (event.attachedState?.beforeEvent) {
|
|
389
|
+
this.options?.attachedState?.restore(event.attachedState.beforeEvent)
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
this.store._undo({ maxRedoLevels: this.options?.maxRedoLevels })
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Redoes the previous action.
|
|
398
|
+
* Will throw if there is no action to redo.
|
|
399
|
+
*/
|
|
400
|
+
@action
|
|
401
|
+
redo() {
|
|
402
|
+
if (!this.canRedo) {
|
|
403
|
+
throw failure("nothing to redo")
|
|
404
|
+
}
|
|
405
|
+
const event = this.redoQueue[this.redoQueue.length - 1]
|
|
406
|
+
|
|
407
|
+
withoutUndo(() => {
|
|
408
|
+
toSingleEvents(event, false).forEach((e) => {
|
|
409
|
+
applyPatches(this.subtreeRoot, e.patches)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
// restore the attached state after the operation was made
|
|
413
|
+
if (event.attachedState?.afterEvent) {
|
|
414
|
+
this.options?.attachedState?.restore(event.attachedState.afterEvent)
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
this.store._redo({ maxUndoLevels: this.options?.maxUndoLevels })
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Disposes the undo middleware.
|
|
423
|
+
*/
|
|
424
|
+
dispose() {
|
|
425
|
+
this.disposer()
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private _isUndoRecordingDisabled = false
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Returns if undo recording is currently disabled or not for this particular `UndoManager`.
|
|
432
|
+
*/
|
|
433
|
+
get isUndoRecordingDisabled() {
|
|
434
|
+
return this._isUndoRecordingDisabled
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Skips the undo recording mechanism for the code block that gets run synchronously inside.
|
|
439
|
+
*
|
|
440
|
+
* @template T Code block return type.
|
|
441
|
+
* @param fn Code block to run.
|
|
442
|
+
* @returns The value returned by the code block.
|
|
443
|
+
*/
|
|
444
|
+
withoutUndo<T>(fn: () => T): T {
|
|
445
|
+
const savedUndoDisabled = this._isUndoRecordingDisabled
|
|
446
|
+
this._isUndoRecordingDisabled = true
|
|
447
|
+
try {
|
|
448
|
+
return fn()
|
|
449
|
+
} finally {
|
|
450
|
+
this._isUndoRecordingDisabled = savedUndoDisabled
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Creates a custom group that can be continued multiple times and then ended.
|
|
456
|
+
* @param groupName Optional group name.
|
|
457
|
+
* @returns An API to continue/end the group.
|
|
458
|
+
*/
|
|
459
|
+
createGroup(groupName?: string) {
|
|
460
|
+
const group = this.store._startGroup(groupName, false, this.options)
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
continue<T>(fn: () => T): T {
|
|
464
|
+
group.resume()
|
|
465
|
+
try {
|
|
466
|
+
return fn()
|
|
467
|
+
} finally {
|
|
468
|
+
group.pause()
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
end() {
|
|
473
|
+
group.end()
|
|
474
|
+
},
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Runs a synchronous code block as an undo group.
|
|
480
|
+
* Note that nested groups are allowed.
|
|
481
|
+
*
|
|
482
|
+
* @param groupName Group name.
|
|
483
|
+
* @param fn Code block.
|
|
484
|
+
* @returns Code block return value.
|
|
485
|
+
*/
|
|
486
|
+
withGroup<T>(groupName: string, fn: () => T): T
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Runs a synchronous code block as an undo group.
|
|
490
|
+
* Note that nested groups are allowed.
|
|
491
|
+
*
|
|
492
|
+
* @param fn Code block.
|
|
493
|
+
* @returns Code block return value.
|
|
494
|
+
*/
|
|
495
|
+
withGroup<T>(fn: () => T): T
|
|
496
|
+
|
|
497
|
+
withGroup<T>(arg1: any, arg2?: any): T {
|
|
498
|
+
let groupName: string | undefined
|
|
499
|
+
let fn: () => T
|
|
500
|
+
if (typeof arg1 === "string") {
|
|
501
|
+
groupName = arg1
|
|
502
|
+
fn = arg2
|
|
503
|
+
} else {
|
|
504
|
+
fn = arg1
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const group = this.store._startGroup(groupName, true, this.options)
|
|
508
|
+
try {
|
|
509
|
+
return fn()
|
|
510
|
+
} finally {
|
|
511
|
+
group.end()
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Runs an asynchronous code block as an undo group.
|
|
517
|
+
* Note that nested groups are allowed.
|
|
518
|
+
*
|
|
519
|
+
* @param groupName Group name.
|
|
520
|
+
* @param fn Flow function.
|
|
521
|
+
* @returns Flow function return value.
|
|
522
|
+
*/
|
|
523
|
+
withGroupFlow<R>(groupName: string, fn: () => Generator<any, R, any>): Promise<R>
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Runs an asynchronous code block as an undo group.
|
|
527
|
+
* Note that nested groups are allowed.
|
|
528
|
+
*
|
|
529
|
+
* @param fn Flow function.
|
|
530
|
+
* @returns Flow function return value.
|
|
531
|
+
*/
|
|
532
|
+
withGroupFlow<R>(fn: () => Generator<any, R, any>): Promise<R>
|
|
533
|
+
|
|
534
|
+
withGroupFlow<R>(arg1: any, arg2?: any): Promise<R> {
|
|
535
|
+
let groupName: string | undefined
|
|
536
|
+
let fn: () => Generator<any, R, any>
|
|
537
|
+
if (typeof arg1 === "string") {
|
|
538
|
+
groupName = arg1
|
|
539
|
+
fn = arg2
|
|
540
|
+
} else {
|
|
541
|
+
fn = arg1
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const gen = fn()
|
|
545
|
+
|
|
546
|
+
const group = this.store._startGroup(groupName, false, this.options)
|
|
547
|
+
|
|
548
|
+
// use bound functions to fix es6 compilation
|
|
549
|
+
const genNext = gen.next.bind(gen)
|
|
550
|
+
const genThrow = gen.throw.bind(gen)
|
|
551
|
+
|
|
552
|
+
const promise = new Promise<R>((resolve, reject) => {
|
|
553
|
+
function onFulfilled(res: any): void {
|
|
554
|
+
group.resume()
|
|
555
|
+
let ret: unknown
|
|
556
|
+
try {
|
|
557
|
+
ret = genNext(res)
|
|
558
|
+
} catch (e) {
|
|
559
|
+
group.end()
|
|
560
|
+
reject(e as Error)
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
group.pause()
|
|
565
|
+
next(ret)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function onRejected(err: unknown): void {
|
|
569
|
+
group.resume()
|
|
570
|
+
let ret: unknown
|
|
571
|
+
try {
|
|
572
|
+
ret = genThrow(err)
|
|
573
|
+
} catch (e) {
|
|
574
|
+
group.end()
|
|
575
|
+
reject(e as Error)
|
|
576
|
+
return
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
group.pause()
|
|
580
|
+
next(ret)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function next(ret: any): void {
|
|
584
|
+
if (ret && typeof ret.then === "function") {
|
|
585
|
+
// an async iterator
|
|
586
|
+
ret.then(next, reject)
|
|
587
|
+
} else if (ret.done) {
|
|
588
|
+
// done
|
|
589
|
+
group.end()
|
|
590
|
+
resolve(ret.value)
|
|
591
|
+
} else {
|
|
592
|
+
// continue
|
|
593
|
+
Promise.resolve(ret.value).then(onFulfilled, onRejected)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
onFulfilled(undefined) // kick off the process
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
return promise
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Creates an instance of `UndoManager`.
|
|
605
|
+
* Do not use directly, use `undoMiddleware` instead.
|
|
606
|
+
*
|
|
607
|
+
* @param disposer
|
|
608
|
+
* @param subtreeRoot
|
|
609
|
+
* @param [store]
|
|
610
|
+
*/
|
|
611
|
+
constructor(
|
|
612
|
+
disposer: ActionMiddlewareDisposer,
|
|
613
|
+
subtreeRoot: object,
|
|
614
|
+
store: UndoStore | undefined,
|
|
615
|
+
options: UndoMiddlewareOptions<unknown> | undefined
|
|
616
|
+
) {
|
|
617
|
+
this.disposer = disposer
|
|
618
|
+
this.subtreeRoot = subtreeRoot
|
|
619
|
+
this.options = options
|
|
620
|
+
|
|
621
|
+
if (getMobxVersion() >= 6) {
|
|
622
|
+
mobx6.makeObservable(this)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
this.store = store ?? new UndoStore({})
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Undo middleware options.
|
|
631
|
+
*/
|
|
632
|
+
export interface UndoMiddlewareOptions<S> {
|
|
633
|
+
/**
|
|
634
|
+
* Max number of undo levels to keep, or undefined for infinite.
|
|
635
|
+
*/
|
|
636
|
+
maxUndoLevels?: number
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Max number of redo levels to keep, or undefined for infinite.
|
|
640
|
+
*/
|
|
641
|
+
maxRedoLevels?: number
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Attached states are states that are saved/restored when undoing/redoing.
|
|
645
|
+
* Usually used to restore state that is not part of the document model such as focus, selection, scroll position, etc.
|
|
646
|
+
*/
|
|
647
|
+
attachedState?: {
|
|
648
|
+
/**
|
|
649
|
+
* Saves a certain state and associates it with the undo step. This state can be restored when undoing/redoing.
|
|
650
|
+
*/
|
|
651
|
+
save(): S
|
|
652
|
+
/**
|
|
653
|
+
* Restores a certain state previously saved.
|
|
654
|
+
* @param s State to restore.
|
|
655
|
+
*/
|
|
656
|
+
restore(s: S): void
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Creates an undo middleware.
|
|
662
|
+
*
|
|
663
|
+
* @param subtreeRoot Subtree root target object.
|
|
664
|
+
* @param store Optional `UndoStore` where to store the undo/redo queues. Use this if you want to
|
|
665
|
+
* store such queues somewhere in your models. If none is provided it will reside in memory.
|
|
666
|
+
* @param options Extra options, such as how to save / restore certain snapshot of the state to be restored when undoing/redoing.
|
|
667
|
+
* @returns An `UndoManager` which allows you to do the manage the undo/redo operations and dispose of the middleware.
|
|
668
|
+
*/
|
|
669
|
+
export function undoMiddleware<S>(
|
|
670
|
+
subtreeRoot: object,
|
|
671
|
+
store?: UndoStore,
|
|
672
|
+
options?: UndoMiddlewareOptions<S>
|
|
673
|
+
): UndoManager {
|
|
674
|
+
assertTweakedObject(subtreeRoot, "subtreeRoot")
|
|
675
|
+
|
|
676
|
+
let manager: UndoManager
|
|
677
|
+
|
|
678
|
+
interface PatchRecorderData {
|
|
679
|
+
recorder: PatchRecorder
|
|
680
|
+
recorderStack: number
|
|
681
|
+
undoRootContext: SimpleActionContext
|
|
682
|
+
group: UndoEventGroup | undefined
|
|
683
|
+
attachedStateBeforeEvent: S | undefined
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const patchRecorderSymbol = Symbol("patchRecorder")
|
|
687
|
+
|
|
688
|
+
function initPatchRecorder(ctx: SimpleActionContext) {
|
|
689
|
+
const group = manager.store._currentGroup
|
|
690
|
+
|
|
691
|
+
const patchRecorderData: PatchRecorderData = {
|
|
692
|
+
recorder: patchRecorder(subtreeRoot, {
|
|
693
|
+
recording: false,
|
|
694
|
+
filter: () => {
|
|
695
|
+
return !(_isGlobalUndoRecordingDisabled || manager.isUndoRecordingDisabled)
|
|
696
|
+
},
|
|
697
|
+
}),
|
|
698
|
+
recorderStack: 0,
|
|
699
|
+
undoRootContext: ctx,
|
|
700
|
+
group,
|
|
701
|
+
|
|
702
|
+
attachedStateBeforeEvent: options?.attachedState?.save(),
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
ctx.rootContext.data[patchRecorderSymbol] = patchRecorderData
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function getPatchRecorderData(ctx: SimpleActionContext): PatchRecorderData {
|
|
709
|
+
return ctx.rootContext.data[patchRecorderSymbol]
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const middlewareDisposer = actionTrackingMiddleware(subtreeRoot, {
|
|
713
|
+
onStart(ctx) {
|
|
714
|
+
if (!getPatchRecorderData(ctx)) {
|
|
715
|
+
initPatchRecorder(ctx)
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
onResume(ctx) {
|
|
719
|
+
const patchRecorderData = getPatchRecorderData(ctx)
|
|
720
|
+
patchRecorderData.recorderStack++
|
|
721
|
+
patchRecorderData.recorder.recording = patchRecorderData.recorderStack > 0
|
|
722
|
+
},
|
|
723
|
+
onSuspend(ctx) {
|
|
724
|
+
const patchRecorderData = getPatchRecorderData(ctx)
|
|
725
|
+
patchRecorderData.recorderStack--
|
|
726
|
+
patchRecorderData.recorder.recording = patchRecorderData.recorderStack > 0
|
|
727
|
+
},
|
|
728
|
+
onFinish(ctx) {
|
|
729
|
+
const patchRecorderData = getPatchRecorderData(ctx)
|
|
730
|
+
if (patchRecorderData && patchRecorderData.undoRootContext === ctx) {
|
|
731
|
+
const patchRecorder = patchRecorderData.recorder
|
|
732
|
+
|
|
733
|
+
if (patchRecorder.events.length > 0) {
|
|
734
|
+
const patches: Patch[] = []
|
|
735
|
+
const inversePatches: Patch[] = []
|
|
736
|
+
|
|
737
|
+
for (const event of patchRecorder.events) {
|
|
738
|
+
patches.push(...event.patches)
|
|
739
|
+
inversePatches.push(...event.inversePatches)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const event = {
|
|
743
|
+
type: UndoEventType.Single,
|
|
744
|
+
targetPath: fastGetRootPath(ctx.target, false).path,
|
|
745
|
+
actionName: ctx.actionName,
|
|
746
|
+
patches,
|
|
747
|
+
inversePatches,
|
|
748
|
+
} as const
|
|
749
|
+
|
|
750
|
+
const parentGroup = patchRecorderData.group
|
|
751
|
+
|
|
752
|
+
if (parentGroup) {
|
|
753
|
+
manager.store._addUndoToParentGroup(parentGroup, event)
|
|
754
|
+
} else {
|
|
755
|
+
manager.store._addUndo({
|
|
756
|
+
event: {
|
|
757
|
+
...event,
|
|
758
|
+
attachedState: {
|
|
759
|
+
beforeEvent: patchRecorderData.attachedStateBeforeEvent,
|
|
760
|
+
afterEvent: options?.attachedState?.save(),
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
maxUndoLevels: options?.maxUndoLevels,
|
|
764
|
+
})
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
patchRecorder.dispose()
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
manager = new UndoManager(middlewareDisposer, subtreeRoot, store, options)
|
|
774
|
+
return manager
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
let _isGlobalUndoRecordingDisabled = false
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Returns if the undo recording mechanism is currently disabled.
|
|
781
|
+
*
|
|
782
|
+
* @returns `true` if it is currently disabled, `false` otherwise.
|
|
783
|
+
*/
|
|
784
|
+
export function isGlobalUndoRecordingDisabled() {
|
|
785
|
+
return _isGlobalUndoRecordingDisabled
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Globally skips the undo recording mechanism for the code block that gets run synchronously inside.
|
|
790
|
+
* Consider using the `withoutUndo` method of a particular `UndoManager` instead.
|
|
791
|
+
*
|
|
792
|
+
* @template T Code block return type.
|
|
793
|
+
* @param fn Code block to run.
|
|
794
|
+
* @returns The value returned by the code block.
|
|
795
|
+
*/
|
|
796
|
+
export function withoutUndo<T>(fn: () => T): T {
|
|
797
|
+
const savedUndoDisabled = _isGlobalUndoRecordingDisabled
|
|
798
|
+
_isGlobalUndoRecordingDisabled = true
|
|
799
|
+
try {
|
|
800
|
+
return fn()
|
|
801
|
+
} finally {
|
|
802
|
+
_isGlobalUndoRecordingDisabled = savedUndoDisabled
|
|
803
|
+
}
|
|
804
|
+
}
|