| Index: chrome/browser/resources/md_bookmarks/reducers.js
|
| diff --git a/chrome/browser/resources/md_bookmarks/reducers.js b/chrome/browser/resources/md_bookmarks/reducers.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..209f9af1ef3daf2013157ba5d9a97ad69c63eed7
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/md_bookmarks/reducers.js
|
| @@ -0,0 +1,304 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview Module of functions which produce a new page state in response
|
| + * to an action. Reducers (in the same sense as Array.prototype.reduce) must be
|
| + * pure functions: they must not modify existing state objects, or make any API
|
| + * calls.
|
| + */
|
| +
|
| +cr.define('bookmarks', function() {
|
| +
|
| + var SelectionState = {};
|
| +
|
| + /**
|
| + * @param {SelectionState} selectionState
|
| + * @param {Action} action
|
| + * @return {SelectionState}
|
| + */
|
| + SelectionState.selectItems = function(selectionState, action) {
|
| + var newState = {};
|
| + if (action.add)
|
| + Object.assign(newState, selectionState.items);
|
| +
|
| + action.items.forEach(function(id) {
|
| + newState[id] = true;
|
| + });
|
| +
|
| + return /** @type {SelectionState} */ (Object.assign({}, selectionState, {
|
| + items: newState,
|
| + anchor: action.anchor,
|
| + }));
|
| + };
|
| +
|
| + /**
|
| + * @param {SelectionState} selectionState
|
| + * @return {SelectionState}
|
| + */
|
| + SelectionState.deselectAll = function(selectionState) {
|
| + return /** @type{SelectionState} */ (Object.assign({}, selectionState, {
|
| + items: {},
|
| + anchor: null,
|
| + }));
|
| + };
|
| +
|
| + /**
|
| + * @param {SelectionState} selection
|
| + * @param {Action} action
|
| + * @return {SelectionState}
|
| + */
|
| + SelectionState.updateSelection = function(selection, action) {
|
| + switch (action.name) {
|
| + case 'select-folder':
|
| + case 'finish-search':
|
| + return SelectionState.deselectAll(selection);
|
| + case 'select-items':
|
| + return SelectionState.selectItems(selection, action);
|
| + }
|
| + return selection;
|
| + };
|
| +
|
| + var SearchState = {};
|
| +
|
| + /**
|
| + * @param {SearchState} search
|
| + * @param {Action} action
|
| + * @return {SearchState}
|
| + */
|
| + SearchState.startSearch = function(search, action) {
|
| + return /** @type {SearchState} */ (Object.assign({}, search, {
|
| + term: action.term,
|
| + inProgress: true,
|
| + }));
|
| + };
|
| +
|
| + /**
|
| + * @param {SearchState} search
|
| + * @param {Action} action
|
| + * @return {SearchState}
|
| + */
|
| + SearchState.finishSearch = function(search, action) {
|
| + return /** @type {SearchState} */ (Object.assign({}, search, {
|
| + inProgress: false,
|
| + results: action.results,
|
| + }));
|
| + };
|
| +
|
| + /** @return {SearchState} */
|
| + SearchState.clearSearch = function() {
|
| + return {
|
| + term: '',
|
| + inProgress: false,
|
| + results: [],
|
| + };
|
| + };
|
| +
|
| + /**
|
| + * @param {SearchState} search
|
| + * @param {Action} action
|
| + * @return {SearchState}
|
| + */
|
| + SearchState.updateSearch = function(search, action) {
|
| + switch (action.name) {
|
| + case 'start-search':
|
| + return SearchState.startSearch(search, action);
|
| + case 'select-folder':
|
| + case 'clear-search':
|
| + return SearchState.clearSearch();
|
| + case 'finish-search':
|
| + return SearchState.finishSearch(search, action);
|
| + default:
|
| + return search;
|
| + }
|
| + };
|
| +
|
| + var NodeState = {};
|
| +
|
| + /**
|
| + * @param {NodeList} nodes
|
| + * @param {string} id
|
| + * @param {function(BookmarkNode):BookmarkNode} callback
|
| + * @return {NodeList}
|
| + */
|
| + NodeState.modifyNode_ = function(nodes, id, callback) {
|
| + var nodeModification = {};
|
| + nodeModification[id] = callback(nodes[id]);
|
| + return Object.assign({}, nodes, nodeModification);
|
| + };
|
| +
|
| + /**
|
| + * @param {NodeList} nodes
|
| + * @param {Action} action
|
| + * @return {NodeList}
|
| + */
|
| + NodeState.editBookmark = function(nodes, action) {
|
| + return NodeState.modifyNode_(nodes, action.id, function(node) {
|
| + return /** @type {BookmarkNode} */ (
|
| + Object.assign({}, node, action.changeInfo));
|
| + });
|
| + };
|
| +
|
| + /**
|
| + * @param {NodeList} nodes
|
| + * @param {Action} action
|
| + * @return {NodeList}
|
| + */
|
| + NodeState.removeBookmark = function(nodes, action) {
|
| + return NodeState.modifyNode_(nodes, action.parentId, function(node) {
|
| + var newChildren = node.children.slice();
|
| + newChildren.splice(action.index, 1);
|
| + return /** @type {BookmarkNode} */ (
|
| + Object.assign({}, node, {children: newChildren}));
|
| + });
|
| + };
|
| +
|
| + /**
|
| + * @param {NodeList} nodes
|
| + * @param {Action} action
|
| + * @return {NodeList}
|
| + */
|
| + NodeState.updateNodes = function(nodes, action) {
|
| + switch (action.name) {
|
| + case 'edit-bookmark':
|
| + return NodeState.editBookmark(nodes, action);
|
| + case 'remove-bookmark':
|
| + return NodeState.removeBookmark(nodes, action);
|
| + case 'refresh-nodes':
|
| + return action.nodes;
|
| + default:
|
| + return nodes;
|
| + }
|
| + };
|
| +
|
| + var SelectedFolderState = {};
|
| +
|
| + /**
|
| + * @param {NodeList} nodes
|
| + * @param {string} ancestorId
|
| + * @param {string} childId
|
| + * @return {boolean}
|
| + */
|
| + SelectedFolderState.isAncestorOf = function(nodes, ancestorId, childId) {
|
| + var currentId = childId;
|
| + // Work upwards through the tree from child.
|
| + while (currentId) {
|
| + if (currentId == ancestorId)
|
| + return true;
|
| + currentId = nodes[currentId].parentId;
|
| + }
|
| + return false;
|
| + };
|
| +
|
| + /**
|
| + * @param {?string} selectedFolder
|
| + * @param {Action} action
|
| + * @param {NodeList} nodes
|
| + * @return {?string}
|
| + */
|
| + SelectedFolderState.updateSelectedFolder = function(
|
| + selectedFolder, action, nodes) {
|
| + // TODO(tsergeant): It should not be possible to select a non-folder.
|
| + switch (action.name) {
|
| + case 'select-folder':
|
| + return action.id;
|
| + case 'change-folder-open':
|
| + // When hiding the selected folder by closing its ancestor, select
|
| + // that ancestor instead.
|
| + if (!action.open && selectedFolder &&
|
| + SelectedFolderState.isAncestorOf(
|
| + nodes, action.id, selectedFolder)) {
|
| + return action.id;
|
| + }
|
| + return selectedFolder;
|
| + case 'finish-search':
|
| + return null;
|
| + case 'clear-search':
|
| + return nodes['0'].children[0];
|
| + default:
|
| + return selectedFolder;
|
| + }
|
| + };
|
| +
|
| + var ClosedFolderState = {};
|
| +
|
| + /**
|
| + * @param {ClosedFolderState} state
|
| + * @param {Action} action
|
| + * @param {NodeList} nodes
|
| + * @return {ClosedFolderState}
|
| + */
|
| + ClosedFolderState.openAncestorsOf = function(state, action, nodes) {
|
| + var id = action.id;
|
| + var modifications = {};
|
| + var parentId = nodes[id].parentId;
|
| + while (parentId) {
|
| + if (state[parentId]) {
|
| + modifications[parentId] = false;
|
| + }
|
| + parentId = nodes[parentId].parentId;
|
| + }
|
| +
|
| + return Object.assign({}, state, modifications);
|
| + };
|
| +
|
| + /**
|
| + * @param {ClosedFolderState} state
|
| + * @param {Action} action
|
| + * @return {ClosedFolderState}
|
| + */
|
| + ClosedFolderState.toggleFolderOpen = function(state, action) {
|
| + var id = action.id;
|
| + var closed = !action.open;
|
| + var modification = {};
|
| + modification[id] = closed;
|
| +
|
| + return Object.assign({}, state, modification);
|
| + };
|
| +
|
| + /**
|
| + * @param {ClosedFolderState} state
|
| + * @param {Action} action
|
| + * @param {NodeList} nodes
|
| + * @return {ClosedFolderState}
|
| + */
|
| + ClosedFolderState.updateClosedFolders = function(state, action, nodes) {
|
| + switch (action.name) {
|
| + case 'change-folder-open':
|
| + return ClosedFolderState.toggleFolderOpen(state, action);
|
| + case 'select-folder':
|
| + return ClosedFolderState.openAncestorsOf(state, action, nodes);
|
| + default:
|
| + return state;
|
| + };
|
| + };
|
| +
|
| + /**
|
| + * Root reducer for the Bookmarks page. This is called by the store in
|
| + * response to an action, and the return value is used to update the UI.
|
| + * @param {BookmarksPageState} state
|
| + * @param {Action} action
|
| + * @return {BookmarksPageState}
|
| + */
|
| + function reduceAction(state, action) {
|
| + return {
|
| + nodes: NodeState.updateNodes(state.nodes, action),
|
| + selectedFolder: SelectedFolderState.updateSelectedFolder(
|
| + state.selectedFolder, action, state.nodes),
|
| + closedFolders: ClosedFolderState.updateClosedFolders(
|
| + state.closedFolders, action, state.nodes),
|
| + search: SearchState.updateSearch(state.search, action),
|
| + selection: SelectionState.updateSelection(state.selection, action),
|
| + };
|
| + }
|
| +
|
| + return {
|
| + reduceAction: reduceAction,
|
| + ClosedFolderState: ClosedFolderState,
|
| + NodeState: NodeState,
|
| + SearchState: SearchState,
|
| + SelectedFolderState: SelectedFolderState,
|
| + SelectionState: SelectionState,
|
| + };
|
| +});
|
|
|