blob: 369916457a58562f77250e86c7055e9e0174be87 [file] [log] [blame]
// 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();
}
});
});
})();