1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// require: cr.js 6// require: cr/ui.js 7// require: cr/ui/tree.js 8 9(function() { 10 /** 11 * A helper function to determine if a node is the root of its type. 12 * 13 * @param {!Object} node The node to check. 14 */ 15 var isTypeRootNode = function(node) { 16 return node.PARENT_ID == 'r' && node.UNIQUE_SERVER_TAG != ''; 17 } 18 19 /** 20 * A helper function to determine if a node is a child of the given parent. 21 * 22 * @param {string} parentId The ID of the parent. 23 * @param {!Object} node The node to check. 24 */ 25 var isChildOf = function(parentId, node) { 26 return node.PARENT_ID == parentId; 27 } 28 29 /** 30 * A helper function to sort sync nodes. 31 * 32 * Sorts by position index if possible, falls back to sorting by name, and 33 * finally sorting by METAHANDLE. 34 * 35 * If this proves to be slow and expensive, we should experiment with moving 36 * this functionality to C++ instead. 37 */ 38 var nodeComparator = function(nodeA, nodeB) { 39 if (nodeA.hasOwnProperty('positionIndex') && 40 nodeB.hasOwnProperty('positionIndex')) { 41 return nodeA.positionIndex - nodeB.positionIndex; 42 } else if (nodeA.NON_UNIQUE_NAME != nodeB.NON_UNIQUE_NAME) { 43 return nodeA.NON_UNIQUE_NAME.localeCompare(nodeB.NON_UNIQUE_NAME); 44 } else { 45 return nodeA.METAHANDLE - nodeB.METAHANDLE; 46 } 47 } 48 49 /** 50 * Updates the node detail view with the details for the given node. 51 * @param {!Object} node The struct representing the node we want to display. 52 */ 53 function updateNodeDetailView(node) { 54 var nodeDetailsView = $('node-details'); 55 nodeDetailsView.hidden = false; 56 jstProcess(new JsEvalContext(node.entry_), nodeDetailsView); 57 } 58 59 /** 60 * Updates the 'Last refresh time' display. 61 * @param {string} The text to display. 62 */ 63 function setLastRefreshTime(str) { 64 $('node-browser-refresh-time').textContent = str; 65 } 66 67 /** 68 * Creates a new sync node tree item. 69 * 70 * @constructor 71 * @param {!Object} node The nodeDetails object for the node as returned by 72 * chrome.sync.getAllNodes(). 73 * @extends {cr.ui.TreeItem} 74 */ 75 var SyncNodeTreeItem = function(node) { 76 var treeItem = new cr.ui.TreeItem(); 77 treeItem.__proto__ = SyncNodeTreeItem.prototype; 78 79 treeItem.entry_ = node; 80 treeItem.label = node.NON_UNIQUE_NAME; 81 if (node.IS_DIR) { 82 treeItem.mayHaveChildren_ = true; 83 84 // Load children on expand. 85 treeItem.expanded_ = false; 86 treeItem.addEventListener('expand', 87 treeItem.handleExpand_.bind(treeItem)); 88 } else { 89 treeItem.classList.add('leaf'); 90 } 91 return treeItem; 92 }; 93 94 SyncNodeTreeItem.prototype = { 95 __proto__: cr.ui.TreeItem.prototype, 96 97 /** 98 * Finds the children of this node and appends them to the tree. 99 */ 100 handleExpand_: function(event) { 101 var treeItem = this; 102 103 if (treeItem.expanded_) { 104 return; 105 } 106 treeItem.expanded_ = true; 107 108 var children = treeItem.tree.allNodes.filter( 109 isChildOf.bind(undefined, treeItem.entry_.ID)); 110 children.sort(nodeComparator); 111 112 children.forEach(function(node) { 113 treeItem.add(new SyncNodeTreeItem(node)); 114 }); 115 }, 116 }; 117 118 /** 119 * Creates a new sync node tree. Technically, it's a forest since it each 120 * type has its own root node for its own tree, but it still looks and acts 121 * mostly like a tree. 122 * 123 * @param {Object=} opt_propertyBag Optional properties. 124 * @constructor 125 * @extends {cr.ui.Tree} 126 */ 127 var SyncNodeTree = cr.ui.define('tree'); 128 129 SyncNodeTree.prototype = { 130 __proto__: cr.ui.Tree.prototype, 131 132 decorate: function() { 133 cr.ui.Tree.prototype.decorate.call(this); 134 this.addEventListener('change', this.handleChange_.bind(this)); 135 this.allNodes = []; 136 }, 137 138 populate: function(nodes) { 139 var tree = this; 140 141 // We store the full set of nodes in the SyncNodeTree object. 142 tree.allNodes = nodes; 143 144 var roots = tree.allNodes.filter(isTypeRootNode); 145 roots.sort(nodeComparator); 146 147 roots.forEach(function(typeRoot) { 148 tree.add(new SyncNodeTreeItem(typeRoot)); 149 }); 150 }, 151 152 handleChange_: function(event) { 153 if (this.selectedItem) { 154 updateNodeDetailView(this.selectedItem); 155 } 156 } 157 }; 158 159 /** 160 * Clears any existing UI state. Useful prior to a refresh. 161 */ 162 function clear() { 163 var treeContainer = $('sync-node-tree-container'); 164 while (treeContainer.firstChild) { 165 treeContainer.removeChild(treeContainer.firstChild); 166 } 167 168 var nodeDetailsView = $('node-details'); 169 nodeDetailsView.hidden = true; 170 } 171 172 /** 173 * Fetch the latest set of nodes and refresh the UI. 174 */ 175 function refresh() { 176 $('node-browser-refresh-button').disabled = true; 177 178 clear(); 179 setLastRefreshTime('In progress since ' + (new Date()).toLocaleString()); 180 181 chrome.sync.getAllNodes(function(nodeMap) { 182 // Put all nodes into one big list that ignores the type. 183 var nodes = nodeMap. 184 map(function(x) { return x.nodes; }). 185 reduce(function(a, b) { return a.concat(b); }); 186 187 var treeContainer = $('sync-node-tree-container'); 188 var tree = document.createElement('tree'); 189 tree.setAttribute('id', 'sync-node-tree'); 190 tree.setAttribute('icon-visibility', 'parent'); 191 treeContainer.appendChild(tree); 192 193 cr.ui.decorate(tree, SyncNodeTree); 194 tree.populate(nodes); 195 196 setLastRefreshTime((new Date()).toLocaleString()); 197 $('node-browser-refresh-button').disabled = false; 198 }); 199 } 200 201 document.addEventListener('DOMContentLoaded', function(e) { 202 $('node-browser-refresh-button').addEventListener('click', refresh); 203 cr.ui.decorate('#sync-node-splitter', cr.ui.Splitter); 204 205 // Automatically trigger a refresh the first time this tab is selected. 206 $('sync-browser-tab').addEventListener('selectedChange', function f(e) { 207 if (this.selected) { 208 $('sync-browser-tab').removeEventListener('selectedChange', f); 209 refresh(); 210 } 211 }); 212 }); 213 214})(); 215