| // Copyright (c) 2011 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. |
| |
| // require: cr.js |
| // require: cr/ui.js |
| // require: cr/ui/tree.js |
| |
| (function() { |
| /** |
| * A helper function to determine if a node is the root of its type. |
| * |
| * @param {!Object} node The node to check. |
| */ |
| 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. |
| * |
| * @constructor |
| * @param {!Object} node The nodeDetails object for the node as returned by |
| * chrome.sync.getAllNodes(). |
| * @extends {cr.ui.TreeItem} |
| */ |
| var SyncNodeTreeItem = function(node) { |
| var treeItem = new cr.ui.TreeItem(); |
| treeItem.__proto__ = SyncNodeTreeItem.prototype; |
| |
| treeItem.entry_ = node; |
| treeItem.label = node.NON_UNIQUE_NAME; |
| if (node.IS_DIR) { |
| treeItem.mayHaveChildren_ = true; |
| |
| // Load children on expand. |
| treeItem.expanded_ = false; |
| treeItem.addEventListener('expand', |
| treeItem.handleExpand_.bind(treeItem)); |
| } else { |
| treeItem.classList.add('leaf'); |
| } |
| return treeItem; |
| }; |
| |
| SyncNodeTreeItem.prototype = { |
| __proto__: cr.ui.TreeItem.prototype, |
| |
| /** |
| * Finds the children of this node and appends them to the tree. |
| */ |
| handleExpand_: function(event) { |
| var treeItem = this; |
| |
| if (treeItem.expanded_) { |
| return; |
| } |
| treeItem.expanded_ = true; |
| |
| 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. 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} |
| */ |
| var SyncNodeTree = cr.ui.define('tree'); |
| |
| SyncNodeTree.prototype = { |
| __proto__: cr.ui.Tree.prototype, |
| |
| decorate: function() { |
| cr.ui.Tree.prototype.decorate.call(this); |
| this.addEventListener('change', this.handleChange_.bind(this)); |
| this.allNodes = []; |
| }, |
| |
| 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) { |
| updateNodeDetailView(this.selectedItem); |
| } |
| } |
| }; |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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(nodeMap) { |
| // Put all nodes into one big list that ignores the type. |
| var nodes = nodeMap. |
| map(function(x) { return x.nodes; }). |
| reduce(function(a, b) { return a.concat(b); }); |
| |
| 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(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(); |
| } |
| }); |
| }); |
| |
| })(); |