|
| 1 | +const sinkNames = ['DOM', 'Store', 'auth$']; // TODO : choose all caps or all mins |
| 2 | +import {ALL, ACTIVE, COMPLETED, IS_LOGGED_IN} from './properties'; |
| 3 | +import {UPDATE, DELETE} from '../../src/drivers/store/properties'; |
| 4 | + |
| 5 | +App({sinkNames}, [ |
| 6 | + OnRoute('/', SwitchCase({ |
| 7 | + on: 'auth$' |
| 8 | + }, [ |
| 9 | + Case({when: IS_LOGGED_IN}, [ |
| 10 | + TodoComponent({routeState: ALL}) // actually will require flip or |
| 11 | + // curry and R.__ |
| 12 | + ]), |
| 13 | + Case({when: complement(IS_LOGGED_IN)}, [ |
| 14 | + LogIn({redirect: '/'}) |
| 15 | + ]) |
| 16 | + ] |
| 17 | + )), |
| 18 | + OnRoute('/active', SwitchCase({ |
| 19 | + on: 'auth$', sinkNames |
| 20 | + }, [ |
| 21 | + Case({when: IS_LOGGED_IN}, [ |
| 22 | + TodoComponent({routeState: ACTIVE}) // actually will require flip or |
| 23 | + // curry and R.__ |
| 24 | + ]), |
| 25 | + Case({when: complement(IS_LOGGED_IN)}, [ |
| 26 | + LogIn({redirect: '/active'}) |
| 27 | + ]) |
| 28 | + ] |
| 29 | + )), |
| 30 | + OnRoute('/completed', TodoComponent({routeState: COMPLETED}) |
| 31 | + ) |
| 32 | +]); |
| 33 | + |
| 34 | +const TodoComponent = ListManager({ |
| 35 | + namespace: 'TODO' |
| 36 | +}, ListFactory({ |
| 37 | + storeRef: '/' |
| 38 | +}, Item)); |
| 39 | + |
| 40 | +// TODO : ListManager |
| 41 | +// ... |
| 42 | +// I am here |
| 43 | +// ... |
| 44 | +// routeState is in settings |
| 45 | +// children DOM to insert in the middle of parent DOM (slot mechanism) |
| 46 | +// parent sinks (events, intents, actions) to merge with children sinks |
| 47 | +// events : complete all, add new todo, clear completed |
| 48 | +// actions : store - update complete all; store : push |
| 49 | +// ... |
| 50 | +function ListManager(listManagerSettings, component) { |
| 51 | + return function listManagerComponent(sources, componentSettings) { |
| 52 | + let {Store, DOM} = sources; |
| 53 | + let settings = merge(listManagerSettings, componentSettings); |
| 54 | + let {sinkNames, namespace, storeRef} = settings; |
| 55 | + |
| 56 | + // should have parent dom as Observable<VNode|Array<VNode>|Null> |
| 57 | + // TODO : adjust DOM merge function in consequence |
| 58 | + // Also App component should add a div to the array if receives one, and |
| 59 | + // filter out null for DOM |
| 60 | + // TODO : slots specs |
| 61 | + // A slot is a VNode which is undefined everywhere except key? or data? |
| 62 | + // hence it has the index of the child to copy there, or a function |
| 63 | + // which takes the number of children and returns an array of those to copy |
| 64 | + function mergeDOMs(parentDOM, childrenDOM, settings) { |
| 65 | + childrenDOM |
| 66 | + } |
| 67 | + |
| 68 | + function makeListManagerSinks(sources, settings) { |
| 69 | + |
| 70 | + } |
| 71 | + |
| 72 | + return m({ |
| 73 | + makeOwnSinks: makeListManagerSinks, |
| 74 | + mergeSinks: { |
| 75 | + DOM: mergeDOMs |
| 76 | + } |
| 77 | + }, settings, [component]); |
| 78 | + |
| 79 | + // TODO : I am here - merge childrenDOM and my DOM |
| 80 | + |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +//// List |
| 85 | +// storeRef : ref where the items will be located |
| 86 | +function ListFactory(listSettings, component) { |
| 87 | + return function listFactoryComponent(sources, componentSettings) { |
| 88 | + let {Store, DOM} = sources; |
| 89 | + // TODO : could also keep settings separate instead of propagating downsream |
| 90 | + let settings = merge(listSettings, componentSettings); |
| 91 | + let {namespace, sinkNames, storeRef} = settings; |
| 92 | + |
| 93 | + // @type Stream<Array<ItemState>> |
| 94 | + let listState$ = Store.in(namespace).get(storeRef); |
| 95 | + |
| 96 | + // TODO !!! |
| 97 | + //// !! how to use todoItemData -> switch only on delete and insert not UPDATE!! |
| 98 | + return SwitchForEachNew({ |
| 99 | + on: listState$, eqFn: onlyDeleteOrInsert, to: 'itemsData', sinkNames |
| 100 | + }, List({ |
| 101 | + valueId: 'todoItemData', |
| 102 | + itemId: 'itemRef', |
| 103 | + }, component)) |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +// TODO : List combinator implementation, use m? or directly inline it? |
| 108 | +// maybe inline it first |
| 109 | +function List(listSettings, component) { |
| 110 | + return function ListComponent(sources, componentSettings) { |
| 111 | + let settings = merge(listSettings, componentSettings); |
| 112 | + // beware of namespace problem : value could come from anywhere up |
| 113 | + let {itemsData, valueId, itemId} = settings; // array on which list is based |
| 114 | + |
| 115 | + // listSinks :: [Sinks], itemsStates :: Array<ItemState> |
| 116 | + let listSinks = itemsData.map((itemData, index) => { |
| 117 | + let componentSettings = merge( |
| 118 | + settings, |
| 119 | + {[itemId]: index}, |
| 120 | + {[valueId]: itemData} |
| 121 | + ); |
| 122 | + return component(sources, componentSettings) |
| 123 | + }); |
| 124 | + |
| 125 | + // This following merge should be the default merge in utils |
| 126 | + const sinkNames = getSinkNames(listSinks); |
| 127 | + // Every sink but DOM |
| 128 | + let finalSinks = sinkNames.reduce((finalSinks, sinkName) => { |
| 129 | + finalSinks[sinkName] = $.merge(listSinks.map(sinks => sinks[sinkName])); |
| 130 | + return finalSinks |
| 131 | + }, {}); |
| 132 | + // TODO : Add DOM treatment $.merge => $.combineLatest(Array Obs |
| 133 | + // VNode, div) |
| 134 | + // TODO : not div, discriminate case of array of size 0 or 1 or 2+ |
| 135 | + |
| 136 | + return finalSinks |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +//// Item |
| 141 | +// itemRef = storeRef + index item, passed by ListFactory parent |
| 142 | +// in Store : {completed: Boolean, text : String} |
| 143 | +function Item(sources, settings) { |
| 144 | + let {Store, DOM} = sources; |
| 145 | + let {namespace, itemRef, todoItemData} = settings; |
| 146 | + // Note : todoItemData is not used here as it already fethced with state$ |
| 147 | + let state$ = Store.in(namespace).get(itemRef); |
| 148 | + |
| 149 | + // TODO : replace events and selectors by actual values from view template |
| 150 | + let events = { |
| 151 | + 'dbl_click': DOM.select('input area').events('double-click'), |
| 152 | + 'radio_click': DOM.select('radio button').events('click'), |
| 153 | + 'enter_keypress': DOM.select('input area').events('enter keypress'), |
| 154 | + 'delete': DOM.select('cross').events('click'), |
| 155 | + }; |
| 156 | + let intents = { |
| 157 | + 'edit_item': events.dbl_click.map(_ => true), |
| 158 | + 'toggle_completed': events.radio_click.map(ev => TODO), // TODO |
| 159 | + 'update_item_text': events.enter_keypress.flatMap(ev => TODO), // TODO |
| 160 | + 'delete_item': events.delete.map(_ => ({itemRef})) |
| 161 | + }; |
| 162 | + let actions = { |
| 163 | + DOM: state$.combineLatest(intents.edit_item, combineFn).map(view), // TODO |
| 164 | + Store: { |
| 165 | + 'toggle_completed': intents.toggle_completed.map(val => ({ |
| 166 | + command: UPDATE, |
| 167 | + ref: itemRef, |
| 168 | + payload: {completed: val}, |
| 169 | + })), |
| 170 | + 'update_item_text': intents.update_item_text.map(text => ({ |
| 171 | + command: UPDATE, |
| 172 | + ref: itemRef, |
| 173 | + payload: {text: text} |
| 174 | + })), |
| 175 | + 'delete_item': intents.delete.map(itemRef => ({ |
| 176 | + command: DELETE, |
| 177 | + ref: itemRef, |
| 178 | + payload: {} |
| 179 | + })) |
| 180 | + } |
| 181 | + }; |
| 182 | + |
| 183 | + return { |
| 184 | + DOM: actions.DOM, |
| 185 | + Store: merge( |
| 186 | + actions.toggle_completed, |
| 187 | + actions.update_item_text, |
| 188 | + actions.delete_item |
| 189 | + ) |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +// TODO : other version with mergeAll and takeUntil to dynamically manage |
| 194 | +// insertion and deletiong |
0 commit comments