8
8
* k
9
9
*
10
10
*/
11
- import { Map , Record } from 'immutable'
11
+ import { Map , Record , Set } from 'immutable'
12
12
import { toImmutable , toJS } from '../immutable-helpers'
13
13
14
14
export const status = {
@@ -17,12 +17,26 @@ export const status = {
17
17
UNKNOWN : 2 ,
18
18
}
19
19
20
- export const Node = Record ( {
20
+ export const RootNode = Record ( {
21
21
status : status . CLEAN ,
22
22
state : 1 ,
23
23
children : Map ( ) ,
24
+ changedPaths : Set ( ) ,
24
25
} )
25
26
27
+ const Node = Record ( {
28
+ status : status . CLEAN ,
29
+ state : 1 ,
30
+ children : Map ( ) ,
31
+ } )
32
+
33
+ /**
34
+ * Denotes that a keypath hasn't changed
35
+ * Makes the Node at the keypath as CLEAN and recursively marks the children as CLEAN
36
+ * @param {Immutable.Map } map
37
+ * @param {Keypath } keypath
38
+ * @return {Status }
39
+ */
26
40
export function unchanged ( map , keypath ) {
27
41
const childKeypath = getChildKeypath ( keypath )
28
42
if ( ! map . hasIn ( childKeypath ) ) {
@@ -36,14 +50,24 @@ export function unchanged(map, keypath) {
36
50
} )
37
51
}
38
52
53
+ /**
54
+ * Denotes that a keypath has changed
55
+ * Traverses to the Node at the keypath and marks as DIRTY, marks all children as UNKNOWN
56
+ * @param {Immutable.Map } map
57
+ * @param {Keypath } keypath
58
+ * @return {Status }
59
+ */
39
60
export function changed ( map , keypath ) {
40
61
const childrenKeypath = getChildKeypath ( keypath ) . concat ( 'children' )
41
- return map
42
- . update ( 'children' , children => recursiveIncrement ( children , keypath ) )
62
+ // TODO(jordan): can this be optimized
63
+ return map . withMutations ( m => {
64
+ m . update ( 'changedPaths' , p => p . add ( toImmutable ( keypath ) ) )
65
+ m . update ( 'children' , children => recursiveIncrement ( children , keypath ) )
43
66
// handle the root node
44
- . update ( 'state' , val => val + 1 )
45
- . set ( 'status' , status . DIRTY )
46
- . updateIn ( childrenKeypath , entry => recursiveSetStatus ( entry , status . UNKNOWN ) )
67
+ m . update ( 'state' , val => val + 1 )
68
+ m . set ( 'status' , status . DIRTY )
69
+ m . updateIn ( childrenKeypath , entry => recursiveSetStatus ( entry , status . UNKNOWN ) )
70
+ } )
47
71
}
48
72
49
73
/**
@@ -63,12 +87,7 @@ export function isEqual(map, keypath, value) {
63
87
return entry . get ( 'state' ) === value ;
64
88
}
65
89
66
- /**
67
- * Increments all unknown states and sets everything to CLEAN
68
- * @param {Immutable.Map } map
69
- * @return {Status }
70
- */
71
- export function incrementAndClean ( map ) {
90
+ function recursiveClean ( map ) {
72
91
if ( map . size === 0 ) {
73
92
return map
74
93
}
@@ -82,11 +101,37 @@ export function incrementAndClean(map) {
82
101
return map
83
102
. update ( 'children' , c => c . withMutations ( m => {
84
103
m . keySeq ( ) . forEach ( key => {
85
- m . update ( key , incrementAndClean )
104
+ m . update ( key , recursiveClean )
86
105
} )
87
106
} ) )
88
107
}
89
108
109
+ /**
110
+ * Increments all unknown states and sets everything to CLEAN
111
+ * @param {Immutable.Map } map
112
+ * @return {Status }
113
+ */
114
+ export function incrementAndClean ( map ) {
115
+ if ( map . size === 0 ) {
116
+ return map
117
+ }
118
+ const changedPaths = map . get ( 'changedPaths' )
119
+ // TODO(jordan): can this be optimized
120
+ return map . withMutations ( m => {
121
+ changedPaths . forEach ( path => {
122
+ m . update ( 'children' , c => traverseAndMarkClean ( c , path ) )
123
+ } )
124
+
125
+ m . set ( 'changedPaths' , Set ( ) )
126
+ const rootStatus = m . get ( 'status' )
127
+ if ( rootStatus === status . DIRTY ) {
128
+ setClean ( m )
129
+ } else if ( rootStatus === status . UNKNOWN ) {
130
+ setClean ( increment ( m ) )
131
+ }
132
+ } )
133
+ }
134
+
90
135
export function get ( map , keypath ) {
91
136
return map . getIn ( getChildKeypath ( keypath ) . concat ( 'state' ) )
92
137
}
@@ -129,6 +174,52 @@ function recursiveIncrement(map, keypath) {
129
174
} )
130
175
}
131
176
177
+ /**
178
+ * Traverses up to a keypath and marks all entries as clean along the way, then recursively traverses over all children
179
+ * @param {Immutable.Map } map
180
+ * @param {Immutable.List } keypath
181
+ * @return {Status }
182
+ */
183
+ function traverseAndMarkClean ( map , keypath ) {
184
+ if ( keypath . size === 0 ) {
185
+ return recursiveCleanChildren ( map )
186
+ }
187
+ return map . withMutations ( map => {
188
+ const key = keypath . first ( )
189
+
190
+ map . update ( key , incrementAndCleanNode )
191
+ map . updateIn ( [ key , 'children' ] , children => traverseAndMarkClean ( children , keypath . rest ( ) ) )
192
+ } )
193
+ }
194
+
195
+ function recursiveCleanChildren ( children ) {
196
+ if ( children . size === 0 ) {
197
+ return children
198
+ }
199
+
200
+ return children . withMutations ( c => {
201
+ c . keySeq ( ) . forEach ( key => {
202
+ c . update ( key , incrementAndCleanNode )
203
+ c . updateIn ( [ key , 'children' ] , recursiveCleanChildren )
204
+ } )
205
+ } )
206
+ }
207
+
208
+ /**
209
+ * Takes a node, marks it CLEAN, if it was UNKNOWN it increments
210
+ * @param {Node } node
211
+ * @return {Status }
212
+ */
213
+ function incrementAndCleanNode ( node ) {
214
+ const nodeStatus = node . get ( 'status' )
215
+ if ( nodeStatus === status . DIRTY ) {
216
+ return setClean ( node )
217
+ } else if ( nodeStatus === status . UNKNOWN ) {
218
+ return setClean ( increment ( node ) )
219
+ }
220
+ return node
221
+ }
222
+
132
223
function recursiveRegister ( map , keypath ) {
133
224
keypath = toImmutable ( keypath )
134
225
if ( keypath . size === 0 ) {
0 commit comments