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