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 |