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

Side by Side Diff: chrome/browser/resources/ntp4/suggestions_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: added dynamic loading of the JS and CSS 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.
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 Suggestions 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 = 'suggestions 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 = 'suggestions-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 suggestions 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 }
142 },
143
144 /**
145 * Allow blacklisting suggestions site using the keyboard.
146 */
147 handleKeyDown_: function(e) {
148 if (!cr.isMac && e.keyCode == 46 || // Del
149 cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
150 this.blacklist_();
151 }
152 },
153
154 /**
155 * Permanently removes a page from Suggestions.
156 */
157 blacklist_: function() {
158 this.showUndoNotification_();
159 chrome.send('blacklistURLFromSuggestions', [this.data_.url]);
160 this.reset();
161 chrome.send('getSuggestions');
162 this.classList.add('blacklisted');
163 },
164
165 showUndoNotification_: function() {
166 var data = this.data_;
167 var self = this;
168 var doUndo = function () {
169 chrome.send('removeURLsFromSuggestionsBlacklist', [data.url]);
170 self.updateForData(data);
171 }
172
173 var undo = {
174 action: doUndo,
175 text: templateData.undothumbnailremove,
176 }
177
178 var undoAll = {
179 action: function() {
180 chrome.send('clearSuggestionsURLsBlacklist', []);
181 },
182 text: templateData.restoreThumbnailsShort,
183 }
184
185 ntp4.showNotification(templateData.thumbnailremovednotification,
186 [undo, undoAll]);
187 },
188
189 /**
190 * Set the size and position of the suggestions tile.
191 * @param {number} size The total size of |this|.
192 * @param {number} x The x-position.
193 * @param {number} y The y-position.
194 * animate.
195 */
196 setBounds: function(size, x, y) {
197 this.style.width = size + 'px';
198 this.style.height = heightForWidth(size) + 'px';
199
200 this.style.left = x + 'px';
201 this.style.right = x + 'px';
202 this.style.top = y + 'px';
203 },
204
205 /**
206 * Returns whether this element can be 'removed' from chrome (i.e. whether
207 * the user can drag it onto the trash and expect something to happen).
208 * @return {boolean} True, since suggestions pages can always be
209 * blacklisted.
210 */
211 canBeRemoved: function() {
212 return true;
213 },
214
215 /**
216 * Removes this element from chrome, i.e. blacklists it.
217 */
218 removeFromChrome: function() {
219 this.blacklist_();
220 this.parentNode.classList.add('finishing-drag');
221 },
222
223 /**
224 * Called when a drag of this tile has ended (after all animations have
225 * finished).
226 */
227 finalizeDrag: function() {
228 this.parentNode.classList.remove('finishing-drag');
229 },
230
231 /**
232 * Called when a drag is starting on the tile. Updates dataTransfer with
233 * data for this tile (for dragging outside of the NTP).
234 */
235 setDragData: function(dataTransfer) {
236 dataTransfer.setData('Text', this.data_.title);
237 dataTransfer.setData('URL', this.data_.url);
238 },
239 };
240
241 var suggestionsPageGridValues = {
242 // The fewest tiles we will show in a row.
243 minColCount: 2,
244 // The suggestions we will show in a row.
245 maxColCount: 4,
246
247 // The smallest a tile can be.
248 minTileWidth: 122,
249 // The biggest a tile can be. 212 (max thumbnail width) + 2.
250 maxTileWidth: 214,
251
252 // The padding between tiles, as a fraction of the tile width.
253 tileSpacingFraction: 1 / 8,
254 };
255 TilePage.initGridValues(suggestionsPageGridValues);
256
257 /**
258 * Calculates the height for a Suggested tile for a given width. The size
259 * is based on the thumbnail, which should have a 212:132 ratio.
260 * @return {number} The height.
261 */
262 function heightForWidth(width) {
263 // The 2s are for borders, the 31 is for the title.
264 return (width - 2) * 132 / 212 + 2 + 31;
265 }
266
267 var THUMBNAIL_COUNT = 8;
268
269 /**
270 * Creates a new SuggestionsPage object.
271 * @constructor
272 * @extends {TilePage}
273 */
274 function SuggestionsPage() {
275 var el = new TilePage(suggestionsPageGridValues);
276 el.__proto__ = SuggestionsPage.prototype;
277 el.initialize();
278
279 return el;
280 }
281
282 SuggestionsPage.prototype = {
283 __proto__: TilePage.prototype,
284
285 initialize: function() {
286 this.classList.add('suggestions-page');
287 this.data_ = null;
288 this.suggestionsTiles_ = this.getElementsByClassName('suggestions real');
289 },
290
291 /**
292 * Create blank (filler) tiles.
293 * @private
294 */
295 createTiles_: function() {
296 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
297 this.appendTile(new Suggested());
298 }
299 },
300
301 /**
302 * Update the tiles after a change to |data_|.
303 */
304 updateTiles_: function() {
305 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
306 var page = this.data_[i];
307 var tile = this.suggestionsTiles_[i];
308
309 if (i >= this.data_.length)
310 tile.reset();
311 else
312 tile.updateForData(page);
313 }
314 },
315
316 /**
317 * Array of suggestions data objects.
318 * @type {Array}
319 */
320 get data() {
321 return this.data_;
322 },
323 set data(data) {
324 var startTime = Date.now();
325
326 // The first time data is set, create the tiles.
327 if (!this.data_) {
328 this.createTiles_();
329 this.data_ = data.slice(0, THUMBNAIL_COUNT);
330 } else {
331 this.data_ = refreshData(this.data_, data);
332 }
333
334 this.updateTiles_();
335 logEvent('suggestions.layout: ' + (Date.now() - startTime));
336 },
337
338 /** @inheritDoc */
339 shouldAcceptDrag: function(e) {
340 return false;
341 },
342
343 /** @inheritDoc */
344 heightForWidth: heightForWidth,
345 };
346
347 /**
348 * We've gotten additional data for Suggestions page. Update our old data with
349 * the new data. The ordering of the new data is not important, except when a
350 * page is pinned. Thus we try to minimize re-ordering.
351 * @param {Array} oldData The current Suggestions page list.
352 * @param {Array} newData The new Suggestions page list.
353 * @return The merged page list that should replace the current page list.
354 */
355 function refreshData(oldData, newData) {
356 oldData = oldData.slice(0, THUMBNAIL_COUNT);
357 newData = newData.slice(0, THUMBNAIL_COUNT);
358
359 // Copy over pinned sites directly.
360 for (var i = 0; i < newData.length; i++) {
361 if (newData[i].pinned) {
362 oldData[i] = newData[i];
363 // Mark the entry as 'updated' so we don't try to update again.
364 oldData[i].updated = true;
365 // Mark the newData page as 'used' so we don't try to re-use it.
366 newData[i].used = true;
367 }
368 }
369
370 // Look through old pages; if they exist in the newData list, keep them
371 // where they are.
372 for (var i = 0; i < oldData.length; i++) {
373 if (!oldData[i] || oldData[i].updated)
374 continue;
375
376 for (var j = 0; j < newData.length; j++) {
377 if (newData[j].used)
378 continue;
379
380 if (newData[j].url == oldData[i].url) {
381 // The background image and other data may have changed.
382 oldData[i] = newData[j];
383 oldData[i].updated = true;
384 newData[j].used = true;
385 break;
386 }
387 }
388 }
389
390 // Look through old pages that haven't been updated yet; replace them.
391 for (var i = 0; i < oldData.length; i++) {
392 if (oldData[i] && oldData[i].updated)
393 continue;
394
395 for (var j = 0; j < newData.length; j++) {
396 if (newData[j].used)
397 continue;
398
399 oldData[i] = newData[j];
400 oldData[i].updated = true;
401 newData[j].used = true;
402 break;
403 }
404
405 if (oldData[i] && !oldData[i].updated)
406 oldData[i] = null;
407 }
408
409 // Clear 'updated' flags so this function will work next time it's called.
410 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
411 if (oldData[i])
412 oldData[i].updated = false;
413 }
414
415 return oldData;
416 }
417
418 return {
419 SuggestionsPage: SuggestionsPage,
420 refreshData: refreshData,
421 };
422 });
423
424 ntp4.getNewTabView().appendTilePage(new ntp4.SuggestionsPage(),
425 ntp4.localStrings.getString('suggestions'),
Evan Stade 2012/02/17 20:07:35 random amount of indent
GeorgeY 2012/02/21 23:32:23 Sorry, changed the call, forgot to adjust the inde
426 false);
427 chrome.send('getSuggestions');
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698