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 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
165 this.sliderFrame = cardSliderFrame; | 165 this.sliderFrame = cardSliderFrame; |
166 this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList, | 166 this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList, |
167 this.sliderFrame.offsetWidth); | 167 this.sliderFrame.offsetWidth); |
168 this.cardSlider.initialize(); | 168 this.cardSlider.initialize(); |
169 | 169 |
170 // Handle the page being changed | 170 // Handle the page being changed |
171 this.pageList.addEventListener( | 171 this.pageList.addEventListener( |
172 cr.ui.CardSlider.EventType.CARD_CHANGED, | 172 cr.ui.CardSlider.EventType.CARD_CHANGED, |
173 this.cardChangedHandler_.bind(this)); | 173 this.cardChangedHandler_.bind(this)); |
174 | 174 |
| 175 // Handle the end of cards being changed (useful if animated). |
| 176 this.pageList.addEventListener( |
| 177 cr.ui.CardSlider.EventType.CARD_CHANGE_ENDED, |
| 178 this.cardChangeEndedHandler_.bind(this)); |
| 179 |
| 180 // Handle cards being added to the card slider. |
| 181 this.pageList.addEventListener( |
| 182 cr.ui.CardSlider.EventType.CARD_ADDED, |
| 183 this.cardAddedHandler_.bind(this)); |
| 184 |
| 185 // Handle cards being removed from the card slider. |
| 186 this.pageList.addEventListener( |
| 187 cr.ui.CardSlider.EventType.CARD_REMOVED, |
| 188 this.cardRemovedHandler_.bind(this)); |
| 189 |
| 190 // Handle tiles being removed from tile pages. |
| 191 this.pageList.addEventListener( |
| 192 ntp4.TilePage.EventType.TILE_REMOVED, |
| 193 this.tileRemovedHandler_.bind(this)); |
| 194 |
175 // Ensure the slider is resized appropriately with the window | 195 // Ensure the slider is resized appropriately with the window |
176 window.addEventListener('resize', this.onWindowResize_.bind(this)); | 196 window.addEventListener('resize', this.onWindowResize_.bind(this)); |
177 | 197 |
178 // Update apps when online state changes. | 198 // Update apps when online state changes. |
179 window.addEventListener('online', | 199 window.addEventListener('online', |
180 this.updateOfflineEnabledApps_.bind(this)); | 200 this.updateOfflineEnabledApps_.bind(this)); |
181 window.addEventListener('offline', | 201 window.addEventListener('offline', |
182 this.updateOfflineEnabledApps_.bind(this)); | 202 this.updateOfflineEnabledApps_.bind(this)); |
183 }, | 203 }, |
184 | 204 |
185 /** | 205 /** |
186 * Appends a tile page. | 206 * Appends a tile page. |
187 * | 207 * |
188 * @param {TilePage} page The page element. | 208 * @param {TilePage} page The page element. |
189 * @param {string} title The title of the tile page. | 209 * @param {string} title The title of the tile page. |
190 * @param {bool} titleIsEditable If true, the title can be changed. | 210 * @param {bool} titleIsEditable If true, the title can be changed. |
191 * @param {TilePage} opt_refNode Optional reference node to insert in front | 211 * @param {TilePage} opt_refNode Optional reference node to insert in front |
192 * of. | 212 * of. |
193 * When opt_refNode is falsey, |page| will just be appended to the end of | 213 * When opt_refNode is falsey, |page| will just be appended to the end of |
194 * the page list. | 214 * the page list. |
195 */ | 215 */ |
196 appendTilePage: function(page, title, titleIsEditable, opt_refNode) { | 216 appendTilePage: function(page, title, titleIsEditable, opt_refNode) { |
197 // When opt_refNode is falsey, insertBefore acts just like appendChild. | 217 if (opt_refNode) { |
198 this.pageList.insertBefore(page, opt_refNode); | 218 var refIndex = this.getTilePageIndex(opt_refNode); |
| 219 this.cardSlider.insertCardAtIndex(page, refIndex); |
| 220 } else { |
| 221 this.cardSlider.appendCard(page); |
| 222 } |
199 | 223 |
200 // Remember special MostVisitedPage. | 224 // Remember special MostVisitedPage. |
201 if (typeof ntp4.MostVisitedPage != 'undefined' && | 225 if (typeof ntp4.MostVisitedPage != 'undefined' && |
202 page instanceof ntp4.MostVisitedPage) { | 226 page instanceof ntp4.MostVisitedPage) { |
203 assert(this.tilePages.length == 1, | 227 assert(this.tilePages.length == 1, |
204 'MostVisitedPage should be added as first tile page'); | 228 'MostVisitedPage should be added as first tile page'); |
205 this.mostVisitedPage = page; | 229 this.mostVisitedPage = page; |
206 } | 230 } |
207 | 231 |
208 // If we're appending an AppsPage and it's a temporary page, animate it. | 232 // If we're appending an AppsPage and it's a temporary page, animate it. |
(...skipping 11 matching lines...) Expand all Loading... |
220 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this)); | 244 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this)); |
221 }, | 245 }, |
222 | 246 |
223 /** | 247 /** |
224 * Called by chrome when an existing app has been disabled or | 248 * Called by chrome when an existing app has been disabled or |
225 * removed/uninstalled from chrome. | 249 * removed/uninstalled from chrome. |
226 * @param {Object} appData A data structure full of relevant information for | 250 * @param {Object} appData A data structure full of relevant information for |
227 * the app. | 251 * the app. |
228 * @param {boolean} isUninstall True if the app is being uninstalled; | 252 * @param {boolean} isUninstall True if the app is being uninstalled; |
229 * false if the app is being disabled. | 253 * false if the app is being disabled. |
| 254 * @param {boolean} fromPage If the removal was from the current page. |
230 */ | 255 */ |
231 appRemoved: function(appData, isUninstall) { | 256 appRemoved: function(appData, isUninstall, fromPage) { |
232 var app = $(appData.id); | 257 var app = $(appData.id); |
233 assert(app, 'trying to remove an app that doesn\'t exist'); | 258 assert(app, 'trying to remove an app that doesn\'t exist'); |
234 | 259 |
235 if (!isUninstall) | 260 if (!isUninstall) { |
236 app.replaceAppData(appData); | 261 app.replaceAppData(appData); |
237 else | 262 } else { |
238 app.remove(); | 263 // If the uninstall was from this page, run the blipout animation and |
| 264 // the tile will be removed in TilePage#onContentsAnimationEnd_. |
| 265 // Otherwise delete the tile without auto-deleting the page to avoid |
| 266 // re-deleting the same page (or the page that slid in to take its |
| 267 // place). |
| 268 if (fromPage) { |
| 269 // Unset the ID immediately, because the app is already gone. But |
| 270 // leave the tile on the page as it animates out. |
| 271 app.id = ''; |
| 272 app.classList.add('removing-tile-contents'); |
| 273 } else { |
| 274 var tilePage = app.tile.tilePage; |
| 275 tilePage.removeTile(app.tile, false, true); |
| 276 this.removeAppsPageIfEmpty_(tilePage, false, true); |
| 277 } |
| 278 } |
239 }, | 279 }, |
240 | 280 |
241 /** | 281 /** |
| 282 * @return {boolean} If the page is still starting up. |
| 283 * @private |
| 284 */ |
| 285 isStartingUp_: function() { |
| 286 return document.documentElement.classList.contains('starting-up'); |
| 287 }, |
| 288 |
| 289 /** |
| 290 * Returns a hashmap of apps pages keyed by page ordinal. |
| 291 * @return {Object.<string, AppsPage>} Map of apps pages. |
| 292 * @private |
| 293 */ |
| 294 getAppsPageOrdinalMap_: function() { |
| 295 var map = {}; |
| 296 for (var i = 0; i < this.appsPages.length; ++i) |
| 297 map[this.appsPages[i].ordinal] = this.appsPages[i]; |
| 298 return map; |
| 299 }, |
| 300 |
| 301 /** |
| 302 * Get an unsorted list of apps page ordinals. |
| 303 */ |
| 304 getAppsPageOrdinals_: function() { |
| 305 return Array.prototype.map.call(this.appsPages, function(page) { |
| 306 return page.ordinal; |
| 307 }); |
| 308 }, |
| 309 |
| 310 /** |
| 311 * Gets an apps page by string ordinal. |
| 312 * @param {string} ordinal The page ordinal we're searching for. |
| 313 * @return {?AppsPage} The apps page with corresponding ordinal or null. |
| 314 * @private |
| 315 */ |
| 316 getAppsPageByOrdinal_: function(ordinal) { |
| 317 assert(typeof ordinal == 'string' && ordinal); |
| 318 return this.getAppsPageOrdinalMap_()[ordinal] || null; |
| 319 }, |
| 320 |
| 321 /** |
| 322 * Find an apps page index via ordinal. |
| 323 * @return {number} The index of the ordinal or -1 if it doesn't exist. |
| 324 */ |
| 325 getPageIndexFromOrdinal_: function(ordinal) { |
| 326 return this.getAppsPageOrdinals_().indexOf(ordinal); |
| 327 }, |
| 328 |
| 329 /** |
| 330 * Finds a position for a specific ordinal in an ordinal map. |
| 331 * @param {Array} ordinalList An array of ordinals. |
| 332 * @param {string} ordinal A specific ordinal to search for. |
| 333 * @return {number} The position where the ordinal should be positioned. |
| 334 */ |
| 335 getPositionForNewOrdinal_: function(ordinalList, ordinal) { |
| 336 assert(Array.isArray(ordinal) && ordinalList.indexOf(ordinal) == -1); |
| 337 var sorted = Array.prototype.concat.call(ordinalList).sort(); |
| 338 function split(num) { |
| 339 return Math.floor(num << 1); |
| 340 } |
| 341 var size = split(sorted.length); |
| 342 var pos = size; |
| 343 while (size > 0) { |
| 344 size = split(size); |
| 345 // This just no-ops when size = 0. |
| 346 pos = ordinal > sorted[pos] ? pos + size : pos - size; |
| 347 } |
| 348 return pos; |
| 349 }, |
| 350 |
| 351 /** |
242 * Callback invoked by chrome with the apps available. | 352 * Callback invoked by chrome with the apps available. |
243 * | 353 * |
244 * Note that calls to this function can occur at any time, not just in | 354 * 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 | 355 * response to a getApps request. For example, when a user |
246 * installs/uninstalls an app on another synchronized devices. | 356 * installs/uninstalls an app on another synchronized devices. |
247 * @param {Object} data An object with all the data on available | 357 * @param {Object} data An object with all the data on available |
248 * applications. | 358 * applications. |
249 */ | 359 */ |
250 getAppsCallback: function(data) { | 360 getAppsCallback: function(data, measureTime) { |
251 var startTime = Date.now(); | 361 var startTime; |
| 362 if (measureTime) |
| 363 startTime = Date.now(); |
252 | 364 |
253 // Clear any existing apps pages and dots. | 365 this.syncAppsPages(data); |
254 // 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 | |
256 // seems unfortunate to have Chrome send us the entire apps list after an | |
257 // uninstall. | |
258 while (this.appsPages.length > 0) { | |
259 var page = this.appsPages[0]; | |
260 var dot = page.navigationDot; | |
261 | |
262 this.eventTracker.remove(page); | |
263 page.tearDown(); | |
264 page.parentNode.removeChild(page); | |
265 dot.parentNode.removeChild(dot); | |
266 } | |
267 | |
268 // Get the array of apps and add any special synthesized entries | |
269 var apps = data.apps; | |
270 | |
271 // Get a list of page names | |
272 var pageNames = data.appPageNames; | |
273 | |
274 function stringListIsEmpty(list) { | |
275 for (var i = 0; i < list.length; i++) { | |
276 if (list[i]) | |
277 return false; | |
278 } | |
279 return true; | |
280 } | |
281 | |
282 // Sort by launch ordinal | |
283 apps.sort(function(a, b) { | |
284 return a.app_launch_ordinal > b.app_launch_ordinal ? 1 : | |
285 a.app_launch_ordinal < b.app_launch_ordinal ? -1 : 0; | |
286 }); | |
287 | |
288 // An app to animate (in case it was just installed). | |
289 var highlightApp; | |
290 | |
291 // Add the apps, creating pages as necessary | |
292 for (var i = 0; i < apps.length; i++) { | |
293 var app = apps[i]; | |
294 var pageIndex = app.page_index || 0; | |
295 while (pageIndex >= this.appsPages.length) { | |
296 var pageName = localStrings.getString('appDefaultPageName'); | |
297 if (this.appsPages.length < pageNames.length) | |
298 pageName = pageNames[this.appsPages.length]; | |
299 | |
300 var origPageCount = this.appsPages.length; | |
301 this.appendTilePage(new ntp4.AppsPage(), pageName, true); | |
302 // Confirm that appsPages is a live object, updated when a new page is | |
303 // added (otherwise we'd have an infinite loop) | |
304 assert(this.appsPages.length == origPageCount + 1, | |
305 'expected new page'); | |
306 } | |
307 | |
308 if (app.id == this.highlightAppId) | |
309 highlightApp = app; | |
310 else | |
311 this.appsPages[pageIndex].appendApp(app); | |
312 } | |
313 | |
314 ntp4.AppsPage.setPromo(data.showPromo ? data : null); | 366 ntp4.AppsPage.setPromo(data.showPromo ? data : null); |
315 | 367 |
316 // Tell the slider about the pages. | 368 // Tell the slider/switchers about the pages. |
317 this.updateSliderCards(); | 369 this.updateSliderCards(); |
| 370 this.updatePageSwitchers(); |
318 | 371 |
319 if (highlightApp) | 372 if (highlightApp) |
320 this.appAdded(highlightApp, true); | 373 this.appAdded(highlightApp, true); |
321 | 374 |
322 // Mark the current page. | 375 // Mark the current page. |
323 this.cardSlider.currentCardValue.navigationDot.classList.add('selected'); | 376 this.cardSlider.currentCardValue.navigationDot.classList.add('selected'); |
324 logEvent('apps.layout: ' + (Date.now() - startTime)); | 377 |
| 378 if (measureTime) |
| 379 logEvent('apps.layout: ' + (Date.now() - startTime)); |
325 | 380 |
326 document.documentElement.classList.remove('starting-up'); | 381 document.documentElement.classList.remove('starting-up'); |
327 }, | 382 }, |
328 | 383 |
329 /** | 384 /** |
330 * Called by chrome when a new app has been added to chrome or has been | 385 * @param {Object<>} |
331 * enabled if previously disabled. | |
332 * @param {Object} appData A data structure full of relevant information for | |
333 * the app. | |
334 */ | 386 */ |
335 appAdded: function(appData, opt_highlight) { | 387 syncAppsPages: function() { |
336 if (appData.id == this.highlightAppId) { | 388 // Remove pages that aren't in the data we were given. |
337 opt_highlight = true; | 389 var existingPages = this.getAppsPageOrdinalMap_(); |
338 this.highlightAppId = null; | 390 for (var i in existingPages) { |
| 391 if (existingPages.hasOwnProperty(i) && |
| 392 !(existingPages[i] in data.appsPages)) { |
| 393 this.removeTilePageAndDot_(existingPages[i]); |
| 394 delete existingPages[i]; |
| 395 } |
339 } | 396 } |
340 | 397 // Create or re-use pages or apps and ensure we're synced. |
341 var pageIndex = appData.page_index || 0; | 398 var pageOrdinals = Object.keys(data.appsPages).sort(); |
342 | 399 for (var i = 0; i < pageOrdinals.length; ++i) { |
343 if (pageIndex >= this.appsPages.length) { | 400 // Re-use previous pages or create new pages as necessary. |
344 while (pageIndex >= this.appsPages.length) { | 401 var appsPage = this.getOrCreateAppsPageAtOrdinal_(pageOrdinals[i]); |
345 this.appendTilePage(new ntp4.AppsPage(), | 402 existingPages[pageOrdinals[i]] = appsPage; |
346 localStrings.getString('appDefaultPageName'), | 403 var page = data.appsPages[pageOrdinals[i]]; |
347 true); | 404 appsPage.navigationDot.displayTitle = page.name; |
| 405 var appOrdinals = Object.keys(page.apps).sort(); |
| 406 for (var j = 0; j < appOrdinals.length; ++j) { |
| 407 var appData = page.apps[appOrdinals[j]]; |
| 408 var app = $(appData.id); |
| 409 if (app) { |
| 410 if (appData.page_ordinal != app.data.page_ordinal || |
| 411 appData.app_launch_ordinal != app.data.app_launch_ordinal) { |
| 412 var originalPage = app.tile.tilePage; |
| 413 var detached = originalPage.removeTile(app); |
| 414 var index = this.getIndexForNewOrdinal_( |
| 415 appOrdinals, appData.app_launch_ordinal); |
| 416 existingPages[appData.page_ordinal].addTileAt(detached, index); |
| 417 } else { |
| 418 app.replaceAppData(appData); |
| 419 } |
| 420 } else { |
| 421 var index = this.getIndexForNewOrdinal_(app.app_launch_ordinal); |
| 422 appsPage.addTileAt(app, app.id == data.highlightId); |
| 423 } |
348 } | 424 } |
349 this.updateSliderCards(); | |
350 } | 425 } |
351 | 426 for (var i = 0; i < this.appsPages.length; ++i) |
352 var page = this.appsPages[pageIndex]; | 427 this.appsPages[i].cleanupDrag(); |
353 var app = $(appData.id); | |
354 if (app) | |
355 app.replaceAppData(appData); | |
356 else | |
357 page.appendApp(appData, opt_highlight); | |
358 }, | 428 }, |
359 | 429 |
360 /** | 430 /** |
361 * Callback invoked by chrome whenever an app preference changes. | |
362 * @param {Object} data An object with all the data on available | |
363 * applications. | |
364 */ | |
365 appsPrefChangedCallback: function(data) { | |
366 for (var i = 0; i < data.apps.length; ++i) { | |
367 $(data.apps[i].id).appData = data.apps[i]; | |
368 } | |
369 | |
370 // Set the App dot names. Skip the first dot (Most Visited). | |
371 var dots = this.dotList.getElementsByClassName('dot'); | |
372 var start = this.mostVisitedPage ? 1 : 0; | |
373 for (var i = start; i < dots.length; ++i) { | |
374 dots[i].displayTitle = data.appPageNames[i - start] || ''; | |
375 } | |
376 }, | |
377 | |
378 /** | |
379 * Invoked whenever the pages in apps-page-list have changed so that | 431 * Invoked whenever the pages in apps-page-list have changed so that |
380 * the Slider knows about the new elements. | 432 * the Slider knows about the new elements. |
381 */ | 433 */ |
382 updateSliderCards: function() { | 434 updateSliderCards: function() { |
383 var pageNo = Math.min(this.cardSlider.currentCard, | 435 var index = -1; |
384 this.tilePages.length - 1); | 436 var tiles = Array.prototype.slice.call(this.tilePages); |
385 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages), | 437 // Clamping this value each time helps self-heal unexpected input. |
386 pageNo); | 438 this.shownPageIndex = Math.max(0, Math.min(this.shownPageIndex, |
| 439 this.appsPages.length - 1)); |
387 switch (this.shownPage) { | 440 switch (this.shownPage) { |
388 case templateData['apps_page_id']: | 441 case templateData.apps_page_id: |
389 this.cardSlider.selectCardByValue( | 442 index = tiles.indexOf(this.appsPages[this.shownPageIndex]); |
390 this.appsPages[Math.min(this.shownPageIndex, | |
391 this.appsPages.length - 1)]); | |
392 break; | 443 break; |
393 case templateData['most_visited_page_id']: | 444 case templateData.most_visited_page_id: |
394 if (this.mostVisitedPage) | 445 index = tiles.indexOf(this.mostVisitedPage); |
395 this.cardSlider.selectCardByValue(this.mostVisitedPage); | |
396 break; | 446 break; |
397 } | 447 } |
| 448 // If shownPage was saved as a page that's now disabled or the shownPage |
| 449 // doesn't exist any more, index will be -1. Change to the preferred |
| 450 // default page (first apps pane) in this case. |
| 451 if (index < 0) { |
| 452 this.shownPage = templateData.apps_page_id; |
| 453 index = tiles.indexOf(this.appsPages[0]); |
| 454 } |
| 455 // Set the new cards and index. |
| 456 this.cardSlider.setCards(tiles, index); |
| 457 |
| 458 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]); |
398 }, | 459 }, |
399 | 460 |
400 /** | 461 /** |
401 * Called whenever tiles should be re-arranging themselves out of the way | 462 * Called whenever tiles should be re-arranging themselves out of the way |
402 * of a moving or insert tile. | 463 * of a moving or insert tile. |
403 */ | 464 */ |
404 enterRearrangeMode: function() { | 465 enterRearrangeMode: function() { |
405 var tempPage = new ntp4.AppsPage(); | 466 var tempPage = new ntp4.AppsPage(); |
406 tempPage.classList.add('temporary'); | 467 tempPage.classList.add('temporary'); |
407 this.appendTilePage(tempPage, | 468 var pageName = localStrings.getString('appDefaultPageName'); |
408 localStrings.getString('appDefaultPageName'), | 469 this.appendTilePage(tempPage, pageName, true); |
409 true); | |
410 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage); | |
411 if (this.cardSlider.currentCard >= tempIndex) | |
412 this.cardSlider.currentCard += 1; | |
413 this.updateSliderCards(); | |
414 | 470 |
415 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved()) | 471 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved()) |
416 $('footer').classList.add('showing-trash-mode'); | 472 $('footer').classList.add('showing-trash-mode'); |
417 }, | 473 }, |
418 | 474 |
419 /** | 475 /** |
420 * Invoked whenever some app is released | 476 * Invoked whenever some app is released |
421 */ | 477 */ |
422 leaveRearrangeMode: function() { | 478 leaveRearrangeMode: function() { |
423 var tempPage = document.querySelector('.tile-page.temporary'); | 479 var tempPage = document.querySelector('.tile-page.temporary'); |
424 var dot = tempPage.navigationDot; | 480 // Either remove a temp page if it's empty or save the page name (as an |
425 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) { | 481 // app has just been dropped on it or created somehow). |
426 dot.animateRemove(); | 482 // TODO(dbeam): Animated removal if currently on temp page. |
427 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage); | 483 if (tempPage && !this.removeAppsPageIfEmpty_(tempPage, true, true)) { |
428 if (this.cardSlider.currentCard > tempIndex) | 484 this.saveAppsPageName(tempPage, |
429 this.cardSlider.currentCard -= 1; | 485 tempPage.navigationDot.displayTitle, |
430 tempPage.parentNode.removeChild(tempPage); | 486 true); |
431 this.updateSliderCards(); | |
432 } else { | |
433 tempPage.classList.remove('temporary'); | 487 tempPage.classList.remove('temporary'); |
434 this.saveAppPageName(tempPage, | |
435 localStrings.getString('appDefaultPageName')); | |
436 } | 488 } |
437 | 489 |
438 $('footer').classList.remove('showing-trash-mode'); | 490 $('footer').classList.remove('showing-trash-mode'); |
439 }, | 491 }, |
440 | 492 |
441 /** | 493 /** |
442 * Callback for the 'pagelayout' event. | 494 * Callback for the 'pagelayout' event. |
443 * @param {Event} e The event. | 495 * @param {Event} e The event. |
444 */ | 496 */ |
445 onPageLayout_: function(e) { | 497 onPageLayout_: function(e) { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
500 /** | 552 /** |
501 * Handler for CARD_CHANGED on cardSlider. | 553 * Handler for CARD_CHANGED on cardSlider. |
502 * @param {Event} e The CARD_CHANGED event. | 554 * @param {Event} e The CARD_CHANGED event. |
503 * @private | 555 * @private |
504 */ | 556 */ |
505 cardChangedHandler_: function(e) { | 557 cardChangedHandler_: function(e) { |
506 var page = e.cardSlider.currentCardValue; | 558 var page = e.cardSlider.currentCardValue; |
507 | 559 |
508 // Don't change shownPage until startup is done (and page changes actually | 560 // Don't change shownPage until startup is done (and page changes actually |
509 // reflect user actions). | 561 // reflect user actions). |
510 if (!document.documentElement.classList.contains('starting-up')) { | 562 if (!this.isStartingUp_()) { |
511 if (page.classList.contains('apps-page')) { | 563 if (page.classList.contains('apps-page')) { |
512 this.shownPage = templateData['apps_page_id']; | 564 this.shownPage = templateData['apps_page_id']; |
513 this.shownPageIndex = this.getAppsPageIndex(page); | 565 this.shownPageIndex = this.getAppsPageIndex(page); |
514 } else if (page.classList.contains('most-visited-page')) { | 566 } else if (page.classList.contains('most-visited-page')) { |
515 this.shownPage = templateData['most_visited_page_id']; | 567 this.shownPage = templateData['most_visited_page_id']; |
516 this.shownPageIndex = 0; | 568 this.shownPageIndex = 0; |
517 } else { | 569 } else { |
518 console.error('unknown page selected'); | 570 console.error('unknown page selected'); |
519 } | 571 } |
520 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]); | 572 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]); |
521 } | 573 } |
522 | 574 |
523 // Update the active dot | 575 // Update the active dot |
524 var curDot = this.dotList.getElementsByClassName('selected')[0]; | 576 var curDot = this.dotList.getElementsByClassName('selected')[0]; |
525 if (curDot) | 577 if (curDot) |
526 curDot.classList.remove('selected'); | 578 curDot.classList.remove('selected'); |
527 page.navigationDot.classList.add('selected'); | 579 page.navigationDot.classList.add('selected'); |
528 this.updatePageSwitchers(); | 580 this.updatePageSwitchers(); |
529 }, | 581 }, |
530 | 582 |
531 /* | 583 /** |
532 * Save the name of an app page. | 584 * Listen for card additions to update the page switchers or the current |
533 * Store the app page name into the preferences store. | 585 * card accordingly. |
534 * @param {AppsPage} appPage The app page for which we wish to save. | 586 * @param {Event} e A card removed or added event. |
535 * @param {string} name The name of the page. | |
536 */ | 587 */ |
537 saveAppPageName: function(appPage, name) { | 588 cardAddedHandler_: function(e) { |
538 var index = this.getAppsPageIndex(appPage); | 589 // When the second arg passed to insertBefore is falsey, it acts just like |
539 assert(index != -1); | 590 // appendChild. |
540 chrome.send('saveAppPageName', [name, index]); | 591 this.pageList.insertBefore(e.addedCard, this.tilePages[e.addedIndex]); |
| 592 if (!this.isStartingUp_()) |
| 593 this.updatePageSwitchers(); |
541 }, | 594 }, |
542 | 595 |
543 /** | 596 /** |
| 597 * Listen for card removals to update the page switchers or the current card |
| 598 * accordingly. |
| 599 * @param {Event} e A card removed or added event. |
| 600 */ |
| 601 cardRemovedHandler_: function(e) { |
| 602 if (!this.isStartingUp_()) |
| 603 this.updatePageSwitchers(); |
| 604 assert(!e.removedCard.classList.contains('selected-card')); |
| 605 e.removedCard.parentNode.removeChild(e.removedCard); |
| 606 }, |
| 607 |
| 608 /** |
| 609 * Save the name of an apps page. |
| 610 * Store the apps page name into the preferences store. |
| 611 * @param {AppsPage} appsPage The app page for which we wish to save. |
| 612 * @param {string} name The name of the page. |
| 613 * @param {boolean} notify If we should notify of when saving the pref. |
| 614 */ |
| 615 saveAppsPageName: function(appsPage, name, notify) { |
| 616 var index = this.getAppsPageIndex(appsPage); |
| 617 assert(index != -1); |
| 618 chrome.send('saveAppsPageName', [name, index, notify]); |
| 619 }, |
| 620 |
| 621 /** |
| 622 * An Array of callbacks to be called on the next CARD_CHANGE_ENDED event |
| 623 * handled from the cardSlider. |
| 624 * @private |
| 625 */ |
| 626 cardChangeEndedCallbacks_: [], |
| 627 |
| 628 /** |
| 629 * Handler for CARD_CHANGE_ENDED on cardSlider. |
| 630 * @param {Event} e The CARD_CHANGE_ENDED event. |
| 631 * @private |
| 632 */ |
| 633 cardChangeEndedHandler_: function(e) { |
| 634 if (!this.isStartingUp_()) { |
| 635 for (var i = 0; i < this.cardChangeEndedCallbacks_.length; ++i) { |
| 636 if (this.cardChangeEndedCallbacks_[i].call(this, e) !== false) |
| 637 this.cardChangeEndedCallbacks_.splice(i--, 1); |
| 638 } |
| 639 } |
| 640 }, |
| 641 |
| 642 /** |
| 643 * Happens when a tile is removed from a tile page. |
| 644 * @param {Event} e An event dispatched from a tile when it is removed. |
| 645 */ |
| 646 tileRemovedHandler_: function(e) { |
| 647 if (e.tilePage instanceof ntp4.AppsPage) |
| 648 this.removeAppsPageIfEmpty_(e.tilePage, e.wasAnimated); |
| 649 }, |
| 650 |
| 651 /** |
| 652 * Remove an apps page if it now has no tiles (is empty). |
| 653 * @param {AppsPage} appsPage A page to check for emptiness. |
| 654 * @param {boolean=} opt_animate Whether the prospective removal should be |
| 655 * animated. |
| 656 * @param {boolean=} opt_dontNotify Whether this NTP's AppLauncherHandler |
| 657 * shouldn't be notified of this pages' prospective removal (default is |
| 658 * to notify). |
| 659 * @return {boolean} If |appsPage| was removed or not. |
| 660 */ |
| 661 removeAppsPageIfEmpty_: function(appsPage, opt_animate, opt_dontNotify) { |
| 662 assert(appsPage instanceof ntp4.AppsPage, |
| 663 '|appsPage| is not really an AppsPage'); |
| 664 |
| 665 if (appsPage.tileCount !== 0) |
| 666 return false; |
| 667 |
| 668 // If the user is currently on the empty apps page, avoid visual issues |
| 669 // by selecting a different page before deleting. If the user isn't on |
| 670 // the empty/deleting page, just delete it right away. |
| 671 var whenOnCorrectPage = function() { |
| 672 if (!opt_dontNotify) |
| 673 chrome.send('deleteEmptyAppsPage', [appsPage.ordinal]); |
| 674 this.removeTilePageAndDot_(appsPage, opt_animate); |
| 675 }; |
| 676 |
| 677 if (appsPage == this.cardSlider.currentCardValue) { |
| 678 var index = this.getAppsPageIndex(appsPage); |
| 679 assert(index != -1); |
| 680 var tempPage = document.querySelector('.apps-page.temporary'); |
| 681 var tempOffset = tempPage.tileCount == 0 ? 1 : 0; |
| 682 // If the apps page being deleted is the last apps page (excluding soon |
| 683 // to be deleted temp pages), move backward one in the card slider. |
| 684 // Otherwise, move forward one. |
| 685 var change = index == this.appsPages.length - 1 - tempOffset ? -1 : 1; |
| 686 var newIndex = this.cardSlider.currentCard + change; |
| 687 this.cardSlider.selectCard(newIndex, opt_animate); |
| 688 // In the case where |opt_animate| is truthy, the card selection is |
| 689 // animated and asynchronous, so we simply append this callback to |
| 690 // this.cardChangeEndedCallbacks_ to be done on the next animated |
| 691 // CARD_CHANGE_ENDED event that switches to the proposed index. |
| 692 if (opt_animate) { |
| 693 this.cardChangeEndedCallbacks_.push(function(e) { |
| 694 if (!e.wasAnimated || e.changedTo != newIndex) |
| 695 return false; |
| 696 whenOnCorrectPage.call(this); |
| 697 }); |
| 698 } else { |
| 699 whenOnCorrectPage.call(this); |
| 700 } |
| 701 } else { |
| 702 whenOnCorrectPage.call(this); |
| 703 } |
| 704 |
| 705 return true; |
| 706 }, |
| 707 |
| 708 /** |
544 * Window resize handler. | 709 * Window resize handler. |
545 * @private | 710 * @private |
546 */ | 711 */ |
547 onWindowResize_: function(e) { | 712 onWindowResize_: function(e) { |
548 this.cardSlider.resize(this.sliderFrame.offsetWidth); | 713 this.cardSlider.resize(this.sliderFrame.offsetWidth); |
549 this.updatePageSwitchers(); | 714 this.updatePageSwitchers(); |
550 }, | 715 }, |
551 | 716 |
552 /** | 717 /** |
553 * Listener for offline status change events. Updates apps that are | 718 * Listener for offline status change events. Updates apps that are |
(...skipping 27 matching lines...) Expand all Loading... |
581 direction = 1; | 746 direction = 1; |
582 else | 747 else |
583 return; | 748 return; |
584 | 749 |
585 var cardIndex = | 750 var cardIndex = |
586 (this.cardSlider.currentCard + direction + | 751 (this.cardSlider.currentCard + direction + |
587 this.cardSlider.cardCount) % this.cardSlider.cardCount; | 752 this.cardSlider.cardCount) % this.cardSlider.cardCount; |
588 this.cardSlider.selectCard(cardIndex, true); | 753 this.cardSlider.selectCard(cardIndex, true); |
589 | 754 |
590 e.stopPropagation(); | 755 e.stopPropagation(); |
591 } | 756 }, |
| 757 |
| 758 /** |
| 759 * Re-order apps on this inactive page when an active NTP gets re-ordered. |
| 760 * @param {Object} data Position hashmap ordered by app id with indices: |
| 761 * {id => {page_index: ##, app_launch_index: ##}} |
| 762 */ |
| 763 appsReordered: function(data) { |
| 764 }, |
| 765 |
| 766 /** |
| 767 * Ensure there is an apps page at the given |ordinal| by checking for an |
| 768 * existing page or creating a new once. |
| 769 * @param {string} ordinal A string ordinal for which to check. |
| 770 * @private |
| 771 */ |
| 772 getOrCreateAppsPageAtOrdinal_: function(ordinal) { |
| 773 var map = this.getAppsPageOrdinalMap_(); |
| 774 if (ordinal in map) |
| 775 return map[ordinal]; |
| 776 |
| 777 var appsPage = new ntp4.AppsPage(ordinal); |
| 778 |
| 779 var index = this.getIndexForNewOrdinal_(Object.keys(map)); |
| 780 assert(index >= 0 && index <= this.appsPage.length); |
| 781 |
| 782 var origPageCount = this.appsPages.length; |
| 783 this.appendTilePage(appsPage, |
| 784 localStrings.getString('appDefaultPageName'), |
| 785 true, |
| 786 this.appsPages[index]); |
| 787 // Confirm that appsPages is a live object, updated when a new page is |
| 788 // added (otherwise we'd have an infinite loop) |
| 789 assert(this.appsPages.length == origPageCount + 1, |
| 790 'expected new page'); |
| 791 |
| 792 return appsPage; |
| 793 }, |
| 794 |
| 795 /** |
| 796 * Returns the index of a given tile page. |
| 797 * @param {TilePage} page The TilePage we wish to find. |
| 798 * @return {number} The index of |page| or -1 if it is not in the |
| 799 * collection. |
| 800 */ |
| 801 getTilePageIndex: function(page) { |
| 802 return Array.prototype.indexOf.call(this.tilePages, page); |
| 803 }, |
| 804 |
| 805 /** |
| 806 * Removes a page and navigation dot (if the navdot exists). |
| 807 * @param {TilePage} page The page to be removed. |
| 808 * @param {boolean=} opt_animate If the removal should be animated. |
| 809 */ |
| 810 removeTilePageAndDot_: function(page, opt_animate) { |
| 811 if (page.navigationDot) |
| 812 page.navigationDot.remove(opt_animate); |
| 813 this.cardSlider.removeCard(page); |
| 814 }, |
592 }; | 815 }; |
593 | 816 |
594 return { | 817 return { |
595 PageListView: PageListView | 818 PageListView: PageListView |
596 }; | 819 }; |
597 }); | 820 }); |
OLD | NEW |