| 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 |