Home | History | Annotate | Download | only in ntp4
      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 cr.define('ntp4', function() {
      6   'use strict';
      7 
      8   var TilePage = ntp4.TilePage;
      9 
     10   /**
     11    * Creates a new Most Visited object for tiling.
     12    * @constructor
     13    * @extends {HTMLAnchorElement}
     14    */
     15   function MostVisited() {
     16     var el = cr.doc.createElement('a');
     17     el.__proto__ = MostVisited.prototype;
     18     el.initialize();
     19 
     20     return el;
     21   }
     22 
     23   MostVisited.prototype = {
     24     __proto__: HTMLAnchorElement.prototype,
     25 
     26     initialize: function() {
     27       this.reset();
     28 
     29       this.addEventListener('click', this.handleClick_.bind(this));
     30       this.addEventListener('keydown', this.handleKeyDown_.bind(this));
     31     },
     32 
     33     /**
     34      * Clears the DOM hierarchy for this node, setting it back to the default
     35      * for a blank thumbnail.
     36      */
     37     reset: function() {
     38       this.className = 'most-visited filler';
     39       // TODO(estade): why do we need edit-mode-border?
     40       this.innerHTML =
     41           '<div class="edit-mode-border fills-parent">' +
     42             '<div class="edit-bar-wrapper">' +
     43               '<div class="edit-bar">' +
     44                 '<div class="pin"></div>' +
     45                 '<div class="spacer"></div>' +
     46                 '<div class="remove"></div>' +
     47               '</div>' +
     48             '</div>' +
     49             '<span class="thumbnail-wrapper fills-parent">' +
     50               '<span class="thumbnail fills-parent">' +
     51                 // thumbnail-shield provides a gradient fade effect.
     52                 '<div class="thumbnail-shield fills-parent"></div>' +
     53               '</span>' +
     54               '<span class="title"></span>' +
     55             '</span>' +
     56           '</div>';
     57 
     58       this.tabIndex = -1;
     59     },
     60 
     61     /**
     62      * Update the appearance of this tile according to |data|.
     63      * @param {Object} data A dictionary of relevant data for the page.
     64      */
     65     updateForData: function(data) {
     66       if (data.filler) {
     67         this.reset();
     68         return;
     69       }
     70 
     71       this.data_ = data;
     72       this.tabIndex = 0;
     73       this.classList.remove('filler');
     74 
     75       var title = this.querySelector('.title');
     76       title.textContent = data.title;
     77       var faviconUrl = data.faviconUrl || 'chrome://favicon/' + data.url;
     78       title.style.backgroundImage = url(faviconUrl);
     79       title.dir = data.direction;
     80 
     81       var thumbnailUrl = data.thumbnailUrl || 'chrome://thumb/' + data.url;
     82       this.querySelector('.thumbnail').style.backgroundImage =
     83           url(thumbnailUrl);
     84 
     85       this.href = data.url;
     86 
     87       this.updatePinnedState_();
     88     },
     89 
     90     /**
     91      * Handles a click on the tile.
     92      * @param {Event} e The click event.
     93      */
     94     handleClick_: function(e) {
     95       var target = e.target;
     96       if (target.classList.contains('pin')) {
     97         this.togglePinned_();
     98         e.preventDefault();
     99       } else if (target.classList.contains('remove')) {
    100         this.blacklist_();
    101         e.preventDefault();
    102       } else {
    103         var index = Array.prototype.indexOf.call(this.parentNode.children,
    104                                                  this);
    105         if (index != -1)
    106           chrome.send('metrics', ['NTP_MostVisited' + index]);
    107       }
    108     },
    109 
    110     /**
    111      * Allow blacklisting most visited site using the keyboard.
    112      */
    113     handleKeyDown_: function(e) {
    114       if (!IS_MAC && e.keyCode == 46 || // Del
    115           IS_MAC && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
    116         this.blacklist_();
    117       }
    118     },
    119 
    120     /**
    121      * Changes the visual state of the page and updates the model.
    122      */
    123     togglePinned_: function() {
    124       var data = this.data_;
    125       data.pinned = !data.pinned;
    126       if (data.pinned) {
    127         chrome.send('addPinnedURL', [
    128           data.url,
    129           data.title,
    130           data.faviconUrl || '',
    131           data.thumbnailUrl || '',
    132           // TODO(estade): should not need to convert index to string.
    133           String(data.index)
    134         ]);
    135       } else {
    136         chrome.send('removePinnedURL', [data.url]);
    137       }
    138 
    139       this.updatePinnedState_();
    140     },
    141 
    142     /**
    143      * Updates the DOM for the current pinned state.
    144      */
    145     updatePinnedState_: function() {
    146       if (this.data_.pinned) {
    147         this.classList.add('pinned');
    148         this.querySelector('.pin').title = templateData.unpinthumbnailtooltip;
    149       } else {
    150         this.classList.remove('pinned');
    151         this.querySelector('.pin').title = templateData.pinthumbnailtooltip;
    152       }
    153     },
    154 
    155     /**
    156      * Permanently removes a page from Most Visited.
    157      */
    158     blacklist_: function() {
    159       chrome.send('blacklistURLFromMostVisited', [url]);
    160       this.reset();
    161       // TODO(estade): request a replacement site.
    162     },
    163 
    164     /**
    165      * Set the size and position of the most visited tile.
    166      * @param {number} size The total size of |this|.
    167      * @param {number} x The x-position.
    168      * @param {number} y The y-position.
    169      *     animate.
    170      */
    171     setBounds: function(size, x, y) {
    172       this.style.width = size + 'px';
    173       this.style.height = heightForWidth(size) + 'px';
    174       this.style.left = x + 'px';
    175       this.style.top = y + 'px';
    176     },
    177   };
    178 
    179   var mostVisitedPageGridValues = {
    180     // The fewest tiles we will show in a row.
    181     minColCount: 2,
    182     // The most tiles we will show in a row.
    183     maxColCount: 4,
    184 
    185     // TODO(estade): Change these to real values.
    186     // The smallest a tile can be.
    187     minTileWidth: 200,
    188     // The biggest a tile can be.
    189     maxTileWidth: 240,
    190   };
    191   TilePage.initGridValues(mostVisitedPageGridValues);
    192 
    193   /**
    194    * Calculates the height for a Most Visited tile for a given width. The size
    195    * is based on the thumbnail, which should have a 212:132 ratio (the rest of
    196    * the arithmetic accounts for padding).
    197    * @return {number} The height.
    198    */
    199   function heightForWidth(width) {
    200     return (width - 6) * 132 / 212 + 29;
    201   }
    202 
    203   var THUMBNAIL_COUNT = 8;
    204 
    205   /**
    206    * Creates a new MostVisitedPage object.
    207    * @param {string} name The display name for the page.
    208    * @constructor
    209    * @extends {TilePage}
    210    */
    211   function MostVisitedPage(name) {
    212     var el = new TilePage(name, mostVisitedPageGridValues);
    213     el.__proto__ = MostVisitedPage.prototype;
    214     el.initialize();
    215 
    216     return el;
    217   }
    218 
    219   MostVisitedPage.prototype = {
    220     __proto__: TilePage.prototype,
    221 
    222     initialize: function() {
    223       this.classList.add('most-visited-page');
    224 
    225       this.data_ = null;
    226       this.mostVisitedTiles_ = this.getElementsByClassName('most-visited');
    227     },
    228 
    229     /**
    230      * Create blank (filler) tiles.
    231      * @private
    232      */
    233     createTiles_: function() {
    234       for (var i = 0; i < THUMBNAIL_COUNT; i++) {
    235         this.appendTile(new MostVisited());
    236       }
    237     },
    238 
    239     /**
    240      * Update the tiles after a change to |data_|.
    241      */
    242     updateTiles_: function() {
    243       for (var i = 0; i < THUMBNAIL_COUNT; i++) {
    244         var page = this.data_[i];
    245         page.index = i;
    246         var tile = this.mostVisitedTiles_[i];
    247 
    248         if (i >= this.data_.length)
    249           tile.reset();
    250         else
    251           tile.updateForData(page);
    252       }
    253     },
    254 
    255     /**
    256      * Array of most visited data objects.
    257      * @type {Array}
    258      */
    259     get data() {
    260       return this.data_;
    261     },
    262     set data(data) {
    263       // The first time data is set, create the tiles.
    264       if (!this.data_)
    265         this.createTiles_();
    266 
    267       // We append the class name with the "filler" so that we can style fillers
    268       // differently.
    269       this.data_ = data.slice(0, THUMBNAIL_COUNT);
    270       this.updateTiles_();
    271     },
    272 
    273     /** @inheritDoc */
    274     heightForWidth: heightForWidth,
    275   };
    276 
    277   return {
    278     MostVisitedPage: MostVisitedPage,
    279   };
    280 });
    281