Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / resources / ntp4 / apps_page.js
blob7c33bd8edc8b955f399d3f3a650561c064525e5e
1 // Copyright (c) 2012 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.
5 cr.define('ntp', function() {
6 'use strict';
8 var APP_LAUNCH = {
9 // The histogram buckets (keep in sync with extension_constants.h).
10 NTP_APPS_MAXIMIZED: 0,
11 NTP_APPS_COLLAPSED: 1,
12 NTP_APPS_MENU: 2,
13 NTP_MOST_VISITED: 3,
14 NTP_RECENTLY_CLOSED: 4,
15 NTP_APP_RE_ENABLE: 16,
16 NTP_WEBSTORE_FOOTER: 18,
17 NTP_WEBSTORE_PLUS_ICON: 19,
20 // Histogram buckets for UMA tracking of where a DnD drop came from.
21 var DRAG_SOURCE = {
22 SAME_APPS_PANE: 0,
23 OTHER_APPS_PANE: 1,
24 MOST_VISITED_PANE: 2,
25 BOOKMARKS_PANE: 3,
26 OUTSIDE_NTP: 4
28 var DRAG_SOURCE_LIMIT = DRAG_SOURCE.OUTSIDE_NTP + 1;
30 /**
31 * App context menu. The class is designed to be used as a singleton with
32 * the app that is currently showing a context menu stored in this.app_.
33 * @constructor
35 function AppContextMenu() {
36 this.__proto__ = AppContextMenu.prototype;
37 this.initialize();
39 cr.addSingletonGetter(AppContextMenu);
41 AppContextMenu.prototype = {
42 initialize: function() {
43 var menu = new cr.ui.Menu;
44 cr.ui.decorate(menu, cr.ui.Menu);
45 menu.classList.add('app-context-menu');
46 this.menu = menu;
48 this.launch_ = this.appendMenuItem_();
49 this.launch_.addEventListener('activate', this.onLaunch_.bind(this));
51 menu.appendChild(cr.ui.MenuItem.createSeparator());
52 this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular');
53 this.launchPinnedTab_ = this.appendMenuItem_('applaunchtypepinned');
54 if (loadTimeData.getBoolean('enableNewBookmarkApps') || !cr.isMac)
55 this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow');
56 this.launchFullscreen_ = this.appendMenuItem_('applaunchtypefullscreen');
58 var self = this;
59 this.forAllLaunchTypes_(function(launchTypeButton, id) {
60 launchTypeButton.addEventListener('activate',
61 self.onLaunchTypeChanged_.bind(self));
62 });
64 this.launchTypeMenuSeparator_ = cr.ui.MenuItem.createSeparator();
65 menu.appendChild(this.launchTypeMenuSeparator_);
66 this.options_ = this.appendMenuItem_('appoptions');
67 this.uninstall_ = this.appendMenuItem_('appuninstall');
69 if (loadTimeData.getBoolean('canShowAppInfoDialog')) {
70 this.appinfo_ = this.appendMenuItem_('appinfodialog');
71 this.appinfo_.addEventListener('activate',
72 this.onShowAppInfo_.bind(this));
73 } else {
74 this.details_ = this.appendMenuItem_('appdetails');
75 this.details_.addEventListener('activate',
76 this.onShowDetails_.bind(this));
79 this.options_.addEventListener('activate',
80 this.onShowOptions_.bind(this));
81 this.uninstall_.addEventListener('activate',
82 this.onUninstall_.bind(this));
84 if (!cr.isChromeOS) {
85 this.createShortcutSeparator_ =
86 menu.appendChild(cr.ui.MenuItem.createSeparator());
87 this.createShortcut_ = this.appendMenuItem_('appcreateshortcut');
88 this.createShortcut_.addEventListener(
89 'activate', this.onCreateShortcut_.bind(this));
92 document.body.appendChild(menu);
95 /**
96 * Appends a menu item to |this.menu|.
97 * @param {string=} opt_textId If defined, the ID for the localized string
98 * that acts as the item's label.
100 appendMenuItem_: function(opt_textId) {
101 var button = cr.doc.createElement('button');
102 this.menu.appendChild(button);
103 cr.ui.decorate(button, cr.ui.MenuItem);
104 if (opt_textId)
105 button.textContent = loadTimeData.getString(opt_textId);
106 return button;
110 * Iterates over all the launch type menu items.
111 * @param {function(cr.ui.MenuItem, number)} f The function to call for each
112 * menu item. The parameters to the function include the menu item and
113 * the associated launch ID.
115 forAllLaunchTypes_: function(f) {
116 // Order matters: index matches launchType id.
117 var launchTypes = [this.launchPinnedTab_,
118 this.launchRegularTab_,
119 this.launchFullscreen_,
120 this.launchNewWindow_];
122 for (var i = 0; i < launchTypes.length; ++i) {
123 if (!launchTypes[i])
124 continue;
126 f(launchTypes[i], i);
131 * Does all the necessary setup to show the menu for the given app.
132 * @param {App} app The App object that will be showing a context menu.
134 setupForApp: function(app) {
135 this.app_ = app;
137 this.launch_.textContent = app.appData.title;
139 var launchTypeWindow = this.launchNewWindow_;
140 this.forAllLaunchTypes_(function(launchTypeButton, id) {
141 launchTypeButton.disabled = false;
142 launchTypeButton.checked = app.appData.launch_type == id;
143 // If bookmark apps are enabled, only show the "Open as window" button.
144 launchTypeButton.hidden = app.appData.packagedApp ||
145 (loadTimeData.getBoolean('enableNewBookmarkApps') &&
146 launchTypeButton != launchTypeWindow);
149 this.launchTypeMenuSeparator_.hidden = app.appData.packagedApp;
151 this.options_.disabled = !app.appData.optionsUrl || !app.appData.enabled;
152 if (this.details_)
153 this.details_.disabled = !app.appData.detailsUrl;
154 this.uninstall_.disabled = !app.appData.mayDisable;
156 if (cr.isMac) {
157 // On Windows and Linux, these should always be visible. On ChromeOS,
158 // they are never created. On Mac, shortcuts can only be created for
159 // new-style packaged apps, so hide the menu item.
160 this.createShortcutSeparator_.hidden = this.createShortcut_.hidden =
161 !app.appData.packagedApp;
166 * Handlers for menu item activation.
167 * @param {Event} e The activation event.
168 * @private
170 onLaunch_: function(e) {
171 chrome.send('launchApp', [this.app_.appId, APP_LAUNCH.NTP_APPS_MENU]);
173 onLaunchTypeChanged_: function(e) {
174 var pressed = e.currentTarget;
175 var app = this.app_;
176 var targetLaunchType = pressed;
177 // When bookmark apps are enabled, hosted apps can only toggle between
178 // open as window and open as tab.
179 if (loadTimeData.getBoolean('enableNewBookmarkApps')) {
180 targetLaunchType = this.launchNewWindow_.checked ?
181 this.launchRegularTab_ : this.launchNewWindow_;
183 this.forAllLaunchTypes_(function(launchTypeButton, id) {
184 if (launchTypeButton == targetLaunchType) {
185 chrome.send('setLaunchType', [app.appId, id]);
186 // Manually update the launch type. We will only get
187 // appsPrefChangeCallback calls after changes to other NTP instances.
188 app.appData.launch_type = id;
192 onShowOptions_: function(e) {
193 window.location = this.app_.appData.optionsUrl;
195 onShowDetails_: function(e) {
196 var url = this.app_.appData.detailsUrl;
197 url = appendParam(url, 'utm_source', 'chrome-ntp-launcher');
198 window.location = url;
200 onUninstall_: function(e) {
201 chrome.send('uninstallApp', [this.app_.appData.id]);
203 onCreateShortcut_: function(e) {
204 chrome.send('createAppShortcut', [this.app_.appData.id]);
206 onShowAppInfo_: function(e) {
207 chrome.send('showAppInfo', [this.app_.appData.id]);
212 * Creates a new App object.
213 * @param {Object} appData The data object that describes the app.
214 * @constructor
215 * @extends {HTMLDivElement}
217 function App(appData) {
218 var el = cr.doc.createElement('div');
219 el.__proto__ = App.prototype;
220 el.initialize(appData);
222 return el;
225 App.prototype = {
226 __proto__: HTMLDivElement.prototype,
229 * Initialize the app object.
230 * @param {Object} appData The data object that describes the app.
232 initialize: function(appData) {
233 this.appData = appData;
234 assert(this.appData_.id, 'Got an app without an ID');
235 this.id = this.appData_.id;
236 this.setAttribute('role', 'menuitem');
238 this.className = 'app focusable';
240 if (!this.appData_.icon_big_exists && this.appData_.icon_small_exists)
241 this.useSmallIcon_ = true;
243 this.appContents_ = this.useSmallIcon_ ?
244 $('app-small-icon-template').cloneNode(true) :
245 $('app-large-icon-template').cloneNode(true);
246 this.appContents_.id = '';
247 this.appendChild(this.appContents_);
249 this.appImgContainer_ = /** @type {HTMLElement} */(
250 this.querySelector('.app-img-container'));
251 this.appImg_ = this.appImgContainer_.querySelector('img');
252 this.setIcon();
254 if (this.useSmallIcon_) {
255 this.imgDiv_ = /** @type {HTMLElement} */(
256 this.querySelector('.app-icon-div'));
257 this.addLaunchClickTarget_(this.imgDiv_);
258 this.imgDiv_.title = this.appData_.full_name;
259 chrome.send('getAppIconDominantColor', [this.id]);
260 } else {
261 this.addLaunchClickTarget_(this.appImgContainer_);
262 this.appImgContainer_.title = this.appData_.full_name;
265 // The app's full name is shown in the tooltip, whereas the short name
266 // is used for the label.
267 var appSpan = /** @type {HTMLElement} */(
268 this.appContents_.querySelector('.title'));
269 appSpan.textContent = this.appData_.title;
270 appSpan.title = this.appData_.full_name;
271 this.addLaunchClickTarget_(appSpan);
273 this.addEventListener('keydown', cr.ui.contextMenuHandler);
274 this.addEventListener('keyup', cr.ui.contextMenuHandler);
276 // This hack is here so that appContents.contextMenu will be the same as
277 // this.contextMenu.
278 var self = this;
279 this.appContents_.__defineGetter__('contextMenu', function() {
280 return self.contextMenu;
282 this.appContents_.addEventListener('contextmenu',
283 cr.ui.contextMenuHandler);
285 this.addEventListener('mousedown', this.onMousedown_, true);
286 this.addEventListener('keydown', this.onKeydown_);
287 this.addEventListener('keyup', this.onKeyup_);
291 * Sets the color of the favicon dominant color bar.
292 * @param {string} color The css-parsable value for the color.
294 set stripeColor(color) {
295 this.querySelector('.color-stripe').style.backgroundColor = color;
299 * Removes the app tile from the page. Should be called after the app has
300 * been uninstalled.
302 remove: function(opt_animate) {
303 // Unset the ID immediately, because the app is already gone. But leave
304 // the tile on the page as it animates out.
305 this.id = '';
306 this.tile.doRemove(opt_animate);
310 * Set the URL of the icon from |appData_|. This won't actually show the
311 * icon until loadIcon() is called (for performance reasons; we don't want
312 * to load icons until we have to).
314 setIcon: function() {
315 var src = this.useSmallIcon_ ? this.appData_.icon_small :
316 this.appData_.icon_big;
317 if (!this.appData_.enabled ||
318 (!this.appData_.offlineEnabled && !navigator.onLine)) {
319 src += '?grayscale=true';
322 this.appImgSrc_ = src;
323 this.classList.add('icon-loading');
327 * Shows the icon for the app. That is, it causes chrome to load the app
328 * icon resource.
330 loadIcon: function() {
331 if (this.appImgSrc_) {
332 this.appImg_.src = this.appImgSrc_;
333 this.appImg_.classList.remove('invisible');
334 this.appImgSrc_ = null;
337 this.classList.remove('icon-loading');
341 * Set the size and position of the app tile.
342 * @param {number} size The total size of |this|.
343 * @param {number} x The x-position.
344 * @param {number} y The y-position.
345 * animate.
347 setBounds: function(size, x, y) {
348 var imgSize = size * APP_IMG_SIZE_FRACTION;
349 this.appImgContainer_.style.width = this.appImgContainer_.style.height =
350 toCssPx(this.useSmallIcon_ ? 16 : imgSize);
351 if (this.useSmallIcon_) {
352 // 3/4 is the ratio of 96px to 128px (the used height and full height
353 // of icons in apps).
354 var iconSize = imgSize * 3 / 4;
355 // The -2 is for the div border to improve the visual alignment for the
356 // icon div.
357 this.imgDiv_.style.width = this.imgDiv_.style.height =
358 toCssPx(iconSize - 2);
359 // Margins set to get the icon placement right and the text to line up.
360 this.imgDiv_.style.marginTop = this.imgDiv_.style.marginBottom =
361 toCssPx((imgSize - iconSize) / 2);
364 this.style.width = this.style.height = toCssPx(size);
365 this.style.left = toCssPx(x);
366 this.style.right = toCssPx(x);
367 this.style.top = toCssPx(y);
371 * Invoked when an app is clicked.
372 * @param {Event} e The click event.
373 * @private
375 onClick_: function(e) {
376 var url = !this.appData_.is_webstore ? '' :
377 appendParam(this.appData_.url,
378 'utm_source',
379 'chrome-ntp-icon');
381 chrome.send('launchApp',
382 [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, url,
383 e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
385 // Don't allow the click to trigger a link or anything
386 e.preventDefault();
390 * Invoked when the user presses a key while the app is focused.
391 * @param {Event} e The key event.
392 * @private
394 onKeydown_: function(e) {
395 if (e.keyIdentifier == 'Enter') {
396 chrome.send('launchApp',
397 [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, '',
398 0, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]);
399 e.preventDefault();
400 e.stopPropagation();
402 this.onKeyboardUsed_(e.keyCode);
406 * Invoked when the user releases a key while the app is focused.
407 * @param {Event} e The key event.
408 * @private
410 onKeyup_: function(e) {
411 this.onKeyboardUsed_(e.keyCode);
415 * Called when the keyboard has been used (key down or up). The .click-focus
416 * hack is removed if the user presses a key that can change focus.
417 * @param {number} keyCode The key code of the keyboard event.
418 * @private
420 onKeyboardUsed_: function(keyCode) {
421 switch (keyCode) {
422 case 9: // Tab.
423 case 37: // Left arrow.
424 case 38: // Up arrow.
425 case 39: // Right arrow.
426 case 40: // Down arrow.
427 this.classList.remove('click-focus');
432 * Adds a node to the list of targets that will launch the app. This list
433 * is also used in onMousedown to determine whether the app contents should
434 * be shown as active (if we don't do this, then clicking anywhere in
435 * appContents, even a part that is outside the ideally clickable region,
436 * will cause the app icon to look active).
437 * @param {HTMLElement} node The node that should be clickable.
439 addLaunchClickTarget_: function(node) {
440 node.classList.add('launch-click-target');
441 node.addEventListener('click', this.onClick_.bind(this));
445 * Handler for mousedown on the App. Adds a class that allows us to
446 * not display as :active for right clicks (specifically, don't pulse on
447 * these occasions). Also, we don't pulse for clicks that aren't within the
448 * clickable regions.
449 * @param {Event} e The mousedown event.
451 onMousedown_: function(e) {
452 // If the current platform uses middle click to autoscroll and this
453 // mousedown isn't handled, onClick_() will never fire. crbug.com/142939
454 if (e.button == 1)
455 e.preventDefault();
457 if (e.button == 2 ||
458 !findAncestorByClass(/** @type {Element} */(e.target),
459 'launch-click-target')) {
460 this.appContents_.classList.add('suppress-active');
461 } else {
462 this.appContents_.classList.remove('suppress-active');
465 // This class is here so we don't show the focus state for apps that
466 // gain keyboard focus via mouse clicking.
467 this.classList.add('click-focus');
471 * Change the appData and update the appearance of the app.
472 * @param {AppInfo} appData The new data object that describes the app.
474 replaceAppData: function(appData) {
475 this.appData_ = appData;
476 this.setIcon();
477 this.loadIcon();
481 * The data and preferences for this app.
482 * @type {Object}
484 set appData(data) {
485 this.appData_ = data;
487 get appData() {
488 return this.appData_;
491 get appId() {
492 return this.appData_.id;
496 * Returns a pointer to the context menu for this app. All apps share the
497 * singleton AppContextMenu. This function is called by the
498 * ContextMenuHandler in response to the 'contextmenu' event.
499 * @type {cr.ui.Menu}
501 get contextMenu() {
502 var menu = AppContextMenu.getInstance();
503 menu.setupForApp(this);
504 return menu.menu;
508 * Returns whether this element can be 'removed' from chrome (i.e. whether
509 * the user can drag it onto the trash and expect something to happen).
510 * @return {boolean} True if the app can be uninstalled.
512 canBeRemoved: function() {
513 return this.appData_.mayDisable;
517 * Uninstalls the app after it's been dropped on the trash.
519 removeFromChrome: function() {
520 chrome.send('uninstallApp', [this.appData_.id, true]);
521 this.tile.tilePage.removeTile(this.tile, true);
525 * Called when a drag is starting on the tile. Updates dataTransfer with
526 * data for this tile.
528 setDragData: function(dataTransfer) {
529 dataTransfer.setData('Text', this.appData_.title);
530 dataTransfer.setData('URL', this.appData_.url);
534 var TilePage = ntp.TilePage;
536 // The fraction of the app tile size that the icon uses.
537 var APP_IMG_SIZE_FRACTION = 4 / 5;
539 var appsPageGridValues = {
540 // The fewest tiles we will show in a row.
541 minColCount: 3,
542 // The most tiles we will show in a row.
543 maxColCount: 6,
545 // The smallest a tile can be.
546 minTileWidth: 64 / APP_IMG_SIZE_FRACTION,
547 // The biggest a tile can be.
548 maxTileWidth: 128 / APP_IMG_SIZE_FRACTION,
550 // The padding between tiles, as a fraction of the tile width.
551 tileSpacingFraction: 1 / 8,
553 TilePage.initGridValues(appsPageGridValues);
556 * Creates a new AppsPage object.
557 * @constructor
558 * @extends {TilePage}
560 function AppsPage() {
561 var el = new TilePage(appsPageGridValues);
562 el.__proto__ = AppsPage.prototype;
563 el.initialize();
565 return el;
568 AppsPage.prototype = {
569 __proto__: TilePage.prototype,
571 initialize: function() {
572 this.classList.add('apps-page');
574 this.addEventListener('cardselected', this.onCardSelected_);
576 this.addEventListener('tilePage:tile_added', this.onTileAdded_);
578 this.content_.addEventListener('scroll', this.onScroll_.bind(this));
582 * Highlight a newly installed app as it's added to the NTP.
583 * @param {AppInfo} appData The data object that describes the app.
585 insertAndHighlightApp: function(appData) {
586 ntp.getCardSlider().selectCardByValue(this);
587 this.content_.scrollTop = this.content_.scrollHeight;
588 this.insertApp(appData, true);
592 * Similar to appendApp, but it respects the app_launch_ordinal field of
593 * |appData|.
594 * @param {Object} appData The data that describes the app.
595 * @param {boolean} animate Whether to animate the insertion.
597 insertApp: function(appData, animate) {
598 var index = this.tileElements_.length;
599 for (var i = 0; i < this.tileElements_.length; i++) {
600 if (appData.app_launch_ordinal <
601 this.tileElements_[i].firstChild.appData.app_launch_ordinal) {
602 index = i;
603 break;
607 this.addTileAt(new App(appData), index, animate);
611 * Handler for 'cardselected' event, fired when |this| is selected. The
612 * first time this is called, we load all the app icons.
613 * @private
615 onCardSelected_: function(e) {
616 var apps = this.querySelectorAll('.app.icon-loading');
617 for (var i = 0; i < apps.length; i++) {
618 apps[i].loadIcon();
623 * Handler for tile additions to this page.
624 * @param {Event} e The tilePage:tile_added event.
626 onTileAdded_: function(e) {
627 assert(e.currentTarget == this);
628 assert(e.addedTile.firstChild instanceof App);
629 if (this.classList.contains('selected-card'))
630 e.addedTile.firstChild.loadIcon();
634 * A handler for when the apps page is scrolled (then we need to reposition
635 * the bubbles.
636 * @private
638 onScroll_: function(e) {
639 if (!this.selected)
640 return;
641 for (var i = 0; i < this.tileElements_.length; i++) {
642 var app = this.tileElements_[i].firstChild;
643 assert(app instanceof App);
647 /** @override */
648 doDragOver: function(e) {
649 // Only animatedly re-arrange if the user is currently dragging an app.
650 var tile = ntp.getCurrentlyDraggingTile();
651 if (tile && tile.querySelector('.app')) {
652 TilePage.prototype.doDragOver.call(this, e);
653 } else {
654 e.preventDefault();
655 this.setDropEffect(e.dataTransfer);
659 /** @override */
660 shouldAcceptDrag: function(e) {
661 if (ntp.getCurrentlyDraggingTile())
662 return true;
663 if (!e.dataTransfer || !e.dataTransfer.types)
664 return false;
665 return Array.prototype.indexOf.call(e.dataTransfer.types,
666 'text/uri-list') != -1;
669 /** @override */
670 addDragData: function(dataTransfer, index) {
671 var sourceId = -1;
672 var currentlyDraggingTile = ntp.getCurrentlyDraggingTile();
673 if (currentlyDraggingTile) {
674 var tileContents = currentlyDraggingTile.firstChild;
675 if (tileContents.classList.contains('app')) {
676 var originalPage = currentlyDraggingTile.tilePage;
677 var samePageDrag = originalPage == this;
678 sourceId = samePageDrag ? DRAG_SOURCE.SAME_APPS_PANE :
679 DRAG_SOURCE.OTHER_APPS_PANE;
680 this.tileGrid_.insertBefore(currentlyDraggingTile,
681 this.tileElements_[index]);
682 this.tileMoved(currentlyDraggingTile);
683 if (!samePageDrag) {
684 originalPage.fireRemovedEvent(currentlyDraggingTile, index, true);
685 this.fireAddedEvent(currentlyDraggingTile, index, true);
687 } else if (currentlyDraggingTile.querySelector('.most-visited')) {
688 this.generateAppForLink(tileContents.data);
689 sourceId = DRAG_SOURCE.MOST_VISITED_PANE;
691 } else {
692 this.addOutsideData_(dataTransfer);
693 sourceId = DRAG_SOURCE.OUTSIDE_NTP;
696 assert(sourceId != -1);
697 chrome.send('metricsHandler:recordInHistogram',
698 ['NewTabPage.AppsPageDragSource', sourceId, DRAG_SOURCE_LIMIT]);
702 * Adds drag data that has been dropped from a source that is not a tile.
703 * @param {Object} dataTransfer The data transfer object that holds drop
704 * data.
705 * @private
707 addOutsideData_: function(dataTransfer) {
708 var url = dataTransfer.getData('url');
709 assert(url);
711 // If the dataTransfer has html data, use that html's text contents as the
712 // title of the new link.
713 var html = dataTransfer.getData('text/html');
714 var title;
715 if (html) {
716 // It's important that we don't attach this node to the document
717 // because it might contain scripts.
718 var node = this.ownerDocument.createElement('div');
719 node.innerHTML = html;
720 title = node.textContent;
723 // Make sure title is >=1 and <=45 characters for Chrome app limits.
724 if (!title)
725 title = url;
726 if (title.length > 45)
727 title = title.substring(0, 45);
728 var data = {url: url, title: title};
730 // Synthesize an app.
731 this.generateAppForLink(data);
735 * Creates a new crx-less app manifest and installs it.
736 * @param {Object} data The data object describing the link. Must have |url|
737 * and |title| members.
739 generateAppForLink: function(data) {
740 assert(data.url != undefined);
741 assert(data.title != undefined);
742 var pageIndex = ntp.getAppsPageIndex(this);
743 chrome.send('generateAppForLink', [data.url, data.title, pageIndex]);
746 /** @override */
747 tileMoved: function(draggedTile) {
748 if (!(draggedTile.firstChild instanceof App))
749 return;
751 var pageIndex = ntp.getAppsPageIndex(this);
752 chrome.send('setPageIndex', [draggedTile.firstChild.appId, pageIndex]);
754 var appIds = [];
755 for (var i = 0; i < this.tileElements_.length; i++) {
756 var tileContents = this.tileElements_[i].firstChild;
757 if (tileContents instanceof App)
758 appIds.push(tileContents.appId);
761 chrome.send('reorderApps', [draggedTile.firstChild.appId, appIds]);
764 /** @override */
765 setDropEffect: function(dataTransfer) {
766 var tile = ntp.getCurrentlyDraggingTile();
767 if (tile && tile.querySelector('.app'))
768 ntp.setCurrentDropEffect(dataTransfer, 'move');
769 else
770 ntp.setCurrentDropEffect(dataTransfer, 'copy');
775 * Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE
776 * histogram. This should only be invoked from the AppLauncherHandler.
777 * @param {string} appId The ID of the app.
779 function launchAppAfterEnable(appId) {
780 chrome.send('launchApp', [appId, APP_LAUNCH.NTP_APP_RE_ENABLE]);
783 return {
784 APP_LAUNCH: APP_LAUNCH,
785 AppsPage: AppsPage,
786 launchAppAfterEnable: launchAppAfterEnable,