Chromium Code Reviews| Index: chrome/browser/resources/sync_internals/sync_node_browser.js |
| diff --git a/chrome/browser/resources/sync_internals/sync_node_browser.js b/chrome/browser/resources/sync_internals/sync_node_browser.js |
| index 8d1ff3d1be0fd28b86ca19bc968981ae5aae94e9..e56790593fa88033abc5505b47ed0d2d8c7d077e 100644 |
| --- a/chrome/browser/resources/sync_internals/sync_node_browser.js |
| +++ b/chrome/browser/resources/sync_internals/sync_node_browser.js |
| @@ -6,52 +6,80 @@ |
| // require: cr/ui.js |
| // require: cr/ui/tree.js |
| -cr.define('chrome.sync', function() { |
| +(function() { |
| /** |
| - * Gets all children of the given node and passes it to the given |
| - * callback. |
| - * @param {string} id The id whose children we want. |
| - * @param {function(Array.<!Object>)} callback The callback to call |
| - * with the list of children summaries. |
| + * A helper function to determine if a node is the root of its type. |
| + * |
| + * @param {!Object} node The node to check. |
| */ |
| - function getSyncNodeChildrenSummaries(id, callback) { |
| - var timer = chrome.sync.makeTimer(); |
| - chrome.sync.getChildNodeIds(id, function(childNodeIds) { |
| - console.debug('getChildNodeIds took ' + |
| - timer.elapsedSeconds + 's to retrieve ' + |
| - childNodeIds.length + ' ids'); |
| - timer = chrome.sync.makeTimer(); |
| - chrome.sync.getNodeSummariesById( |
| - childNodeIds, function(childrenSummaries) { |
| - console.debug('getNodeSummariesById took ' + |
| - timer.elapsedSeconds + 's to retrieve summaries for ' + |
| - childrenSummaries.length + ' nodes'); |
| - callback(childrenSummaries); |
| - }); |
| - }); |
| + var isTypeRootNode = function(node) { |
| + return node.PARENT_ID == 'r' && node.UNIQUE_SERVER_TAG != ''; |
| + } |
| + |
| + /** |
| + * A helper function to determine if a node is a child of the given parent. |
| + * |
| + * @param {string} parentId The ID of the parent. |
| + * @param {!Object} node The node to check. |
| + */ |
| + var isChildOf = function(parentId, node) { |
| + return node.PARENT_ID == parentId; |
| + } |
| + |
| + /** |
| + * A helper function to sort sync nodes. |
| + * |
| + * Sorts by position index if possible, falls back to sorting by name, and |
| + * finally sorting by METAHANDLE |
|
Nicolas Zea
2014/01/13 20:10:14
Can the sorting not be done in the C++ code by hav
rlarocque
2014/01/13 21:56:17
That could work. I can see two reasons in favor o
Nicolas Zea
2014/01/14 22:14:33
Maybe just add a note that we can consider sorting
rlarocque
2014/01/14 22:20:43
Done.
|
| + */ |
| + var nodeComparator = function(nodeA, nodeB) { |
| + if (nodeA.hasOwnProperty('positionIndex') && |
| + nodeB.hasOwnProperty('positionIndex')) { |
| + return nodeA.positionIndex - nodeB.positionIndex; |
| + } else if (nodeA.NON_UNIQUE_NAME != nodeB.NON_UNIQUE_NAME) { |
| + return nodeA.NON_UNIQUE_NAME.localeCompare(nodeB.NON_UNIQUE_NAME); |
| + } else { |
| + return nodeA.METAHANDLE - nodeB.METAHANDLE; |
| + } |
| + } |
| + |
| + /** |
| + * Updates the node detail view with the details for the given node. |
| + * @param {!Object} node The struct representing the node we want to display. |
| + */ |
| + function updateNodeDetailView(node) { |
| + var nodeDetailsView = $('node-details'); |
| + nodeDetailsView.hidden = false; |
| + jstProcess(new JsEvalContext(node.entry_), nodeDetailsView); |
| + } |
| + |
| + /** |
| + * Updates the 'Last refresh time' display. |
| + * @param {string} The text to display. |
| + */ |
| + function setLastRefreshTime(str) { |
| + $('node-browser-refresh-time').textContent = str; |
| } |
| /** |
| * Creates a new sync node tree item. |
| - * @param {{id: string, title: string, isFolder: boolean}} |
| - * nodeSummary The nodeSummary object for the node (as returned |
| - * by chrome.sync.getNodeSummariesById()). |
| + * |
| * @constructor |
| + * @param {!Object} node The nodeDetails object for the node as returned by |
| + * chrome.sync.getAllNodes(). |
| * @extends {cr.ui.TreeItem} |
| */ |
| - var SyncNodeTreeItem = function(nodeSummary) { |
| - var treeItem = new cr.ui.TreeItem({ |
| - id_: nodeSummary.id |
| - }); |
| + var SyncNodeTreeItem = function(node) { |
| + var treeItem = new cr.ui.TreeItem(); |
| treeItem.__proto__ = SyncNodeTreeItem.prototype; |
| - treeItem.label = nodeSummary.title; |
| - if (nodeSummary.isFolder) { |
| + treeItem.entry_ = node; |
| + treeItem.label = node.NON_UNIQUE_NAME; |
| + if (node.IS_DIR) { |
| treeItem.mayHaveChildren_ = true; |
| - // Load children asynchronously on expand. |
| - // TODO(akalin): Add a throbber while loading? |
| - treeItem.triggeredLoad_ = false; |
| + // Load children on expand. |
| + treeItem.expanded_ = false; |
| treeItem.addEventListener('expand', |
| treeItem.handleExpand_.bind(treeItem)); |
| } else { |
| @@ -64,55 +92,31 @@ cr.define('chrome.sync', function() { |
| __proto__: cr.ui.TreeItem.prototype, |
| /** |
| - * Retrieves the details for this node. |
| - * @param {function(Object)} callback The callback that will be |
| - * called with the node details, or null if it could not be |
| - * retrieved. |
| + * Finds the children of this node and appends them to the tree. |
| */ |
| - getDetails: function(callback) { |
| - chrome.sync.getNodeDetailsById([this.id_], function(nodeDetails) { |
| - callback(nodeDetails[0] || null); |
| - }); |
| - }, |
| - |
| handleExpand_: function(event) { |
| - if (!this.triggeredLoad_) { |
| - getSyncNodeChildrenSummaries(this.id_, this.addChildNodes_.bind(this)); |
| - this.triggeredLoad_ = true; |
| - } |
| - }, |
| + var treeItem = this; |
| - /** |
| - * Adds children from the list of children summaries. |
| - * @param {Array.<{id: string, title: string, isFolder: boolean}>} |
| - * childrenSummaries The list of children summaries with which |
| - * to create the child nodes. |
| - */ |
| - addChildNodes_: function(childrenSummaries) { |
| - var timer = chrome.sync.makeTimer(); |
| - for (var i = 0; i < childrenSummaries.length; ++i) { |
| - var childTreeItem = new SyncNodeTreeItem(childrenSummaries[i]); |
| - this.add(childTreeItem); |
| + if (treeItem.expanded_) { |
| + return; |
| } |
| - console.debug('adding ' + childrenSummaries.length + |
| - ' children took ' + timer.elapsedSeconds + 's'); |
| - } |
| - }; |
| + treeItem.expanded_ = true; |
| - /** |
| - * Updates the node detail view with the details for the given node. |
| - * @param {!Object} nodeDetails The details for the node we want |
| - * to display. |
| - */ |
| - function updateNodeDetailView(nodeDetails) { |
| - var nodeBrowser = document.getElementById('node-browser'); |
| - // TODO(akalin): Write a nicer detail viewer. |
| - nodeDetails.entry = JSON.stringify(nodeDetails.entry, null, 2); |
| - jstProcess(new JsEvalContext(nodeDetails), nodeBrowser); |
| - } |
| + var children = treeItem.tree.allNodes.filter( |
| + isChildOf.bind(undefined, treeItem.entry_.ID)); |
| + children.sort(nodeComparator); |
| + |
| + children.forEach(function(node) { |
| + treeItem.add(new SyncNodeTreeItem(node)); |
| + }); |
| + }, |
| + }; |
| /** |
| - * Creates a new sync node tree. |
| + * Creates a new sync node tree. Technically, it's a forest since it each |
| + * type has its own root node for its own tree, but it still looks and acts |
| + * mostly like a tree. |
| + * |
| * @param {Object=} opt_propertyBag Optional properties. |
| * @constructor |
| * @extends {cr.ui.Tree} |
| @@ -125,57 +129,78 @@ cr.define('chrome.sync', function() { |
| decorate: function() { |
| cr.ui.Tree.prototype.decorate.call(this); |
| this.addEventListener('change', this.handleChange_.bind(this)); |
| - chrome.sync.getRootNodeDetails(this.makeRoot_.bind(this)); |
| + this.allNodes = []; |
| }, |
| - /** |
| - * Creates the root of the tree. |
| - * @param {{id: string, title: string, isFolder: boolean}} |
| - * rootNodeSummary The summary info for the root node. |
| - */ |
| - makeRoot_: function(rootNodeSummary) { |
| - // The root node usually doesn't have a title. |
| - rootNodeSummary.title = rootNodeSummary.title || 'Root'; |
| - var rootTreeItem = new SyncNodeTreeItem(rootNodeSummary); |
| - this.add(rootTreeItem); |
| + populate: function(nodes) { |
| + var tree = this; |
| + |
| + // We store the full set of nodes in the SyncNodeTree object. |
| + tree.allNodes = nodes; |
| + |
| + var roots = tree.allNodes.filter(isTypeRootNode); |
| + roots.sort(nodeComparator); |
| + |
| + roots.forEach(function(typeRoot) { |
| + tree.add(new SyncNodeTreeItem(typeRoot)); |
| + }); |
| }, |
| handleChange_: function(event) { |
| if (this.selectedItem) { |
| - this.selectedItem.getDetails(updateNodeDetailView); |
| + updateNodeDetailView(this.selectedItem); |
| } |
| } |
| }; |
| - function decorateSyncNodeBrowser(syncNodeBrowser) { |
| - cr.ui.decorate(syncNodeBrowser, SyncNodeTree); |
| + /** |
| + * Clears any existing UI state. Useful prior to a refresh. |
| + */ |
| + function clear() { |
| + var treeContainer = $('sync-node-tree-container'); |
| + while (treeContainer.firstChild) { |
| + treeContainer.removeChild(treeContainer.firstChild); |
| + } |
| + |
| + var nodeDetailsView = $('node-details'); |
| + nodeDetailsView.hidden = true; |
| } |
| - // This is needed because JsTemplate (which is needed by |
| - // updateNodeDetailView) is loaded at the end of the file after |
| - // everything else. |
| - // |
| - // TODO(akalin): Remove dependency on JsTemplate and get rid of |
| - // this. |
| - var domLoaded = false; |
| - var pendingSyncNodeBrowsers = []; |
| - function decorateSyncNodeBrowserAfterDOMLoad(id) { |
| - var e = document.getElementById(id); |
| - if (domLoaded) { |
| - decorateSyncNodeBrowser(e); |
| - } else { |
| - pendingSyncNodeBrowsers.push(e); |
| - } |
| + /** |
| + * Fetch the latest set of nodes and refresh the UI. |
| + */ |
| + function refresh() { |
| + $('node-browser-refresh-button').disabled = true; |
| + |
| + clear(); |
| + setLastRefreshTime('In progress since ' + (new Date()).toLocaleString()); |
| + |
| + chrome.sync.getAllNodes(function(nodes) { |
| + var treeContainer = $('sync-node-tree-container'); |
| + var tree = document.createElement('tree'); |
| + tree.setAttribute('id', 'sync-node-tree'); |
| + tree.setAttribute('icon-visibility', 'parent'); |
| + treeContainer.appendChild(tree); |
| + |
| + cr.ui.decorate(tree, SyncNodeTree); |
| + tree.populate(nodes); |
| + |
| + setLastRefreshTime((new Date()).toLocaleString()); |
| + $('node-browser-refresh-button').disabled = false; |
| + }); |
| } |
| - document.addEventListener('DOMContentLoaded', function() { |
| - for (var i = 0; i < pendingSyncNodeBrowsers.length; ++i) { |
| - decorateSyncNodeBrowser(pendingSyncNodeBrowsers[i]); |
| - } |
| - domLoaded = true; |
| + document.addEventListener('DOMContentLoaded', function(e) { |
| + $('node-browser-refresh-button').addEventListener('click', refresh); |
| + cr.ui.decorate('#sync-node-splitter', cr.ui.Splitter); |
| + |
| + // Automatically trigger a refresh the first time this tab is selected. |
| + $('sync-browser-tab').addEventListener('selectedChange', function f(e) { |
| + if (this.selected) { |
| + $('sync-browser-tab').removeEventListener('selectedChange', f); |
| + refresh(); |
| + } |
| + }); |
| }); |
| - return { |
| - decorateSyncNodeBrowser: decorateSyncNodeBrowserAfterDOMLoad |
| - }; |
| -}); |
| +})(); |