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

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

Issue 8637001: [NTP4] Auto-deletion of empty apps panes. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebasing rbyers and csilv's changes Created 9 years 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
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 /** 5 /**
6 * @fileoverview PageListView implementation. 6 * @fileoverview PageListView implementation.
7 * PageListView manages page list, dot list, switcher buttons and handles apps 7 * PageListView manages page list, dot list, switcher buttons and handles apps
8 * pages callbacks from backend. 8 * pages callbacks from backend.
9 * 9 *
10 * Note that you need to have AppLauncherHandler in your WebUI to use this code. 10 * Note that you need to have AppLauncherHandler in your WebUI to use this code.
(...skipping 11 matching lines...) Expand all
22 /** 22 /**
23 * Creates a PageListView object. 23 * Creates a PageListView object.
24 * @constructor 24 * @constructor
25 * @extends {Object} 25 * @extends {Object}
26 */ 26 */
27 function PageListView() { 27 function PageListView() {
28 } 28 }
29 29
30 PageListView.prototype = { 30 PageListView.prototype = {
31 /** 31 /**
32 * A list of all 'apps-page' elements.
33 * @type {!NodeList|undefined}
34 */
35 appsPages: undefined,
36
37 /**
32 * The CardSlider object to use for changing app pages. 38 * The CardSlider object to use for changing app pages.
33 * @type {CardSlider|undefined} 39 * @type {CardSlider|undefined}
34 */ 40 */
35 cardSlider: undefined, 41 cardSlider: undefined,
36 42
37 /** 43 /**
38 * The frame div for cardSlider. 44 * The 'dots-list' element.
39 * @type {!Element|undefined} 45 * @type {!Element|undefined}
40 */ 46 */
41 sliderFrame: undefined, 47 dotList: undefined,
42 48
43 /** 49 /**
44 * The 'page-list' element. 50 * EventTracker for managing event listeners for page events.
45 * @type {!Element|undefined} 51 * @type {!EventTracker}
46 */ 52 */
47 pageList: undefined, 53 eventTracker: new EventTracker,
48 54
49 /** 55 /**
50 * A list of all 'tile-page' elements. 56 * If non-null, this is the ID of the app to highlight to the user the next
51 * @type {!NodeList|undefined} 57 * time getAppsCallback runs. "Highlight" in this case means to switch to
58 * the page and run the new tile animation.
59 * @type {String}
52 */ 60 */
53 tilePages: undefined, 61 highlightAppId: null,
54
55 /**
56 * A list of all 'apps-page' elements.
57 * @type {!NodeList|undefined}
58 */
59 appsPages: undefined,
60 62
61 /** 63 /**
62 * The Most Visited page. 64 * The Most Visited page.
63 * @type {!Element|undefined} 65 * @type {!Element|undefined}
64 */ 66 */
65 mostVisitedPage: undefined, 67 mostVisitedPage: undefined,
66 68
67 /** 69 /**
68 * The 'dots-list' element. 70 * The 'page-list' element.
69 * @type {!Element|undefined} 71 * @type {!Element|undefined}
70 */ 72 */
71 dotList: undefined, 73 pageList: undefined,
72 74
73 /** 75 /**
74 * The left and right paging buttons. 76 * The left and right paging buttons.
75 * @type {!Element|undefined} 77 * @type {!Element|undefined}
76 */ 78 */
77 pageSwitcherStart: undefined, 79 pageSwitcherStart: undefined,
78 pageSwitcherEnd: undefined, 80 pageSwitcherEnd: undefined,
79 81
80 /** 82 /**
81 * The 'trash' element. Note that technically this is unnecessary,
82 * JavaScript creates the object for us based on the id. But I don't want
83 * to rely on the ID being the same, and JSCompiler doesn't know about it.
84 * @type {!Element|undefined}
85 */
86 trash: undefined,
87
88 /**
89 * The type of page that is currently shown. The value is a numerical ID. 83 * The type of page that is currently shown. The value is a numerical ID.
90 * @type {number} 84 * @type {number}
91 */ 85 */
92 shownPage: 0, 86 shownPage: 0,
93 87
94 /** 88 /**
95 * The index of the page that is currently shown, within the page type. 89 * The index of the page that is currently shown, within the page type.
96 * For example if the third Apps page is showing, this will be 2. 90 * For example if the third Apps page is showing, this will be 2.
97 * @type {number} 91 * @type {number}
98 */ 92 */
99 shownPageIndex: 0, 93 shownPageIndex: 0,
100 94
101 /** 95 /**
102 * EventTracker for managing event listeners for page events. 96 * The frame div for this.cardSlider.
103 * @type {!EventTracker} 97 * @type {!Element|undefined}
104 */ 98 */
105 eventTracker: new EventTracker, 99 sliderFrame: undefined,
106 100
107 /** 101 /**
108 * If non-null, this is the ID of the app to highlight to the user the next 102 * A list of all 'tile-page' elements.
109 * time getAppsCallback runs. "Highlight" in this case means to switch to 103 * @type {!NodeList|undefined}
110 * the page and run the new tile animation.
111 * @type {String}
112 */ 104 */
113 highlightAppId: null, 105 tilePages: undefined,
106
107 /**
108 * The 'trash' element. Note that technically this is unnecessary,
109 * JavaScript creates the object for us based on the id. But I don't want
110 * to rely on the ID being the same, and JSCompiler doesn't know about it.
111 * @type {!Element|undefined}
112 */
113 trash: undefined,
114 114
115 /** 115 /**
116 * Initializes page list view. 116 * Initializes page list view.
117 * @param {!Element} pageList A DIV element to host all pages. 117 * @param {!Element} pageList A DIV element to host all pages.
118 * @param {!Element} dotList An UL element to host nav dots. Each dot 118 * @param {!Element} dotList An UL element to host nav dots. Each dot
119 * represents a page. 119 * represents a page.
120 * @param {!Element} cardSliderFrame The card slider frame that hosts 120 * @param {!Element} cardSliderFrame The card slider frame that hosts
121 * pageList and switcher buttons. 121 * pageList and switcher buttons.
122 * @param {!Element|undefined} opt_trash Optional trash element. 122 * @param {!Element|undefined} opt_trash Optional trash element.
123 * @param {!Element|undefined} opt_pageSwitcherStart Optional start page 123 * @param {!Element|undefined} opt_pageSwitcherStart Optional start page
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
176 window.addEventListener('resize', this.onWindowResize_.bind(this)); 176 window.addEventListener('resize', this.onWindowResize_.bind(this));
177 177
178 // Update apps when online state changes. 178 // Update apps when online state changes.
179 window.addEventListener('online', 179 window.addEventListener('online',
180 this.updateOfflineEnabledApps_.bind(this)); 180 this.updateOfflineEnabledApps_.bind(this));
181 window.addEventListener('offline', 181 window.addEventListener('offline',
182 this.updateOfflineEnabledApps_.bind(this)); 182 this.updateOfflineEnabledApps_.bind(this));
183 }, 183 },
184 184
185 /** 185 /**
186 * Called by chrome when a new app has been added to chrome or has been
187 * enabled if previously disabled.
188 * @param {Object} appData A data structure full of relevant information for
189 * the app.
190 */
191 appAdded: function(appData, opt_highlight) {
192 if (appData.id == this.highlightAppId) {
193 opt_highlight = true;
194 this.highlightAppId = null;
195 }
196
197 var pageIndex = appData.page_index || 0;
198 this.ensureAppsPageAtIndex_(pageIndex);
199
200 var page = this.appsPages[pageIndex];
201 var app = $(appData.id);
202 if (app)
203 app.replaceAppData(appData);
204 else
205 page.appendApp(appData, opt_highlight);
206 },
207
208 /**
209 * Called by chrome when an existing app has been disabled or
210 * removed/uninstalled from chrome.
211 * @param {Object} appData A data structure full of relevant information for
212 * the app.
213 * @param {boolean} isUninstall True if the app is being uninstalled;
214 * false if the app is being disabled.
215 */
216 appRemoved: function(appData, isUninstall, fromPage) {
217 var app = $(appData.id);
218 assert(app, 'trying to remove an app that doesn\'t exist');
219
220 if (!isUninstall) {
221 app.replaceAppData(appData);
222 } else {
223 // Unset the ID immediately, because the app is already gone. But leave
224 // the tile on the page as it animates out.
225 app.id = '';
226 // If the uninstall was from this page, run the blipout animation and
227 // remove the tile in the webkitAnimationEnd handler in apps_page.js.
228 // Otherwise delete the tile without auto-deleting the page to avoid
229 // re-deleting the same page (or the page that slid in to take its
230 // place).
231 if (fromPage) {
232 app.classList.add('removing-tile-contents');
233 } else {
234 var tilePage = app.tile.tilePage;
235 app.tile.remove(false, true);
236 this.removeAppsPageIfEmpty(tilePage, false, true);
237 }
238 }
239 },
240
241 /**
242 * Re-order apps on this invactive page when an active NTP gets re-ordered.
243 * @param {Object} data Position hashmap ordered by app id with indices:
244 * {id => {page_index: ##, app_launch_index: ##}}
245 */
246 appsReordered: function(data) {
247 var pages = Object.keys(data);
248 for (var i = 0; i < pages.length; ++i) {
249 var page = data[pages[i]];
250 var indices = Object.keys(page);
251 for (var j = 0; j < indices.length; ++j) {
252 var app = $(page[indices[j]]);
253 if (i != this.getAppsPageIndex(app.tile.tilePage) ||
254 j != app.tile.index) {
255 var tile = app.tile.remove(false, true);
256 this.ensureAppsPageAtIndex_(i);
257 var appsPage = this.appsPages[i];
258 appsPage.addTileAt(tile.firstChild, j);
259 if (appsPage.classList.contains('selected-card'))
260 app.loadIcon();
261 }
262 }
263 }
264 for (var i = 0; i < this.appsPages.length; ++i) {
265 // If a page is now empty, remove it and move backward one in our
266 // array-like object as it is a live NodeList and the length/indice
267 // change when you update it. Otherwise, reuse cleanupDrag() to
268 // re-position the tiles like when a user is done dragging.
269 if (this.removeAppsPageIfEmpty(this.appsPages[i], false, true))
270 --i;
271 else
272 this.appsPages[i].cleanupDrag();
273 }
274 },
275
276 /**
186 * Appends a tile page. 277 * Appends a tile page.
187 * 278 *
188 * @param {TilePage} page The page element. 279 * @param {TilePage} page The page element.
189 * @param {string} title The title of the tile page. 280 * @param {string} title The title of the tile page.
190 * @param {bool} titleIsEditable If true, the title can be changed. 281 * @param {bool} titleIsEditable If true, the title can be changed.
191 * @param {TilePage} opt_refNode Optional reference node to insert in front 282 * @param {TilePage} opt_refNode Optional reference node to insert in front
Evan Stade 2011/12/02 00:53:24 never end your sentences in a preposition. This sh
Dan Beam 2011/12/02 01:25:11 I read this part too late, damn it, lol. "I need
192 * of. 283 * of.
193 * When opt_refNode is falsey, |page| will just be appended to the end of 284 * When opt_refNode is falsey, |page| will just be appended to the end of
194 * the page list. 285 * the page list.
195 */ 286 */
196 appendTilePage: function(page, title, titleIsEditable, opt_refNode) { 287 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
197 // When opt_refNode is falsey, insertBefore acts just like appendChild. 288 // When opt_refNode is falsey, insertBefore acts just like appendChild.
198 this.pageList.insertBefore(page, opt_refNode); 289 this.pageList.insertBefore(page, opt_refNode);
199 290
291 if (!document.documentElement.classList.contains('starting-up')) {
292 // If we're inserting a card in front of the current card, add 1 to the
293 // current index to adjust for the new card in the order.
294 var index = Array.prototype.indexOf.call(this.tilePages, page);
295 if (typeof this.cardSlider.currentCard != 'undefined' &&
296 index <= this.cardSlider.currentCard) {
297 this.cardSlider.currentCard += 1;
298 }
299 if (page instanceof ntp4.AppsPage &&
300 this.shownPage == templateData['apps_page_id'] &&
301 this.getAppsPageIndex(page) <= this.shownPageIndex) {
302 this.shownPageIndex += 1;
303 }
304 }
305
200 // Remember special MostVisitedPage. 306 // Remember special MostVisitedPage.
201 if (typeof ntp4.MostVisitedPage != 'undefined' && 307 if (typeof ntp4.MostVisitedPage != 'undefined' &&
202 page instanceof ntp4.MostVisitedPage) { 308 page instanceof ntp4.MostVisitedPage) {
203 assert(this.tilePages.length == 1, 309 assert(this.tilePages.length == 1,
204 'MostVisitedPage should be added as first tile page'); 310 'MostVisitedPage should be added as first tile page');
205 this.mostVisitedPage = page; 311 this.mostVisitedPage = page;
206 } 312 }
207 313
208 // If we're appending an AppsPage and it's a temporary page, animate it. 314 // If we're appending an AppsPage and it's a temporary page, animate it.
209 var animate = page instanceof ntp4.AppsPage && 315 var animate = page instanceof ntp4.AppsPage &&
210 page.classList.contains('temporary'); 316 page.classList.contains('temporary');
211 // Make a deep copy of the dot template to add a new one. 317 // Make a deep copy of the dot template to add a new one.
212 var newDot = new ntp4.NavDot(page, title, titleIsEditable, animate); 318 var newDot = new ntp4.NavDot(page, title, titleIsEditable, animate);
213 page.navigationDot = newDot; 319 page.navigationDot = newDot;
214 this.dotList.insertBefore(newDot, opt_refNode ? opt_refNode.navigationDot 320 this.dotList.insertBefore(newDot, opt_refNode ? opt_refNode.navigationDot
215 : null); 321 : null);
216 // Set a tab index on the first dot. 322 // Set a tab index on the first dot.
217 if (this.dotList.dots.length == 1) 323 if (this.dotList.dots.length == 1)
218 newDot.tabIndex = 3; 324 newDot.tabIndex = 3;
219 325
220 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this)); 326 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this));
221 }, 327 },
222 328
223 /** 329 /**
224 * Called by chrome when an existing app has been disabled or 330 * Callback invoked by chrome whenever an app preference changes.
225 * removed/uninstalled from chrome. 331 * @param {Object} data An object with all the data on available
226 * @param {Object} appData A data structure full of relevant information for 332 * applications.
227 * the app.
228 * @param {boolean} isUninstall True if the app is being uninstalled;
229 * false if the app is being disabled.
230 */ 333 */
231 appRemoved: function(appData, isUninstall) { 334 appsPrefChangedCallback: function(data) {
232 var app = $(appData.id); 335 for (var i = 0; i < data.apps.length; ++i) {
233 assert(app, 'trying to remove an app that doesn\'t exist'); 336 var el = $(data.apps[i].id);
337 // There won't be an element when the temporary page is first saved on a
338 // drop on a new apps page.
339 if (el)
340 el.appData = data.apps[i];
341 }
234 342
235 if (!isUninstall) 343 // Set the App dot names. Skip dots that don't belong to apps pages.
236 app.replaceAppData(appData); 344 var dots = this.dotList.getElementsByClassName('dot');
237 else 345 var firstAppDot = this.getTilePageIndex(this.appsPages[0]);
238 app.remove(); 346 for (var i = 0; i < this.appsPages.length; ++i) {
347 dots[firstAppDot + i].displayTitle = data.appsPageNames[i] || '';
348 }
239 }, 349 },
240 350
241 /** 351 /**
352 * Handler for CARD_CHANGED on cardSlider.
353 * @param {Event} e The CARD_CHANGED event.
354 * @private
355 */
356 cardChangedHandler_: function(e) {
357 var page = e.cardSlider.currentCardValue;
358
359 // Don't change shownPage until startup is done (and page changes actually
360 // reflect user actions).
361 if (!document.documentElement.classList.contains('starting-up')) {
362 var prevPage = this.shownPage;
363 var prevIndex = this.shownPageIndex;
364 if (page.classList.contains('apps-page')) {
365 this.shownPage = templateData['apps_page_id'];
366 this.shownPageIndex = this.getAppsPageIndex(page);
367 } else if (page.classList.contains('most-visited-page')) {
368 this.shownPage = templateData['most_visited_page_id'];
369 this.shownPageIndex = 0;
370 } else {
371 console.error('unknown page selected');
372 }
373 if (prevPage != this.shownPage || prevIndex != this.shownPageIndex)
374 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
375 }
376
377 // Update the active dot
378 var curDot = this.dotList.getElementsByClassName('selected')[0];
379 if (curDot)
380 curDot.classList.remove('selected');
381 page.navigationDot.classList.add('selected');
382 this.updatePageSwitchers();
383 },
384
385 /**
386 * Ensure there is an apps page at the given |index|. If index is smaller
387 * than the current number of apps pages, additional apps pages will be
388 * created.
389 * @param {number} index An apps page index to ensure exists.
390 */
391 ensureAppsPageAtIndex_: function(index) {
392 if (index >= this.appsPages.length) {
393 var pageName = localStrings.getString('appDefaultPageName');
394 while (index >= this.appsPages.length)
395 this.appendTilePage(new ntp4.AppsPage(), pageName, true);
396 this.updateSliderCards();
397 }
398 },
399
400 /**
401 * Called whenever tiles should be re-arranging themselves out of the way
402 * of a moving or insert tile.
403 */
404 enterRearrangeMode: function() {
405 var tempPage = new ntp4.AppsPage();
406 tempPage.classList.add('temporary');
407 var pageName = localStrings.getString('appDefaultPageName');
408 this.appendTilePage(tempPage, pageName, true);
409 this.updateSliderCards();
410 this.updatePageSwitchers();
411
412 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved())
413 $('footer').classList.add('showing-trash-mode');
414 },
415
416 /**
242 * Callback invoked by chrome with the apps available. 417 * Callback invoked by chrome with the apps available.
243 * 418 *
244 * Note that calls to this function can occur at any time, not just in 419 * Note that calls to this function can occur at any time, not just in
245 * response to a getApps request. For example, when a user 420 * response to a getApps request. For example, when a user
246 * installs/uninstalls an app on another synchronized devices. 421 * installs/uninstalls an app on another synchronized devices.
247 * @param {Object} data An object with all the data on available 422 * @param {Object} data An object with all the data on available
248 * applications. 423 * applications.
249 */ 424 */
250 getAppsCallback: function(data) { 425 getAppsCallback: function(data) {
251 var startTime = Date.now(); 426 var startTime = Date.now();
252 427
253 // Clear any existing apps pages and dots. 428 // Clear any existing apps pages and dots.
254 // TODO(rbyers): It might be nice to preserve animation of dots after an 429 // TODO(rbyers): It might be nice to preserve animation of dots after an
255 // uninstall. Could we re-use the existing page and dot elements? It 430 // uninstall. Could we re-use the existing page and dot elements? It
256 // seems unfortunate to have Chrome send us the entire apps list after an 431 // seems unfortunate to have Chrome send us the entire apps list after an
257 // uninstall. 432 // uninstall.
258 while (this.appsPages.length > 0) { 433 while (this.appsPages.length > 0) {
259 var page = this.appsPages[0]; 434 var page = this.appsPages[0];
260 var dot = page.navigationDot;
261 435
262 this.eventTracker.remove(page); 436 this.eventTracker.remove(page);
263 page.tearDown(); 437 this.removeTilePage(page);
264 page.parentNode.removeChild(page);
265 dot.parentNode.removeChild(dot);
266 } 438 }
267 439
268 // Get the array of apps and add any special synthesized entries 440 // Get the array of apps and add any special synthesized entries
269 var apps = data.apps; 441 var apps = data.apps;
270 442
271 // Get a list of page names 443 // Get a list of page names
272 var pageNames = data.appPageNames; 444 var pageNames = data.appsPageNames;
273 445
274 function stringListIsEmpty(list) { 446 function stringListIsEmpty(list) {
275 for (var i = 0; i < list.length; i++) { 447 for (var i = 0; i < list.length; i++) {
276 if (list[i]) 448 if (list[i])
277 return false; 449 return false;
278 } 450 }
279 return true; 451 return true;
280 } 452 }
281 453
282 // Sort by launch index 454 // Sort by launch index
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
319 this.appAdded(highlightApp, true); 491 this.appAdded(highlightApp, true);
320 492
321 // Mark the current page. 493 // Mark the current page.
322 this.cardSlider.currentCardValue.navigationDot.classList.add('selected'); 494 this.cardSlider.currentCardValue.navigationDot.classList.add('selected');
323 logEvent('apps.layout: ' + (Date.now() - startTime)); 495 logEvent('apps.layout: ' + (Date.now() - startTime));
324 496
325 document.documentElement.classList.remove('starting-up'); 497 document.documentElement.classList.remove('starting-up');
326 }, 498 },
327 499
328 /** 500 /**
329 * Called by chrome when a new app has been added to chrome or has been 501 * Returns the index of the given apps page.
330 * enabled if previously disabled. 502 * @param {AppsPage} page The AppsPage we wish to find.
331 * @param {Object} appData A data structure full of relevant information for 503 * @return {number} The index of |page| or -1 if it is not in the
332 * the app. 504 * collection.
333 */ 505 */
334 appAdded: function(appData, opt_highlight) { 506 getAppsPageIndex: function(page) {
335 if (appData.id == this.highlightAppId) { 507 return Array.prototype.indexOf.call(this.appsPages, page);
336 opt_highlight = true;
337 this.highlightAppId = null;
338 }
339
340 var pageIndex = appData.page_index || 0;
341
342 if (pageIndex >= this.appsPages.length) {
343 while (pageIndex >= this.appsPages.length) {
344 this.appendTilePage(new ntp4.AppsPage(),
345 localStrings.getString('appDefaultPageName'),
346 true);
347 }
348 this.updateSliderCards();
349 }
350
351 var page = this.appsPages[pageIndex];
352 var app = $(appData.id);
353 if (app)
354 app.replaceAppData(appData);
355 else
356 page.appendApp(appData, opt_highlight);
357 }, 508 },
358 509
359 /** 510 /**
360 * Callback invoked by chrome whenever an app preference changes. 511 * Returns the index of a given tile page.
361 * @param {Object} data An object with all the data on available 512 * @param {TilePage} page The TilePage we wish to find.
362 * applications. 513 * @return {number} The index of |page| or -1 if it is not in the
514 * collection.
363 */ 515 */
364 appsPrefChangedCallback: function(data) { 516 getTilePageIndex: function(page) {
365 for (var i = 0; i < data.apps.length; ++i) { 517 return Array.prototype.indexOf.call(this.tilePages, page);
366 $(data.apps[i].id).appData = data.apps[i];
367 }
368
369 // Set the App dot names. Skip the first dot (Most Visited).
370 var dots = this.dotList.getElementsByClassName('dot');
371 var start = this.mostVisitedPage ? 1 : 0;
372 for (var i = start; i < dots.length; ++i) {
373 dots[i].displayTitle = data.appPageNames[i - start] || '';
374 }
375 },
376
377 /**
378 * Invoked whenever the pages in apps-page-list have changed so that
379 * the Slider knows about the new elements.
380 */
381 updateSliderCards: function() {
382 var pageNo = Math.min(this.cardSlider.currentCard,
383 this.tilePages.length - 1);
384 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
385 pageNo);
386 switch (this.shownPage) {
387 case templateData['apps_page_id']:
388 this.cardSlider.selectCardByValue(
389 this.appsPages[Math.min(this.shownPageIndex,
390 this.appsPages.length - 1)]);
391 break;
392 case templateData['most_visited_page_id']:
393 if (this.mostVisitedPage)
394 this.cardSlider.selectCardByValue(this.mostVisitedPage);
395 break;
396 }
397 },
398
399 /**
400 * Called whenever tiles should be re-arranging themselves out of the way
401 * of a moving or insert tile.
402 */
403 enterRearrangeMode: function() {
404 var tempPage = new ntp4.AppsPage();
405 tempPage.classList.add('temporary');
406 this.appendTilePage(tempPage,
407 localStrings.getString('appDefaultPageName'),
408 true);
409 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
410 if (this.cardSlider.currentCard >= tempIndex)
411 this.cardSlider.currentCard += 1;
412 this.updateSliderCards();
413
414 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved())
415 $('footer').classList.add('showing-trash-mode');
416 }, 518 },
417 519
418 /** 520 /**
419 * Invoked whenever some app is released 521 * Invoked whenever some app is released
420 */ 522 */
421 leaveRearrangeMode: function() { 523 leaveRearrangeMode: function() {
422 var tempPage = document.querySelector('.tile-page.temporary'); 524 var tempPage = document.querySelector('.tile-page.temporary');
423 var dot = tempPage.navigationDot; 525 // If there's a temp page and it wasn't removed for being empty.
424 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) { 526 if (tempPage && !this.removeAppsPageIfEmpty(tempPage, true)) {
425 dot.animateRemove(); 527 this.saveAppsPageName(tempPage, tempPage.navigationDot.displayTitle);
426 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
427 if (this.cardSlider.currentCard > tempIndex)
428 this.cardSlider.currentCard -= 1;
429 tempPage.parentNode.removeChild(tempPage);
430 this.updateSliderCards();
431 } else {
432 tempPage.classList.remove('temporary'); 528 tempPage.classList.remove('temporary');
433 this.saveAppPageName(tempPage,
434 localStrings.getString('appDefaultPageName'));
435 } 529 }
436 530
437 $('footer').classList.remove('showing-trash-mode'); 531 $('footer').classList.remove('showing-trash-mode');
438 }, 532 },
439 533
440 /** 534 /**
535 * Move to a non-empty page before/while we delete an empty one.
536 * @param {Element} appsPage An empty apps page that we want to move off of
537 * (probably because it's empty).
538 * @param {=boolean} animate Whether we should animate or not.
539 * @param {=function} callback A callback to be executed when the move is
540 * done.
541 */
542 moveOffEmptyAppsPage: function(appsPage, opt_animate, opt_callback) {
543 assert(appsPage instanceof ntp4.AppsPage,
544 '|appsPage| is really an AppsPage');
545 // Select the page further toward the end than the empty |appsPage|. If
546 // the empty |appsPage| is at the end, the apps page before it will be
547 // selected.
548 var index = this.getAppsPageIndex(appsPage);
549 assert(index >= 0, '|appsPage| is in this.appsPages');
550 var direction = (index == this.appsPages.length - 1 ? -1 : 1);
551 this.cardSlider.selectCard(this.cardSlider.currentCard + direction,
552 opt_animate, opt_callback);
553 },
554
555 /**
556 * Handler for key events on the page. Ctrl-Arrow will switch the visible
557 * page.
558 * @param {Event} e The KeyboardEvent.
559 * @private
560 */
561 onDocKeyDown_: function(e) {
562 if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
563 return;
564
565 var direction = 0;
566 if (e.keyIdentifier == 'Left')
567 direction = -1;
568 else if (e.keyIdentifier == 'Right')
569 direction = 1;
570 else
571 return;
572
573 var cardIndex =
574 (this.cardSlider.currentCard + direction +
575 this.cardSlider.cardCount) % this.cardSlider.cardCount;
576 this.cardSlider.selectCard(cardIndex, true);
577
578 e.stopPropagation();
579 },
580
581 /**
441 * Callback for the 'pagelayout' event. 582 * Callback for the 'pagelayout' event.
442 * @param {Event} e The event. 583 * @param {Event} e The event.
443 */ 584 */
444 onPageLayout_: function(e) { 585 onPageLayout_: function(e) {
445 if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) != 586 if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) !=
446 this.cardSlider.currentCard) { 587 this.cardSlider.currentCard) {
447 return; 588 return;
448 } 589 }
449 590
450 this.updatePageSwitchers(); 591 this.updatePageSwitchers();
451 }, 592 },
452 593
453 /** 594 /**
595 * Window resize handler.
596 * @private
597 */
598 onWindowResize_: function(e) {
599 this.cardSlider.resize(this.sliderFrame.offsetWidth);
600 this.updatePageSwitchers();
601 },
602
603 /**
604 * Utility function to remove both a tile page and corresponding nav dot.
605 * NOTE: If |animated| is true, this function will return before the
606 * animation is finished.
607 * @param {Element} appsPage An apps page to be removed if it has no tiles.
608 * @param {=boolean} opt_animate An optional param to animate the removal.
609 * @param {=boolean} opt_dontSave Whether we should notify other NTPs this
610 * page was deleted (defaults to yes).
611 * @returns {boolean} If the page was removed.
612 */
613 removeAppsPageIfEmpty: function(appsPage, opt_animate, opt_dontSave) {
614 if (appsPage && appsPage.tileCount === 0) {
615 var whenDone = function() {
616 if (!opt_dontSave) {
617 chrome.send('deleteAppsPage',
618 [this.getAppsPageIndex(appsPage),
619 appsPage.classList.contains('temporary')]);
620 }
621 this.removeTilePage(appsPage, opt_animate);
622 this.updateSliderCards();
623 this.updatePageSwitchers();
624 };
625 if (appsPage == this.cardSlider.currentCardValue)
626 this.moveOffEmptyAppsPage(appsPage, opt_animate, whenDone.bind(this));
627 else
628 whenDone.call(this);
629 return true;
630 }
631 return false;
632 },
633
634 /**
635 * Removes a page and navigation dot (if the navdot exists).
636 * @param {Element} page The page to be removed.
637 * @param {boolean} animate If the removal should be animated.
638 */
639 removeTilePage: function(page, animate) {
640 if (!document.documentElement.classList.contains('starting-up')) {
641 // If the card being deleted is before the current card, keep the same
642 // spot in the list by removing 1 from the current card index.
643 var index = Array.prototype.indexOf.call(this.tilePages, page);
644 if (index < this.cardSlider.currentCard) {
645 this.cardSlider.currentCard -= 1;
646 }
647 // If the page we're about to delete is in front of the current page,
648 // remove 1 from the shown page index.
649 if (page instanceof ntp4.AppsPage &&
650 this.shownPage == templateData['apps_page_id'] &&
651 this.getAppsPageIndex(page) < this.shownPageIndex) {
652 this.shownPageIndex -= 1;
653 }
654 }
655 if (page.navigationDot)
656 page.navigationDot.remove(animate);
657 page.remove();
658 },
659
660 /*
661 * Save the name of an app page.
662 * Store the app page name into the preferences store.
663 * @param {AppsPage} appsPage The app page for which we wish to save.
664 * @param {string} name The name of the page.
665 */
666 saveAppsPageName: function(appsPage, name) {
667 var index = this.getAppsPageIndex(appsPage);
668 assert(index != -1);
669 chrome.send('saveAppsPageName', [name, index]);
670 },
671
672 /**
454 * Adjusts the size and position of the page switchers according to the 673 * Adjusts the size and position of the page switchers according to the
455 * layout of the current card. 674 * layout of the current card.
456 */ 675 */
457 updatePageSwitchers: function() { 676 updatePageSwitchers: function() {
458 if (!this.pageSwitcherStart || !this.pageSwitcherEnd) 677 if (!this.pageSwitcherStart || !this.pageSwitcherEnd)
459 return; 678 return;
460 679
461 var page = this.cardSlider.currentCardValue; 680 var page = this.cardSlider.currentCardValue;
462 681
463 this.pageSwitcherStart.hidden = !page || 682 this.pageSwitcherStart.hidden = !page ||
(...skipping 17 matching lines...) Expand all
481 pageSwitcherRight.style.right = scrollbarWidth + 'px'; 700 pageSwitcherRight.style.right = scrollbarWidth + 'px';
482 701
483 var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px'; 702 var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px';
484 pageSwitcherLeft.style.top = offsetTop; 703 pageSwitcherLeft.style.top = offsetTop;
485 pageSwitcherRight.style.top = offsetTop; 704 pageSwitcherRight.style.top = offsetTop;
486 pageSwitcherLeft.style.paddingBottom = offsetTop; 705 pageSwitcherLeft.style.paddingBottom = offsetTop;
487 pageSwitcherRight.style.paddingBottom = offsetTop; 706 pageSwitcherRight.style.paddingBottom = offsetTop;
488 }, 707 },
489 708
490 /** 709 /**
491 * Returns the index of the given page. 710 * Invoked whenever the pages in apps-page-list have changed so that
492 * @param {AppsPage} page The AppsPage for we wish to find. 711 * the Slider knows about the new elements.
493 * @return {number} The index of |page|, or -1 if it is not here.
494 */ 712 */
495 getAppsPageIndex: function(page) { 713 updateSliderCards: function() {
496 return Array.prototype.indexOf.call(this.appsPages, page); 714 var index = -1;
715 var prevPage = this.shownPage;
716 var prevIndex = this.shownPageIndex;
717 var tiles = Array.prototype.slice.call(this.tilePages);
718 // It doesn't hurt to clamp value this every time, even if it's only
719 // applicable to apps. It helps self-heal strange / un-expected data, i.e.
720 // going from an apps page to the most visited page directly without
721 // setting this.shownPageIndex = 0 somehow.
722 this.shownPageIndex = Math.max(0, Math.min(this.shownPageIndex,
723 this.appsPages.length - 1));
724 switch (this.shownPage) {
725 case templateData['apps_page_id']:
726 index = tiles.indexOf(this.appsPages[this.shownPageIndex]);
727 break;
728 case templateData['most_visited_page_id']:
729 index = tiles.indexOf(this.mostVisitedPage);
730 break;
731 }
732 // If shownPage was saved as a page that's now disabled or the shownPage
733 // doesn't exist any more, index will be -1. Change to the preferred
734 // default page (first apps pane) in this case.
735 if (index < 0) {
736 this.shownPage = templateData['apps_page_id'];
737 index = tiles.indexOf(this.appsPages[0]);
738 }
739 // Set the new cards and index.
740 this.cardSlider.setCards(tiles, index);
741 // If we got new page or index, update the pref.
742 if (prevIndex != this.shownPageIndex || prevPage != this.shownPage)
743 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
497 }, 744 },
498 745
499 /** 746 /**
500 * Handler for CARD_CHANGED on cardSlider.
501 * @param {Event} e The CARD_CHANGED event.
502 * @private
503 */
504 cardChangedHandler_: function(e) {
505 var page = e.cardSlider.currentCardValue;
506
507 // Don't change shownPage until startup is done (and page changes actually
508 // reflect user actions).
509 if (!document.documentElement.classList.contains('starting-up')) {
510 if (page.classList.contains('apps-page')) {
511 this.shownPage = templateData['apps_page_id'];
512 this.shownPageIndex = this.getAppsPageIndex(page);
513 } else if (page.classList.contains('most-visited-page')) {
514 this.shownPage = templateData['most_visited_page_id'];
515 this.shownPageIndex = 0;
516 } else {
517 console.error('unknown page selected');
518 }
519 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
520 }
521
522 // Update the active dot
523 var curDot = this.dotList.getElementsByClassName('selected')[0];
524 if (curDot)
525 curDot.classList.remove('selected');
526 page.navigationDot.classList.add('selected');
527 this.updatePageSwitchers();
528 },
529
530 /*
531 * Save the name of an app page.
532 * Store the app page name into the preferences store.
533 * @param {AppsPage} appPage The app page for which we wish to save.
534 * @param {string} name The name of the page.
535 */
536 saveAppPageName: function(appPage, name) {
537 var index = this.getAppsPageIndex(appPage);
538 assert(index != -1);
539 chrome.send('saveAppPageName', [name, index]);
540 },
541
542 /**
543 * Window resize handler.
544 * @private
545 */
546 onWindowResize_: function(e) {
547 this.cardSlider.resize(this.sliderFrame.offsetWidth);
548 this.updatePageSwitchers();
549 },
550
551 /**
552 * Listener for offline status change events. Updates apps that are 747 * Listener for offline status change events. Updates apps that are
553 * not offline-enabled to be grayscale if the browser is offline. 748 * not offline-enabled to be grayscale if the browser is offline.
554 * @private 749 * @private
555 */ 750 */
556 updateOfflineEnabledApps_: function() { 751 updateOfflineEnabledApps_: function() {
557 var apps = document.querySelectorAll('.app'); 752 var apps = document.querySelectorAll('.app');
558 for (var i = 0; i < apps.length; ++i) { 753 for (var i = 0; i < apps.length; ++i) {
559 if (apps[i].appData.enabled && !apps[i].appData.offline_enabled) { 754 if (apps[i].appData.enabled && !apps[i].appData.offline_enabled) {
560 apps[i].setIcon(); 755 apps[i].setIcon();
561 apps[i].loadIcon(); 756 apps[i].loadIcon();
562 } 757 }
563 } 758 }
564 }, 759 },
565
566 /**
567 * Handler for key events on the page. Ctrl-Arrow will switch the visible
568 * page.
569 * @param {Event} e The KeyboardEvent.
570 * @private
571 */
572 onDocKeyDown_: function(e) {
573 if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
574 return;
575
576 var direction = 0;
577 if (e.keyIdentifier == 'Left')
578 direction = -1;
579 else if (e.keyIdentifier == 'Right')
580 direction = 1;
581 else
582 return;
583
584 var cardIndex =
585 (this.cardSlider.currentCard + direction +
586 this.cardSlider.cardCount) % this.cardSlider.cardCount;
587 this.cardSlider.selectCard(cardIndex, true);
588
589 e.stopPropagation();
590 }
591 }; 760 };
592 761
593 return { 762 return {
594 PageListView: PageListView 763 PageListView: PageListView
595 }; 764 };
596 }); 765 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698