1
1
import {
2
- COMMAND_MAP ,
2
+ CHANGE_MAP ,
3
3
MUTATION_FILTER ,
4
4
SENDER ,
5
5
} from './constants' ;
@@ -17,12 +17,16 @@ import traceExtension, {
17
17
} from '@harlem/extension-trace' ;
18
18
19
19
import {
20
+ matchGetFilter ,
20
21
objectFromPath ,
22
+ typeIsMatchable ,
23
+ typeIsObject ,
21
24
} from '@harlem/utilities' ;
22
25
23
26
import type {
24
- CommandType ,
25
- HistoryCommand ,
27
+ ChangeType ,
28
+ HistoryGroup ,
29
+ MutationTrace ,
26
30
Options ,
27
31
} from './types' ;
28
32
@@ -31,18 +35,23 @@ export * from './types';
31
35
function getOptions ( options ?: Partial < Options > ) : Options {
32
36
return {
33
37
max : 50 ,
34
- mutations : [ ] ,
38
+ mutations : '*' ,
35
39
...options ,
36
40
} ;
37
41
}
38
42
39
43
export default function historyExtension < TState extends BaseState > ( options ?: Partial < Options > ) {
40
44
const _options = getOptions ( options ) ;
41
- const mutationLookup = new Map ( _options . mutations . map ( ( { name, description } ) => [ name , description ] ) ) ;
45
+ const groups = getMutationGroups ( _options . mutations ) ;
46
+
42
47
const createTraceExtension = traceExtension < TState > ( {
43
48
autoStart : true ,
44
49
} ) ;
45
50
51
+ function mutationFilter ( mutation : string ) {
52
+ return groups . some ( ( { filter } ) => filter ( mutation ) ) ;
53
+ }
54
+
46
55
return ( store : InternalStore < TState > ) => {
47
56
store . register ( 'extensions' , 'history' , ( ) => _options ) ;
48
57
@@ -52,21 +61,51 @@ export default function historyExtension<TState extends BaseState>(options?: Par
52
61
onTraceResult,
53
62
} = createTraceExtension ( store ) ;
54
63
55
- let position = 0 ;
56
- let commands = [ ] as HistoryCommand [ ] ;
57
- let results = [ ] as TraceResult < any > [ ] ;
64
+ const createHistoryState = ( ) => {
65
+ const results : TraceResult < any > [ ] = [ ] ;
66
+ const historyGroups = groups . reduce ( ( out , { key } ) => {
67
+ out [ key ] = {
68
+ position : - 1 ,
69
+ history : [ ] ,
70
+ } ;
71
+
72
+ return out ;
73
+ } , { } as Record < string , HistoryGroup > ) ;
74
+
75
+ return {
76
+ results,
77
+ groups : historyGroups ,
78
+ } ;
79
+ } ;
58
80
59
- function executeCommand ( type : CommandType , command : HistoryCommand ) {
60
- store . write ( `extension:history:${ type } ` , SENDER , state => {
61
- const tasks = COMMAND_MAP [ type ] ;
81
+ let historyState = createHistoryState ( ) ;
82
+
83
+ store . on ( EVENTS . mutation . before , ( event ?: EventPayload < TriggerEventData > ) => {
84
+ if ( ! event || MUTATION_FILTER . test ( event . data . name ) || ! mutationFilter ( event . data . name ) ) {
85
+ return ;
86
+ }
62
87
63
- let {
64
- results,
65
- } = command ;
88
+ startTrace ( [
89
+ 'set' ,
90
+ 'deleteProperty' ,
91
+ ] ) ;
66
92
67
- if ( type === 'undo' ) {
68
- results = results . slice ( ) . reverse ( ) ;
69
- }
93
+ const listener = onTraceResult ( result => historyState . results . push ( result ) ) ;
94
+
95
+ store . once ( EVENTS . mutation . after , ( ) => {
96
+ stopTrace ( ) ;
97
+ processResults ( event . data . name ) ;
98
+
99
+ listener . dispose ( ) ;
100
+ } ) ;
101
+ } ) ;
102
+
103
+ function applyChange ( type : ChangeType , change : MutationTrace ) {
104
+ store . write ( `extension:history:${ type } ` , SENDER , state => {
105
+ const tasks = CHANGE_MAP [ type ] ;
106
+ const results = type === 'exec'
107
+ ? change . results
108
+ : change . results . slice ( ) . reverse ( ) ;
70
109
71
110
results . forEach ( ( { gate, nodes, prop, newValue, oldValue } ) => {
72
111
const target = objectFromPath ( state , nodes ) ;
@@ -78,68 +117,65 @@ export default function historyExtension<TState extends BaseState>(options?: Par
78
117
} ) ;
79
118
}
80
119
81
- function processResults ( name : string ) {
82
- if ( results . length === 0 ) {
120
+ function processResults ( mutation : string ) {
121
+ if ( historyState . results . length === 0 ) {
83
122
return ;
84
123
}
85
124
86
- if ( commands . length >= _options . max ) {
87
- commands . shift ( ) ;
88
- }
89
-
90
- commands . push ( {
91
- name,
92
- results : Array . from ( results ) ,
93
- } ) ;
125
+ for ( const { key, filter } of groups ) {
126
+ if ( ! filter ( mutation ) ) {
127
+ continue ;
128
+ }
94
129
95
- results = [ ] ;
96
- position = commands . length - 1 ;
97
- }
130
+ const historyGroup = historyState . groups [ key ] ;
98
131
99
- store . on ( EVENTS . mutation . before , ( event ?: EventPayload < TriggerEventData > ) => {
100
- if ( ! event || MUTATION_FILTER . test ( event . data . name ) || ( mutationLookup . size > 0 && ! mutationLookup . has ( event . data . name ) ) ) {
101
- return ;
102
- }
132
+ if ( historyGroup . history . length - 1 !== historyGroup . position ) {
133
+ historyGroup . history = historyGroup . history . slice ( 0 , historyGroup . position + 1 ) ;
134
+ }
103
135
104
- startTrace ( [
105
- 'set' ,
106
- 'deleteProperty' ,
107
- ] ) ;
136
+ if ( historyGroup . history . length >= _options . max ) {
137
+ historyGroup . history . shift ( ) ;
138
+ }
108
139
109
- const listener = onTraceResult ( result => results . push ( result ) ) ;
140
+ historyGroup . history . push ( {
141
+ name : mutation ,
142
+ results : Array . from ( historyState . results ) ,
143
+ } ) ;
110
144
111
- store . once ( EVENTS . mutation . after , ( ) => {
112
- stopTrace ( ) ;
113
- processResults ( event . data . name ) ;
145
+ historyGroup . position = historyGroup . history . length - 1 ;
146
+ }
114
147
115
- listener . dispose ( ) ;
116
- } ) ;
117
- } ) ;
148
+ historyState . results = [ ] ;
149
+ }
118
150
119
- function run ( type : CommandType , offset : number ) {
120
- const command = commands [ position ] ;
151
+ function run ( groupKey : string , type : ChangeType , offset : number ) {
152
+ const historyGroup = historyState . groups [ groupKey ] ;
121
153
122
- if ( ! command ) {
154
+ if ( ! historyGroup ) {
123
155
return ;
124
156
}
125
157
126
- executeCommand ( type , command ) ;
158
+ const changeIndex = historyGroup . position + ( offset === - 1 ? 0 : 1 ) ;
159
+ const change = historyGroup . history [ changeIndex ] ;
160
+
161
+ if ( ! change ) {
162
+ return ;
163
+ }
127
164
128
- position = Math . max ( 0 , Math . min ( commands . length - 1 , position + offset ) ) ;
165
+ applyChange ( type , change ) ;
166
+ historyGroup . position = Math . max ( - 1 , Math . min ( historyGroup . history . length - 1 , historyGroup . position + offset ) ) ;
129
167
}
130
168
131
- function undo ( ) {
132
- run ( 'undo' , - 1 ) ;
169
+ function undo ( group : string = '' ) {
170
+ run ( group , 'undo' , - 1 ) ;
133
171
}
134
172
135
- function redo ( ) {
136
- run ( 'exec' , 1 ) ;
173
+ function redo ( group : string = '' ) {
174
+ run ( group , 'exec' , 1 ) ;
137
175
}
138
176
139
177
function clearHistory ( ) {
140
- position = 0 ;
141
- commands = [ ] ;
142
- results = [ ] ;
178
+ historyState = createHistoryState ( ) ;
143
179
}
144
180
145
181
return {
@@ -148,4 +184,25 @@ export default function historyExtension<TState extends BaseState>(options?: Par
148
184
clearHistory,
149
185
} ;
150
186
} ;
187
+ }
188
+
189
+ function getMutationGroups ( mutations : Options [ 'mutations' ] ) {
190
+ const hasGroups = typeIsObject ( mutations ) && 'groups' in mutations ;
191
+ const groups = hasGroups ? mutations . groups : { } ;
192
+
193
+ if ( ! hasGroups || typeIsMatchable ( mutations ) ) {
194
+ groups [ '' ] = mutations ;
195
+ }
196
+
197
+ return Object . entries ( groups )
198
+ . map ( ( [ key , matcher ] ) => {
199
+ const matchable = typeIsMatchable ( matcher ) ? matcher : {
200
+ include : matcher ,
201
+ } ;
202
+
203
+ return {
204
+ key,
205
+ filter : matchGetFilter ( matchable ) ,
206
+ } ;
207
+ } ) ;
151
208
}
0 commit comments