| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview Module of functions which produce a new page state in response |
| 7 * to an action. Reducers (in the same sense as Array.prototype.reduce) must be |
| 8 * pure functions: they must not modify existing state objects, or make any API |
| 9 * calls. |
| 10 */ |
| 11 |
| 12 cr.define('bookmarks', function() { |
| 13 |
| 14 var SelectionState = {}; |
| 15 |
| 16 /** |
| 17 * @param {SelectionState} selectionState |
| 18 * @param {Action} action |
| 19 * @return {SelectionState} |
| 20 */ |
| 21 SelectionState.selectItems = function(selectionState, action) { |
| 22 var newState = {}; |
| 23 if (action.add) |
| 24 Object.assign(newState, selectionState.items); |
| 25 |
| 26 action.items.forEach(function(id) { |
| 27 newState[id] = true; |
| 28 }); |
| 29 |
| 30 return /** @type {SelectionState} */ (Object.assign({}, selectionState, { |
| 31 items: newState, |
| 32 anchor: action.anchor, |
| 33 })); |
| 34 }; |
| 35 |
| 36 /** |
| 37 * @param {SelectionState} selectionState |
| 38 * @return {SelectionState} |
| 39 */ |
| 40 SelectionState.deselectAll = function(selectionState) { |
| 41 return /** @type{SelectionState} */ (Object.assign({}, selectionState, { |
| 42 items: {}, |
| 43 anchor: null, |
| 44 })); |
| 45 }; |
| 46 |
| 47 /** |
| 48 * @param {SelectionState} selection |
| 49 * @param {Action} action |
| 50 * @return {SelectionState} |
| 51 */ |
| 52 SelectionState.updateSelection = function(selection, action) { |
| 53 switch (action.name) { |
| 54 case 'select-folder': |
| 55 case 'finish-search': |
| 56 return SelectionState.deselectAll(selection); |
| 57 case 'select-items': |
| 58 return SelectionState.selectItems(selection, action); |
| 59 } |
| 60 return selection; |
| 61 }; |
| 62 |
| 63 var SearchState = {}; |
| 64 |
| 65 /** |
| 66 * @param {SearchState} search |
| 67 * @param {Action} action |
| 68 * @return {SearchState} |
| 69 */ |
| 70 SearchState.startSearch = function(search, action) { |
| 71 return /** @type {SearchState} */ (Object.assign({}, search, { |
| 72 term: action.term, |
| 73 inProgress: true, |
| 74 })); |
| 75 }; |
| 76 |
| 77 /** |
| 78 * @param {SearchState} search |
| 79 * @param {Action} action |
| 80 * @return {SearchState} |
| 81 */ |
| 82 SearchState.finishSearch = function(search, action) { |
| 83 return /** @type {SearchState} */ (Object.assign({}, search, { |
| 84 inProgress: false, |
| 85 results: action.results, |
| 86 })); |
| 87 }; |
| 88 |
| 89 /** @return {SearchState} */ |
| 90 SearchState.clearSearch = function() { |
| 91 return { |
| 92 term: '', |
| 93 inProgress: false, |
| 94 results: [], |
| 95 }; |
| 96 }; |
| 97 |
| 98 /** |
| 99 * @param {SearchState} search |
| 100 * @param {Action} action |
| 101 * @return {SearchState} |
| 102 */ |
| 103 SearchState.updateSearch = function(search, action) { |
| 104 switch (action.name) { |
| 105 case 'start-search': |
| 106 return SearchState.startSearch(search, action); |
| 107 case 'select-folder': |
| 108 case 'clear-search': |
| 109 return SearchState.clearSearch(); |
| 110 case 'finish-search': |
| 111 return SearchState.finishSearch(search, action); |
| 112 default: |
| 113 return search; |
| 114 } |
| 115 }; |
| 116 |
| 117 var NodeState = {}; |
| 118 |
| 119 /** |
| 120 * @param {NodeList} nodes |
| 121 * @param {string} id |
| 122 * @param {function(BookmarkNode):BookmarkNode} callback |
| 123 * @return {NodeList} |
| 124 */ |
| 125 NodeState.modifyNode_ = function(nodes, id, callback) { |
| 126 var nodeModification = {}; |
| 127 nodeModification[id] = callback(nodes[id]); |
| 128 return Object.assign({}, nodes, nodeModification); |
| 129 }; |
| 130 |
| 131 /** |
| 132 * @param {NodeList} nodes |
| 133 * @param {Action} action |
| 134 * @return {NodeList} |
| 135 */ |
| 136 NodeState.editBookmark = function(nodes, action) { |
| 137 return NodeState.modifyNode_(nodes, action.id, function(node) { |
| 138 return /** @type {BookmarkNode} */ ( |
| 139 Object.assign({}, node, action.changeInfo)); |
| 140 }); |
| 141 }; |
| 142 |
| 143 /** |
| 144 * @param {NodeList} nodes |
| 145 * @param {Action} action |
| 146 * @return {NodeList} |
| 147 */ |
| 148 NodeState.removeBookmark = function(nodes, action) { |
| 149 return NodeState.modifyNode_(nodes, action.parentId, function(node) { |
| 150 var newChildren = node.children.slice(); |
| 151 newChildren.splice(action.index, 1); |
| 152 return /** @type {BookmarkNode} */ ( |
| 153 Object.assign({}, node, {children: newChildren})); |
| 154 }); |
| 155 }; |
| 156 |
| 157 /** |
| 158 * @param {NodeList} nodes |
| 159 * @param {Action} action |
| 160 * @return {NodeList} |
| 161 */ |
| 162 NodeState.updateNodes = function(nodes, action) { |
| 163 switch (action.name) { |
| 164 case 'edit-bookmark': |
| 165 return NodeState.editBookmark(nodes, action); |
| 166 case 'remove-bookmark': |
| 167 return NodeState.removeBookmark(nodes, action); |
| 168 case 'refresh-nodes': |
| 169 return action.nodes; |
| 170 default: |
| 171 return nodes; |
| 172 } |
| 173 }; |
| 174 |
| 175 var SelectedFolderState = {}; |
| 176 |
| 177 /** |
| 178 * @param {NodeList} nodes |
| 179 * @param {string} ancestorId |
| 180 * @param {string} childId |
| 181 * @return {boolean} |
| 182 */ |
| 183 SelectedFolderState.isAncestorOf = function(nodes, ancestorId, childId) { |
| 184 var currentId = childId; |
| 185 // Work upwards through the tree from child. |
| 186 while (currentId) { |
| 187 if (currentId == ancestorId) |
| 188 return true; |
| 189 currentId = nodes[currentId].parentId; |
| 190 } |
| 191 return false; |
| 192 }; |
| 193 |
| 194 /** |
| 195 * @param {?string} selectedFolder |
| 196 * @param {Action} action |
| 197 * @param {NodeList} nodes |
| 198 * @return {?string} |
| 199 */ |
| 200 SelectedFolderState.updateSelectedFolder = function( |
| 201 selectedFolder, action, nodes) { |
| 202 // TODO(tsergeant): It should not be possible to select a non-folder. |
| 203 switch (action.name) { |
| 204 case 'select-folder': |
| 205 return action.id; |
| 206 case 'change-folder-open': |
| 207 // When hiding the selected folder by closing its ancestor, select |
| 208 // that ancestor instead. |
| 209 if (!action.open && selectedFolder && |
| 210 SelectedFolderState.isAncestorOf( |
| 211 nodes, action.id, selectedFolder)) { |
| 212 return action.id; |
| 213 } |
| 214 return selectedFolder; |
| 215 case 'finish-search': |
| 216 return null; |
| 217 case 'clear-search': |
| 218 return nodes['0'].children[0]; |
| 219 default: |
| 220 return selectedFolder; |
| 221 } |
| 222 }; |
| 223 |
| 224 var ClosedFolderState = {}; |
| 225 |
| 226 /** |
| 227 * @param {ClosedFolderState} state |
| 228 * @param {Action} action |
| 229 * @param {NodeList} nodes |
| 230 * @return {ClosedFolderState} |
| 231 */ |
| 232 ClosedFolderState.openAncestorsOf = function(state, action, nodes) { |
| 233 var id = action.id; |
| 234 var modifications = {}; |
| 235 var parentId = nodes[id].parentId; |
| 236 while (parentId) { |
| 237 if (state[parentId]) { |
| 238 modifications[parentId] = false; |
| 239 } |
| 240 parentId = nodes[parentId].parentId; |
| 241 } |
| 242 |
| 243 return Object.assign({}, state, modifications); |
| 244 }; |
| 245 |
| 246 /** |
| 247 * @param {ClosedFolderState} state |
| 248 * @param {Action} action |
| 249 * @return {ClosedFolderState} |
| 250 */ |
| 251 ClosedFolderState.toggleFolderOpen = function(state, action) { |
| 252 var id = action.id; |
| 253 var closed = !action.open; |
| 254 var modification = {}; |
| 255 modification[id] = closed; |
| 256 |
| 257 return Object.assign({}, state, modification); |
| 258 }; |
| 259 |
| 260 /** |
| 261 * @param {ClosedFolderState} state |
| 262 * @param {Action} action |
| 263 * @param {NodeList} nodes |
| 264 * @return {ClosedFolderState} |
| 265 */ |
| 266 ClosedFolderState.updateClosedFolders = function(state, action, nodes) { |
| 267 switch (action.name) { |
| 268 case 'change-folder-open': |
| 269 return ClosedFolderState.toggleFolderOpen(state, action); |
| 270 case 'select-folder': |
| 271 return ClosedFolderState.openAncestorsOf(state, action, nodes); |
| 272 default: |
| 273 return state; |
| 274 }; |
| 275 }; |
| 276 |
| 277 /** |
| 278 * Root reducer for the Bookmarks page. This is called by the store in |
| 279 * response to an action, and the return value is used to update the UI. |
| 280 * @param {BookmarksPageState} state |
| 281 * @param {Action} action |
| 282 * @return {BookmarksPageState} |
| 283 */ |
| 284 function reduceAction(state, action) { |
| 285 return { |
| 286 nodes: NodeState.updateNodes(state.nodes, action), |
| 287 selectedFolder: SelectedFolderState.updateSelectedFolder( |
| 288 state.selectedFolder, action, state.nodes), |
| 289 closedFolders: ClosedFolderState.updateClosedFolders( |
| 290 state.closedFolders, action, state.nodes), |
| 291 search: SearchState.updateSearch(state.search, action), |
| 292 selection: SelectionState.updateSelection(state.selection, action), |
| 293 }; |
| 294 } |
| 295 |
| 296 return { |
| 297 reduceAction: reduceAction, |
| 298 ClosedFolderState: ClosedFolderState, |
| 299 NodeState: NodeState, |
| 300 SearchState: SearchState, |
| 301 SelectedFolderState: SelectedFolderState, |
| 302 SelectionState: SelectionState, |
| 303 }; |
| 304 }); |
| OLD | NEW |