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

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

Issue 1141843004: NTP Zombie Code Slayer II: Suggestions Page (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: and remove urls Created 5 years, 7 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
(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('ntp', function() {
6 'use strict';
7
8 var TilePage = ntp.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. TODO(georgey) make it a template.
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 '<span class="score"></span>';
65
66 this.querySelector('.close-button').title =
67 loadTimeData.getString('removethumbnailtooltip');
68
69 this.tabIndex = -1;
70 this.data_ = null;
71 this.removeAttribute('id');
72 this.title = '';
73 },
74
75 /**
76 * Update the appearance of this tile according to |data|.
77 * @param {{score: number, url: string, title: string, direction: string,
78 * filler: boolean}} data A dictionary of relevant data for the page.
79 * @see chrome/browser/ui/webui/ntp/suggestions_source_top_sites.cc
80 * @see chrome/browser/ui/webui/ntp/most_visited_handler.cc
81 */
82 updateForData: function(data) {
83 if (this.classList.contains('blacklisted') && data) {
84 // Animate appearance of new tile.
85 this.classList.add('new-tile-contents');
86 }
87 this.classList.remove('blacklisted');
88
89 if (!data || data.filler) {
90 if (this.data_)
91 this.reset();
92 return;
93 }
94
95 var id = tileID++;
96 this.id = 'suggestions-tile-' + id;
97 this.data_ = data;
98 this.classList.add('focusable');
99
100 var faviconDiv = this.querySelector('.favicon');
101 var faviconUrl = 'chrome://favicon/size/16@1x/' + data.url;
102 faviconDiv.style.backgroundImage = url(faviconUrl);
103 chrome.send('getFaviconDominantColor', [faviconUrl, this.id]);
104
105 var title = this.querySelector('.title');
106 title.textContent = data.title;
107 title.dir = data.direction;
108
109 var score = this.querySelector('.score');
110 score.textContent = data.score;
111
112 // Sets the tooltip.
113 this.title = data.title;
114
115 var thumbnailUrl;
116 thumbnailUrl = data.urlImage ? data.urlImage :
117 'chrome://thumb/' + data.url;
118
119 this.querySelector('.thumbnail').style.backgroundImage =
120 url(thumbnailUrl);
121
122 this.href = data.url;
123
124 this.classList.remove('filler');
125 },
126
127 /**
128 * Sets the color of the favicon dominant color bar.
129 * @param {string} color The css-parsable value for the color.
130 */
131 set stripeColor(color) {
132 this.querySelector('.color-stripe').style.backgroundColor = color;
133 },
134
135 /**
136 * Handles a click on the tile.
137 * @param {Event} e The click event.
138 */
139 handleClick_: function(e) {
140 if (e.target.classList.contains('close-button')) {
141 this.blacklist_();
142 e.preventDefault();
143 } else {
144 // Records the index of this tile.
145 chrome.send('metricsHandler:recordInHistogram',
146 ['NewTabPage.SuggestedSite', this.index, 8]);
147 chrome.send('suggestedSitesAction',
148 [ntp.NtpFollowAction.CLICKED_TILE]);
149 }
150 },
151
152 /**
153 * Allow blacklisting suggestions site using the keyboard.
154 * @param {Event} e The keydown event.
155 */
156 handleKeyDown_: function(e) {
157 if (!cr.isMac && e.keyCode == 46 || // Del
158 cr.isMac && e.metaKey && e.keyCode == 8) { // Cmd + Backspace
159 this.blacklist_();
160 }
161 },
162
163 /**
164 * Permanently removes a page from Suggestions.
165 */
166 blacklist_: function() {
167 this.showUndoNotification_();
168 chrome.send('blacklistURLFromSuggestions', [this.data_.url]);
169 this.reset();
170 chrome.send('getSuggestions');
171 this.classList.add('blacklisted');
172 },
173
174 /**
175 * Shows notification that you can undo blacklisting.
176 */
177 showUndoNotification_: function() {
178 var data = this.data_;
179 var self = this;
180 var doUndo = function() {
181 chrome.send('removeURLsFromSuggestionsBlacklist', [data.url]);
182 self.updateForData(data);
183 };
184
185 var undo = {
186 action: doUndo,
187 text: loadTimeData.getString('undothumbnailremove'),
188 };
189
190 var undoAll = {
191 action: function() {
192 chrome.send('clearSuggestionsURLsBlacklist');
193 },
194 text: loadTimeData.getString('restoreThumbnailsShort'),
195 };
196
197 ntp.showNotification(
198 loadTimeData.getString('thumbnailremovednotification'),
199 [undo, undoAll]);
200 },
201
202 /**
203 * Set the size and position of the suggestions tile.
204 * @param {number} size The total size of |this|.
205 * @param {number} x The x-position.
206 * @param {number} y The y-position.
207 */
208 setBounds: function(size, x, y) {
209 this.style.width = size + 'px';
210 this.style.height = heightForWidth(size) + 'px';
211
212 this.style.left = x + 'px';
213 this.style.right = x + 'px';
214 this.style.top = y + 'px';
215 },
216
217 /**
218 * Returns whether this element can be 'removed' from chrome (i.e. whether
219 * the user can drag it onto the trash and expect something to happen).
220 * @return {boolean} True, since suggestions pages can always be
221 * blacklisted.
222 */
223 canBeRemoved: function() {
224 return true;
225 },
226
227 /**
228 * Removes this element from chrome, i.e. blacklists it.
229 */
230 removeFromChrome: function() {
231 this.blacklist_();
232 this.parentNode.classList.add('finishing-drag');
233 },
234
235 /**
236 * Called when a drag of this tile has ended (after all animations have
237 * finished).
238 */
239 finalizeDrag: function() {
240 this.parentNode.classList.remove('finishing-drag');
241 },
242
243 /**
244 * Called when a drag is starting on the tile. Updates dataTransfer with
245 * data for this tile (for dragging outside of the NTP).
246 * @param {DataTransfer} dataTransfer The drag event data store.
247 */
248 setDragData: function(dataTransfer) {
249 dataTransfer.setData('Text', this.data_.title);
250 dataTransfer.setData('URL', this.data_.url);
251 },
252 };
253
254 var suggestionsPageGridValues = {
255 // The fewest tiles we will show in a row.
256 minColCount: 2,
257 // The suggestions we will show in a row.
258 maxColCount: 4,
259
260 // The smallest a tile can be.
261 minTileWidth: 122,
262 // The biggest a tile can be. 212 (max thumbnail width) + 2.
263 maxTileWidth: 214,
264
265 // The padding between tiles, as a fraction of the tile width.
266 tileSpacingFraction: 1 / 8,
267 };
268 TilePage.initGridValues(suggestionsPageGridValues);
269
270 /**
271 * Calculates the height for a Suggestion tile for a given width. The size
272 * is based on the thumbnail, which should have a 212:132 ratio.
273 * @return {number} The height.
274 */
275 function heightForWidth(width) {
276 // The 2s are for borders, the 36 is for the title and score.
277 return (width - 2) * 132 / 212 + 2 + 36;
278 }
279
280 var THUMBNAIL_COUNT = 8;
281
282 /**
283 * Creates a new SuggestionsPage object.
284 * @constructor
285 * @extends {TilePage}
286 */
287 function SuggestionsPage() {
288 var el = new TilePage(suggestionsPageGridValues);
289 el.__proto__ = SuggestionsPage.prototype;
290 el.initialize();
291
292 return el;
293 }
294
295 SuggestionsPage.prototype = {
296 __proto__: TilePage.prototype,
297
298 initialize: function() {
299 this.classList.add('suggestions-page');
300 this.data_ = null;
301 this.suggestionsTiles_ = this.getElementsByClassName('suggestions real');
302
303 this.addEventListener('carddeselected', this.handleCardDeselected_);
304 this.addEventListener('cardselected', this.handleCardSelected_);
305 },
306
307 /**
308 * Create blank (filler) tiles.
309 * @private
310 */
311 createTiles_: function() {
312 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
313 this.appendTile(new Suggestion(), false);
314 }
315 },
316
317 /**
318 * Update the tiles after a change to |this.data_|.
319 */
320 updateTiles_: function() {
321 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
322 var page = this.data_[i];
323 var tile = this.suggestionsTiles_[i];
324
325 if (i >= this.data_.length)
326 tile.reset();
327 else
328 tile.updateForData(page);
329 }
330 },
331
332 /**
333 * Handles the 'card deselected' event (i.e. the user clicked to another
334 * pane).
335 * @param {Event} e The CardChanged event.
336 */
337 handleCardDeselected_: function(e) {
338 if (!document.documentElement.classList.contains('starting-up')) {
339 chrome.send('suggestedSitesAction',
340 [ntp.NtpFollowAction.CLICKED_OTHER_NTP_PANE]);
341 }
342 },
343
344 /**
345 * Handles the 'card selected' event (i.e. the user clicked to select the
346 * Suggested pane).
347 * @param {Event} e The CardChanged event.
348 */
349 handleCardSelected_: function(e) {
350 if (!document.documentElement.classList.contains('starting-up'))
351 chrome.send('suggestedSitesSelected');
352 },
353
354 /**
355 * Array of suggestions data objects.
356 * @type {Array}
357 */
358 get data() {
359 return this.data_;
360 },
361 set data(data) {
362 var startTime = Date.now();
363
364 // The first time data is set, create the tiles.
365 if (!this.data_) {
366 this.createTiles_();
367 this.data_ = data.slice(0, THUMBNAIL_COUNT);
368 } else {
369 this.data_ = refreshData(this.data_, data);
370 }
371
372 this.updateTiles_();
373 this.updateFocusableElement();
374 logEvent('suggestions.layout: ' + (Date.now() - startTime));
375 },
376
377 /** @override */
378 shouldAcceptDrag: function(e) {
379 return false;
380 },
381
382 /** @override */
383 heightForWidth: heightForWidth,
384 };
385
386 /**
387 * Executed once the NTP has loaded. Checks if the Suggested pane is
388 * shown or not. If it is shown, the 'suggestedSitesSelected' message is sent
389 * to the C++ code, to record the fact that the user has seen this pane.
390 */
391 SuggestionsPage.onLoaded = function() {
392 if (ntp.getCardSlider() &&
393 ntp.getCardSlider().currentCardValue &&
394 ntp.getCardSlider().currentCardValue.classList
395 .contains('suggestions-page')) {
396 chrome.send('suggestedSitesSelected');
397 }
398 };
399
400 /**
401 * We've gotten additional data for Suggestions page. Update our old data with
402 * the new data. The ordering of the new data is not important, except when a
403 * page is pinned. Thus we try to minimize re-ordering.
404 * @param {Array} oldData The current Suggestions page list.
405 * @param {Array} newData The new Suggestions page list.
406 * @return {Array} The merged page list that should replace the current page
407 * list.
408 */
409 function refreshData(oldData, newData) {
410 oldData = oldData.slice(0, THUMBNAIL_COUNT);
411 newData = newData.slice(0, THUMBNAIL_COUNT);
412
413 // Copy over pinned sites directly.
414 for (var i = 0; i < newData.length; i++) {
415 if (newData[i].pinned) {
416 oldData[i] = newData[i];
417 // Mark the entry as 'updated' so we don't try to update again.
418 oldData[i].updated = true;
419 // Mark the newData page as 'used' so we don't try to re-use it.
420 newData[i].used = true;
421 }
422 }
423
424 // Look through old pages; if they exist in the newData list, keep them
425 // where they are.
426 for (var i = 0; i < oldData.length; i++) {
427 if (!oldData[i] || oldData[i].updated)
428 continue;
429
430 for (var j = 0; j < newData.length; j++) {
431 if (newData[j].used)
432 continue;
433
434 if (newData[j].url == oldData[i].url) {
435 // The background image and other data may have changed.
436 oldData[i] = newData[j];
437 oldData[i].updated = true;
438 newData[j].used = true;
439 break;
440 }
441 }
442 }
443
444 // Look through old pages that haven't been updated yet; replace them.
445 for (var i = 0; i < oldData.length; i++) {
446 if (oldData[i] && oldData[i].updated)
447 continue;
448
449 for (var j = 0; j < newData.length; j++) {
450 if (newData[j].used)
451 continue;
452
453 oldData[i] = newData[j];
454 oldData[i].updated = true;
455 newData[j].used = true;
456 break;
457 }
458
459 if (oldData[i] && !oldData[i].updated)
460 oldData[i] = null;
461 }
462
463 // Clear 'updated' flags so this function will work next time it's called.
464 for (var i = 0; i < THUMBNAIL_COUNT; i++) {
465 if (oldData[i])
466 oldData[i].updated = false;
467 }
468
469 return oldData;
470 }
471
472 return {
473 SuggestionsPage: SuggestionsPage,
474 refreshData: refreshData,
475 };
476 });
477
478 document.addEventListener('ntpLoaded', ntp.SuggestionsPage.onLoaded);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698