| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview Module of functions which produce a new page state in response | 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 | 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 | 8 * pure functions: they must not modify existing state objects, or make any API |
| 9 * calls. | 9 * calls. |
| 10 */ | 10 */ |
| 11 | 11 |
| 12 cr.define('bookmarks', function() { | 12 cr.define('bookmarks', function() { |
| 13 var NodeState = {}; |
| 14 |
| 15 /** |
| 16 * @param {NodeList} nodes |
| 17 * @param {string} id |
| 18 * @param {function(BookmarkNode):BookmarkNode} callback |
| 19 * @return {NodeList} |
| 20 */ |
| 21 NodeState.modifyNode_ = function(nodes, id, callback) { |
| 22 var nodeModification = {}; |
| 23 nodeModification[id] = callback(nodes[id]); |
| 24 return Object.assign({}, nodes, nodeModification); |
| 25 }; |
| 26 |
| 27 /** |
| 28 * @param {NodeList} nodes |
| 29 * @param {Action} action |
| 30 * @return {NodeList} |
| 31 */ |
| 32 NodeState.editBookmark = function(nodes, action) { |
| 33 // Do not allow folders to change URL (making them no longer folders). |
| 34 if (!nodes[action.id].url && action.changeInfo.url) |
| 35 delete action.changeInfo.url; |
| 36 |
| 37 return NodeState.modifyNode_(nodes, action.id, function(node) { |
| 38 return /** @type {BookmarkNode} */ ( |
| 39 Object.assign({}, node, action.changeInfo)); |
| 40 }); |
| 41 }; |
| 42 |
| 43 /** |
| 44 * @param {NodeList} nodes |
| 45 * @param {Action} action |
| 46 * @return {NodeList} |
| 47 */ |
| 48 NodeState.removeBookmark = function(nodes, action) { |
| 49 return NodeState.modifyNode_(nodes, action.parentId, function(node) { |
| 50 var newChildren = node.children.slice(); |
| 51 newChildren.splice(action.index, 1); |
| 52 return /** @type {BookmarkNode} */ ( |
| 53 Object.assign({}, node, {children: newChildren})); |
| 54 }); |
| 55 }; |
| 56 |
| 57 /** |
| 58 * @param {NodeList} nodes |
| 59 * @param {Action} action |
| 60 * @return {NodeList} |
| 61 */ |
| 62 NodeState.updateNodes = function(nodes, action) { |
| 63 switch (action.name) { |
| 64 case 'edit-bookmark': |
| 65 return NodeState.editBookmark(nodes, action); |
| 66 case 'remove-bookmark': |
| 67 return NodeState.removeBookmark(nodes, action); |
| 68 case 'refresh-nodes': |
| 69 return action.nodes; |
| 70 default: |
| 71 return nodes; |
| 72 } |
| 73 }; |
| 74 |
| 75 var SelectedFolderState = {}; |
| 76 |
| 77 /** |
| 78 * @param {NodeList} nodes |
| 79 * @param {string} ancestorId |
| 80 * @param {string} childId |
| 81 * @return {boolean} |
| 82 */ |
| 83 SelectedFolderState.isAncestorOf = function(nodes, ancestorId, childId) { |
| 84 var currentId = childId; |
| 85 // Work upwards through the tree from child. |
| 86 while (currentId) { |
| 87 if (currentId == ancestorId) |
| 88 return true; |
| 89 currentId = nodes[currentId].parentId; |
| 90 } |
| 91 return false; |
| 92 }; |
| 93 |
| 94 /** |
| 95 * @param {?string} selectedFolder |
| 96 * @param {Action} action |
| 97 * @param {NodeList} nodes |
| 98 * @return {?string} |
| 99 */ |
| 100 SelectedFolderState.updateSelectedFolder = function( |
| 101 selectedFolder, action, nodes) { |
| 102 // TODO(tsergeant): It should not be possible to select a non-folder. |
| 103 switch (action.name) { |
| 104 case 'select-folder': |
| 105 return action.id; |
| 106 case 'change-folder-open': |
| 107 // When hiding the selected folder by closing its ancestor, select |
| 108 // that ancestor instead. |
| 109 if (!action.open && selectedFolder && |
| 110 SelectedFolderState.isAncestorOf( |
| 111 nodes, action.id, selectedFolder)) { |
| 112 return action.id; |
| 113 } |
| 114 return selectedFolder; |
| 115 default: |
| 116 return selectedFolder; |
| 117 } |
| 118 }; |
| 119 |
| 120 var ClosedFolderState = {}; |
| 121 |
| 122 /** |
| 123 * @param {ClosedFolderState} state |
| 124 * @param {Action} action |
| 125 * @param {NodeList} nodes |
| 126 * @return {ClosedFolderState} |
| 127 */ |
| 128 ClosedFolderState.openAncestorsOf = function(state, action, nodes) { |
| 129 var id = action.id; |
| 130 var modifications = {}; |
| 131 var parentId = nodes[id].parentId; |
| 132 while (parentId) { |
| 133 if (state[parentId]) { |
| 134 modifications[parentId] = false; |
| 135 } |
| 136 parentId = nodes[parentId].parentId; |
| 137 } |
| 138 |
| 139 return Object.assign({}, state, modifications); |
| 140 }; |
| 141 |
| 142 /** |
| 143 * @param {ClosedFolderState} state |
| 144 * @param {Action} action |
| 145 * @return {ClosedFolderState} |
| 146 */ |
| 147 ClosedFolderState.changeFolderOpen = function(state, action) { |
| 148 var closed = !action.open; |
| 149 var modification = {}; |
| 150 modification[action.id] = closed; |
| 151 |
| 152 return Object.assign({}, state, modification); |
| 153 }; |
| 154 |
| 155 /** |
| 156 * @param {ClosedFolderState} state |
| 157 * @param {Action} action |
| 158 * @param {NodeList} nodes |
| 159 * @return {ClosedFolderState} |
| 160 */ |
| 161 ClosedFolderState.updateClosedFolders = function(state, action, nodes) { |
| 162 switch (action.name) { |
| 163 case 'change-folder-open': |
| 164 return ClosedFolderState.changeFolderOpen(state, action); |
| 165 case 'select-folder': |
| 166 return ClosedFolderState.openAncestorsOf(state, action, nodes); |
| 167 default: |
| 168 return state; |
| 169 }; |
| 170 }; |
| 171 |
| 13 /** | 172 /** |
| 14 * Root reducer for the Bookmarks page. This is called by the store in | 173 * Root reducer for the Bookmarks page. This is called by the store in |
| 15 * response to an action, and the return value is used to update the UI. | 174 * response to an action, and the return value is used to update the UI. |
| 16 * @param {!BookmarksPageState} state | 175 * @param {!BookmarksPageState} state |
| 17 * @param {Action} action | 176 * @param {Action} action |
| 18 * @return {!BookmarksPageState} | 177 * @return {!BookmarksPageState} |
| 19 */ | 178 */ |
| 20 function reduceAction(state, action) { | 179 function reduceAction(state, action) { |
| 21 return {}; | 180 return { |
| 181 nodes: NodeState.updateNodes(state.nodes, action), |
| 182 selectedFolder: SelectedFolderState.updateSelectedFolder( |
| 183 state.selectedFolder, action, state.nodes), |
| 184 closedFolders: ClosedFolderState.updateClosedFolders( |
| 185 state.closedFolders, action, state.nodes), |
| 186 }; |
| 22 } | 187 } |
| 23 | 188 |
| 24 return { | 189 return { |
| 25 reduceAction: reduceAction, | 190 reduceAction: reduceAction, |
| 191 ClosedFolderState: ClosedFolderState, |
| 192 NodeState: NodeState, |
| 193 SelectedFolderState: SelectedFolderState, |
| 26 }; | 194 }; |
| 27 }); | 195 }); |
| OLD | NEW |