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

Side by Side Diff: chrome/browser/resources/ntp4/suggested_page.js

Issue 9358031: Added new adaptive "Suggest" tab on the New Tab Page, behing the flag, for the experiments. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Removed "most visited" references Created 8 years, 10 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
Evan Stade 2012/02/09 01:54:23 I prefer this feature be called SuggestionsPage, n
GeorgeY 2012/02/10 00:00:36 Done.
Evan Stade 2012/02/11 01:56:05 I still see "suggested" in most places.
GeorgeY 2012/02/17 01:09:40 renamed all
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 * A counter for generating unique tile IDs.
12 */
13 var tileID = 0;
14
15 /**
16 * Creates a new Suggested page object for tiling.
17 * @constructor
18 * @extends {HTMLAnchorElement}
19 */
20 function Suggested() {
21 var el = cr.doc.createElement('a');
22 el.__proto__ = Suggested.prototype;
23 el.initialize();
24
25 return el;
26 }
27
28 Suggested.prototype = {
29 __proto__: HTMLAnchorElement.prototype,
30
31 initialize: function() {
32 this.reset();
33
34 this.addEventListener('click', this.handleClick_);
35 this.addEventListener('keydown', this.handleKeyDown_);
36 },
37
38 get index() {
39 assert(this.tile);
40 return this.tile.index;
41 },
42
43 get data() {
44 return this.data_;
45 },
46
47 /**
48 * Clears the DOM hierarchy for this node, setting it back to the default
49 * for a blank thumbnail.
50 */
51 reset: function() {
52 this.className = 'suggested filler real';
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
65 this.querySelector('.close-button').title =
66 templateData.removethumbnailtooltip;
67
68 this.tabIndex = -1;
69 this.data_ = null;
70 this.removeAttribute('id');
71 this.title = '';
72 },
73
74 /**
75 * Update the appearance of this tile according to |data|.
76 * @param {Object} data A dictionary of relevant data for the page.
77 */
78 updateForData: function(data) {
79 if (this.classList.contains('blacklisted') && data) {
80 // Animate appearance of new tile.
81 this.classList.add('new-tile-contents');
82 }
83 this.classList.remove('blacklisted');
84
85 if (!data || data.filler) {
86 if (this.data_)
87 this.reset();
88 return;
89 }
90
91 var id = tileID++;
92 this.id = 'suggested-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 },
124
125 /**
126 * Handles a click on the tile.
127 * @param {Event} e The click event.
128 */
129 handleClick_: function(e) {
130 if (e.target.classList.contains('close-button')) {
131 this.blacklist_();
132 e.preventDefault();
133 } else {
134 // Records an app launch from the suggested page (Chrome will decide
135 // 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
137 // recorded. Can this be fixed?
138 chrome.send('recordAppLaunchByURL',
139 [encodeURIComponent(this.href),
140 ntp4.APP_LAUNCH.NTP_MOST_VISITED]);
141 // Records the index of this tile.
142 // TODO(georgey) add metric.
143 // chrome.send('metricsHandler:recordInHistogram',
144 // ['NTP_Suggested', this.index, 8]);
145 }
146 },
147
148 /**
149 * Allow blacklisting suggested site using the keyboard.
150 */
151 handleKeyDown_: function(e) {
152 if (!cr.isMac && e.keyCode == 46 || // Del
153 cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
154 this.blacklist_();
155 }
156 },
157
158 /**
159 * Permanently removes a page from Suggested.
160 */
161 blacklist_: function() {
162 this.showUndoNotification_();
163 chrome.send('blacklistURLFromSuggested', [this.data_.url]);
164 this.reset();
165 chrome.send('getSuggested');
166 this.classList.add('blacklisted');
167 },
168
169 showUndoNotification_: function() {
170 var data = this.data_;
171 var self = this;
172 var doUndo = function () {
173 chrome.send('removeURLsFromSuggestedBlacklist', [data.url]);
174 self.updateForData(data);
175 }
176
177 var undo = {
178 action: doUndo,
179 text: templateData.undothumbnailremove,
180 }
181
182 var undoAll = {
183 action: function() {
184 chrome.send('clearSuggestedURLsBlacklist', []);
185 },
186 text: templateData.restoreThumbnailsShort,
187 }
188
189 ntp4.showNotification(templateData.thumbnailremovednotification,
190 [undo, undoAll]);
191 },
192
193 /**
194 * Set the size and position of the suggested tile.
195 * @param {number} size The total size of |this|.
196 * @param {number} x The x-position.
197 * @param {number} y The y-position.
198 * animate.
199 */
200 setBounds: function(size, x, y) {
201 this.style.width = size + 'px';
202 this.style.height = heightForWidth(size) + 'px';
203
204 this.style.left = x + 'px';
205 this.style.right = x + 'px';
206 this.style.top = y + 'px';
207 },
208
209 /**
210 * Returns whether this element can be 'removed' from chrome (i.e. whether
211 * the user can drag it onto the trash and expect something to happen).
212 * @return {boolean} True, since suggested pages can always be
213 * blacklisted.
214 */
215 canBeRemoved: function() {
216 return true;
217 },
218
219 /**
220 * Removes this element from chrome, i.e. blacklists it.
221 */
222 removeFromChrome: function() {
223 this.blacklist_();
224 this.parentNode.classList.add('finishing-drag');
225 },
226
227 /**
228 * Called when a drag of this tile has ended (after all animations have
229 * finished).
230 */
231 finalizeDrag: function() {
232 this.parentNode.classList.remove('finishing-drag');
233 },
234
235 /**
236 * Called when a drag is starting on the tile. Updates dataTransfer with
237 * data for this tile (for dragging outside of the NTP).
238 */
239 setDragData: function(dataTransfer) {
240 dataTransfer.setData('Text', this.data_.title);
241 dataTransfer.setData('URL', this.data_.url);
242 },
243 };
244
245 var suggestedPageGridValues = {
246 // The fewest tiles we will show in a row.
247 minColCount: 2,
248 // The suggested we will show in a row.
249 maxColCount: 4,
250
251 // The smallest a tile can be.
252 minTileWidth: 122,
253 // The biggest a tile can be. 212 (max thumbnail width) + 2.
254 maxTileWidth: 214,
255
256 // The padding between tiles, as a fraction of the tile width.
257 tileSpacingFraction: 1 / 8,
258 };
259 TilePage.initGridValues(suggestedPageGridValues);
260
261 /**
262 * Calculates the height for a Suggested tile for a given width. The size
263 * is based on the thumbnail, which should have a 212:132 ratio.
264 * @return {number} The height.
265 */
266 function heightForWidth(width) {
267 // The 2s are for borders, the 31 is for the title.
268 return (width - 2) * 132 / 212 + 2 + 31;
269 }
270
271 var THUMBNAIL_COUNT = 8;
272
273 /**
274 * Creates a new SuggestedPage object.
275 * @constructor
276 * @extends {TilePage}
277 */
278 function SuggestedPage() {
279 var el = new TilePage(suggestedPageGridValues);
280 el.__proto__ = SuggestedPage.prototype;
281 el.initialize();
282
283 return el;
284 }
285
286 SuggestedPage.prototype = {
287 __proto__: TilePage.prototype,
288
289 initialize: function() {
290 this.classList.add('suggested-page');
291 this.data_ = null;
292 this.suggestedTiles_ = this.getElementsByClassName('suggested real');
293 },
294
295 /**
296 * Create blank (filler) tiles.
297 * @private
298 */
299 createTiles_: function() {
300 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
301 this.appendTile(new Suggested());
302 }
303 },
304
305 /**
306 * Update the tiles after a change to |data_|.
307 */
308 updateTiles_: function() {
309 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
310 var page = this.data_[i];
311 var tile = this.suggestedTiles_[i];
312
313 if (i >= this.data_.length)
314 tile.reset();
315 else
316 tile.updateForData(page);
317 }
318 },
319
320 /**
321 * Array of suggested data objects.
322 * @type {Array}
323 */
324 get data() {
325 return this.data_;
326 },
327 set data(data) {
328 var startTime = Date.now();
329
330 // The first time data is set, create the tiles.
331 if (!this.data_) {
332 this.createTiles_();
333 this.data_ = data.slice(0, THUMBNAIL_COUNT);
334 } else {
335 this.data_ = refreshData(this.data_, data);
336 }
337
338 this.updateTiles_();
339 logEvent('suggested.layout: ' + (Date.now() - startTime));
340 },
341
342 /** @inheritDoc */
343 shouldAcceptDrag: function(e) {
344 return false;
345 },
346
347 /** @inheritDoc */
348 heightForWidth: heightForWidth,
349 };
350
351 /**
352 * We've gotten additional data for Suggested page. Update our old data with
353 * the new data. The ordering of the new data is not important, except when a
354 * page is pinned. Thus we try to minimize re-ordering.
355 * @param {Object} oldData The current Suggested page list.
356 * @param {Object} newData The new Suggested page list.
357 * @return The merged page list that should replace the current page list.
358 */
359 function refreshData(oldData, newData) {
360 oldData = oldData.slice(0, THUMBNAIL_COUNT);
361 newData = newData.slice(0, THUMBNAIL_COUNT);
362
363 // Copy over pinned sites directly.
364 for (var j = 0; j < newData.length; j++) {
365 if (newData[j].pinned) {
366 oldData[j] = newData[j];
367 // Mark the entry as 'updated' so we don't try to update again.
368 oldData[j].updated = true;
369 // Mark the newData page as 'used' so we don't try to re-use it.
370 newData[j].used = true;
371 }
372 }
373
374 // Look through old pages; if they exist in the newData list, keep them
375 // where they are.
376 for (var i = 0; i < oldData.length; i++) {
377 if (!oldData[i] || oldData[i].updated)
378 continue;
379
380 for (var j = 0; j < newData.length; j++) {
381 if (newData[j].used)
382 continue;
383
384 if (newData[j].url == oldData[i].url) {
385 // The background image and other data may have changed.
386 oldData[i] = newData[j];
387 oldData[i].updated = true;
388 newData[j].used = true;
389 break;
390 }
391 }
392 }
393
394 // Look through old pages that haven't been updated yet; replace them.
395 for (var i = 0; i < oldData.length; i++) {
396 if (oldData[i] && oldData[i].updated)
397 continue;
398
399 for (var j = 0; j < newData.length; j++) {
400 if (newData[j].used)
401 continue;
402
403 oldData[i] = newData[j];
404 oldData[i].updated = true;
405 newData[j].used = true;
406 break;
407 }
408
409 if (oldData[i] && !oldData[i].updated)
410 oldData[i] = null;
411 }
412
413 // Clear 'updated' flags so this function will work next time it's called.
414 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
415 if (oldData[i])
416 oldData[i].updated = false;
417 }
418
419 return oldData;
420 };
421
422 return {
423 SuggestedPage: SuggestedPage,
424 refreshData: refreshData,
425 };
426 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698