| // Copyright (c) 2011 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. |
| |
| var MAX_APPS_PER_ROW = []; |
| MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4; |
| MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6; |
| |
| function getAppsCallback(data) { |
| logEvent('received apps'); |
| |
| // In the case of prefchange-triggered updates, we don't receive this flag. |
| // Just leave it set as it was before in that case. |
| if ('showPromo' in data) |
| apps.showPromo = data.showPromo; |
| |
| var appsSection = $('apps'); |
| var appsSectionContent = $('apps-content'); |
| var appsMiniview = appsSection.getElementsByClassName('miniview')[0]; |
| var appsPromo = $('apps-promo'); |
| var appsPromoLink = $('apps-promo-link'); |
| var appsPromoPing = APP_LAUNCH_URL.PING_WEBSTORE + '+' + apps.showPromo; |
| var webStoreEntry, webStoreMiniEntry; |
| |
| // Hide menu options that are not supported on the OS or windowing system. |
| |
| // The "Launch as Window" menu option. |
| $('apps-launch-type-window-menu-item').hidden = data.disableAppWindowLaunch; |
| |
| // The "Create App Shortcut" menu option. |
| $('apps-create-shortcut-command-menu-item').hidden = |
| $('apps-create-shortcut-command-separator').hidden = |
| data.disableCreateAppShortcut; |
| |
| // Hide the context menu, if there is any open. |
| cr.ui.contextMenuHandler.hideMenu(); |
| |
| appsMiniview.textContent = ''; |
| appsSectionContent.textContent = ''; |
| |
| data.apps.sort(function(a,b) { |
| return a.app_launch_index - b.app_launch_index; |
| }); |
| |
| // Determines if the web store link should be detached and place in the |
| // top right of the screen. |
| apps.detachWebstoreEntry = |
| !apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode]; |
| |
| markNewApps(data.apps); |
| apps.data = data.apps; |
| |
| clearClosedMenu(apps.menu); |
| |
| // We wait for the app icons to load before displaying them, but never wait |
| // longer than 200ms. |
| apps.loadedImages = 0; |
| apps.imageTimer = setTimeout(apps.showImages.bind(apps), 200); |
| |
| data.apps.forEach(function(app) { |
| appsSectionContent.appendChild(apps.createElement(app)); |
| }); |
| |
| if (data.showPromo) { |
| // Add the promo content... |
| $('apps-promo-heading').textContent = data.promoHeader; |
| appsPromoLink.href = data.promoLink; |
| appsPromoLink.textContent = data.promoButton; |
| appsPromoLink.ping = appsPromoPing; |
| $('apps-promo-hide').textContent = data.promoExpire; |
| |
| // ... then display the promo. |
| document.documentElement.classList.add('apps-promo-visible'); |
| } else { |
| document.documentElement.classList.remove('apps-promo-visible'); |
| } |
| |
| // Only show the web store entry if there are apps installed, since the promo |
| // is sufficient otherwise. |
| if (data.apps.length > 0) { |
| webStoreEntry = apps.createWebStoreElement(); |
| webStoreEntry.querySelector('a').ping = appsPromoPing; |
| appsSectionContent.appendChild(webStoreEntry); |
| if (apps.detachWebstoreEntry) { |
| webStoreEntry.classList.add('loner'); |
| } else { |
| webStoreEntry.classList.remove('loner'); |
| apps.data.push('web-store-entry'); |
| } |
| } |
| |
| data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) { |
| appsMiniview.appendChild(apps.createMiniviewElement(app)); |
| addClosedMenuEntryWithLink(apps.menu, apps.createClosedMenuElement(app)); |
| }); |
| if (data.apps.length < MAX_MINIVIEW_ITEMS) { |
| webStoreMiniEntry = apps.createWebStoreMiniElement(); |
| webStoreMiniEntry.querySelector('a').ping = appsPromoPing; |
| appsMiniview.appendChild(webStoreMiniEntry); |
| addClosedMenuEntryWithLink(apps.menu, |
| apps.createWebStoreClosedMenuElement()); |
| } |
| |
| if (!data.showLauncher) |
| hideSection(Section.APPS); |
| else |
| appsSection.classList.remove('disabled'); |
| |
| addClosedMenuFooter(apps.menu, 'apps', MENU_APPS, Section.APPS); |
| |
| apps.loaded = true; |
| |
| if (appsPromoLink) |
| appsPromoLink.ping = appsPromoPing; |
| maybeDoneLoading(); |
| |
| // Disable the animations when the app launcher is being (re)initailized. |
| apps.layout({disableAnimations:true}); |
| |
| if (isDoneLoading()) { |
| updateMiniviewClipping(appsMiniview); |
| layoutSections(); |
| } |
| } |
| |
| function markNewApps(data) { |
| var oldData = apps.data; |
| data.forEach(function(app) { |
| if (hashParams['app-id'] == app['id']) { |
| delete hashParams['app-id']; |
| app.isNew = true; |
| } else if (oldData && |
| !oldData.some(function(id) { return id == app.id; })) { |
| app.isNew = true; |
| } else { |
| app.isNew = false; |
| } |
| }); |
| } |
| |
| function appsPrefChangeCallback(data) { |
| // Currently the only pref that is watched is the launch type. |
| data.apps.forEach(function(app) { |
| var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']'); |
| if (appLink) |
| appLink.setAttribute('launch-type', app['launch_type']); |
| }); |
| } |
| |
| // Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE histogram. |
| // This should only be invoked from the AppLauncherHandler. |
| function launchAppAfterEnable(appId) { |
| chrome.send('launchApp', [appId, APP_LAUNCH.NTP_APP_RE_ENABLE]); |
| } |
| |
| var apps = (function() { |
| |
| function createElement(app) { |
| var div = document.createElement('div'); |
| div.className = 'app'; |
| |
| var a = div.appendChild(document.createElement('a')); |
| a.setAttribute('app-id', app['id']); |
| a.setAttribute('launch-type', app['launch_type']); |
| a.draggable = false; |
| a.xtitle = a.textContent = app['name']; |
| a.href = app['launch_url']; |
| |
| return div; |
| } |
| |
| /** |
| * Launches an application. |
| * @param {string} appId Application to launch. |
| * @param {MouseEvent} opt_mouseEvent Mouse event from the click that |
| * triggered the launch, used to detect modifier keys that change |
| * the tab's disposition. |
| */ |
| function launchApp(appId, opt_mouseEvent) { |
| var args = [appId, getAppLaunchType()]; |
| if (opt_mouseEvent) { |
| // Launch came from a click - add details of the click |
| // Otherwise it came from a 'command' event from elsewhere in the UI. |
| args.push(opt_mouseEvent.altKey, opt_mouseEvent.ctrlKey, |
| opt_mouseEvent.metaKey, opt_mouseEvent.shiftKey, |
| opt_mouseEvent.button); |
| } |
| chrome.send('launchApp', args); |
| } |
| |
| function isAppSectionMaximized() { |
| return getAppLaunchType() == APP_LAUNCH.NTP_APPS_MAXIMIZED && |
| !$('apps').classList.contains('disabled'); |
| } |
| |
| function isAppsMenu(node) { |
| return node.id == 'apps-menu'; |
| } |
| |
| function getAppLaunchType() { |
| // We determine if the apps section is maximized, collapsed or in menu mode |
| // based on the class of the apps section. |
| if ($('apps').classList.contains('menu')) |
| return APP_LAUNCH.NTP_APPS_MENU; |
| else if ($('apps').classList.contains('collapsed')) |
| return APP_LAUNCH.NTP_APPS_COLLAPSED; |
| else |
| return APP_LAUNCH.NTP_APPS_MAXIMIZED; |
| } |
| |
| /** |
| * @this {!HTMLAnchorElement} |
| */ |
| function handleClick(e) { |
| var appId = e.currentTarget.getAttribute('app-id'); |
| if (!appDragAndDrop.isDragging()) |
| launchApp(appId, e); |
| return false; |
| } |
| |
| // Keep in sync with LaunchType in extension_prefs.h |
| var LaunchType = { |
| LAUNCH_PINNED: 0, |
| LAUNCH_REGULAR: 1, |
| LAUNCH_FULLSCREEN: 2, |
| LAUNCH_WINDOW: 3 |
| }; |
| |
| // Keep in sync with LaunchContainer in extension_constants.h |
| var LaunchContainer = { |
| LAUNCH_WINDOW: 0, |
| LAUNCH_PANEL: 1, |
| LAUNCH_TAB: 2 |
| }; |
| |
| var currentApp; |
| var promoHasBeenSeen = false; |
| |
| function addContextMenu(el, app) { |
| el.addEventListener('contextmenu', cr.ui.contextMenuHandler); |
| el.addEventListener('keydown', cr.ui.contextMenuHandler); |
| el.addEventListener('keyup', cr.ui.contextMenuHandler); |
| |
| Object.defineProperty(el, 'contextMenu', { |
| get: function() { |
| currentApp = app; |
| |
| $('apps-launch-command').label = app['name']; |
| $('apps-options-command').canExecuteChange(); |
| |
| var launchTypeEl; |
| if (el.getAttribute('app-id') === app['id']) { |
| launchTypeEl = el; |
| } else { |
| appLinkSel = 'a[app-id=' + app['id'] + ']'; |
| launchTypeEl = el.querySelector(appLinkSel); |
| } |
| |
| var launchType = launchTypeEl.getAttribute('launch-type'); |
| var launchContainer = app['launch_container']; |
| var isPanel = launchContainer == LaunchContainer.LAUNCH_PANEL; |
| |
| // Update the commands related to the launch type. |
| var launchTypeIds = ['apps-launch-type-pinned', |
| 'apps-launch-type-regular', |
| 'apps-launch-type-fullscreen', |
| 'apps-launch-type-window']; |
| launchTypeIds.forEach(function(id) { |
| var command = $(id); |
| command.disabled = isPanel; |
| command.checked = !isPanel && |
| launchType == command.getAttribute('launch-type'); |
| }); |
| |
| return $('app-context-menu'); |
| } |
| }); |
| } |
| |
| document.addEventListener('command', function(e) { |
| if (!currentApp) |
| return; |
| |
| var commandId = e.command.id; |
| switch (commandId) { |
| case 'apps-options-command': |
| window.location = currentApp['options_url']; |
| break; |
| case 'apps-launch-command': |
| launchApp(currentApp['id']); |
| break; |
| case 'apps-uninstall-command': |
| chrome.send('uninstallApp', [currentApp['id']]); |
| break; |
| case 'apps-create-shortcut-command': |
| chrome.send('createAppShortcut', [currentApp['id']]); |
| break; |
| case 'apps-launch-type-pinned': |
| case 'apps-launch-type-regular': |
| case 'apps-launch-type-fullscreen': |
| case 'apps-launch-type-window': |
| chrome.send('setLaunchType', |
| [currentApp['id'], |
| Number(e.command.getAttribute('launch-type'))]); |
| break; |
| } |
| }); |
| |
| document.addEventListener('canExecute', function(e) { |
| switch (e.command.id) { |
| case 'apps-options-command': |
| e.canExecute = currentApp && currentApp['options_url']; |
| break; |
| case 'apps-launch-command': |
| e.canExecute = true; |
| break; |
| case 'apps-uninstall-command': |
| e.canExecute = !currentApp['can_uninstall']; |
| break; |
| } |
| }); |
| |
| // Moves the element at position |from| in array |arr| to position |to|. |
| function arrayMove(arr, from, to) { |
| var element = arr.splice(from, 1); |
| arr.splice(to, 0, element[0]); |
| } |
| |
| // The autoscroll rate during drag and drop, in px per second. |
| var APP_AUTOSCROLL_RATE = 400; |
| |
| return { |
| loaded: false, |
| |
| menu: $('apps-menu'), |
| |
| showPromo: false, |
| |
| detachWebstoreEntry: false, |
| |
| scrollMouseXY_: null, |
| |
| scrollListener_: null, |
| |
| // The list of app ids, in order, of each app in the launcher. |
| data_: null, |
| get data() { return this.data_; }, |
| set data(data) { |
| this.data_ = data.map(function(app) { |
| return app.id; |
| }); |
| this.invalidate_(); |
| }, |
| |
| dirty_: true, |
| invalidate_: function() { |
| this.dirty_ = true; |
| }, |
| |
| visible_: true, |
| get visible() { |
| return this.visible_; |
| }, |
| set visible(visible) { |
| this.visible_ = visible; |
| this.invalidate_(); |
| }, |
| |
| maybePingPromoSeen_: function() { |
| if (promoHasBeenSeen || !this.showPromo || !isAppSectionMaximized()) |
| return; |
| |
| promoHasBeenSeen = true; |
| chrome.send('promoSeen', []); |
| }, |
| |
| // DragAndDropDelegate |
| |
| dragContainer: $('apps-content'), |
| transitionsDuration: 200, |
| |
| get dragItem() { return this.dragItem_; }, |
| set dragItem(dragItem) { |
| if (this.dragItem_ != dragItem) { |
| this.dragItem_ = dragItem; |
| this.invalidate_(); |
| } |
| }, |
| |
| // The dimensions of each item in the app launcher. |
| dimensions_: null, |
| get dimensions() { |
| if (this.dimensions_) |
| return this.dimensions_; |
| |
| var width = 124; |
| var height = 136; |
| |
| var marginWidth = 6; |
| var marginHeight = 10; |
| |
| var borderWidth = 0; |
| var borderHeight = 0; |
| |
| this.dimensions_ = { |
| width: width + marginWidth + borderWidth, |
| height: height + marginHeight + borderHeight |
| }; |
| |
| return this.dimensions_; |
| }, |
| |
| // Gets the item under the mouse event |e|. Returns null if there is no |
| // item or if the item is not draggable. |
| getItem: function(e) { |
| var item = findAncestorByClass(e.target, 'app'); |
| |
| // You can't drag the web store launcher. |
| if (item && item.classList.contains('web-store-entry')) |
| return null; |
| |
| return item; |
| }, |
| |
| // Returns true if |coordinates| point to a valid drop location. The |
| // coordinates are relative to the drag container and the object should |
| // have the 'x' and 'y' properties set. |
| canDropOn: function(coordinates) { |
| var cols = MAX_APPS_PER_ROW[layoutMode]; |
| var rows = Math.ceil(this.data.length / cols); |
| |
| var bottom = rows * this.dimensions.height; |
| var right = cols * this.dimensions.width; |
| |
| if (coordinates.x >= right || coordinates.x < 0 || |
| coordinates.y >= bottom || coordinates.y < 0) |
| return false; |
| |
| var position = this.getIndexAt_(coordinates); |
| var appCount = this.data.length; |
| |
| if (!this.detachWebstoreEntry) |
| appCount--; |
| |
| return position >= 0 && position < appCount; |
| }, |
| |
| setDragPlaceholder: function(coordinates) { |
| var position = this.getIndexAt_(coordinates); |
| var appId = this.dragItem.querySelector('a').getAttribute('app-id'); |
| var current = this.data.indexOf(appId); |
| |
| if (current == position || current < 0) |
| return; |
| |
| arrayMove(this.data, current, position); |
| this.invalidate_(); |
| this.layout(); |
| }, |
| |
| getIndexAt_: function(coordinates) { |
| var w = this.dimensions.width; |
| var h = this.dimensions.height; |
| |
| var appsPerRow = MAX_APPS_PER_ROW[layoutMode]; |
| |
| var row = Math.floor(coordinates.y / h); |
| var col = Math.floor(coordinates.x / w); |
| var index = appsPerRow * row + col; |
| |
| var appCount = this.data.length; |
| var rows = Math.ceil(appCount / appsPerRow); |
| |
| // Rather than making the free space on the last row invalid, we |
| // map it to the last valid position. |
| if (index >= appCount && index < appsPerRow * rows) |
| return appCount-1; |
| |
| return index; |
| }, |
| |
| scrollPage: function(xy) { |
| var rect = this.dragContainer.getBoundingClientRect(); |
| |
| // Here, we calculate the visible boundaries of the app launcher, which |
| // are then used to determine when we should auto-scroll. |
| var top = $('apps').getBoundingClientRect().bottom; |
| var bottomFudge = 15; // Fudge factor due to a gradient mask. |
| var bottom = top + maxiviewVisibleHeight - bottomFudge; |
| var left = rect.left + window.scrollX; |
| var right = Math.min(window.innerWidth, rect.left + rect.width); |
| |
| var dy = Math.min(0, xy.y - top) + Math.max(0, xy.y - bottom); |
| var dx = Math.min(0, xy.x - left) + Math.max(0, xy.x - right); |
| |
| if (dx == 0 && dy == 0) { |
| this.stopScroll_(); |
| return; |
| } |
| |
| // If we scroll the page directly from this method, it may be choppy and |
| // inconsistent. Instead, we loop using animation frames, and scroll at a |
| // speed that's independent of how many times this method is called. |
| this.scrollMouseXY_ = {dx: dx, dy: dy}; |
| |
| if (!this.scrollListener_) { |
| this.scrollListener_ = this.scrollImpl_.bind(this); |
| this.scrollStep_(); |
| } |
| }, |
| |
| scrollStep_: function() { |
| this.scrollStart_ = Date.now(); |
| window.webkitRequestAnimationFrame(this.scrollListener_); |
| }, |
| |
| scrollImpl_: function(time) { |
| if (!appDragAndDrop.isDragging()) { |
| this.stopScroll_(); |
| return; |
| } |
| |
| if (!this.scrollMouseXY_) |
| return; |
| |
| var step = time - this.scrollStart_; |
| |
| window.scrollBy( |
| this.calcScroll_(this.scrollMouseXY_.dx, step), |
| this.calcScroll_(this.scrollMouseXY_.dy, step)); |
| |
| this.scrollStep_(); |
| }, |
| |
| calcScroll_: function(delta, step) { |
| if (delta == 0) |
| return 0; |
| |
| // Increase the multiplier for every 50px the mouse is beyond the edge. |
| var sign = delta > 0 ? 1 : -1; |
| var scalar = APP_AUTOSCROLL_RATE * step / 1000; |
| var multiplier = Math.floor(Math.abs(delta) / 50) + 1; |
| |
| return sign * scalar * multiplier; |
| }, |
| |
| stopScroll_: function() { |
| this.scrollListener_ = null; |
| this.scrollMouseXY_ = null; |
| }, |
| |
| saveDrag: function(draggedItem) { |
| this.invalidate_(); |
| this.layout(); |
| |
| var draggedAppId = draggedItem.querySelector('a').getAttribute('app-id'); |
| var appIds = this.data.filter(function(id) { |
| return id != 'web-store-entry'; |
| }); |
| |
| // Wait until the transitions are complete before notifying the browser. |
| // Otherwise, the apps will be re-rendered while still transitioning. |
| setTimeout(function() { |
| chrome.send('reorderApps', [draggedAppId, appIds]); |
| }, this.transitionsDuration + 10); |
| }, |
| |
| layout: function(options) { |
| options = options || {}; |
| if (!this.dirty_ && options.force != true) |
| return; |
| |
| try { |
| var container = this.dragContainer; |
| if (options.disableAnimations) |
| container.setAttribute('launcher-animations', false); |
| var d0 = Date.now(); |
| this.layoutImpl_(); |
| this.dirty_ = false; |
| logEvent('apps.layout: ' + (Date.now() - d0)); |
| |
| } finally { |
| if (options.disableAnimations) { |
| // We need to re-enable animations asynchronously, so that the |
| // animations are still disabled for this layout update. |
| setTimeout(function() { |
| container.setAttribute('launcher-animations', true); |
| }, 0); |
| } |
| } |
| }, |
| |
| layoutImpl_: function() { |
| var apps = this.data || []; |
| var rects = this.getLayoutRects_(apps.length); |
| var appsContent = this.dragContainer; |
| |
| // Ping the PROMO_SEEN histogram only when the promo is maximized, and |
| // maximum once per NTP load. |
| this.maybePingPromoSeen_(); |
| |
| if (!this.visible) |
| return; |
| |
| for (var i = 0; i < apps.length; i++) { |
| var app = appsContent.querySelector('[app-id='+apps[i]+']').parentNode; |
| |
| // If the node is being dragged, don't try to place it in the grid. |
| if (app == this.dragItem) |
| continue; |
| |
| app.style.left = rects[i].left + 'px'; |
| app.style.top = rects[i].top + 'px'; |
| } |
| |
| // We need to set the container's height manually because the apps use |
| // absolute positioning. |
| var rows = Math.ceil(apps.length / MAX_APPS_PER_ROW[layoutMode]); |
| appsContent.style.height = (rows * this.dimensions.height) + 'px'; |
| }, |
| |
| getLayoutRects_: function(appCount) { |
| var availableWidth = this.dragContainer.offsetWidth; |
| var rtl = isRtl(); |
| var rects = []; |
| var w = this.dimensions.width; |
| var h = this.dimensions.height; |
| var appsPerRow = MAX_APPS_PER_ROW[layoutMode]; |
| |
| for (var i = 0; i < appCount; i++) { |
| var top = Math.floor(i / appsPerRow) * h; |
| var left = (i % appsPerRow) * w; |
| |
| // Reflect the X axis if an RTL language is active. |
| if (rtl) |
| left = availableWidth - left - w; |
| rects[i] = {left: left, top: top}; |
| } |
| return rects; |
| }, |
| |
| get loadedImages() { |
| return this.loadedImages_; |
| }, |
| |
| set loadedImages(value) { |
| this.loadedImages_ = value; |
| if (this.loadedImages_ == 0) |
| return; |
| |
| // Each application icon is loaded asynchronously. Here, we display |
| // the icons once they've all been loaded to make it look nicer. |
| if (this.loadedImages_ == this.data.length) { |
| this.showImages(); |
| return; |
| } |
| |
| // We won't actually have the visible height until the sections have |
| // been layed out. |
| if (!maxiviewVisibleHeight) |
| return; |
| |
| // If we know the visible height of the maxiview, then we can don't need |
| // to wait for all the icons. Instead, we wait until the visible portion |
| // have been loaded. |
| var appsPerRow = MAX_APPS_PER_ROW[layoutMode]; |
| var rows = Math.ceil(maxiviewVisibleHeight / this.dimensions.height); |
| var count = Math.min(appsPerRow * rows, this.data.length); |
| if (this.loadedImages_ == count) { |
| this.showImages(); |
| return; |
| } |
| }, |
| |
| showImages: function() { |
| $('apps-content').classList.add('visible'); |
| clearTimeout(this.imageTimer); |
| }, |
| |
| createElement: function(app) { |
| var div = createElement(app); |
| var a = div.firstChild; |
| |
| a.onclick = handleClick; |
| a.ping = getAppPingUrl( |
| 'PING_BY_ID', this.showPromo, 'NTP_APPS_MAXIMIZED'); |
| a.style.backgroundImage = url(app['icon_big']); |
| if (app.isNew) { |
| div.setAttribute('new', 'new'); |
| // Delay changing the attribute a bit to let the page settle down a bit. |
| setTimeout(function() { |
| // Make sure the new icon is scrolled into view. |
| document.body.scrollTop = document.body.scrollHeight; |
| |
| // This will trigger the 'bounce' animation defined in apps.css. |
| div.setAttribute('new', 'installed'); |
| }, 500); |
| div.addEventListener('webkitAnimationEnd', function(e) { |
| div.removeAttribute('new'); |
| }); |
| } |
| |
| // CSS background images don't fire 'load' events, so we use an Image. |
| var img = new Image(); |
| img.onload = function() { this.loadedImages++; }.bind(this); |
| img.src = app['icon_big']; |
| |
| var settingsButton = div.appendChild(new cr.ui.ContextMenuButton); |
| settingsButton.className = 'app-settings'; |
| settingsButton.title = localStrings.getString('appsettings'); |
| |
| addContextMenu(div, app); |
| |
| return div; |
| }, |
| |
| createMiniviewElement: function(app) { |
| var span = document.createElement('span'); |
| var a = span.appendChild(document.createElement('a')); |
| |
| a.setAttribute('app-id', app['id']); |
| a.textContent = app['name']; |
| a.href = app['launch_url']; |
| a.onclick = handleClick; |
| a.ping = getAppPingUrl( |
| 'PING_BY_ID', this.showPromo, 'NTP_APPS_COLLAPSED'); |
| a.style.backgroundImage = url(app['icon_small']); |
| a.className = 'item'; |
| span.appendChild(a); |
| |
| addContextMenu(span, app); |
| |
| return span; |
| }, |
| |
| createClosedMenuElement: function(app) { |
| var a = document.createElement('a'); |
| a.setAttribute('app-id', app['id']); |
| a.textContent = app['name']; |
| a.href = app['launch_url']; |
| a.onclick = handleClick; |
| a.ping = getAppPingUrl( |
| 'PING_BY_ID', this.showPromo, 'NTP_APPS_MENU'); |
| a.style.backgroundImage = url(app['icon_small']); |
| a.className = 'item'; |
| |
| addContextMenu(a, app); |
| |
| return a; |
| }, |
| |
| createWebStoreElement: function() { |
| var elm = createElement({ |
| 'id': 'web-store-entry', |
| 'name': localStrings.getString('web_store_title'), |
| 'launch_url': localStrings.getString('web_store_url') |
| }); |
| elm.classList.add('web-store-entry'); |
| return elm; |
| }, |
| |
| createWebStoreMiniElement: function() { |
| var span = document.createElement('span'); |
| span.appendChild(this.createWebStoreClosedMenuElement()); |
| return span; |
| }, |
| |
| createWebStoreClosedMenuElement: function() { |
| var a = document.createElement('a'); |
| a.textContent = localStrings.getString('web_store_title'); |
| a.href = localStrings.getString('web_store_url'); |
| a.style.backgroundImage = url('chrome://theme/IDR_PRODUCT_LOGO_16'); |
| a.className = 'item'; |
| return a; |
| } |
| }; |
| })(); |
| |
| // Enable drag and drop reordering of the app launcher. |
| var appDragAndDrop = new DragAndDropController(apps); |