| 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..d20b864e4ec9513c2b37d6ff042e952d2b931ac2 100644
|
| --- a/chrome/browser/resources/sync_internals/sync_node_browser.js
|
| +++ b/chrome/browser/resources/sync_internals/sync_node_browser.js
|
| @@ -6,52 +6,83 @@
|
| // 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.
|
| + *
|
| + * If this proves to be slow and expensive, we should experiment with moving
|
| + * this functionality to C++ instead.
|
| + */
|
| + 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 +95,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 +132,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
|
| - };
|
| -});
|
| +})();
|
|
|