| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview PageListView implementation. |
| * PageListView manages page list, dot list, switcher buttons and handles apps |
| * pages callbacks from backend. |
| * |
| * Note that you need to have AppLauncherHandler in your WebUI to use this code. |
| */ |
| |
| /** |
| * @typedef {{app_launch_ordinal: string, |
| * description: string, |
| * detailsUrl: string, |
| * direction: string, |
| * enabled: boolean, |
| * full_name: string, |
| * full_name_direction: string, |
| * homepageUrl: string, |
| * icon_big: string, |
| * icon_big_exists: boolean, |
| * icon_small: string, |
| * icon_small_exists: boolean, |
| * id: string, |
| * is_component: boolean, |
| * is_webstore: boolean, |
| * kioskEnabled: boolean, |
| * kioskMode: boolean, |
| * kioskOnly: boolean, |
| * launch_container: number, |
| * launch_type: number, |
| * mayDisable: boolean, |
| * name: string, |
| * offlineEnabled: boolean, |
| * optionsUrl: string, |
| * packagedApp: boolean, |
| * page_index: number, |
| * title: string, |
| * url: string, |
| * version: string}} |
| * @see chrome/browser/ui/webui/ntp/app_launcher_handler.cc |
| */ |
| var AppInfo; |
| |
| cr.define('ntp', function() { |
| 'use strict'; |
| |
| /** |
| * Creates a PageListView object. |
| * @constructor |
| * @extends {Object} |
| */ |
| function PageListView() { |
| } |
| |
| PageListView.prototype = { |
| /** |
| * The CardSlider object to use for changing app pages. |
| * @type {cr.ui.CardSlider|undefined} |
| */ |
| cardSlider: undefined, |
| |
| /** |
| * The frame div for this.cardSlider. |
| * @type {!Element|undefined} |
| */ |
| sliderFrame: undefined, |
| |
| /** |
| * The 'page-list' element. |
| * @type {!Element|undefined} |
| */ |
| pageList: undefined, |
| |
| /** |
| * A list of all 'tile-page' elements. |
| * @type {!NodeList|undefined} |
| */ |
| tilePages: undefined, |
| |
| /** |
| * A list of all 'apps-page' elements. |
| * @type {!NodeList|undefined} |
| */ |
| appsPages: undefined, |
| |
| /** |
| * The 'dots-list' element. |
| * @type {!Element|undefined} |
| */ |
| dotList: undefined, |
| |
| /** |
| * The left and right paging buttons. |
| * @type {!ntp.PageSwitcher|undefined} |
| */ |
| pageSwitcherStart: undefined, |
| pageSwitcherEnd: undefined, |
| |
| /** |
| * The 'trash' element. Note that technically this is unnecessary, |
| * JavaScript creates the object for us based on the id. But I don't want |
| * to rely on the ID being the same, and JSCompiler doesn't know about it. |
| * @type {!Element|undefined} |
| */ |
| trash: undefined, |
| |
| /** |
| * The index of the page that is currently shown. For example if the third |
| * page is showing, this will be 2. |
| * @type {number} |
| */ |
| shownPageIndex: 0, |
| |
| /** |
| * EventTracker for managing event listeners for page events. |
| * @type {!EventTracker} |
| */ |
| eventTracker: new EventTracker, |
| |
| /** |
| * If non-null, this is the ID of the app to highlight to the user the next |
| * time getAppsCallback runs. "Highlight" in this case means to switch to |
| * the page and run the new tile animation. |
| * @type {?string} |
| */ |
| highlightAppId: null, |
| |
| /** |
| * Initializes page list view. |
| * @param {!Element} pageList A DIV element to host all pages. |
| * @param {!Element} dotList An UL element to host nav dots. Each dot |
| * represents a page. |
| * @param {!Element} cardSliderFrame The card slider frame that hosts |
| * pageList and switcher buttons. |
| * @param {!Element|undefined} opt_trash Optional trash element. |
| * @param {!ntp.PageSwitcher|undefined} opt_pageSwitcherStart Optional start |
| * page switcher button. |
| * @param {!ntp.PageSwitcher|undefined} opt_pageSwitcherEnd Optional end |
| * page switcher button. |
| */ |
| initialize: function(pageList, dotList, cardSliderFrame, opt_trash, |
| opt_pageSwitcherStart, opt_pageSwitcherEnd) { |
| this.pageList = pageList; |
| |
| this.dotList = dotList; |
| cr.ui.decorate(this.dotList, ntp.DotList); |
| |
| this.trash = opt_trash; |
| if (this.trash) |
| new ntp.Trash(this.trash); |
| |
| this.pageSwitcherStart = opt_pageSwitcherStart; |
| if (this.pageSwitcherStart) |
| ntp.initializePageSwitcher(this.pageSwitcherStart); |
| |
| this.pageSwitcherEnd = opt_pageSwitcherEnd; |
| if (this.pageSwitcherEnd) |
| ntp.initializePageSwitcher(this.pageSwitcherEnd); |
| |
| this.shownPageIndex = loadTimeData.getInteger('shown_page_index'); |
| |
| // TODO(dbeam): remove showApps and everything that says if (apps). |
| assert(loadTimeData.getBoolean('showApps')); |
| |
| // Request data on the apps so we can fill them in. |
| // Note that this is kicked off asynchronously. 'getAppsCallback' will |
| // be invoked at some point after this function returns. |
| chrome.send('getApps'); |
| |
| document.addEventListener('keydown', this.onDocKeyDown_.bind(this)); |
| |
| this.tilePages = this.pageList.getElementsByClassName('tile-page'); |
| this.appsPages = this.pageList.getElementsByClassName('apps-page'); |
| |
| // Initialize the cardSlider without any cards at the moment. |
| this.sliderFrame = cardSliderFrame; |
| this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList, |
| this.sliderFrame.offsetWidth); |
| |
| // Prevent touch events from triggering any sort of native scrolling if |
| // there are multiple cards in the slider frame. |
| var cardSlider = this.cardSlider; |
| cardSliderFrame.addEventListener('touchmove', function(e) { |
| if (cardSlider.cardCount <= 1) |
| return; |
| e.preventDefault(); |
| }, true); |
| |
| // Handle mousewheel events anywhere in the card slider, so that wheel |
| // events on the page switchers will still scroll the page. |
| // This listener must be added before the card slider is initialized, |
| // because it needs to be called before the card slider's handler. |
| cardSliderFrame.addEventListener('mousewheel', function(e) { |
| if (cardSlider.currentCardValue.handleMouseWheel(e)) { |
| e.preventDefault(); // Prevent default scroll behavior. |
| e.stopImmediatePropagation(); // Prevent horizontal card flipping. |
| } |
| }); |
| |
| this.cardSlider.initialize( |
| loadTimeData.getBoolean('isSwipeTrackingFromScrollEventsEnabled')); |
| |
| // Handle events from the card slider. |
| this.pageList.addEventListener('cardSlider:card_changed', |
| this.onCardChanged_.bind(this)); |
| this.pageList.addEventListener('cardSlider:card_added', |
| this.onCardAdded_.bind(this)); |
| this.pageList.addEventListener('cardSlider:card_removed', |
| this.onCardRemoved_.bind(this)); |
| |
| // Ensure the slider is resized appropriately with the window. |
| window.addEventListener('resize', this.onWindowResize_.bind(this)); |
| |
| // Update apps when online state changes. |
| window.addEventListener('online', |
| this.updateOfflineEnabledApps_.bind(this)); |
| window.addEventListener('offline', |
| this.updateOfflineEnabledApps_.bind(this)); |
| }, |
| |
| /** |
| * Appends a tile page. |
| * |
| * @param {!ntp.TilePage} page The page element. |
| * @param {string} title The title of the tile page. |
| * @param {boolean} titleIsEditable If true, the title can be changed. |
| * @param {ntp.TilePage=} opt_refNode Optional reference node to insert in |
| * front of. |
| * When opt_refNode is falsey, |page| will just be appended to the end of |
| * the page list. |
| */ |
| appendTilePage: function(page, title, titleIsEditable, opt_refNode) { |
| if (opt_refNode) { |
| var refIndex = this.getTilePageIndex(opt_refNode); |
| this.cardSlider.addCardAtIndex(page, refIndex); |
| } else { |
| this.cardSlider.appendCard(page); |
| } |
| |
| // If we're appending an AppsPage and it's a temporary page, animate it. |
| var animate = page instanceof ntp.AppsPage && |
| page.classList.contains('temporary'); |
| // Make a deep copy of the dot template to add a new one. |
| var newDot = new ntp.NavDot(page, title, titleIsEditable, animate); |
| page.navigationDot = newDot; |
| this.dotList.insertBefore(newDot, |
| opt_refNode ? opt_refNode.navigationDot : null); |
| // Set a tab index on the first dot. |
| if (this.dotList.dots.length == 1) |
| newDot.tabIndex = 3; |
| |
| this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this)); |
| }, |
| |
| /** |
| * Called by chrome when an app has changed positions. |
| * @param {AppInfo} appData The data for the app. This contains page and |
| * position indices. |
| */ |
| appMoved: function(appData) { |
| assert(loadTimeData.getBoolean('showApps')); |
| |
| var app = /** @type {ntp.App} */($(appData.id)); |
| assert(app, 'trying to move an app that doesn\'t exist'); |
| app.remove(false); |
| |
| this.appsPages[appData.page_index].insertApp(appData, false); |
| }, |
| |
| /** |
| * Called by chrome when an existing app has been disabled or |
| * removed/uninstalled from chrome. |
| * @param {AppInfo} appData A data structure full of relevant information |
| * for the app. |
| * @param {boolean} isUninstall True if the app is being uninstalled; |
| * false if the app is being disabled. |
| * @param {boolean} fromPage True if the removal was from the current page. |
| */ |
| appRemoved: function(appData, isUninstall, fromPage) { |
| assert(loadTimeData.getBoolean('showApps')); |
| |
| var app = /** @type {ntp.App} */($(appData.id)); |
| assert(app, 'trying to remove an app that doesn\'t exist'); |
| |
| if (!isUninstall) |
| app.replaceAppData(appData); |
| else |
| app.remove(!!fromPage); |
| }, |
| |
| /** |
| * @return {boolean} If the page is still starting up. |
| * @private |
| */ |
| isStartingUp_: function() { |
| return document.documentElement.classList.contains('starting-up'); |
| }, |
| |
| /** |
| * Tracks whether apps have been loaded at least once. |
| * @type {boolean} |
| * @private |
| */ |
| appsLoaded_: false, |
| |
| /** |
| * Callback invoked by chrome with the apps available. |
| * |
| * Note that calls to this function can occur at any time, not just in |
| * response to a getApps request. For example, when a user |
| * installs/uninstalls an app on another synchronized devices. |
| * @param {{apps: Array<AppInfo>, appPageNames: Array<string>}} data |
| * An object with all the data on available applications. |
| */ |
| getAppsCallback: function(data) { |
| assert(loadTimeData.getBoolean('showApps')); |
| |
| var startTime = Date.now(); |
| |
| // Remember this to select the correct card when done rebuilding. |
| var prevCurrentCard = this.cardSlider.currentCard; |
| |
| // Make removal of pages and dots as quick as possible with less DOM |
| // operations, reflows, or repaints. We set currentCard = 0 and remove |
| // from the end to not encounter any auto-magic card selections in the |
| // process and we hide the card slider throughout. |
| this.cardSlider.currentCard = 0; |
| |
| // Clear any existing apps pages and dots. |
| // TODO(rbyers): It might be nice to preserve animation of dots after an |
| // uninstall. Could we re-use the existing page and dot elements? It |
| // seems unfortunate to have Chrome send us the entire apps list after an |
| // uninstall. |
| while (this.appsPages.length > 0) |
| this.removeTilePageAndDot_(this.appsPages[this.appsPages.length - 1]); |
| |
| // Get the array of apps and add any special synthesized entries |
| var apps = data.apps; |
| |
| // Get a list of page names |
| var pageNames = data.appPageNames; |
| |
| function stringListIsEmpty(list) { |
| for (var i = 0; i < list.length; i++) { |
| if (list[i]) |
| return false; |
| } |
| return true; |
| } |
| |
| // Sort by launch ordinal |
| apps.sort(function(a, b) { |
| return a.app_launch_ordinal > b.app_launch_ordinal ? 1 : |
| a.app_launch_ordinal < b.app_launch_ordinal ? -1 : 0; |
| }); |
| |
| // An app to animate (in case it was just installed). |
| var highlightApp; |
| |
| // If there are any pages after the apps, add new pages before them. |
| var lastAppsPage = (this.appsPages.length > 0) ? |
| this.appsPages[this.appsPages.length - 1] : null; |
| var lastAppsPageIndex = (lastAppsPage != null) ? |
| Array.prototype.indexOf.call(this.tilePages, lastAppsPage) : -1; |
| var nextPageAfterApps = lastAppsPageIndex != -1 ? |
| this.tilePages[lastAppsPageIndex + 1] : null; |
| |
| // Add the apps, creating pages as necessary |
| for (var i = 0; i < apps.length; i++) { |
| var app = apps[i]; |
| var pageIndex = app.page_index || 0; |
| while (pageIndex >= this.appsPages.length) { |
| var pageName = loadTimeData.getString('appDefaultPageName'); |
| if (this.appsPages.length < pageNames.length) |
| pageName = pageNames[this.appsPages.length]; |
| |
| var origPageCount = this.appsPages.length; |
| this.appendTilePage(new ntp.AppsPage(), pageName, true, |
| nextPageAfterApps); |
| // Confirm that appsPages is a live object, updated when a new page is |
| // added (otherwise we'd have an infinite loop) |
| assert(this.appsPages.length == origPageCount + 1, |
| 'expected new page'); |
| } |
| |
| if (app.id == this.highlightAppId) |
| highlightApp = app; |
| else |
| this.appsPages[pageIndex].insertApp(app, false); |
| } |
| |
| this.cardSlider.currentCard = prevCurrentCard; |
| |
| if (highlightApp) |
| this.appAdded(highlightApp, true); |
| |
| logEvent('apps.layout: ' + (Date.now() - startTime)); |
| |
| // Tell the slider about the pages and mark the current page. |
| this.updateSliderCards(); |
| this.cardSlider.currentCardValue.navigationDot.classList.add('selected'); |
| |
| if (!this.appsLoaded_) { |
| this.appsLoaded_ = true; |
| cr.dispatchSimpleEvent(document, 'sectionready', true, true); |
| } |
| this.updateAppLauncherPromoHiddenState_(); |
| }, |
| |
| /** |
| * Called by chrome when a new app has been added to chrome or has been |
| * enabled if previously disabled. |
| * @param {AppInfo} appData A data structure full of relevant information |
| * for the app. |
| * @param {boolean=} opt_highlight Whether the app about to be added should |
| * be highlighted. |
| */ |
| appAdded: function(appData, opt_highlight) { |
| assert(loadTimeData.getBoolean('showApps')); |
| |
| if (appData.id == this.highlightAppId) { |
| opt_highlight = true; |
| this.highlightAppId = null; |
| } |
| |
| var pageIndex = appData.page_index || 0; |
| |
| if (pageIndex >= this.appsPages.length) { |
| while (pageIndex >= this.appsPages.length) { |
| this.appendTilePage(new ntp.AppsPage(), |
| loadTimeData.getString('appDefaultPageName'), |
| true); |
| } |
| this.updateSliderCards(); |
| } |
| |
| var page = this.appsPages[pageIndex]; |
| var app = $(appData.id); |
| if (app) { |
| app.replaceAppData(appData); |
| } else if (opt_highlight) { |
| page.insertAndHighlightApp(appData); |
| this.setShownPage_(appData.page_index); |
| } else { |
| page.insertApp(appData, false); |
| } |
| }, |
| |
| /** |
| * Callback invoked by chrome whenever an app preference changes. |
| * @param {Object} data An object with all the data on available |
| * applications. |
| */ |
| appsPrefChangedCallback: function(data) { |
| assert(loadTimeData.getBoolean('showApps')); |
| |
| for (var i = 0; i < data.apps.length; ++i) { |
| $(data.apps[i].id).appData = data.apps[i]; |
| } |
| |
| // Set the App dot names. |
| var dots = this.dotList.getElementsByClassName('dot'); |
| for (var i = 0; i < dots.length; ++i) { |
| dots[i].displayTitle = data.appPageNames[i] || ''; |
| } |
| }, |
| |
| /** |
| * Callback invoked by chrome whenever the app launcher promo pref changes. |
| * @param {boolean} show Identifies if we should show or hide the promo. |
| */ |
| appLauncherPromoPrefChangeCallback: function(show) { |
| loadTimeData.overrideValues({showAppLauncherPromo: show}); |
| this.updateAppLauncherPromoHiddenState_(); |
| }, |
| |
| /** |
| * Updates the hidden state of the app launcher promo based on the page |
| * shown and load data content. |
| * @private |
| */ |
| updateAppLauncherPromoHiddenState_: function() { |
| $('app-launcher-promo').hidden = |
| !loadTimeData.getBoolean('showAppLauncherPromo'); |
| }, |
| |
| /** |
| * Invoked whenever the pages in apps-page-list have changed so that |
| * the Slider knows about the new elements. |
| */ |
| updateSliderCards: function() { |
| var pageNo = Math.max(0, Math.min(this.cardSlider.currentCard, |
| this.tilePages.length - 1)); |
| this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages), |
| pageNo); |
| if (loadTimeData.getBoolean('showApps')) { |
| this.cardSlider.selectCardByValue( |
| this.appsPages[Math.min(this.shownPageIndex, |
| this.appsPages.length - 1)]); |
| } |
| }, |
| |
| /** |
| * Called whenever tiles should be re-arranging themselves out of the way |
| * of a moving or insert tile. |
| */ |
| enterRearrangeMode: function() { |
| if (loadTimeData.getBoolean('showApps')) { |
| var tempPage = new ntp.AppsPage(); |
| tempPage.classList.add('temporary'); |
| var pageName = loadTimeData.getString('appDefaultPageName'); |
| this.appendTilePage(tempPage, pageName, true); |
| } |
| |
| if (ntp.getCurrentlyDraggingTile().firstChild.canBeRemoved()) { |
| $('footer').classList.add('showing-trash-mode'); |
| $('footer-menu-container').style.minWidth = $('trash').offsetWidth - |
| $('chrome-web-store-link').offsetWidth + 'px'; |
| } |
| |
| document.documentElement.classList.add('dragging-mode'); |
| }, |
| |
| /** |
| * Invoked whenever some app is released |
| */ |
| leaveRearrangeMode: function() { |
| var tempPage = /** @type {ntp.AppsPage} */( |
| document.querySelector('.tile-page.temporary')); |
| if (tempPage) { |
| var dot = tempPage.navigationDot; |
| if (!tempPage.tileCount && |
| tempPage != this.cardSlider.currentCardValue) { |
| this.removeTilePageAndDot_(tempPage, true); |
| } else { |
| tempPage.classList.remove('temporary'); |
| this.saveAppPageName(tempPage, |
| loadTimeData.getString('appDefaultPageName')); |
| } |
| } |
| |
| $('footer').classList.remove('showing-trash-mode'); |
| $('footer-menu-container').style.minWidth = ''; |
| document.documentElement.classList.remove('dragging-mode'); |
| }, |
| |
| /** |
| * Callback for the 'pagelayout' event. |
| * @param {Event} e The event. |
| */ |
| onPageLayout_: function(e) { |
| if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) != |
| this.cardSlider.currentCard) { |
| return; |
| } |
| |
| this.updatePageSwitchers(); |
| }, |
| |
| /** |
| * Adjusts the size and position of the page switchers according to the |
| * layout of the current card, and updates the aria-label attributes of |
| * the page switchers. |
| */ |
| updatePageSwitchers: function() { |
| if (!this.pageSwitcherStart || !this.pageSwitcherEnd) |
| return; |
| |
| var page = this.cardSlider.currentCardValue; |
| |
| this.pageSwitcherStart.hidden = !page || |
| (this.cardSlider.currentCard == 0); |
| this.pageSwitcherEnd.hidden = !page || |
| (this.cardSlider.currentCard == this.cardSlider.cardCount - 1); |
| |
| if (!page) |
| return; |
| |
| var pageSwitcherLeft = isRTL() ? this.pageSwitcherEnd : |
| this.pageSwitcherStart; |
| var pageSwitcherRight = isRTL() ? this.pageSwitcherStart : |
| this.pageSwitcherEnd; |
| var scrollbarWidth = page.scrollbarWidth; |
| pageSwitcherLeft.style.width = |
| (page.sideMargin + 13) + 'px'; |
| pageSwitcherLeft.style.left = '0'; |
| pageSwitcherRight.style.width = |
| (page.sideMargin - scrollbarWidth + 13) + 'px'; |
| pageSwitcherRight.style.right = scrollbarWidth + 'px'; |
| |
| var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px'; |
| pageSwitcherLeft.style.top = offsetTop; |
| pageSwitcherRight.style.top = offsetTop; |
| pageSwitcherLeft.style.paddingBottom = offsetTop; |
| pageSwitcherRight.style.paddingBottom = offsetTop; |
| |
| // Update the aria-label attributes of the two page switchers. |
| this.pageSwitcherStart.updateButtonAccessibleLabel(this.dotList.dots); |
| this.pageSwitcherEnd.updateButtonAccessibleLabel(this.dotList.dots); |
| }, |
| |
| /** |
| * Returns the index of the given apps page. |
| * @param {ntp.AppsPage} page The AppsPage we wish to find. |
| * @return {number} The index of |page| or -1 if it is not in the |
| * collection. |
| */ |
| getAppsPageIndex: function(page) { |
| return Array.prototype.indexOf.call(this.appsPages, page); |
| }, |
| |
| /** |
| * Handler for cardSlider:card_changed events from this.cardSlider. |
| * @param {Event} e The cardSlider:card_changed event. |
| * @private |
| */ |
| onCardChanged_: function(e) { |
| var page = e.cardSlider.currentCardValue; |
| |
| // Don't change shownPage until startup is done (and page changes actually |
| // reflect user actions). |
| if (!this.isStartingUp_()) { |
| // TODO(dbeam): is this ever false? |
| if (page.classList.contains('apps-page')) |
| this.setShownPage_(this.getAppsPageIndex(page)); |
| else |
| console.error('unknown page selected'); |
| } |
| |
| // Update the active dot |
| var curDot = this.dotList.getElementsByClassName('selected')[0]; |
| if (curDot) |
| curDot.classList.remove('selected'); |
| page.navigationDot.classList.add('selected'); |
| this.updatePageSwitchers(); |
| }, |
| |
| /** |
| * Saves/updates the newly selected page to open when first loading the NTP. |
| * @param {number} shownPageIndex The new shown page index. |
| * @private |
| */ |
| setShownPage_: function(shownPageIndex) { |
| assert(shownPageIndex >= 0); |
| this.shownPageIndex = shownPageIndex; |
| chrome.send('pageSelected', [this.shownPageIndex]); |
| this.updateAppLauncherPromoHiddenState_(); |
| }, |
| |
| /** |
| * Listen for card additions to update the page switchers or the current |
| * card accordingly. |
| * @param {Event} e A card removed or added event. |
| */ |
| onCardAdded_: function(e) { |
| // When the second arg passed to insertBefore is falsey, it acts just like |
| // appendChild. |
| this.pageList.insertBefore(e.addedCard, this.tilePages[e.addedIndex]); |
| this.onCardAddedOrRemoved_(); |
| }, |
| |
| /** |
| * Listen for card removals to update the page switchers or the current card |
| * accordingly. |
| * @param {Event} e A card removed or added event. |
| */ |
| onCardRemoved_: function(e) { |
| e.removedCard.parentNode.removeChild(e.removedCard); |
| this.onCardAddedOrRemoved_(); |
| }, |
| |
| /** |
| * Called when a card is removed or added. |
| * @private |
| */ |
| onCardAddedOrRemoved_: function() { |
| if (this.isStartingUp_()) |
| return; |
| |
| // Without repositioning there were issues - http://crbug.com/133457. |
| this.cardSlider.repositionFrame(); |
| this.updatePageSwitchers(); |
| }, |
| |
| /** |
| * Save the name of an apps page. |
| * Store the apps page name into the preferences store. |
| * @param {ntp.AppsPage} appPage The app page for which we wish to save. |
| * @param {string} name The name of the page. |
| */ |
| saveAppPageName: function(appPage, name) { |
| var index = this.getAppsPageIndex(appPage); |
| assert(index != -1); |
| chrome.send('saveAppPageName', [name, index]); |
| }, |
| |
| /** |
| * Window resize handler. |
| * @private |
| */ |
| onWindowResize_: function(e) { |
| this.cardSlider.resize(this.sliderFrame.offsetWidth); |
| this.updatePageSwitchers(); |
| }, |
| |
| /** |
| * Listener for offline status change events. Updates apps that are |
| * not offline-enabled to be grayscale if the browser is offline. |
| * @private |
| */ |
| updateOfflineEnabledApps_: function() { |
| var apps = document.querySelectorAll('.app'); |
| for (var i = 0; i < apps.length; ++i) { |
| if (apps[i].appData.enabled && !apps[i].appData.offlineEnabled) { |
| apps[i].setIcon(); |
| apps[i].loadIcon(); |
| } |
| } |
| }, |
| |
| /** |
| * Handler for key events on the page. Ctrl-Arrow will switch the visible |
| * page. |
| * @param {Event} e The KeyboardEvent. |
| * @private |
| */ |
| onDocKeyDown_: function(e) { |
| if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) |
| return; |
| |
| var direction = 0; |
| if (e.key == 'ArrowLeft') |
| direction = -1; |
| else if (e.key == 'ArrowRight') |
| direction = 1; |
| else |
| return; |
| |
| var cardIndex = |
| (this.cardSlider.currentCard + direction + |
| this.cardSlider.cardCount) % this.cardSlider.cardCount; |
| this.cardSlider.selectCard(cardIndex, true); |
| |
| e.stopPropagation(); |
| }, |
| |
| /** |
| * Returns the index of a given tile page. |
| * @param {ntp.TilePage} page The TilePage we wish to find. |
| * @return {number} The index of |page| or -1 if it is not in the |
| * collection. |
| */ |
| getTilePageIndex: function(page) { |
| return Array.prototype.indexOf.call(this.tilePages, page); |
| }, |
| |
| /** |
| * Removes a page and navigation dot (if the navdot exists). |
| * @param {ntp.TilePage} page The page to be removed. |
| * @param {boolean=} opt_animate If the removal should be animated. |
| */ |
| removeTilePageAndDot_: function(page, opt_animate) { |
| if (page.navigationDot) |
| page.navigationDot.remove(opt_animate); |
| this.cardSlider.removeCard(page); |
| }, |
| }; |
| |
| return { |
| PageListView: PageListView |
| }; |
| }); |