Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(912)

Side by Side Diff: chrome/browser/resources/ntp_search/most_visited_page.js

Issue 10829131: Refactoring NTP5: new implementation of TilePage and MostVisitedPage (which now inherits from Thumb… (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Manually fixing mock_data_autogen.js lines with more than 80 chars. Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('ntp', function() { 5 cr.define('ntp', function() {
6 'use strict'; 6 'use strict';
7 7
8 var TilePage = ntp.TilePage; 8 var Thumbnail = ntp.Thumbnail;
9 9 var ThumbnailPage = ntp.ThumbnailPage;
10 /**
11 * A counter for generating unique tile IDs.
12 */
13 var tileID = 0;
14 10
15 /** 11 /**
16 * Creates a new Most Visited object for tiling. 12 * Creates a new Most Visited object for tiling.
17 * @constructor 13 * @constructor
14 * @extends {Thumbnail}
18 * @extends {HTMLAnchorElement} 15 * @extends {HTMLAnchorElement}
16 * @param {Object} config Tile page configuration object.
19 */ 17 */
20 function MostVisited() { 18 function MostVisited(config) {
21 var el = cr.doc.createElement('a'); 19 var el = cr.doc.createElement('a');
22 el.__proto__ = MostVisited.prototype; 20 el.__proto__ = MostVisited.prototype;
23 el.initialize(); 21 el.initialize(config);
24 22
25 return el; 23 return el;
26 } 24 }
27 25
28 MostVisited.prototype = { 26 MostVisited.prototype = {
29 __proto__: HTMLAnchorElement.prototype, 27 __proto__: Thumbnail.prototype,
30 28
31 initialize: function() { 29 /**
32 this.reset(); 30 * Initializes a MostVisited Thumbnail.
31 * @param {Object} config TilePage configuration object.
32 */
33 initialize: function(config) {
34 Thumbnail.prototype.initialize.apply(this, arguments);
33 35
34 this.addEventListener('click', this.handleClick_); 36 this.addEventListener('click', this.handleClick_);
35 this.addEventListener('keydown', this.handleKeyDown_); 37 this.addEventListener('keydown', this.handleKeyDown_);
36 }, 38 },
37 39
38 get index() {
39 assert(this.tile);
40 return this.tile.index;
41 },
42
43 get data() {
44 return this.data_;
45 },
46
47 /** 40 /**
48 * Clears the DOM hierarchy for this node, setting it back to the default 41 * Clears the DOM hierarchy for this node, setting it back to the default
49 * for a blank thumbnail. 42 * for a blank thumbnail.
50 */ 43 */
51 reset: function() { 44 reset: function() {
52 this.className = 'most-visited filler real'; 45 Thumbnail.prototype.reset.apply(this, arguments);
53 this.innerHTML =
54 '<span class="thumbnail-wrapper fills-parent">' +
55 '<div class="close-button"></div>' +
56 '<span class="thumbnail fills-parent">' +
57 // thumbnail-shield provides a gradient fade effect.
58 '<div class="thumbnail-shield fills-parent"></div>' +
59 '</span>' +
60 '<span class="favicon"></span>' +
61 '</span>' +
62 '<div class="color-stripe"></div>' +
63 '<span class="title"></span>';
64 46
65 this.querySelector('.close-button').title = 47 var closeButton = cr.doc.createElement('div');
66 loadTimeData.getString('removethumbnailtooltip'); 48 closeButton.className = 'close-button';
67 49 closeButton.title = loadTimeData.getString('removethumbnailtooltip');
68 this.tabIndex = -1; 50 this.appendChild(closeButton);
69 this.data_ = null;
70 this.removeAttribute('id');
71 this.title = '';
72 }, 51 },
73 52
74 /** 53 /**
75 * Update the appearance of this tile according to |data|. 54 * Update the appearance of this tile according to |data|.
76 * @param {Object} data A dictionary of relevant data for the page. 55 * @param {Object} data A dictionary of relevant data for the page.
77 */ 56 */
78 updateForData: function(data) { 57 updateForData: function(data) {
79 if (this.classList.contains('blacklisted') && data) { 58 if (this.classList.contains('blacklisted') && data) {
80 // Animate appearance of new tile. 59 // Animate appearance of new tile.
81 this.classList.add('new-tile-contents'); 60 this.classList.add('new-tile-contents');
82 } 61 }
83 this.classList.remove('blacklisted'); 62 this.classList.remove('blacklisted');
84 63
85 if (!data || data.filler) { 64 Thumbnail.prototype.updateForData.apply(this, arguments);
86 if (this.data_)
87 this.reset();
88 return;
89 }
90
91 var id = tileID++;
92 this.id = 'most-visited-tile-' + id;
93 this.data_ = data;
94 this.classList.add('focusable');
95
96 var faviconDiv = this.querySelector('.favicon');
97 var faviconUrl = 'chrome://favicon/size/16/' + data.url;
98 faviconDiv.style.backgroundImage = url(faviconUrl);
99 chrome.send('getFaviconDominantColor', [faviconUrl, this.id]);
100
101 var title = this.querySelector('.title');
102 title.textContent = data.title;
103 title.dir = data.direction;
104
105 // Sets the tooltip.
106 this.title = data.title;
107
108 var thumbnailUrl = 'chrome://thumb/' + data.url;
109 this.querySelector('.thumbnail').style.backgroundImage =
110 url(thumbnailUrl);
111
112 this.href = data.url;
113
114 this.classList.remove('filler');
115 },
116
117 /**
118 * Sets the color of the favicon dominant color bar.
119 * @param {string} color The css-parsable value for the color.
120 */
121 set stripeColor(color) {
122 this.querySelector('.color-stripe').style.backgroundColor = color;
123 }, 65 },
124 66
125 /** 67 /**
126 * Handles a click on the tile. 68 * Handles a click on the tile.
127 * @param {Event} e The click event. 69 * @param {Event} e The click event.
70 * @private
128 */ 71 */
129 handleClick_: function(e) { 72 handleClick_: function(e) {
130 if (e.target.classList.contains('close-button')) { 73 if (e.target.classList.contains('close-button')) {
131 this.blacklist_(); 74 this.blacklist_();
132 e.preventDefault(); 75 e.preventDefault();
133 } else { 76 } else {
134 // Records an app launch from the most visited page (Chrome will decide 77 // Records an app launch from the most visited page (Chrome will decide
135 // whether the url is an app). TODO(estade): this only works for clicks; 78 // whether the url is an app). TODO(estade): this only works for clicks;
136 // other actions like "open in new tab" from the context menu won't be 79 // other actions like "open in new tab" from the context menu won't be
137 // recorded. Can this be fixed? 80 // recorded. Can this be fixed?
138 chrome.send('recordAppLaunchByURL', 81 chrome.send('recordAppLaunchByURL',
139 [encodeURIComponent(this.href), 82 [encodeURIComponent(this.href),
140 ntp.APP_LAUNCH.NTP_MOST_VISITED]); 83 ntp.APP_LAUNCH.NTP_MOST_VISITED]);
141 // Records the index of this tile. 84 // Records the index of this tile.
142 chrome.send('metricsHandler:recordInHistogram', 85 chrome.send('metricsHandler:recordInHistogram',
143 ['NewTabPage.MostVisited', this.index, 8]); 86 ['NewTabPage.MostVisited', this.index, 8]);
144 chrome.send('mostVisitedAction', 87 chrome.send('mostVisitedAction',
145 [ntp.NtpFollowAction.CLICKED_TILE]); 88 [ntp.NtpFollowAction.CLICKED_TILE]);
146 } 89 }
147 }, 90 },
148 91
149 /** 92 /**
150 * Allow blacklisting most visited site using the keyboard. 93 * Allow blacklisting most visited site using the keyboard.
94 * @private
151 */ 95 */
152 handleKeyDown_: function(e) { 96 handleKeyDown_: function(e) {
153 if (!cr.isMac && e.keyCode == 46 || // Del 97 if (!cr.isMac && e.keyCode == 46 || // Del
154 cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace 98 cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
155 this.blacklist_(); 99 this.blacklist_();
156 } 100 }
157 }, 101 },
158 102
159 /** 103 /**
160 * Permanently removes a page from Most Visited. 104 * Permanently removes a page from Most Visited.
105 * @private
161 */ 106 */
162 blacklist_: function() { 107 blacklist_: function() {
163 this.showUndoNotification_(); 108 this.showUndoNotification_();
164 chrome.send('blacklistURLFromMostVisited', [this.data_.url]); 109 chrome.send('blacklistURLFromMostVisited', [this.data_.url]);
165 this.reset(); 110 this.reset();
166 chrome.send('getMostVisited'); 111 chrome.send('getMostVisited');
167 this.classList.add('blacklisted'); 112 this.classList.add('blacklisted');
168 }, 113 },
169 114
115 /**
116 * Shows the undo notification when blacklisting a most visited site.
117 * @private
118 */
170 showUndoNotification_: function() { 119 showUndoNotification_: function() {
171 var data = this.data_; 120 var data = this.data_;
172 var self = this; 121 var self = this;
173 var doUndo = function() { 122 var doUndo = function() {
174 chrome.send('removeURLsFromMostVisitedBlacklist', [data.url]); 123 chrome.send('removeURLsFromMostVisitedBlacklist', [data.url]);
175 self.updateForData(data); 124 self.updateForData(data);
176 } 125 };
177 126
178 var undo = { 127 var undo = {
179 action: doUndo, 128 action: doUndo,
180 text: loadTimeData.getString('undothumbnailremove'), 129 text: loadTimeData.getString('undothumbnailremove'),
181 }; 130 };
182 131
183 var undoAll = { 132 var undoAll = {
184 action: function() { 133 action: function() {
185 chrome.send('clearMostVisitedURLsBlacklist'); 134 chrome.send('clearMostVisitedURLsBlacklist');
186 }, 135 },
187 text: loadTimeData.getString('restoreThumbnailsShort'), 136 text: loadTimeData.getString('restoreThumbnailsShort'),
188 }; 137 };
189 138
190 ntp.showNotification( 139 ntp.showNotification(
191 loadTimeData.getString('thumbnailremovednotification'), 140 loadTimeData.getString('thumbnailremovednotification'),
192 [undo, undoAll]); 141 [undo, undoAll]);
193 }, 142 },
194 143
195 /** 144 /**
196 * Set the size and position of the most visited tile.
197 * @param {number} size The total size of |this|.
198 * @param {number} x The x-position.
199 * @param {number} y The y-position.
200 * animate.
201 */
202 setBounds: function(size, x, y) {
203 this.style.width = toCssPx(size);
204 this.style.height = toCssPx(heightForWidth(size));
205
206 this.style.left = toCssPx(x);
207 this.style.right = toCssPx(x);
208 this.style.top = toCssPx(y);
209 },
210
211 /**
212 * Returns whether this element can be 'removed' from chrome (i.e. whether 145 * Returns whether this element can be 'removed' from chrome (i.e. whether
213 * the user can drag it onto the trash and expect something to happen). 146 * the user can drag it onto the trash and expect something to happen).
214 * @return {boolean} True, since most visited pages can always be 147 * @return {boolean} True, since most visited pages can always be
215 * blacklisted. 148 * blacklisted.
216 */ 149 */
217 canBeRemoved: function() { 150 canBeRemoved: function() {
218 return true; 151 return true;
219 }, 152 }
220
221 /**
222 * Removes this element from chrome, i.e. blacklists it.
223 */
224 removeFromChrome: function() {
225 this.blacklist_();
226 this.parentNode.classList.add('finishing-drag');
227 },
228
229 /**
230 * Called when a drag of this tile has ended (after all animations have
231 * finished).
232 */
233 finalizeDrag: function() {
234 this.parentNode.classList.remove('finishing-drag');
235 },
236
237 /**
238 * Called when a drag is starting on the tile. Updates dataTransfer with
239 * data for this tile (for dragging outside of the NTP).
240 */
241 setDragData: function(dataTransfer) {
242 dataTransfer.setData('Text', this.data_.title);
243 dataTransfer.setData('URL', this.data_.url);
244 },
245 }; 153 };
246 154
247 var mostVisitedPageGridValues = {
248 // The fewest tiles we will show in a row.
249 minColCount: 2,
250 // The most tiles we will show in a row.
251 maxColCount: 4,
252
253 // The smallest a tile can be.
254 minTileWidth: 122,
255 // The biggest a tile can be. 212 (max thumbnail width) + 2.
256 maxTileWidth: 214,
257
258 // The padding between tiles, as a fraction of the tile width.
259 tileSpacingFraction: 1 / 8,
260 };
261 TilePage.initGridValues(mostVisitedPageGridValues);
262
263 /**
264 * Calculates the height for a Most Visited tile for a given width. The size
265 * is based on the thumbnail, which should have a 212:132 ratio.
266 * @return {number} The height.
267 */
268 function heightForWidth(width) {
269 // The 2s are for borders, the 31 is for the title.
270 return (width - 2) * 132 / 212 + 2 + 31;
271 }
272
273 var THUMBNAIL_COUNT = 8;
274
275 /** 155 /**
276 * Creates a new MostVisitedPage object. 156 * Creates a new MostVisitedPage object.
277 * @constructor 157 * @constructor
278 * @extends {TilePage} 158 * @extends {TilePage}
279 */ 159 */
280 function MostVisitedPage() { 160 function MostVisitedPage() {
281 var el = new TilePage(mostVisitedPageGridValues); 161 var el = new ThumbnailPage();
282 el.__proto__ = MostVisitedPage.prototype; 162 el.__proto__ = MostVisitedPage.prototype;
283 el.initialize(); 163 el.initialize();
284 164
285 return el; 165 return el;
286 } 166 }
287 167
288 MostVisitedPage.prototype = { 168 MostVisitedPage.prototype = {
289 __proto__: TilePage.prototype, 169 __proto__: ThumbnailPage.prototype,
290 170
291 initialize: function() { 171 ThumbnailClass: MostVisited,
292 this.classList.add('most-visited-page');
293 this.data_ = null;
294 this.mostVisitedTiles_ = this.getElementsByClassName('most-visited real');
295
296 this.addEventListener('carddeselected', this.handleCardDeselected_);
297 this.addEventListener('cardselected', this.handleCardSelected_);
298 },
299 172
300 /** 173 /**
301 * Create blank (filler) tiles. 174 * Initializes a MostVisitedPage.
302 * @private
303 */ 175 */
304 createTiles_: function() { 176 initialize: function() {
305 for (var i = 0; i < THUMBNAIL_COUNT; i++) { 177 ThumbnailPage.prototype.initialize.apply(this, arguments);
306 this.appendTile(new MostVisited());
307 }
308 },
309 178
310 /** 179 this.classList.add('most-visited-page');
311 * Update the tiles after a change to |data_|.
312 */
313 updateTiles_: function() {
314 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
315 var page = this.data_[i];
316 var tile = this.mostVisitedTiles_[i];
317
318 if (i >= this.data_.length)
319 tile.reset();
320 else
321 tile.updateForData(page);
322 }
323 }, 180 },
324 181
325 /** 182 /**
326 * Handles the 'card deselected' event (i.e. the user clicked to another 183 * Handles the 'card deselected' event (i.e. the user clicked to another
327 * pane). 184 * pane).
185 * @private
328 * @param {Event} e The CardChanged event. 186 * @param {Event} e The CardChanged event.
329 */ 187 */
330 handleCardDeselected_: function(e) { 188 handleCardDeselected_: function(e) {
331 if (!document.documentElement.classList.contains('starting-up')) { 189 if (!document.documentElement.classList.contains('starting-up')) {
332 chrome.send('mostVisitedAction', 190 chrome.send('mostVisitedAction',
333 [ntp.NtpFollowAction.CLICKED_OTHER_NTP_PANE]); 191 [ntp.NtpFollowAction.CLICKED_OTHER_NTP_PANE]);
334 } 192 }
335 }, 193 },
336 194
337 /** 195 /**
338 * Handles the 'card selected' event (i.e. the user clicked to select the 196 * Handles the 'card selected' event (i.e. the user clicked to select the
339 * Most Visited pane). 197 * this page's pane).
198 * @private
340 * @param {Event} e The CardChanged event. 199 * @param {Event} e The CardChanged event.
341 */ 200 */
342 handleCardSelected_: function(e) { 201 handleCardSelected_: function(e) {
343 if (!document.documentElement.classList.contains('starting-up')) 202 if (!document.documentElement.classList.contains('starting-up')) {
203 this.layout_();
344 chrome.send('mostVisitedSelected'); 204 chrome.send('mostVisitedSelected');
205 }
345 }, 206 },
346 207
347 /** 208 /**
348 * Array of most visited data objects. 209 * Sets the data that will be used to create Thumbnails.
349 * @type {Array} 210 * TODO(pedrosimonetti): Move data handling related code to TilePage. Make
211 * sure the new logic works with Apps without requiring duplicating code.
212 * @param {Array} data The array of data.
213 * @type (Array}
350 */ 214 */
351 get data() {
352 return this.data_;
353 },
354 set data(data) { 215 set data(data) {
355 var startTime = Date.now(); 216 var startTime = Date.now();
217 var maxTileCount = this.config_.maxTileCount;
356 218
357 // The first time data is set, create the tiles. 219 // The first time data is set, create the tiles.
358 if (!this.data_) { 220 if (!this.data_) {
359 this.createTiles_(); 221 this.data_ = data.slice(0, maxTileCount);
360 this.data_ = data.slice(0, THUMBNAIL_COUNT); 222 this.createTiles_(this.data_.length);
361 } else { 223 } else {
362 this.data_ = refreshData(this.data_, data); 224 this.data_ = refreshData(this.data_, data, maxTileCount);
363 } 225 }
364 226
365 this.updateTiles_(); 227 this.updateTiles_();
366 logEvent('mostVisited.layout: ' + (Date.now() - startTime)); 228 logEvent('mostVisited.layout: ' + (Date.now() - startTime));
367 }, 229 },
368
369 /** @inheritDoc */
370 shouldAcceptDrag: function(e) {
371 return false;
372 },
373
374 /** @inheritDoc */
375 heightForWidth: heightForWidth,
376 }; 230 };
377 231
378 /** 232 /**
379 * Executed once the NTP has loaded. Checks if the Most Visited pane is 233 * Executed once the NTP has loaded. Checks if the Most Visited pane is
380 * shown or not. If it is shown, the 'mostVisitedSelected' message is sent 234 * shown or not. If it is shown, the 'mostVisitedSelected' message is sent
381 * to the C++ code, to record the fact that the user has seen this pane. 235 * to the C++ code, to record the fact that the user has seen this pane.
382 */ 236 */
383 MostVisitedPage.onLoaded = function() { 237 MostVisitedPage.onLoaded = function() {
384 if (ntp.getCardSlider() && 238 if (ntp.getCardSlider() &&
385 ntp.getCardSlider().currentCardValue && 239 ntp.getCardSlider().currentCardValue &&
386 ntp.getCardSlider().currentCardValue.classList 240 ntp.getCardSlider().currentCardValue.classList
387 .contains('most-visited-page')) { 241 .contains('most-visited-page')) {
242 ntp.getCardSlider().currentCardValue.layout_(true);
388 chrome.send('mostVisitedSelected'); 243 chrome.send('mostVisitedSelected');
389 } 244 }
390 } 245 };
391 246
392 /** 247 /**
393 * We've gotten additional Most Visited data. Update our old data with the 248 * We've gotten additional Most Visited data. Update our old data with the
394 * new data. The ordering of the new data is not important, except when a 249 * new data. The ordering of the new data is not important, except when a
395 * page is pinned. Thus we try to minimize re-ordering. 250 * page is pinned. Thus we try to minimize re-ordering.
396 * @param {Array} oldData The current Most Visited page list. 251 * @param {Array} oldData The current Most Visited page list.
397 * @param {Array} newData The new Most Visited page list. 252 * @param {Array} newData The new Most Visited page list.
398 * @return {Array} The merged page list that should replace the current page 253 * @return {Array} The merged page list that should replace the current page
399 * list. 254 * list.
400 */ 255 */
401 function refreshData(oldData, newData) { 256 function refreshData(oldData, newData, maxTileCount) {
402 oldData = oldData.slice(0, THUMBNAIL_COUNT); 257 oldData = oldData.slice(0, maxTileCount);
403 newData = newData.slice(0, THUMBNAIL_COUNT); 258 newData = newData.slice(0, maxTileCount);
404 259
405 // Copy over pinned sites directly. 260 // Copy over pinned sites directly.
406 for (var j = 0; j < newData.length; j++) { 261 for (var j = 0; j < newData.length; j++) {
407 if (newData[j].pinned) { 262 if (newData[j].pinned) {
408 oldData[j] = newData[j]; 263 oldData[j] = newData[j];
409 // Mark the entry as 'updated' so we don't try to update again. 264 // Mark the entry as 'updated' so we don't try to update again.
410 oldData[j].updated = true; 265 oldData[j].updated = true;
411 // Mark the newData page as 'used' so we don't try to re-use it. 266 // Mark the newData page as 'used' so we don't try to re-use it.
412 newData[j].used = true; 267 newData[j].used = true;
413 } 268 }
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
446 oldData[i].updated = true; 301 oldData[i].updated = true;
447 newData[j].used = true; 302 newData[j].used = true;
448 break; 303 break;
449 } 304 }
450 305
451 if (oldData[i] && !oldData[i].updated) 306 if (oldData[i] && !oldData[i].updated)
452 oldData[i] = null; 307 oldData[i] = null;
453 } 308 }
454 309
455 // Clear 'updated' flags so this function will work next time it's called. 310 // Clear 'updated' flags so this function will work next time it's called.
456 for (var i = 0; i < THUMBNAIL_COUNT; i++) { 311 for (var i = 0; i < maxTileCount; i++) {
457 if (oldData[i]) 312 if (oldData[i])
458 oldData[i].updated = false; 313 oldData[i].updated = false;
459 } 314 }
460 315
461 return oldData; 316 return oldData;
462 }; 317 }
463 318
464 return { 319 return {
465 MostVisitedPage: MostVisitedPage, 320 MostVisitedPage: MostVisitedPage,
466 refreshData: refreshData, 321 refreshData: refreshData,
467 }; 322 };
468 }); 323 });
469 324
470 document.addEventListener('ntpLoaded', ntp.MostVisitedPage.onLoaded); 325 document.addEventListener('ntpLoaded', ntp.MostVisitedPage.onLoaded);
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp_search/most_visited_page.css ('k') | chrome/browser/resources/ntp_search/new_tab.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698