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