88 * k
99 *
1010 */
11- import { Map , Record } from 'immutable'
11+ import { Map , Record , Set } from 'immutable'
1212import { toImmutable , toJS } from '../immutable-helpers'
1313
1414export const status = {
@@ -17,12 +17,26 @@ export const status = {
1717 UNKNOWN : 2 ,
1818}
1919
20- export const Node = Record ( {
20+ export const RootNode = Record ( {
2121 status : status . CLEAN ,
2222 state : 1 ,
2323 children : Map ( ) ,
24+ changedPaths : Set ( ) ,
2425} )
2526
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+ */
2640export function unchanged ( map , keypath ) {
2741 const childKeypath = getChildKeypath ( keypath )
2842 if ( ! map . hasIn ( childKeypath ) ) {
@@ -36,14 +50,24 @@ export function unchanged(map, keypath) {
3650 } )
3751}
3852
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+ */
3960export function changed ( map , keypath ) {
4061 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 ) )
4366 // 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+ } )
4771}
4872
4973/**
@@ -63,12 +87,7 @@ export function isEqual(map, keypath, value) {
6387 return entry . get ( 'state' ) === value ;
6488}
6589
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 ) {
7291 if ( map . size === 0 ) {
7392 return map
7493 }
@@ -82,11 +101,37 @@ export function incrementAndClean(map) {
82101 return map
83102 . update ( 'children' , c => c . withMutations ( m => {
84103 m . keySeq ( ) . forEach ( key => {
85- m . update ( key , incrementAndClean )
104+ m . update ( key , recursiveClean )
86105 } )
87106 } ) )
88107}
89108
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+
90135export function get ( map , keypath ) {
91136 return map . getIn ( getChildKeypath ( keypath ) . concat ( 'state' ) )
92137}
@@ -129,6 +174,52 @@ function recursiveIncrement(map, keypath) {
129174 } )
130175}
131176
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+
132223function recursiveRegister ( map , keypath ) {
133224 keypath = toImmutable ( keypath )
134225 if ( keypath . size === 0 ) {
0 commit comments