OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 }); |
OLD | NEW |