Home | History | Annotate | Download | only in sync_internals
      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