Browse code
continue unifying lenses
Showing 3 changed files
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,88 @@ |
1 |
+import * as R from 'ramda'; |
|
2 |
+import { |
|
3 |
+ Map, |
|
4 |
+ fromJS |
|
5 |
+} from 'immutable'; |
|
6 |
+ |
|
7 |
+let Symbol = (window['Symbol'] !== undefined) ? window['Symbol'] : x => `_Symbol__${x}`; |
|
8 |
+ |
|
9 |
+export const fireListeners = Symbol('fireListeners'); |
|
10 |
+ |
|
11 |
+const noOpTransform = val => val; |
|
12 |
+ |
|
13 |
+export function lensTransformer( |
|
14 |
+ lens, |
|
15 |
+ getTransform = noOpTransform, |
|
16 |
+ setTransform = noOpTransform |
|
17 |
+) { |
|
18 |
+ return { |
|
19 |
+ get: () => getTransform(lens.get()), |
|
20 |
+ set: (val) => lens.set(setTransform(val)), |
|
21 |
+ withValue(cb) { |
|
22 |
+ return cb(this.get()); |
|
23 |
+ }, |
|
24 |
+ |
|
25 |
+ }; |
|
26 |
+} |
|
27 |
+ |
|
28 |
+export function makeLens(key, self) { |
|
29 |
+ const createLens = (keyPath) => { |
|
30 |
+ |
|
31 |
+ return { |
|
32 |
+ get() { |
|
33 |
+ let result = self._getState().getIn(keyPath); |
|
34 |
+ if (result && result.toJS !== undefined) { |
|
35 |
+ result = result.toJS(); |
|
36 |
+ } |
|
37 |
+ return result; |
|
38 |
+ }, |
|
39 |
+ set(val) { |
|
40 |
+ const oldState = self._getState(); |
|
41 |
+ |
|
42 |
+ self.localStore = self.localStore.setIn(keyPath, fromJS(val)); |
|
43 |
+ this._addSetAction(keyPath, R.clone(val)); |
|
44 |
+ |
|
45 |
+ self[fireListeners](oldState, self._getState()); |
|
46 |
+ }, |
|
47 |
+ |
|
48 |
+ _prepareForSet(keyPath) { |
|
49 |
+ if (self.localStore.getIn(keyPath) === undefined) { |
|
50 |
+ for (let x = 0; x < keyPath.length; x++) { |
|
51 |
+ const subPath = keyPath.slice(0, x); |
|
52 |
+ if (self.localStore.getIn(subPath) === undefined && self.localStore.hasIn(subPath)) { |
|
53 |
+ self.localStore = self.localStore.setIn(subPath, Map()); |
|
54 |
+ } |
|
55 |
+ } |
|
56 |
+ } |
|
57 |
+ }, |
|
58 |
+ |
|
59 |
+ _addSetAction(keyPath, val) { |
|
60 |
+ let lastAction = self.actions[self.actions.length - 1]; |
|
61 |
+ if (lastAction && (lastAction[0][0] === 'lensFor') && R.equals(lastAction[0][1], keyPath)) { |
|
62 |
+ self.actions.pop(); |
|
63 |
+ } |
|
64 |
+ |
|
65 |
+ self.actions.push([ |
|
66 |
+ ['lensFor', keyPath], |
|
67 |
+ ['set', val] |
|
68 |
+ ]); |
|
69 |
+ }, |
|
70 |
+ |
|
71 |
+ lensFor(key) { |
|
72 |
+ let subPath = key instanceof Array ? key : [key]; |
|
73 |
+ return createLens([...keyPath, ...subPath]); |
|
74 |
+ }, |
|
75 |
+ |
|
76 |
+ withValue(cb, ...additional) { |
|
77 |
+ return cb(this.get(), ...additional); |
|
78 |
+ }, |
|
79 |
+ |
|
80 |
+ swap(cb) { |
|
81 |
+ this.set(cb(this.get())); |
|
82 |
+ return this.get(); |
|
83 |
+ } |
|
84 |
+ }; |
|
85 |
+ }; |
|
86 |
+ |
|
87 |
+ return createLens(key instanceof Array ? key : [key]); |
|
88 |
+} |
|
0 | 89 |
\ No newline at end of file |
... | ... |
@@ -3,111 +3,23 @@ import { |
3 | 3 |
Map, |
4 | 4 |
fromJS |
5 | 5 |
} from 'immutable'; |
6 |
- |
|
7 |
-const noOpTransform = val => val; |
|
8 |
- |
|
9 |
-export function lensTransformer( |
|
10 |
- lens, |
|
11 |
- getTransform = noOpTransform, |
|
12 |
- setTransform = noOpTransform |
|
13 |
-) { |
|
14 |
- return { |
|
15 |
- get: () => getTransform(lens.get()), |
|
16 |
- set: (val) => lens.set(setTransform(val)), |
|
17 |
- withValue(cb) { |
|
18 |
- return cb(this.get()); |
|
19 |
- }, |
|
20 |
- |
|
21 |
- }; |
|
22 |
-} |
|
23 |
- |
|
24 |
-let Symbol = (window['Symbol'] !== undefined) ? window['Symbol'] : x => `_Symbol__${x}`; |
|
25 |
- |
|
26 |
-let fireListeners = Symbol('fireListeners'); |
|
27 |
- |
|
28 |
-function makeLens(key, self) { |
|
29 |
- const createLens = (keyPath) => { |
|
30 |
- const lens = R.lensPath(keyPath); |
|
31 |
- |
|
32 |
- return { |
|
33 |
- get() { |
|
34 |
- let result = self._getState().getIn(keyPath); |
|
35 |
- if (result && result.toJS !== undefined) { |
|
36 |
- result = result.toJS(); |
|
37 |
- } |
|
38 |
- return result; |
|
39 |
- }, |
|
40 |
- set(val) { |
|
41 |
- const oldState = self._getState(); |
|
42 |
- |
|
43 |
- self.localStore = self.localStore.setIn(keyPath, fromJS(val)); |
|
44 |
- this._addSetAction(keyPath, R.clone(val)); |
|
45 |
- |
|
46 |
- self[fireListeners](oldState, self._getState()); |
|
47 |
- }, |
|
48 |
- |
|
49 |
- _prepareForSet(keyPath) { |
|
50 |
- if (self.localStore.getIn(keyPath) === undefined) { |
|
51 |
- for (let x = 0; x < keyPath.length; x++) { |
|
52 |
- const subPath = keyPath.slice(0, x); |
|
53 |
- if (self.localStore.getIn(subPath) === undefined && self.localStore.hasIn(subPath)) { |
|
54 |
- self.localStore = self.localStore.setIn(subPath, Map()); |
|
55 |
- } |
|
56 |
- } |
|
57 |
- } |
|
58 |
- }, |
|
59 |
- |
|
60 |
- _addSetAction(keyPath, val) { |
|
61 |
- let lastAction = self.actions[self.actions.length - 1]; |
|
62 |
- if (lastAction && (lastAction[0][0] === 'lensFor') && R.equals(lastAction[0][1], keyPath)) { |
|
63 |
- self.actions.pop(); |
|
64 |
- } |
|
65 |
- |
|
66 |
- self.actions.push([ |
|
67 |
- ['lensFor', keyPath], |
|
68 |
- ['set', val] |
|
69 |
- ]); |
|
70 |
- }, |
|
71 |
- |
|
72 |
- lensFor(key) { |
|
73 |
- let subPath = key instanceof Array ? key : [key]; |
|
74 |
- return createLens([...keyPath, ...subPath]); |
|
75 |
- }, |
|
76 |
- |
|
77 |
- withValue(cb) { |
|
78 |
- return cb(this.get()); |
|
79 |
- } |
|
80 |
- }; |
|
81 |
- }; |
|
82 |
- |
|
83 |
- return createLens(key instanceof Array ? key : [key]); |
|
84 |
-}; |
|
85 |
- |
|
6 |
+import { makeLens, lensTransformer, fireListeners } from './lens'; |
|
86 | 7 |
|
87 | 8 |
function RecordingStateContainer(container) { |
88 | 9 |
this.localStore = Map(); |
89 | 10 |
this.container = container; |
90 | 11 |
this.listeners = []; |
91 | 12 |
this.actions = []; |
92 |
-}; |
|
93 |
- |
|
13 |
+} |
|
94 | 14 |
|
95 | 15 |
RecordingStateContainer.prototype = { |
96 |
- set(key, value) { |
|
97 |
- this.lensFor(key).set(value); |
|
98 |
- }, |
|
16 |
+ set(key, value) { this.lensFor(key).set(value); }, |
|
99 | 17 |
|
100 |
- get(key) { |
|
101 |
- return this.lensFor(key).get(); |
|
102 |
- }, |
|
18 |
+ get(key) { return this.lensFor(key).get(); }, |
|
103 | 19 |
|
104 | 20 |
setState(newState) { |
105 | 21 |
this.localStore = this.localStore.merge(newState); |
106 |
- this.actions = [ |
|
107 |
- [ |
|
108 |
- ['setState', this.localStore] |
|
109 |
- ] |
|
110 |
- ]; |
|
22 |
+ this.actions = [ [ ['setState', this.localStore] ] ]; |
|
111 | 23 |
}, |
112 | 24 |
|
113 | 25 |
_getState() { |
... | ... |
@@ -163,24 +75,24 @@ RecordingStateContainer.prototype = { |
163 | 75 |
|
164 | 76 |
}; |
165 | 77 |
|
166 |
-export default function StateContainer(data = {}, computed = {}) { |
|
78 |
+export { lensTransformer }; |
|
79 |
+ |
|
80 |
+export default function StateContainer(data = {}) { |
|
167 | 81 |
|
168 | 82 |
|
169 | 83 |
var listeners = []; |
170 | 84 |
var state = [fromJS(data)]; |
171 | 85 |
|
172 |
- function fireListeners(oldState, newState) { |
|
173 |
- listeners.forEach((listener) => { |
|
174 |
- listener(oldState, newState); |
|
175 |
- }); |
|
176 |
- } |
|
177 |
- |
|
178 | 86 |
return { |
87 |
+ [fireListeners](oldState, newState) { |
|
88 |
+ listeners.forEach((listener) => { |
|
89 |
+ listener(oldState, newState); |
|
90 |
+ }); |
|
91 |
+ }, |
|
92 |
+ |
|
179 | 93 |
lensFor(key) { |
180 | 94 |
let self = this; |
181 | 95 |
const createLens = (keyPath) => { |
182 |
- const lens = R.lensPath(keyPath); |
|
183 |
- |
|
184 | 96 |
return { |
185 | 97 |
get() { |
186 | 98 |
let result = self._currentState.getIn(keyPath); |
... | ... |
@@ -202,7 +114,7 @@ export default function StateContainer(data = {}, computed = {}) { |
202 | 114 |
} |
203 | 115 |
|
204 | 116 |
self._currentState = self._currentState.setIn(keyPath, fromJS(val)); |
205 |
- fireListeners(oldState, self._currentState); |
|
117 |
+ self[fireListeners](oldState, self._currentState); |
|
206 | 118 |
}, |
207 | 119 |
lensFor(key) { |
208 | 120 |
let subPath = key instanceof Array ? key : [key]; |
... | ... |
@@ -211,6 +123,11 @@ export default function StateContainer(data = {}, computed = {}) { |
211 | 123 |
|
212 | 124 |
withValue(cb) { |
213 | 125 |
return cb(this.get()); |
126 |
+ }, |
|
127 |
+ |
|
128 |
+ swap(cb) { |
|
129 |
+ this.set(cb(this.get())); |
|
130 |
+ return this.get(); |
|
214 | 131 |
} |
215 | 132 |
}; |
216 | 133 |
}; |
... | ... |
@@ -250,14 +167,10 @@ export default function StateContainer(data = {}, computed = {}) { |
250 | 167 |
* */ |
251 | 168 |
setState(newState) { |
252 | 169 |
const oldState = this._currentState; |
253 |
- let mergedState; |
|
254 | 170 |
|
255 | 171 |
this._currentState = this._currentState.merge(newState); |
256 | 172 |
|
257 |
- fireListeners(oldState, this._currentState); |
|
258 |
- }, |
|
259 |
- beforeUpdate(listener) { |
|
260 |
- |
|
173 |
+ this[fireListeners](oldState, this._currentState); |
|
261 | 174 |
}, |
262 | 175 |
onUpdate(listener) { |
263 | 176 |
listeners.push(listener); |
... | ... |
@@ -1,8 +1,6 @@ |
1 |
-import StateContainer, {LensWrapper, lensTransformer} from '../src/state_container'; |
|
1 |
+import StateContainer, {lensTransformer} from '../src/state_container'; |
|
2 | 2 |
import sinon from 'sinon'; |
3 | 3 |
|
4 |
-import R from 'ramda'; |
|
5 |
- |
|
6 | 4 |
test("initial state setting works", () => { |
7 | 5 |
const container = new StateContainer({foo: 'bar'}); |
8 | 6 |
|