OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview PageListView implementation. |
| 7 * PageListView manages page list, dot list, switcher buttons and handles apps |
| 8 * pages callbacks from backend. |
| 9 * |
| 10 * Note that you need to have AppLauncherHandler in your WebUI to use this code. |
| 11 */ |
| 12 |
| 13 cr.define('ntp4', function() { |
| 14 'use strict'; |
| 15 |
| 16 /** |
| 17 * Object for accessing localized strings. |
| 18 * @type {!LocalStrings} |
| 19 */ |
| 20 var localStrings = new LocalStrings; |
| 21 |
| 22 /** |
| 23 * Creates a PageListView object. |
| 24 * @constructor |
| 25 * @extends {Object} |
| 26 */ |
| 27 function PageListView() { |
| 28 } |
| 29 |
| 30 PageListView.prototype = { |
| 31 /** |
| 32 * The CardSlider object to use for changing app pages. |
| 33 * @type {CardSlider|undefined} |
| 34 */ |
| 35 cardSlider: undefined, |
| 36 |
| 37 /** |
| 38 * The frame div for cardSlider. |
| 39 * @type {!Element|undefined} |
| 40 */ |
| 41 sliderFrame: undefined, |
| 42 |
| 43 /** |
| 44 * The 'page-list' element. |
| 45 * @type {!Element|undefined} |
| 46 */ |
| 47 pageList: undefined, |
| 48 |
| 49 /** |
| 50 * A list of all 'tile-page' elements. |
| 51 * @type {!NodeList|undefined} |
| 52 */ |
| 53 tilePages: undefined, |
| 54 |
| 55 /** |
| 56 * A list of all 'apps-page' elements. |
| 57 * @type {!NodeList|undefined} |
| 58 */ |
| 59 appsPages: undefined, |
| 60 |
| 61 /** |
| 62 * The Most Visited page. |
| 63 * @type {!Element|undefined} |
| 64 */ |
| 65 mostVisitedPage: undefined, |
| 66 |
| 67 /** |
| 68 * The Bookmarks page. |
| 69 * @type {!Element|undefined} |
| 70 */ |
| 71 bookmarksPage: undefined, |
| 72 |
| 73 /** |
| 74 * The 'dots-list' element. |
| 75 * @type {!Element|undefined} |
| 76 */ |
| 77 dotList: undefined, |
| 78 |
| 79 /** |
| 80 * The left and right paging buttons. |
| 81 * @type {!Element|undefined} |
| 82 */ |
| 83 pageSwitcherStart: undefined, |
| 84 pageSwitcherEnd: undefined, |
| 85 |
| 86 /** |
| 87 * The 'trash' element. Note that technically this is unnecessary, |
| 88 * JavaScript creates the object for us based on the id. But I don't want |
| 89 * to rely on the ID being the same, and JSCompiler doesn't know about it. |
| 90 * @type {!Element|undefined} |
| 91 */ |
| 92 trash: undefined, |
| 93 |
| 94 /** |
| 95 * The type of page that is currently shown. The value is a numerical ID. |
| 96 * @type {number} |
| 97 */ |
| 98 shownPage: 0, |
| 99 |
| 100 /** |
| 101 * The index of the page that is currently shown, within the page type. |
| 102 * For example if the third Apps page is showing, this will be 2. |
| 103 * @type {number} |
| 104 */ |
| 105 shownPageIndex: 0, |
| 106 |
| 107 /** |
| 108 * EventTracker for managing event listeners for page events. |
| 109 * @type {!EventTracker} |
| 110 */ |
| 111 eventTracker: new EventTracker, |
| 112 |
| 113 /** |
| 114 * If non-null, this is the ID of the app to highlight to the user the next |
| 115 * time getAppsCallback runs. "Highlight" in this case means to switch to |
| 116 * the page and run the new tile animation. |
| 117 * @type {String} |
| 118 */ |
| 119 highlightAppId: null, |
| 120 |
| 121 /** |
| 122 * Initializes page list view. |
| 123 * @param {!Element} pageList A DIV element to host all pages. |
| 124 * @param {!Element} dotList An UL element to host nav dots. Each dot |
| 125 * represents a page. |
| 126 * @param {!Element} cardSliderFrame The card slider frame that hosts |
| 127 * pageList and switcher buttons. |
| 128 * @param {!Element|undefined} opt_trash Optional trash element. |
| 129 * @param {!Element|undefined} opt_pageSwitcherStart Optional start page |
| 130 * switcher button. |
| 131 * @param {!Element|undefined} opt_pageSwitcherEnd Optional end page |
| 132 * switcher button. |
| 133 */ |
| 134 initialize: function(pageList, dotList, cardSliderFrame, opt_trash, |
| 135 opt_pageSwitcherStart, opt_pageSwitcherEnd) { |
| 136 this.pageList = pageList; |
| 137 |
| 138 this.dotList = dotList; |
| 139 cr.ui.decorate(this.dotList, ntp4.DotList); |
| 140 |
| 141 this.trash = opt_trash; |
| 142 if (this.trash) |
| 143 new ntp4.Trash(this.trash); |
| 144 |
| 145 this.pageSwitcherStart = opt_pageSwitcherStart; |
| 146 if (this.pageSwitcherStart) |
| 147 ntp4.initializePageSwitcher(this.pageSwitcherStart); |
| 148 |
| 149 this.pageSwitcherEnd = opt_pageSwitcherEnd; |
| 150 if (this.pageSwitcherEnd) |
| 151 ntp4.initializePageSwitcher(this.pageSwitcherEnd); |
| 152 |
| 153 this.shownPage = templateData['shown_page_type']; |
| 154 this.shownPageIndex = templateData['shown_page_index']; |
| 155 |
| 156 // Request data on the apps so we can fill them in. |
| 157 // Note that this is kicked off asynchronously. 'getAppsCallback' will be |
| 158 // invoked at some point after this function returns. |
| 159 chrome.send('getApps'); |
| 160 |
| 161 document.addEventListener('keydown', this.onDocKeyDown_.bind(this)); |
| 162 // Prevent touch events from triggering any sort of native scrolling |
| 163 document.addEventListener('touchmove', function(e) { |
| 164 e.preventDefault(); |
| 165 }, true); |
| 166 |
| 167 this.tilePages = this.pageList.getElementsByClassName('tile-page'); |
| 168 this.appsPages = this.pageList.getElementsByClassName('apps-page'); |
| 169 |
| 170 // Initialize the cardSlider without any cards at the moment |
| 171 this.sliderFrame = cardSliderFrame; |
| 172 this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList, |
| 173 this.sliderFrame.offsetWidth); |
| 174 this.cardSlider.initialize(); |
| 175 |
| 176 // Handle the page being changed |
| 177 this.pageList.addEventListener( |
| 178 cr.ui.CardSlider.EventType.CARD_CHANGED, |
| 179 this.cardChangedHandler_.bind(this)); |
| 180 |
| 181 // Ensure the slider is resized appropriately with the window |
| 182 window.addEventListener('resize', this.onWindowResize_.bind(this)); |
| 183 |
| 184 // Update apps when online state changes. |
| 185 window.addEventListener('online', |
| 186 this.updateOfflineEnabledApps_.bind(this)); |
| 187 window.addEventListener('offline', |
| 188 this.updateOfflineEnabledApps_.bind(this)); |
| 189 }, |
| 190 |
| 191 /** |
| 192 * Appends a tile page (for bookmarks or most visited). |
| 193 * |
| 194 * @param {TilePage} page The page element. |
| 195 * @param {string} title The title of the tile page. |
| 196 * @param {bool} titleIsEditable If true, the title can be changed. |
| 197 * @param {TilePage} opt_refNode Optional reference node to insert in front |
| 198 * of. |
| 199 * When opt_refNode is falsey, |page| will just be appended to the end of |
| 200 * the page list. |
| 201 */ |
| 202 appendTilePage: function(page, title, titleIsEditable, opt_refNode) { |
| 203 // If no opt_refNode given, use bookmarksPage (if any). |
| 204 if (!opt_refNode) |
| 205 opt_refNode = this.bookmarksPage; |
| 206 |
| 207 // When opt_refNode is falsey, insertBefore acts just like appendChild. |
| 208 this.pageList.insertBefore(page, opt_refNode); |
| 209 |
| 210 // Remember special MostVisitedPage and BookmarksPage. |
| 211 if (typeof ntp4.MostVisitedPage != 'undefined' && |
| 212 page instanceof ntp4.MostVisitedPage) { |
| 213 assert(this.tilePages.length == 1, |
| 214 'MostVisitedPage should be added as first tile page'); |
| 215 this.mostVisitedPage = page; |
| 216 } |
| 217 if (typeof ntp4.BookmarksPage != 'undefined' && |
| 218 page instanceof ntp4.BookmarksPage) { |
| 219 this.bookmarksPage = page; |
| 220 } |
| 221 |
| 222 // If we're appending an AppsPage and it's a temporary page, animate it. |
| 223 var animate = page instanceof ntp4.AppsPage && |
| 224 page.classList.contains('temporary'); |
| 225 // Make a deep copy of the dot template to add a new one. |
| 226 var newDot = new ntp4.NavDot(page, title, titleIsEditable, animate); |
| 227 page.navigationDot = newDot; |
| 228 this.dotList.insertBefore(newDot, opt_refNode ? opt_refNode.navigationDot |
| 229 : null); |
| 230 // Set a tab index on the first dot. |
| 231 if (this.dotList.dots.length == 1) |
| 232 newDot.tabIndex = 3; |
| 233 |
| 234 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this)); |
| 235 }, |
| 236 |
| 237 /** |
| 238 * Called by chrome when an existing app has been disabled or |
| 239 * removed/uninstalled from chrome. |
| 240 * @param {Object} appData A data structure full of relevant information for |
| 241 * the app. |
| 242 * @param {boolean} isUninstall True if the app is being uninstalled; |
| 243 * false if the app is being disabled. |
| 244 */ |
| 245 appRemoved: function(appData, isUninstall) { |
| 246 var app = $(appData.id); |
| 247 assert(app, 'trying to remove an app that doesn\'t exist'); |
| 248 |
| 249 if (!isUninstall) |
| 250 app.replaceAppData(appData); |
| 251 else |
| 252 app.remove(); |
| 253 }, |
| 254 |
| 255 /** |
| 256 * Callback invoked by chrome with the apps available. |
| 257 * |
| 258 * Note that calls to this function can occur at any time, not just in |
| 259 * response to a getApps request. For example, when a user |
| 260 * installs/uninstalls an app on another synchronized devices. |
| 261 * @param {Object} data An object with all the data on available |
| 262 * applications. |
| 263 */ |
| 264 getAppsCallback: function(data) { |
| 265 var startTime = Date.now(); |
| 266 |
| 267 // Clear any existing apps pages and dots. |
| 268 // TODO(rbyers): It might be nice to preserve animation of dots after an |
| 269 // uninstall. Could we re-use the existing page and dot elements? It |
| 270 // seems unfortunate to have Chrome send us the entire apps list after an |
| 271 // uninstall. |
| 272 while (this.appsPages.length > 0) { |
| 273 var page = this.appsPages[0]; |
| 274 var dot = page.navigationDot; |
| 275 |
| 276 this.eventTracker.remove(page); |
| 277 page.tearDown(); |
| 278 page.parentNode.removeChild(page); |
| 279 dot.parentNode.removeChild(dot); |
| 280 } |
| 281 |
| 282 // Get the array of apps and add any special synthesized entries |
| 283 var apps = data.apps; |
| 284 |
| 285 // Get a list of page names |
| 286 var pageNames = data.appPageNames; |
| 287 |
| 288 function stringListIsEmpty(list) { |
| 289 for (var i = 0; i < list.length; i++) { |
| 290 if (list[i]) |
| 291 return false; |
| 292 } |
| 293 return true; |
| 294 } |
| 295 |
| 296 // Sort by launch index |
| 297 apps.sort(function(a, b) { |
| 298 return a.app_launch_index - b.app_launch_index; |
| 299 }); |
| 300 |
| 301 // An app to animate (in case it was just installed). |
| 302 var highlightApp; |
| 303 |
| 304 // Add the apps, creating pages as necessary |
| 305 for (var i = 0; i < apps.length; i++) { |
| 306 var app = apps[i]; |
| 307 var pageIndex = app.page_index || 0; |
| 308 while (pageIndex >= this.appsPages.length) { |
| 309 var pageName = localStrings.getString('appDefaultPageName'); |
| 310 if (this.appsPages.length < pageNames.length) |
| 311 pageName = pageNames[this.appsPages.length]; |
| 312 |
| 313 var origPageCount = this.appsPages.length; |
| 314 this.appendTilePage(new ntp4.AppsPage(), pageName, true); |
| 315 // Confirm that appsPages is a live object, updated when a new page is |
| 316 // added (otherwise we'd have an infinite loop) |
| 317 assert(this.appsPages.length == origPageCount + 1, |
| 318 'expected new page'); |
| 319 } |
| 320 |
| 321 if (app.id == this.highlightAppId) |
| 322 highlightApp = app; |
| 323 else |
| 324 this.appsPages[pageIndex].appendApp(app); |
| 325 } |
| 326 |
| 327 ntp4.AppsPage.setPromo(data.showPromo ? data : null); |
| 328 |
| 329 // Tell the slider about the pages. |
| 330 this.updateSliderCards(); |
| 331 |
| 332 if (highlightApp) |
| 333 this.appAdded(highlightApp, true); |
| 334 |
| 335 // Mark the current page. |
| 336 this.cardSlider.currentCardValue.navigationDot.classList.add('selected'); |
| 337 logEvent('apps.layout: ' + (Date.now() - startTime)); |
| 338 |
| 339 document.documentElement.classList.remove('starting-up'); |
| 340 }, |
| 341 |
| 342 /** |
| 343 * Called by chrome when a new app has been added to chrome or has been |
| 344 * enabled if previously disabled. |
| 345 * @param {Object} appData A data structure full of relevant information for |
| 346 * the app. |
| 347 */ |
| 348 appAdded: function(appData, opt_highlight) { |
| 349 if (appData.id == this.highlightAppId) { |
| 350 opt_highlight = true; |
| 351 this.highlightAppId = null; |
| 352 } |
| 353 |
| 354 var pageIndex = appData.page_index || 0; |
| 355 |
| 356 if (pageIndex >= this.appsPages.length) { |
| 357 while (pageIndex >= this.appsPages.length) { |
| 358 this.appendTilePage(new ntp4.AppsPage(), |
| 359 localStrings.getString('appDefaultPageName'), |
| 360 true); |
| 361 } |
| 362 this.updateSliderCards(); |
| 363 } |
| 364 |
| 365 var page = this.appsPages[pageIndex]; |
| 366 var app = $(appData.id); |
| 367 if (app) |
| 368 app.replaceAppData(appData); |
| 369 else |
| 370 page.appendApp(appData, opt_highlight); |
| 371 }, |
| 372 |
| 373 /** |
| 374 * Callback invoked by chrome whenever an app preference changes. |
| 375 * @param {Object} data An object with all the data on available |
| 376 * applications. |
| 377 */ |
| 378 appsPrefChangedCallback: function(data) { |
| 379 for (var i = 0; i < data.apps.length; ++i) { |
| 380 $(data.apps[i].id).appData = data.apps[i]; |
| 381 } |
| 382 |
| 383 // Set the App dot names. Skip the first and last dots (Most Visited and |
| 384 // Bookmarks). |
| 385 var dots = this.dotList.getElementsByClassName('dot'); |
| 386 // TODO(csilv): Remove this calcluation if/when we remove the flag for |
| 387 // for the bookmarks page. |
| 388 var start = this.mostVisitedPage ? 1 : 0; |
| 389 var length = this.bookmarksPage ? dots.length - 1 : dots.length; |
| 390 for (var i = start; i < length; ++i) { |
| 391 dots[i].displayTitle = data.appPageNames[i - start] || ''; |
| 392 } |
| 393 }, |
| 394 |
| 395 /** |
| 396 * Invoked whenever the pages in apps-page-list have changed so that |
| 397 * the Slider knows about the new elements. |
| 398 */ |
| 399 updateSliderCards: function() { |
| 400 var pageNo = Math.min(this.cardSlider.currentCard, |
| 401 this.tilePages.length - 1); |
| 402 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages), |
| 403 pageNo); |
| 404 switch (this.shownPage) { |
| 405 case templateData['apps_page_id']: |
| 406 this.cardSlider.selectCardByValue( |
| 407 this.appsPages[Math.min(this.shownPageIndex, |
| 408 this.appsPages.length - 1)]); |
| 409 break; |
| 410 case templateData['bookmarks_page_id']: |
| 411 if (this.bookmarksPage) |
| 412 this.cardSlider.selectCardByValue(this.bookmarksPage); |
| 413 break; |
| 414 case templateData['most_visited_page_id']: |
| 415 if (this.mostVisitedPage) |
| 416 this.cardSlider.selectCardByValue(this.mostVisitedPage); |
| 417 break; |
| 418 } |
| 419 }, |
| 420 |
| 421 /** |
| 422 * Called whenever tiles should be re-arranging themselves out of the way |
| 423 * of a moving or insert tile. |
| 424 */ |
| 425 enterRearrangeMode: function() { |
| 426 var tempPage = new ntp4.AppsPage(); |
| 427 tempPage.classList.add('temporary'); |
| 428 this.appendTilePage(tempPage, |
| 429 localStrings.getString('appDefaultPageName'), |
| 430 true); |
| 431 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage); |
| 432 if (this.cardSlider.currentCard >= tempIndex) |
| 433 this.cardSlider.currentCard += 1; |
| 434 this.updateSliderCards(); |
| 435 |
| 436 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved()) |
| 437 $('footer').classList.add('showing-trash-mode'); |
| 438 }, |
| 439 |
| 440 /** |
| 441 * Invoked whenever some app is released |
| 442 * @param {cr.ui.Grabber.Event} e The Grabber RELEASE event. |
| 443 */ |
| 444 leaveRearrangeMode: function(e) { |
| 445 var tempPage = document.querySelector('.tile-page.temporary'); |
| 446 var dot = tempPage.navigationDot; |
| 447 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) { |
| 448 dot.animateRemove(); |
| 449 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage); |
| 450 if (this.cardSlider.currentCard > tempIndex) |
| 451 this.cardSlider.currentCard -= 1; |
| 452 tempPage.parentNode.removeChild(tempPage); |
| 453 this.updateSliderCards(); |
| 454 } else { |
| 455 tempPage.classList.remove('temporary'); |
| 456 this.saveAppPageName(tempPage, |
| 457 localStrings.getString('appDefaultPageName')); |
| 458 } |
| 459 |
| 460 $('footer').classList.remove('showing-trash-mode'); |
| 461 }, |
| 462 |
| 463 /** |
| 464 * Callback for the 'pagelayout' event. |
| 465 * @param {Event} e The event. |
| 466 */ |
| 467 onPageLayout_: function(e) { |
| 468 if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) != |
| 469 this.cardSlider.currentCard) { |
| 470 return; |
| 471 } |
| 472 |
| 473 this.updatePageSwitchers(); |
| 474 }, |
| 475 |
| 476 /** |
| 477 * Adjusts the size and position of the page switchers according to the |
| 478 * layout of the current card. |
| 479 */ |
| 480 updatePageSwitchers: function() { |
| 481 if (!this.pageSwitcherStart || !this.pageSwitcherEnd) |
| 482 return; |
| 483 |
| 484 var page = this.cardSlider.currentCardValue; |
| 485 |
| 486 this.pageSwitcherStart.hidden = !page || |
| 487 (this.cardSlider.currentCard == 0); |
| 488 this.pageSwitcherEnd.hidden = !page || |
| 489 (this.cardSlider.currentCard == this.cardSlider.cardCount - 1); |
| 490 |
| 491 if (!page) |
| 492 return; |
| 493 |
| 494 var pageSwitcherLeft = isRTL() ? this.pageSwitcherEnd |
| 495 : this.pageSwitcherStart; |
| 496 var pageSwitcherRight = isRTL() ? this.pageSwitcherStart |
| 497 : this.pageSwitcherEnd; |
| 498 var scrollbarWidth = page.scrollbarWidth; |
| 499 pageSwitcherLeft.style.width = |
| 500 (page.sideMargin + 13) + 'px'; |
| 501 pageSwitcherLeft.style.left = '0'; |
| 502 pageSwitcherRight.style.width = |
| 503 (page.sideMargin - scrollbarWidth + 13) + 'px'; |
| 504 pageSwitcherRight.style.right = scrollbarWidth + 'px'; |
| 505 |
| 506 var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px'; |
| 507 pageSwitcherLeft.style.top = offsetTop; |
| 508 pageSwitcherRight.style.top = offsetTop; |
| 509 pageSwitcherLeft.style.paddingBottom = offsetTop; |
| 510 pageSwitcherRight.style.paddingBottom = offsetTop; |
| 511 }, |
| 512 |
| 513 /** |
| 514 * Returns the index of the given page. |
| 515 * @param {AppsPage} page The AppsPage for we wish to find. |
| 516 * @return {number} The index of |page|, or -1 if it is not here. |
| 517 */ |
| 518 getAppsPageIndex: function(page) { |
| 519 return Array.prototype.indexOf.call(this.appsPages, page); |
| 520 }, |
| 521 |
| 522 /** |
| 523 * Handler for CARD_CHANGED on cardSlider. |
| 524 * @param {Event} e The CARD_CHANGED event. |
| 525 * @private |
| 526 */ |
| 527 cardChangedHandler_: function(e) { |
| 528 var page = e.cardSlider.currentCardValue; |
| 529 |
| 530 // Don't change shownPage until startup is done (and page changes actually |
| 531 // reflect user actions). |
| 532 if (!document.documentElement.classList.contains('starting-up')) { |
| 533 if (page.classList.contains('apps-page')) { |
| 534 this.shownPage = templateData['apps_page_id']; |
| 535 this.shownPageIndex = this.getAppsPageIndex(page); |
| 536 } else if (page.classList.contains('most-visited-page')) { |
| 537 this.shownPage = templateData['most_visited_page_id']; |
| 538 this.shownPageIndex = 0; |
| 539 } else if (page.classList.contains('bookmarks-page')) { |
| 540 this.shownPage = templateData['bookmarks_page_id']; |
| 541 this.shownPageIndex = 0; |
| 542 } else { |
| 543 console.error('unknown page selected'); |
| 544 } |
| 545 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]); |
| 546 } |
| 547 |
| 548 // Update the active dot |
| 549 var curDot = this.dotList.getElementsByClassName('selected')[0]; |
| 550 if (curDot) |
| 551 curDot.classList.remove('selected'); |
| 552 page.navigationDot.classList.add('selected'); |
| 553 this.updatePageSwitchers(); |
| 554 }, |
| 555 |
| 556 /* |
| 557 * Save the name of an app page. |
| 558 * Store the app page name into the preferences store. |
| 559 * @param {AppsPage} appPage The app page for which we wish to save. |
| 560 * @param {string} name The name of the page. |
| 561 */ |
| 562 saveAppPageName: function(appPage, name) { |
| 563 var index = this.getAppsPageIndex(appPage); |
| 564 assert(index != -1); |
| 565 chrome.send('saveAppPageName', [name, index]); |
| 566 }, |
| 567 |
| 568 /** |
| 569 * Window resize handler. |
| 570 * @private |
| 571 */ |
| 572 onWindowResize_: function(e) { |
| 573 this.cardSlider.resize(this.sliderFrame.offsetWidth); |
| 574 this.updatePageSwitchers(); |
| 575 }, |
| 576 |
| 577 /** |
| 578 * Listener for offline status change events. Updates apps that are |
| 579 * not offline-enabled to be grayscale if the browser is offline. |
| 580 * @private |
| 581 */ |
| 582 updateOfflineEnabledApps_: function() { |
| 583 var apps = document.querySelectorAll('.app'); |
| 584 for (var i = 0; i < apps.length; ++i) { |
| 585 if (apps[i].appData.enabled && !apps[i].appData.offline_enabled) { |
| 586 apps[i].setIcon(); |
| 587 apps[i].loadIcon(); |
| 588 } |
| 589 } |
| 590 }, |
| 591 |
| 592 /** |
| 593 * Handler for key events on the page. Ctrl-Arrow will switch the visible |
| 594 * page. |
| 595 * @param {Event} e The KeyboardEvent. |
| 596 * @private |
| 597 */ |
| 598 onDocKeyDown_: function(e) { |
| 599 if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) |
| 600 return; |
| 601 |
| 602 var direction = 0; |
| 603 if (e.keyIdentifier == 'Left') |
| 604 direction = -1; |
| 605 else if (e.keyIdentifier == 'Right') |
| 606 direction = 1; |
| 607 else |
| 608 return; |
| 609 |
| 610 var cardIndex = |
| 611 (this.cardSlider.currentCard + direction + |
| 612 this.cardSlider.cardCount) % this.cardSlider.cardCount; |
| 613 this.cardSlider.selectCard(cardIndex, true); |
| 614 |
| 615 e.stopPropagation(); |
| 616 } |
| 617 }; |
| 618 |
| 619 return { |
| 620 PageListView: PageListView |
| 621 }; |
| 622 }); |
OLD | NEW |