OLD | NEW |
| (Empty) |
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. | |
4 | |
5 cr.define('ntp', function() { | |
6 'use strict'; | |
7 | |
8 var Tile = ntp.Tile; | |
9 var TilePage = ntp.TilePage; | |
10 var APP_LAUNCH = ntp.APP_LAUNCH; | |
11 | |
12 // Histogram buckets for UMA tracking of where a DnD drop came from. | |
13 var DRAG_SOURCE = { | |
14 SAME_APPS_PANE: 0, | |
15 OTHER_APPS_PANE: 1, | |
16 MOST_VISITED_PANE: 2, | |
17 BOOKMARKS_PANE: 3, | |
18 OUTSIDE_NTP: 4 | |
19 }; | |
20 var DRAG_SOURCE_LIMIT = DRAG_SOURCE.OUTSIDE_NTP + 1; | |
21 | |
22 /** | |
23 * App context menu. The class is designed to be used as a singleton with | |
24 * the app that is currently showing a context menu stored in this.app_. | |
25 * @constructor | |
26 */ | |
27 function AppContextMenu() { | |
28 this.__proto__ = AppContextMenu.prototype; | |
29 this.initialize(); | |
30 } | |
31 cr.addSingletonGetter(AppContextMenu); | |
32 | |
33 AppContextMenu.prototype = { | |
34 initialize: function() { | |
35 var menu = new cr.ui.Menu; | |
36 cr.ui.decorate(menu, cr.ui.Menu); | |
37 menu.classList.add('app-context-menu'); | |
38 this.menu = menu; | |
39 | |
40 this.launch_ = this.appendMenuItem_(); | |
41 this.launch_.addEventListener('activate', this.onLaunch_.bind(this)); | |
42 | |
43 menu.appendChild(cr.ui.MenuItem.createSeparator()); | |
44 this.launchRegularTab_ = this.appendMenuItem_('applaunchtyperegular'); | |
45 this.launchPinnedTab_ = this.appendMenuItem_('applaunchtypepinned'); | |
46 if (!cr.isMac) | |
47 this.launchNewWindow_ = this.appendMenuItem_('applaunchtypewindow'); | |
48 this.launchFullscreen_ = this.appendMenuItem_('applaunchtypefullscreen'); | |
49 | |
50 var self = this; | |
51 this.forAllLaunchTypes_(function(launchTypeButton, id) { | |
52 launchTypeButton.addEventListener('activate', | |
53 self.onLaunchTypeChanged_.bind(self)); | |
54 }); | |
55 | |
56 menu.appendChild(cr.ui.MenuItem.createSeparator()); | |
57 this.options_ = this.appendMenuItem_('appoptions'); | |
58 this.details_ = this.appendMenuItem_('appdetails'); | |
59 this.disableNotifications_ = | |
60 this.appendMenuItem_('appdisablenotifications'); | |
61 this.uninstall_ = this.appendMenuItem_('appuninstall'); | |
62 this.options_.addEventListener('activate', | |
63 this.onShowOptions_.bind(this)); | |
64 this.details_.addEventListener('activate', | |
65 this.onShowDetails_.bind(this)); | |
66 this.disableNotifications_.addEventListener( | |
67 'activate', this.onDisableNotifications_.bind(this)); | |
68 this.uninstall_.addEventListener('activate', | |
69 this.onUninstall_.bind(this)); | |
70 | |
71 if (!cr.isMac && !cr.isChromeOS) { | |
72 menu.appendChild(cr.ui.MenuItem.createSeparator()); | |
73 this.createShortcut_ = this.appendMenuItem_('appcreateshortcut'); | |
74 this.createShortcut_.addEventListener( | |
75 'activate', this.onCreateShortcut_.bind(this)); | |
76 } | |
77 | |
78 document.body.appendChild(menu); | |
79 }, | |
80 | |
81 /** | |
82 * Appends a menu item to |this.menu|. | |
83 * @param {?string} textId If non-null, the ID for the localized string | |
84 * that acts as the item's label. | |
85 */ | |
86 appendMenuItem_: function(textId) { | |
87 var button = cr.doc.createElement('button'); | |
88 this.menu.appendChild(button); | |
89 cr.ui.decorate(button, cr.ui.MenuItem); | |
90 if (textId) | |
91 button.textContent = loadTimeData.getString(textId); | |
92 return button; | |
93 }, | |
94 | |
95 /** | |
96 * Iterates over all the launch type menu items. | |
97 * @param {function(cr.ui.MenuItem, number)} f The function to call for each | |
98 * menu item. The parameters to the function include the menu item and | |
99 * the associated launch ID. | |
100 */ | |
101 forAllLaunchTypes_: function(f) { | |
102 // Order matters: index matches launchType id. | |
103 var launchTypes = [this.launchPinnedTab_, | |
104 this.launchRegularTab_, | |
105 this.launchFullscreen_, | |
106 this.launchNewWindow_]; | |
107 | |
108 for (var i = 0; i < launchTypes.length; ++i) { | |
109 if (!launchTypes[i]) | |
110 continue; | |
111 | |
112 f(launchTypes[i], i); | |
113 } | |
114 }, | |
115 | |
116 /** | |
117 * Does all the necessary setup to show the menu for the given app. | |
118 * @param {App} app The App object that will be showing a context menu. | |
119 */ | |
120 setupForApp: function(app) { | |
121 this.app_ = app; | |
122 | |
123 this.launch_.textContent = app.data.title; | |
124 | |
125 this.forAllLaunchTypes_(function(launchTypeButton, id) { | |
126 launchTypeButton.disabled = false; | |
127 launchTypeButton.checked = app.data.launch_type == id; | |
128 }); | |
129 | |
130 this.options_.disabled = !app.data.optionsUrl || !app.data.enabled; | |
131 this.details_.disabled = !app.data.detailsUrl; | |
132 this.uninstall_.disabled = !app.data.mayDisable; | |
133 | |
134 this.disableNotifications_.hidden = true; | |
135 var notificationsDisabled = app.data.notifications_disabled; | |
136 if (typeof notificationsDisabled != 'undefined') { | |
137 this.disableNotifications_.hidden = false; | |
138 this.disableNotifications_.checked = notificationsDisabled; | |
139 } | |
140 }, | |
141 | |
142 /** | |
143 * Handlers for menu item activation. | |
144 * @param {Event} e The activation event. | |
145 * @private | |
146 */ | |
147 onLaunch_: function(e) { | |
148 chrome.send('launchApp', [this.app_.appId, APP_LAUNCH.NTP_APPS_MENU]); | |
149 }, | |
150 onLaunchTypeChanged_: function(e) { | |
151 var pressed = e.currentTarget; | |
152 var app = this.app_; | |
153 this.forAllLaunchTypes_(function(launchTypeButton, id) { | |
154 if (launchTypeButton == pressed) { | |
155 chrome.send('setLaunchType', [app.appId, id]); | |
156 // Manually update the launch type. We will only get | |
157 // appsPrefChangeCallback calls after changes to other NTP instances. | |
158 app.data.launch_type = id; | |
159 } | |
160 }); | |
161 }, | |
162 onShowOptions_: function(e) { | |
163 window.location = this.app_.data.optionsUrl; | |
164 }, | |
165 onShowDetails_: function(e) { | |
166 var url = this.app_.data.detailsUrl; | |
167 url = appendParam(url, 'utm_source', 'chrome-ntp-launcher'); | |
168 window.location = url; | |
169 }, | |
170 onDisableNotifications_: function(e) { | |
171 var app = this.app_; | |
172 app.removeBubble(); | |
173 // Toggle the current disable setting. | |
174 var newSetting = !this.disableNotifications_.checked; | |
175 app.data.notifications_disabled = newSetting; | |
176 chrome.send('setNotificationsDisabled', [app.data.id, newSetting]); | |
177 }, | |
178 onUninstall_: function(e) { | |
179 chrome.send('uninstallApp', [this.app_.data.id]); | |
180 }, | |
181 onCreateShortcut_: function(e) { | |
182 chrome.send('createAppShortcut', [this.app_.data.id]); | |
183 }, | |
184 }; | |
185 | |
186 /** | |
187 * Creates a new App object. | |
188 * @param {Object=} opt_data The data representing the app. | |
189 * @constructor | |
190 * @extends {HTMLDivElement} | |
191 */ | |
192 function App(opt_data) { | |
193 var el = cr.doc.createElement('div'); | |
194 el.__proto__ = App.prototype; | |
195 el.initialize_(); | |
196 | |
197 if (opt_data) | |
198 el.data = opt_data; | |
199 | |
200 return el; | |
201 } | |
202 | |
203 App.prototype = Tile.subclass({ | |
204 __proto__: HTMLDivElement.prototype, | |
205 | |
206 /** | |
207 * Initialize the app object. | |
208 * @private | |
209 */ | |
210 initialize_: function() { | |
211 Tile.prototype.initialize.apply(this, arguments); | |
212 | |
213 this.classList.add('app'); | |
214 this.classList.add('focusable'); | |
215 }, | |
216 | |
217 /** | |
218 * Formats this app according to |data|. | |
219 * @param {Object} data The data object that describes the app. | |
220 * @private | |
221 */ | |
222 formatApp_: function(data) { | |
223 assert(this.data_.id, 'Got an app without an ID'); | |
224 this.id = this.data_.id; | |
225 this.setAttribute('role', 'menuitem'); | |
226 | |
227 if (!this.data_.icon_big_exists && this.data_.icon_small_exists) | |
228 this.useSmallIcon_ = true; | |
229 | |
230 // TODO(pedrosimonetti): Fix crbug.com/165612 | |
231 if (!this.appContents_) { | |
232 this.appContents_ = this.useSmallIcon_ ? | |
233 $('app-small-icon-template').cloneNode(true) : | |
234 $('app-large-icon-template').cloneNode(true); | |
235 this.appContents_.id = ''; | |
236 this.appendChild(this.appContents_); | |
237 } | |
238 | |
239 this.appImgContainer_ = this.querySelector('.app-img-container'); | |
240 this.appImg_ = this.appImgContainer_.querySelector('img'); | |
241 this.setIcon(); | |
242 | |
243 var appTitle; | |
244 if (this.useSmallIcon_) { | |
245 this.classList.add('small-icon'); | |
246 this.imgDiv_ = this.querySelector('.app-icon-div'); | |
247 this.addLaunchClickTarget_(this.imgDiv_); | |
248 this.imgDiv_.title = this.data_.title; | |
249 appTitle = formatTitle(this.data_.title); | |
250 chrome.send('getAppIconDominantColor', [this.id]); | |
251 } else { | |
252 this.classList.remove('small-icon'); | |
253 this.addLaunchClickTarget_(this.appImgContainer_); | |
254 this.appImgContainer_.title = this.data_.title; | |
255 appTitle = this.data_.title; | |
256 } | |
257 | |
258 var appSpan = this.appContents_.querySelector('.title'); | |
259 appSpan.textContent = appTitle; | |
260 appSpan.title = this.data_.title; | |
261 this.addLaunchClickTarget_(appSpan); | |
262 | |
263 var notification = this.data_.notification; | |
264 var hasNotification = typeof notification != 'undefined' && | |
265 typeof notification['title'] != 'undefined' && | |
266 typeof notification['body'] != 'undefined' && | |
267 !this.data_.notifications_disabled; | |
268 if (hasNotification) | |
269 this.setupNotification_(notification); | |
270 | |
271 this.addEventListener('keydown', cr.ui.contextMenuHandler); | |
272 this.addEventListener('keyup', cr.ui.contextMenuHandler); | |
273 | |
274 // This hack is here so that appContents.contextMenu will be the same as | |
275 // this.contextMenu. | |
276 var self = this; | |
277 this.appContents_.__defineGetter__('contextMenu', function() { | |
278 return self.contextMenu; | |
279 }); | |
280 this.appContents_.addEventListener('contextmenu', | |
281 cr.ui.contextMenuHandler); | |
282 | |
283 this.addEventListener('mousedown', this.onMousedown_, true); | |
284 this.addEventListener('keydown', this.onKeydown_); | |
285 this.addEventListener('keyup', this.onKeyup_); | |
286 }, | |
287 | |
288 /** | |
289 * Sets the color of the favicon dominant color bar. | |
290 * @param {string} color The css-parsable value for the color. | |
291 */ | |
292 set stripeColor(color) { | |
293 this.querySelector('.color-stripe').style.backgroundColor = color; | |
294 }, | |
295 | |
296 /** | |
297 * Removes the app tile from the page. Should be called after the app has | |
298 * been uninstalled. | |
299 */ | |
300 remove: function(opt_animate) { | |
301 // Unset the ID immediately, because the app is already gone. But leave | |
302 // the tile on the page as it animates out. | |
303 this.id = ''; | |
304 | |
305 if (opt_animate) { | |
306 var cell = this.tileCell; | |
307 var tilePage = cell.tilePage; | |
308 tilePage.dataList_.splice(cell.index, 1); | |
309 tilePage.animateTileRemoval(cell.index, tilePage.dataList_); | |
310 } else { | |
311 this.tileCell.doRemove(opt_animate); | |
312 } | |
313 }, | |
314 | |
315 /** | |
316 * Set the URL of the icon from |this.data_|. This won't actually show the | |
317 * icon until loadIcon() is called (for performance reasons; we don't want | |
318 * to load icons until we have to). | |
319 */ | |
320 setIcon: function() { | |
321 var src = this.useSmallIcon_ ? this.data_.icon_small : | |
322 this.data_.icon_big; | |
323 if (!this.data_.enabled || | |
324 (!this.data_.offlineEnabled && !navigator.onLine)) { | |
325 src += '?grayscale=true'; | |
326 } | |
327 | |
328 this.appImgSrc_ = src; | |
329 this.classList.add('icon-loading'); | |
330 }, | |
331 | |
332 /** | |
333 * Shows the icon for the app. That is, it causes chrome to load the app | |
334 * icon resource. | |
335 */ | |
336 loadIcon: function() { | |
337 if (this.appImgSrc_) { | |
338 this.appImg_.src = this.appImgSrc_; | |
339 this.appImg_.classList.remove('invisible'); | |
340 this.appImgSrc_ = null; | |
341 } | |
342 | |
343 this.classList.remove('icon-loading'); | |
344 }, | |
345 | |
346 /** | |
347 * Creates a bubble node. | |
348 * @param {Object} notification The notification to show in the bubble. | |
349 * @param {boolean} full Whether we want the headline or just the content. | |
350 * @private | |
351 */ | |
352 createBubbleNode_: function(notification, full) { | |
353 if (!full) { | |
354 var titleItem = this.ownerDocument.createElement('span'); | |
355 titleItem.textContent = notification['title']; | |
356 return titleItem; | |
357 } else { | |
358 var container = this.ownerDocument.createElement('div'); | |
359 | |
360 var messageItem = this.ownerDocument.createElement('div'); | |
361 messageItem.textContent = notification['body']; | |
362 container.appendChild(messageItem); | |
363 | |
364 if (notification['linkUrl'] && notification['linkText']) { | |
365 var anchor = this.ownerDocument.createElement('a'); | |
366 anchor.href = notification['linkUrl']; | |
367 anchor.textContent = notification['linkText']; | |
368 container.appendChild(anchor); | |
369 } | |
370 | |
371 return container; | |
372 } | |
373 }, | |
374 | |
375 /** | |
376 * Sets up a notification for the app icon. | |
377 * @param {Object} notification The notification to show in the bubble. | |
378 * @private | |
379 */ | |
380 setupNotification_: function(notification) { | |
381 if (notification) { | |
382 var infoBubble; | |
383 if (!this.currentBubbleShowing_) { | |
384 // Create a new bubble. | |
385 infoBubble = new cr.ui.ExpandableBubble; | |
386 infoBubble.anchorNode = this; | |
387 infoBubble.appId = this.data_.id; | |
388 infoBubble.handleCloseEvent = function() { | |
389 chrome.send('closeNotification', [this.appId]); | |
390 infoBubble.hide(); | |
391 }; | |
392 } else { | |
393 // Reuse the old bubble instead of popping up a new bubble over | |
394 // the old one. | |
395 infoBubble = this.currentBubbleShowing_; | |
396 infoBubble.collapseBubble_(); | |
397 } | |
398 infoBubble.contentTitle = this.createBubbleNode_(notification, false); | |
399 infoBubble.content = this.createBubbleNode_(notification, true); | |
400 infoBubble.show(); | |
401 infoBubble.resizeAndReposition(); | |
402 | |
403 this.currentBubbleShowing_ = infoBubble; | |
404 } | |
405 }, | |
406 | |
407 /** | |
408 * Removes the info bubble if there is one. | |
409 */ | |
410 removeBubble: function() { | |
411 if (this.currentBubbleShowing_) { | |
412 this.currentBubbleShowing_.hide(); | |
413 this.currentBubbleShowing_ = null; | |
414 } | |
415 }, | |
416 | |
417 /** | |
418 * Invoked when an app is clicked. | |
419 * @param {Event} e The click event. | |
420 * @private | |
421 */ | |
422 onClick_: function(e) { | |
423 var url = !this.data_.is_webstore ? '' : | |
424 appendParam(this.data_.url, | |
425 'utm_source', | |
426 'chrome-ntp-icon'); | |
427 | |
428 chrome.send('launchApp', | |
429 [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, url, | |
430 e.button, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]); | |
431 | |
432 // Don't allow the click to trigger a link or anything | |
433 e.preventDefault(); | |
434 }, | |
435 | |
436 /** | |
437 * Invoked when the user presses a key while the app is focused. | |
438 * @param {Event} e The key event. | |
439 * @private | |
440 */ | |
441 onKeydown_: function(e) { | |
442 if (e.keyIdentifier == 'Enter') { | |
443 chrome.send('launchApp', | |
444 [this.appId, APP_LAUNCH.NTP_APPS_MAXIMIZED, '', | |
445 0, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey]); | |
446 e.preventDefault(); | |
447 e.stopPropagation(); | |
448 } | |
449 this.onKeyboardUsed_(e.keyCode); | |
450 }, | |
451 | |
452 /** | |
453 * Invoked when the user releases a key while the app is focused. | |
454 * @param {Event} e The key event. | |
455 * @private | |
456 */ | |
457 onKeyup_: function(e) { | |
458 this.onKeyboardUsed_(e.keyCode); | |
459 }, | |
460 | |
461 /** | |
462 * Called when the keyboard has been used (key down or up). The .click-focus | |
463 * hack is removed if the user presses a key that can change focus. | |
464 * @param {number} keyCode The key code of the keyboard event. | |
465 * @private | |
466 */ | |
467 onKeyboardUsed_: function(keyCode) { | |
468 switch (keyCode) { | |
469 case 9: // Tab. | |
470 case 37: // Left arrow. | |
471 case 38: // Up arrow. | |
472 case 39: // Right arrow. | |
473 case 40: // Down arrow. | |
474 this.classList.remove('click-focus'); | |
475 } | |
476 }, | |
477 | |
478 /** | |
479 * Adds a node to the list of targets that will launch the app. This list | |
480 * is also used in onMousedown to determine whether the app contents should | |
481 * be shown as active (if we don't do this, then clicking anywhere in | |
482 * appContents, even a part that is outside the ideally clickable region, | |
483 * will cause the app icon to look active). | |
484 * @param {HTMLElement} node The node that should be clickable. | |
485 */ | |
486 addLaunchClickTarget_: function(node) { | |
487 node.classList.add('launch-click-target'); | |
488 node.addEventListener('click', this.onClick_.bind(this)); | |
489 }, | |
490 | |
491 /** | |
492 * Handler for mousedown on the App. Adds a class that allows us to | |
493 * not display as :active for right clicks and clicks on app notifications | |
494 * (specifically, don't pulse on these occasions). Also, we don't pulse | |
495 * for clicks that aren't within the clickable regions. | |
496 * @param {Event} e The mousedown event. | |
497 */ | |
498 onMousedown_: function(e) { | |
499 if (e.button == 2 || | |
500 !findAncestorByClass(e.target, 'launch-click-target')) { | |
501 this.appContents_.classList.add('suppress-active'); | |
502 } else { | |
503 this.appContents_.classList.remove('suppress-active'); | |
504 } | |
505 | |
506 // This class is here so we don't show the focus state for apps that | |
507 // gain keyboard focus via mouse clicking. | |
508 this.classList.add('click-focus'); | |
509 }, | |
510 | |
511 /** | |
512 * Change the data and update the appearance of the app. | |
513 * @param {Object} data The new data object that describes the app. | |
514 */ | |
515 replaceAppData: function(data) { | |
516 assert(data); | |
517 this.data = data; | |
518 this.setIcon(); | |
519 this.loadIcon(); | |
520 }, | |
521 | |
522 /** | |
523 * The data and preferences for this app. | |
524 * @type {Object} | |
525 */ | |
526 set data(data) { | |
527 Object.getOwnPropertyDescriptor(Tile.prototype, 'data').set.apply(this, | |
528 arguments); | |
529 | |
530 this.formatApp_(data); | |
531 }, | |
532 get data() { | |
533 return this.data_; | |
534 }, | |
535 | |
536 get appId() { | |
537 return this.data_.id; | |
538 }, | |
539 | |
540 /** | |
541 * Returns a pointer to the context menu for this app. All apps share the | |
542 * singleton AppContextMenu. This function is called by the | |
543 * ContextMenuHandler in response to the 'contextmenu' event. | |
544 * @type {cr.ui.Menu} | |
545 */ | |
546 get contextMenu() { | |
547 var menu = AppContextMenu.getInstance(); | |
548 menu.setupForApp(this); | |
549 return menu.menu; | |
550 }, | |
551 | |
552 /** | |
553 * Returns whether this element can be 'removed' from chrome (i.e. whether | |
554 * the user can drag it onto the trash and expect something to happen). | |
555 * @return {boolean} True if the app can be uninstalled. | |
556 */ | |
557 canBeRemoved: function() { | |
558 return this.data_.mayDisable; | |
559 }, | |
560 | |
561 /** | |
562 * Uninstalls the app after it's been dropped on the trash. | |
563 */ | |
564 removeFromChrome: function() { | |
565 chrome.send('uninstallApp', [this.data_.id, true]); | |
566 this.tile.tilePage.removeTile(this.tile, true); | |
567 if (this.currentBubbleShowing_) | |
568 this.currentBubbleShowing_.hide(); | |
569 }, | |
570 }); | |
571 | |
572 /** | |
573 * Creates a new AppsPage object. | |
574 * @constructor | |
575 * @extends {TilePage} | |
576 */ | |
577 function AppsPage() { | |
578 var el = new TilePage(); | |
579 el.__proto__ = AppsPage.prototype; | |
580 el.initialize(); | |
581 | |
582 return el; | |
583 } | |
584 | |
585 AppsPage.prototype = { | |
586 __proto__: TilePage.prototype, | |
587 | |
588 /** | |
589 * Reference to the Tile subclass that will be used to create the tiles. | |
590 * @constructor | |
591 * @extends {Tile} | |
592 */ | |
593 TileClass: App, | |
594 | |
595 // The config object should be defined by a TilePage subclass if it | |
596 // wants the non-default behavior. | |
597 config: { | |
598 // The width of a cell. | |
599 cellWidth: 70, | |
600 // The start margin of a cell (left or right according to text direction). | |
601 cellMarginStart: 20, | |
602 // The maximum number of Tiles to be displayed. | |
603 maxTileCount: 512, | |
604 // Whether the TilePage content will be scrollable. | |
605 scrollable: true, | |
606 }, | |
607 | |
608 initialize: function() { | |
609 TilePage.prototype.initialize.apply(this, arguments); | |
610 | |
611 this.classList.add('apps-page'); | |
612 | |
613 this.addEventListener('cardselected', this.onCardSelected_); | |
614 // Add event listeners for two events, so we can temporarily suppress | |
615 // the app notification bubbles when the app card slides in and out of | |
616 // view. | |
617 this.addEventListener('carddeselected', this.onCardDeselected_); | |
618 this.addEventListener('cardSlider:card_change_ended', | |
619 this.onCardChangeEnded_); | |
620 | |
621 this.addEventListener('tilePage:tile_added', this.onTileAdded_); | |
622 }, | |
623 | |
624 /** | |
625 * Highlight a newly installed app as it's added to the NTP. | |
626 * @param {Object} data The data object that describes the app. | |
627 */ | |
628 insertAndHighlightApp: function(data) { | |
629 ntp.getCardSlider().selectCardByValue(this); | |
630 this.insertApp(data, true); | |
631 }, | |
632 | |
633 /** | |
634 * Inserts an App into the TilePage, preserving the alphabetical order. | |
635 * @param {Object} data The data that describes the app. | |
636 * @param {boolean} animate Whether to animate the insertion. | |
637 */ | |
638 insertApp: function(data, animate) { | |
639 var index = this.tiles_.length; | |
640 for (var i = 0; i < this.tiles_.length; i++) { | |
641 if (data.title.toLocaleLowerCase() < | |
642 this.tiles_[i].data.title.toLocaleLowerCase()) { | |
643 index = i; | |
644 break; | |
645 } | |
646 } | |
647 | |
648 if (animate) { | |
649 this.dataList_.splice(index, 0, data); | |
650 this.animateTileRestoration(index, this.dataList_); | |
651 } else { | |
652 var app = new App(data); | |
653 this.addTileAt(app, index); | |
654 } | |
655 }, | |
656 | |
657 /** | |
658 * Handler for 'cardselected' event, fired when |this| is selected. The | |
659 * first time this is called, we load all the app icons. | |
660 * @private | |
661 */ | |
662 onCardSelected_: function(e) { | |
663 var apps = this.querySelectorAll('.app.icon-loading'); | |
664 for (var i = 0; i < apps.length; i++) { | |
665 apps[i].loadIcon(); | |
666 if (apps[i].currentBubbleShowing_) | |
667 apps[i].currentBubbleShowing_.suppressed = false; | |
668 } | |
669 }, | |
670 | |
671 /** | |
672 * Handler for tile additions to this page. | |
673 * @param {Event} e The tilePage:tile_added event. | |
674 */ | |
675 onTileAdded_: function(e) { | |
676 assert(e.currentTarget == this); | |
677 assert(e.addedTile instanceof App); | |
678 if (this.classList.contains('selected-card')) | |
679 e.addedTile.loadIcon(); | |
680 }, | |
681 | |
682 /** | |
683 * Handler for the when this.cardSlider ends change its card. If animated, | |
684 * this happens when the -webkit-transition is done, otherwise happens | |
685 * immediately (but after cardSlider:card_changed). | |
686 * @private | |
687 */ | |
688 onCardChangeEnded_: function(e) { | |
689 for (var i = 0; i < this.tiles_.length; i++) { | |
690 var app = this.tiles_[i]; | |
691 assert(app instanceof App); | |
692 if (app.currentBubbleShowing_) | |
693 app.currentBubbleShowing_.suppressed = false; | |
694 } | |
695 }, | |
696 | |
697 /** | |
698 * Handler for the 'carddeselected' event, fired when the user switches | |
699 * to another 'card' than the App 'card' on the NTP (|this| gets | |
700 * deselected). | |
701 * @private | |
702 */ | |
703 onCardDeselected_: function(e) { | |
704 for (var i = 0; i < this.tiles_.length; i++) { | |
705 var app = this.tiles_[i]; | |
706 assert(app instanceof App); | |
707 if (app.currentBubbleShowing_) | |
708 app.currentBubbleShowing_.suppressed = true; | |
709 } | |
710 }, | |
711 | |
712 /** @override */ | |
713 onScroll: function() { | |
714 TilePage.prototype.onScroll.apply(this, arguments); | |
715 | |
716 for (var i = 0; i < this.tiles_.length; i++) { | |
717 var app = this.tiles_[i]; | |
718 assert(app instanceof App); | |
719 if (app.currentBubbleShowing_) | |
720 app.currentBubbleShowing_.resizeAndReposition(); | |
721 } | |
722 }, | |
723 | |
724 /** | |
725 * Creates a new crx-less app manifest and installs it. | |
726 * @param {Object} data The data object describing the link. Must have |url| | |
727 * and |title| members. | |
728 */ | |
729 generateAppForLink: function(data) { | |
730 assert(data.url != undefined); | |
731 assert(data.title != undefined); | |
732 chrome.send('generateAppForLink', [data.url, data.title, 0]); | |
733 }, | |
734 }; | |
735 | |
736 /** | |
737 * Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE | |
738 * histogram. This should only be invoked from the AppLauncherHandler. | |
739 * @param {string} appID The ID of the app. | |
740 */ | |
741 function launchAppAfterEnable(appId) { | |
742 chrome.send('launchApp', [appId, APP_LAUNCH.NTP_APP_RE_ENABLE]); | |
743 } | |
744 | |
745 function appNotificationChanged(id, notification) { | |
746 var app = $(id); | |
747 // The app might have been uninstalled, or notifications might be disabled. | |
748 if (app && !app.data.notifications_disabled) | |
749 app.setupNotification_(notification); | |
750 } | |
751 | |
752 /** | |
753 * Formats titles by removing the leading 'http://www.' part of the URL, | |
754 * and the last slash, so 'http://www.test.com/' becomes 'test.com'. | |
755 * @param {string} title Page's title. | |
756 * @return {string} The formatted title. | |
757 */ | |
758 function formatTitle(title) { | |
759 return title.replace(/^(https?\:\/\/)?(www\.)?|\/$/gi, ''); | |
760 } | |
761 | |
762 return { | |
763 appNotificationChanged: appNotificationChanged, | |
764 AppsPage: AppsPage, | |
765 launchAppAfterEnable: launchAppAfterEnable, | |
766 }; | |
767 }); | |
OLD | NEW |