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

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

Powered by Google App Engine
This is Rietveld 408576698