Chromium Code Reviews| 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 |