Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(559)

Side by Side Diff: chrome/browser/resources/ntp/apps.js

Issue 8036002: ntp: remove ntp3 resources (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 var MAX_APPS_PER_ROW = [];
6 MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4;
7 MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6;
8
9 function getAppsCallback(data) {
10 logEvent('received apps');
11
12 // In the case of prefchange-triggered updates, we don't receive this flag.
13 // Just leave it set as it was before in that case.
14 if ('showPromo' in data)
15 apps.showPromo = data.showPromo;
16
17 var appsSection = $('apps');
18 var appsSectionContent = $('apps-content');
19 var appsMiniview = appsSection.getElementsByClassName('miniview')[0];
20 var appsPromo = $('apps-promo');
21 var appsPromoLink = $('apps-promo-link');
22 var appsPromoPing = APP_LAUNCH_URL.PING_WEBSTORE + '+' + apps.showPromo;
23 var webStoreEntry, webStoreMiniEntry;
24
25 // Hide menu options that are not supported on the OS or windowing system.
26
27 // The "Launch as Window" menu option.
28 $('apps-launch-type-window-menu-item').hidden = data.disableAppWindowLaunch;
29
30 // The "Create App Shortcut" menu option.
31 $('apps-create-shortcut-command-menu-item').hidden =
32 $('apps-create-shortcut-command-separator').hidden =
33 data.disableCreateAppShortcut;
34
35 // Hide the context menu, if there is any open.
36 cr.ui.contextMenuHandler.hideMenu();
37
38 appsMiniview.textContent = '';
39 appsSectionContent.textContent = '';
40
41 data.apps.sort(function(a,b) {
42 return a.app_launch_index - b.app_launch_index;
43 });
44
45 // Determines if the web store link should be detached and place in the
46 // top right of the screen.
47 apps.detachWebstoreEntry =
48 !apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode];
49
50 markNewApps(data.apps);
51 apps.data = data.apps;
52
53 clearClosedMenu(apps.menu);
54
55 // We wait for the app icons to load before displaying them, but never wait
56 // longer than 200ms.
57 apps.loadedImages = 0;
58 apps.imageTimer = setTimeout(apps.showImages.bind(apps), 200);
59
60 data.apps.forEach(function(app) {
61 appsSectionContent.appendChild(apps.createElement(app));
62 });
63
64 if (data.showPromo) {
65 // Add the promo content...
66 $('apps-promo-heading').textContent = data.promoHeader;
67 appsPromoLink.href = data.promoLink;
68 appsPromoLink.textContent = data.promoButton;
69 appsPromoLink.ping = appsPromoPing;
70 $('apps-promo').style.background =
71 "url('" + data.promoLogo + "') no-repeat";
72 $('apps-promo-hide').textContent = data.promoExpire;
73
74 // ... then display the promo.
75 document.documentElement.classList.add('apps-promo-visible');
76 } else {
77 document.documentElement.classList.remove('apps-promo-visible');
78 }
79
80 // Only show the web store entry if there are apps installed or the promo
81 // is not available.
82 if (data.apps.length > 0 || !data.showPromo) {
83 webStoreEntry = apps.createWebStoreElement();
84 webStoreEntry.querySelector('a').ping = appsPromoPing;
85 appsSectionContent.appendChild(webStoreEntry);
86 if (apps.detachWebstoreEntry) {
87 webStoreEntry.classList.add('loner');
88 } else {
89 webStoreEntry.classList.remove('loner');
90 apps.data.push('web-store-entry');
91 }
92 }
93
94 data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) {
95 appsMiniview.appendChild(apps.createMiniviewElement(app));
96 addClosedMenuEntryWithLink(apps.menu, apps.createClosedMenuElement(app));
97 });
98 if (data.apps.length < MAX_MINIVIEW_ITEMS) {
99 webStoreMiniEntry = apps.createWebStoreMiniElement();
100 webStoreMiniEntry.querySelector('a').ping = appsPromoPing;
101 appsMiniview.appendChild(webStoreMiniEntry);
102 addClosedMenuEntryWithLink(apps.menu,
103 apps.createWebStoreClosedMenuElement());
104 }
105
106 if (!data.showLauncher)
107 hideSection(Section.APPS);
108 else
109 appsSection.classList.remove('disabled');
110
111 addClosedMenuFooter(apps.menu, 'apps', MENU_APPS, Section.APPS);
112
113 apps.loaded = true;
114
115 if (appsPromoLink)
116 appsPromoLink.ping = appsPromoPing;
117 maybeDoneLoading();
118
119 // Disable the animations when the app launcher is being (re)initailized.
120 apps.layout({disableAnimations:true});
121
122 if (isDoneLoading()) {
123 updateMiniviewClipping(appsMiniview);
124 layoutSections();
125 }
126 }
127
128 function markNewApps(data) {
129 var oldData = apps.data;
130 data.forEach(function(app) {
131 if (hashParams['app-id'] == app['id']) {
132 delete hashParams['app-id'];
133 app.isNew = true;
134 } else if (oldData &&
135 !oldData.some(function(id) { return id == app.id; })) {
136 app.isNew = true;
137 } else {
138 app.isNew = false;
139 }
140 });
141 }
142
143 function appsPrefChangeCallback(data) {
144 // Currently the only pref that is watched is the launch type.
145 data.apps.forEach(function(app) {
146 var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']');
147 if (appLink)
148 appLink.setAttribute('launch-type', app['launch_type']);
149 });
150 }
151
152 function appNotificationChanged(id, lastNotification) {
153 // TODO(asargent/finnur): Don't update all apps at once, do it in a more
154 // fine grained way.
155 chrome.send('getApps');
156 }
157
158 // Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE histogram.
159 // This should only be invoked from the AppLauncherHandler.
160 function launchAppAfterEnable(appId) {
161 chrome.send('launchApp', [appId, APP_LAUNCH.NTP_APP_RE_ENABLE]);
162 }
163
164 // Shows the notification bubble for a given app (the one clicked on).
165 function showNotificationBubble(event) {
166 var item = findAncestorByClass(event.target, 'app-anchor');
167 var title = item.getAttribute('notification-title');
168 var message = item.getAttribute('notification-message');
169 var link = item.getAttribute('notification-link');
170 var link_message = item.getAttribute('notification-link-message');
171
172 if (!title || !message)
173 return;
174
175 // Set the content to the right text.
176 $('app-notification-title').textContent = title;
177 $('app-notification-message').textContent = message;
178 $('app-notification-link').href = link;
179 $('app-notification-link').textContent = link_message;
180
181 var target = event.target;
182 while (target.parentElement && target.tagName != "A") {
183 target = target.parentElement;
184 }
185
186 // Move the bubble to the right location.
187 var bubble = $('app-notification-bubble');
188 var x = target.parentElement.offsetLeft +
189 target.parentElement.offsetWidth - 20;
190 var y = target.parentElement.offsetTop + 20;
191 bubble.style.left = x + "px";
192 bubble.style.top = y + "px";
193
194 // Move the arrow and shadow to the right location.
195 var arrow_container = $('arrow-container');
196 y += 26;
197 x -= arrow_container.style.width + 25;
198 arrow_container.style.left = x + "px";
199 arrow_container.style.top = y + "px";
200
201 // Animate the bubble into view.
202 bubble.classList.add("notification-bubble-opened");
203 bubble.classList.remove("notification-bubble-closed");
204 arrow_container.classList.add("notification-bubble-opened");
205 arrow_container.classList.remove("notification-bubble-closed");
206
207 bubble.focus();
208 }
209
210 // Hide the notification bubble.
211 function hideNotificationBubble(event) {
212 // This will fade the bubble out of existence.
213 $('app-notification-bubble').classList.add("notification-bubble-closed");
214 $('app-notification-bubble').classList.remove("notification-bubble-opened");
215 $('arrow-container').classList.add("notification-bubble-closed");
216 $('arrow-container').classList.remove("notification-bubble-opened");
217 }
218
219 var apps = (function() {
220
221 function createElement(app) {
222 var div = document.createElement('div');
223 div.className = 'app';
224
225 var a = div.appendChild(document.createElement('a'));
226 a.className = 'app-anchor';
227 a.setAttribute('app-id', app['id']);
228 a.setAttribute('launch-type', app['launch_type']);
229 if (typeof(app['notification']) != "undefined") {
230 a.setAttribute('notification-title', app['notification']['title']);
231 a.setAttribute('notification-message', app['notification']['body']);
232 if (typeof(app['notification']['linkUrl']) != "undefined" &&
233 typeof(app['notification']['linkText']) != "undefined") {
234 a.setAttribute('notification-link', app['notification']['linkUrl']);
235 a.setAttribute('notification-link-message',
236 app['notification']['linkText']);
237 }
238 }
239 a.draggable = false;
240 a.href = app['launch_url'];
241
242 var span = a.appendChild(document.createElement('span'));
243 span.textContent = app['name'];
244
245 span = a.appendChild(document.createElement('span'));
246 span.className = "app_notification";
247 span.textContent =
248 typeof(app['notification']) != "undefined" &&
249 typeof(app['notification']['title']) != "undefined" ?
250 app['notification']['title'] : "";
251 span.onclick = handleClick;
252
253 $("app-notification-close").onclick = hideNotificationBubble;
254 $("app-notification-bubble").setAttribute("tabIndex", 0);
255 $("app-notification-bubble").onblur = hideNotificationBubble;
256
257 return div;
258 }
259
260 /**
261 * Launches an application.
262 * @param {string} appId Application to launch.
263 * @param {MouseEvent} opt_mouseEvent Mouse event from the click that
264 * triggered the launch, used to detect modifier keys that change
265 * the tab's disposition.
266 */
267 function launchApp(appId, opt_mouseEvent) {
268 var args = [appId, getAppLaunchType()];
269 if (opt_mouseEvent) {
270 // Launch came from a click - add details of the click
271 // Otherwise it came from a 'command' event from elsewhere in the UI.
272 args.push(opt_mouseEvent.altKey, opt_mouseEvent.ctrlKey,
273 opt_mouseEvent.metaKey, opt_mouseEvent.shiftKey,
274 opt_mouseEvent.button);
275 }
276 chrome.send('launchApp', args);
277 }
278
279 function isAppSectionMaximized() {
280 return getAppLaunchType() == APP_LAUNCH.NTP_APPS_MAXIMIZED &&
281 !$('apps').classList.contains('disabled');
282 }
283
284 function isAppsMenu(node) {
285 return node.id == 'apps-menu';
286 }
287
288 function getAppLaunchType() {
289 // We determine if the apps section is maximized, collapsed or in menu mode
290 // based on the class of the apps section.
291 if ($('apps').classList.contains('menu'))
292 return APP_LAUNCH.NTP_APPS_MENU;
293 else if ($('apps').classList.contains('collapsed'))
294 return APP_LAUNCH.NTP_APPS_COLLAPSED;
295 else
296 return APP_LAUNCH.NTP_APPS_MAXIMIZED;
297 }
298
299 /**
300 * @this {!HTMLAnchorElement}
301 */
302 function handleClick(e) {
303 var appId = e.currentTarget.getAttribute('app-id');
304 if (appId == null) {
305 showNotificationBubble(e);
306 e.stopPropagation();
307 return false;
308 }
309
310 if (!appDragAndDrop.isDragging())
311 launchApp(appId, e);
312 return false;
313 }
314
315 // Keep in sync with LaunchType in extension_prefs.h
316 var LaunchType = {
317 LAUNCH_PINNED: 0,
318 LAUNCH_REGULAR: 1,
319 LAUNCH_FULLSCREEN: 2,
320 LAUNCH_WINDOW: 3
321 };
322
323 // Keep in sync with LaunchContainer in extension_constants.h
324 var LaunchContainer = {
325 LAUNCH_WINDOW: 0,
326 LAUNCH_PANEL: 1,
327 LAUNCH_TAB: 2
328 };
329
330 var currentApp;
331 var promoHasBeenSeen = false;
332
333 function addContextMenu(el, app) {
334 el.addEventListener('contextmenu', cr.ui.contextMenuHandler);
335 el.addEventListener('keydown', cr.ui.contextMenuHandler);
336 el.addEventListener('keyup', cr.ui.contextMenuHandler);
337
338 Object.defineProperty(el, 'contextMenu', {
339 get: function() {
340 currentApp = app;
341
342 $('apps-launch-command').label = app['name'];
343 $('apps-options-command').canExecuteChange();
344 $('apps-uninstall-command').canExecuteChange();
345
346 var launchTypeEl;
347 if (el.getAttribute('app-id') === app['id']) {
348 launchTypeEl = el;
349 } else {
350 appLinkSel = 'a[app-id=' + app['id'] + ']';
351 launchTypeEl = el.querySelector(appLinkSel);
352 }
353
354 var launchType = launchTypeEl.getAttribute('launch-type');
355 var launchContainer = app['launch_container'];
356 var isPanel = launchContainer == LaunchContainer.LAUNCH_PANEL;
357
358 // Update the commands related to the launch type.
359 var launchTypeIds = ['apps-launch-type-pinned',
360 'apps-launch-type-regular',
361 'apps-launch-type-fullscreen',
362 'apps-launch-type-window'];
363 launchTypeIds.forEach(function(id) {
364 var command = $(id);
365 command.disabled = isPanel;
366 command.checked = !isPanel &&
367 launchType == command.getAttribute('launch-type');
368 });
369
370 return $('app-context-menu');
371 }
372 });
373 }
374
375 document.addEventListener('command', function(e) {
376 if (!currentApp)
377 return;
378
379 var commandId = e.command.id;
380 switch (commandId) {
381 case 'apps-options-command':
382 window.location = currentApp['options_url'];
383 break;
384 case 'apps-launch-command':
385 launchApp(currentApp['id']);
386 break;
387 case 'apps-uninstall-command':
388 chrome.send('uninstallApp', [currentApp['id']]);
389 break;
390 case 'apps-create-shortcut-command':
391 chrome.send('createAppShortcut', [currentApp['id']]);
392 break;
393 case 'apps-launch-type-pinned':
394 case 'apps-launch-type-regular':
395 case 'apps-launch-type-fullscreen':
396 case 'apps-launch-type-window':
397 chrome.send('setLaunchType',
398 [currentApp['id'],
399 Number(e.command.getAttribute('launch-type'))]);
400 break;
401 }
402 });
403
404 document.addEventListener('canExecute', function(e) {
405 switch (e.command.id) {
406 case 'apps-options-command':
407 e.canExecute = currentApp && currentApp['options_url'];
408 break;
409 case 'apps-launch-command':
410 e.canExecute = true;
411 break;
412 case 'apps-uninstall-command':
413 e.canExecute = currentApp && currentApp['can_uninstall'];
414 break;
415 }
416 });
417
418 // Moves the element at position |from| in array |arr| to position |to|.
419 function arrayMove(arr, from, to) {
420 var element = arr.splice(from, 1);
421 arr.splice(to, 0, element[0]);
422 }
423
424 // The autoscroll rate during drag and drop, in px per second.
425 var APP_AUTOSCROLL_RATE = 400;
426
427 return {
428 loaded: false,
429
430 menu: $('apps-menu'),
431
432 showPromo: false,
433
434 detachWebstoreEntry: false,
435
436 scrollMouseXY_: null,
437
438 scrollListener_: null,
439
440 // The list of app ids, in order, of each app in the launcher.
441 data_: null,
442 get data() { return this.data_; },
443 set data(data) {
444 this.data_ = data.map(function(app) {
445 return app.id;
446 });
447 this.invalidate_();
448 },
449
450 dirty_: true,
451 invalidate_: function() {
452 this.dirty_ = true;
453 },
454
455 visible_: true,
456 get visible() {
457 return this.visible_;
458 },
459 set visible(visible) {
460 this.visible_ = visible;
461 this.invalidate_();
462 },
463
464 maybePingPromoSeen_: function() {
465 if (promoHasBeenSeen || !this.showPromo || !isAppSectionMaximized())
466 return;
467
468 promoHasBeenSeen = true;
469 chrome.send('promoSeen', []);
470 },
471
472 // DragAndDropDelegate
473
474 dragContainer: $('apps-content'),
475 transitionsDuration: 200,
476
477 get dragItem() { return this.dragItem_; },
478 set dragItem(dragItem) {
479 if (this.dragItem_ != dragItem) {
480 this.dragItem_ = dragItem;
481 this.invalidate_();
482 }
483 },
484
485 // The dimensions of each item in the app launcher.
486 dimensions_: null,
487 get dimensions() {
488 if (this.dimensions_)
489 return this.dimensions_;
490
491 var width = 124;
492 var height = 136;
493
494 var marginWidth = 6;
495 var marginHeight = 10;
496
497 var borderWidth = 0;
498 var borderHeight = 0;
499
500 this.dimensions_ = {
501 width: width + marginWidth + borderWidth,
502 height: height + marginHeight + borderHeight
503 };
504
505 return this.dimensions_;
506 },
507
508 // Gets the item under the mouse event |e|. Returns null if there is no
509 // item or if the item is not draggable.
510 getItem: function(e) {
511 var item = findAncestorByClass(e.target, 'app');
512
513 // You can't drag the web store launcher.
514 if (item && item.classList.contains('web-store-entry'))
515 return null;
516
517 return item;
518 },
519
520 // Returns true if |coordinates| point to a valid drop location. The
521 // coordinates are relative to the drag container and the object should
522 // have the 'x' and 'y' properties set.
523 canDropOn: function(coordinates) {
524 var cols = MAX_APPS_PER_ROW[layoutMode];
525 var rows = Math.ceil(this.data.length / cols);
526
527 var bottom = rows * this.dimensions.height;
528 var right = cols * this.dimensions.width;
529
530 if (coordinates.x >= right || coordinates.x < 0 ||
531 coordinates.y >= bottom || coordinates.y < 0)
532 return false;
533
534 var position = this.getIndexAt_(coordinates);
535 var appCount = this.data.length;
536
537 if (!this.detachWebstoreEntry)
538 appCount--;
539
540 return position >= 0 && position < appCount;
541 },
542
543 setDragPlaceholder: function(coordinates) {
544 var position = this.getIndexAt_(coordinates);
545 var appId = this.dragItem.querySelector('a').getAttribute('app-id');
546 var current = this.data.indexOf(appId);
547
548 if (current == position || current < 0)
549 return;
550
551 arrayMove(this.data, current, position);
552 this.invalidate_();
553 this.layout();
554 },
555
556 getIndexAt_: function(coordinates) {
557 var w = this.dimensions.width;
558 var h = this.dimensions.height;
559
560 var appsPerRow = MAX_APPS_PER_ROW[layoutMode];
561
562 var row = Math.floor(coordinates.y / h);
563 var col = Math.floor(coordinates.x / w);
564 var index = appsPerRow * row + col;
565
566 var appCount = this.data.length;
567 var rows = Math.ceil(appCount / appsPerRow);
568
569 // Rather than making the free space on the last row invalid, we
570 // map it to the last valid position.
571 if (index >= appCount && index < appsPerRow * rows)
572 return appCount-1;
573
574 return index;
575 },
576
577 scrollPage: function(xy) {
578 var rect = this.dragContainer.getBoundingClientRect();
579
580 // Here, we calculate the visible boundaries of the app launcher, which
581 // are then used to determine when we should auto-scroll.
582 var top = $('apps').getBoundingClientRect().bottom;
583 var bottomFudge = 15; // Fudge factor due to a gradient mask.
584 var bottom = top + maxiviewVisibleHeight - bottomFudge;
585 var left = rect.left + window.scrollX;
586 var right = Math.min(window.innerWidth, rect.left + rect.width);
587
588 var dy = Math.min(0, xy.y - top) + Math.max(0, xy.y - bottom);
589 var dx = Math.min(0, xy.x - left) + Math.max(0, xy.x - right);
590
591 if (dx == 0 && dy == 0) {
592 this.stopScroll_();
593 return;
594 }
595
596 // If we scroll the page directly from this method, it may be choppy and
597 // inconsistent. Instead, we loop using animation frames, and scroll at a
598 // speed that's independent of how many times this method is called.
599 this.scrollMouseXY_ = {dx: dx, dy: dy};
600
601 if (!this.scrollListener_) {
602 this.scrollListener_ = this.scrollImpl_.bind(this);
603 this.scrollStep_();
604 }
605 },
606
607 scrollStep_: function() {
608 this.scrollStart_ = Date.now();
609 window.webkitRequestAnimationFrame(this.scrollListener_);
610 },
611
612 scrollImpl_: function(time) {
613 if (!appDragAndDrop.isDragging()) {
614 this.stopScroll_();
615 return;
616 }
617
618 if (!this.scrollMouseXY_)
619 return;
620
621 var step = time - this.scrollStart_;
622
623 window.scrollBy(
624 this.calcScroll_(this.scrollMouseXY_.dx, step),
625 this.calcScroll_(this.scrollMouseXY_.dy, step));
626
627 this.scrollStep_();
628 },
629
630 calcScroll_: function(delta, step) {
631 if (delta == 0)
632 return 0;
633
634 // Increase the multiplier for every 50px the mouse is beyond the edge.
635 var sign = delta > 0 ? 1 : -1;
636 var scalar = APP_AUTOSCROLL_RATE * step / 1000;
637 var multiplier = Math.floor(Math.abs(delta) / 50) + 1;
638
639 return sign * scalar * multiplier;
640 },
641
642 stopScroll_: function() {
643 this.scrollListener_ = null;
644 this.scrollMouseXY_ = null;
645 },
646
647 saveDrag: function(draggedItem) {
648 this.invalidate_();
649 this.layout();
650
651 var draggedAppId = draggedItem.querySelector('a').getAttribute('app-id');
652 var appIds = this.data.filter(function(id) {
653 return id != 'web-store-entry';
654 });
655
656 // Wait until the transitions are complete before notifying the browser.
657 // Otherwise, the apps will be re-rendered while still transitioning.
658 setTimeout(function() {
659 chrome.send('reorderApps', [draggedAppId, appIds]);
660 }, this.transitionsDuration + 10);
661 },
662
663 layout: function(options) {
664 options = options || {};
665 if (!this.dirty_ && options.force != true)
666 return;
667
668 try {
669 var container = this.dragContainer;
670 if (options.disableAnimations)
671 container.setAttribute('launcher-animations', false);
672 var d0 = Date.now();
673 this.layoutImpl_();
674 this.dirty_ = false;
675 logEvent('apps.layout: ' + (Date.now() - d0));
676
677 } finally {
678 if (options.disableAnimations) {
679 // We need to re-enable animations asynchronously, so that the
680 // animations are still disabled for this layout update.
681 setTimeout(function() {
682 container.setAttribute('launcher-animations', true);
683 }, 0);
684 }
685 }
686 },
687
688 layoutImpl_: function() {
689 var apps = this.data || [];
690 var rects = this.getLayoutRects_(apps.length);
691 var appsContent = this.dragContainer;
692
693 // Ping the PROMO_SEEN histogram only when the promo is maximized, and
694 // maximum once per NTP load.
695 this.maybePingPromoSeen_();
696
697 if (!this.visible)
698 return;
699
700 for (var i = 0; i < apps.length; i++) {
701 var app = appsContent.querySelector('[app-id='+apps[i]+']').parentNode;
702
703 // If the node is being dragged, don't try to place it in the grid.
704 if (app == this.dragItem)
705 continue;
706
707 app.style.left = rects[i].left + 'px';
708 app.style.top = rects[i].top + 'px';
709 }
710
711 // We need to set the container's height manually because the apps use
712 // absolute positioning.
713 var rows = Math.ceil(apps.length / MAX_APPS_PER_ROW[layoutMode]);
714 appsContent.style.height = (rows * this.dimensions.height) + 'px';
715 },
716
717 getLayoutRects_: function(appCount) {
718 var availableWidth = this.dragContainer.offsetWidth;
719 var rtl = isRtl();
720 var rects = [];
721 var w = this.dimensions.width;
722 var h = this.dimensions.height;
723 var appsPerRow = MAX_APPS_PER_ROW[layoutMode];
724
725 for (var i = 0; i < appCount; i++) {
726 var top = Math.floor(i / appsPerRow) * h;
727 var left = (i % appsPerRow) * w;
728
729 // Reflect the X axis if an RTL language is active.
730 if (rtl)
731 left = availableWidth - left - w;
732 rects[i] = {left: left, top: top};
733 }
734 return rects;
735 },
736
737 get loadedImages() {
738 return this.loadedImages_;
739 },
740
741 set loadedImages(value) {
742 this.loadedImages_ = value;
743 if (this.loadedImages_ == 0)
744 return;
745
746 // Each application icon is loaded asynchronously. Here, we display
747 // the icons once they've all been loaded to make it look nicer.
748 if (this.loadedImages_ == this.data.length) {
749 this.showImages();
750 return;
751 }
752
753 // We won't actually have the visible height until the sections have
754 // been layed out.
755 if (!maxiviewVisibleHeight)
756 return;
757
758 // If we know the visible height of the maxiview, then we can don't need
759 // to wait for all the icons. Instead, we wait until the visible portion
760 // have been loaded.
761 var appsPerRow = MAX_APPS_PER_ROW[layoutMode];
762 var rows = Math.ceil(maxiviewVisibleHeight / this.dimensions.height);
763 var count = Math.min(appsPerRow * rows, this.data.length);
764 if (this.loadedImages_ == count) {
765 this.showImages();
766 return;
767 }
768 },
769
770 showImages: function() {
771 $('apps-content').classList.add('visible');
772 clearTimeout(this.imageTimer);
773 },
774
775 createElement: function(app) {
776 var container = document.createElement('div');
777 var div = createElement(app);
778 container.appendChild(div);
779 var a = div.firstChild;
780
781 a.onclick = handleClick;
782 a.ping = getAppPingUrl(
783 'PING_BY_ID', this.showPromo, 'NTP_APPS_MAXIMIZED');
784 a.style.backgroundImage = url(app['icon_big']);
785 if (app.isNew) {
786 div.setAttribute('new', 'new');
787 // Delay changing the attribute a bit to let the page settle down a bit.
788 setTimeout(function() {
789 // Make sure the new icon is scrolled into view.
790 document.body.scrollTop = document.body.scrollHeight;
791
792 // This will trigger the 'bounce' animation defined in apps.css.
793 div.setAttribute('new', 'installed');
794 }, 500);
795 div.addEventListener('webkitAnimationEnd', function(e) {
796 div.removeAttribute('new');
797 });
798 }
799
800 // CSS background images don't fire 'load' events, so we use an Image.
801 var img = new Image();
802 img.onload = function() { this.loadedImages++; }.bind(this);
803 img.src = app['icon_big'];
804
805 // User cannot change launch options or uninstall component extension.
806 if (!app['is_component']) {
807 var settingsButton = div.appendChild(new cr.ui.ContextMenuButton);
808 settingsButton.className = 'app-settings';
809 settingsButton.title = localStrings.getString('appsettings');
810 addContextMenu(div, app);
811 }
812
813 if (app.notifications && app.notifications.length > 0) {
814 // Create the notification div below the app icon that is used to
815 // trigger the hidden notification bubble to appear.
816 var notification = document.createElement('div')
817 container.appendChild(notification);
818 var title = document.createElement('span');
819 title.textContent = app.notifications[0].title;
820 notification.appendChild(title);
821 notification.appendChild(document.createElement('br'));
822
823 var body = document.createElement('span');
824 container.appendChild(body);
825 body.textContent = app.notifications[0].body;
826 notification.appendChild(body);
827 if (app.notifications[0].linkUrl) {
828 notification.appendChild(document.createElement('br'));
829 var link = document.createElement('a');
830 link.href = app.notifications[0].linkUrl;
831 link.textContent = app.notifications[0].linkText ?
832 app.notifications[0].linkText : "link";
833 notification.appendChild(link);
834 }
835 }
836
837 return container;
838 },
839
840 createMiniviewElement: function(app) {
841 var span = document.createElement('span');
842 var a = span.appendChild(document.createElement('a'));
843
844 a.setAttribute('app-id', app['id']);
845 a.textContent = app['name'];
846 a.href = app['launch_url'];
847 a.onclick = handleClick;
848 a.ping = getAppPingUrl(
849 'PING_BY_ID', this.showPromo, 'NTP_APPS_COLLAPSED');
850 a.style.backgroundImage = url(app['icon_small']);
851 a.className = 'item';
852 span.appendChild(a);
853
854 // User cannot change launch options or uninstall component extension.
855 if (!app['is_component']) {
856 addContextMenu(span, app);
857 }
858
859 return span;
860 },
861
862 createClosedMenuElement: function(app) {
863 var a = document.createElement('a');
864 a.setAttribute('app-id', app['id']);
865 a.textContent = app['name'];
866 a.href = app['launch_url'];
867 a.onclick = handleClick;
868 a.ping = getAppPingUrl(
869 'PING_BY_ID', this.showPromo, 'NTP_APPS_MENU');
870 a.style.backgroundImage = url(app['icon_small']);
871 a.className = 'item';
872
873 // User cannot change launch options or uninstall component extension.
874 if (!app['is_component']) {
875 addContextMenu(a, app);
876 }
877
878 return a;
879 },
880
881 createWebStoreElement: function() {
882 var elm = createElement({
883 'id': 'web-store-entry',
884 'name': localStrings.getString('web_store_title'),
885 'launch_url': localStrings.getString('web_store_url')
886 });
887 elm.classList.add('web-store-entry');
888 return elm;
889 },
890
891 createWebStoreMiniElement: function() {
892 var span = document.createElement('span');
893 span.appendChild(this.createWebStoreClosedMenuElement());
894 return span;
895 },
896
897 createWebStoreClosedMenuElement: function() {
898 var a = document.createElement('a');
899 a.textContent = localStrings.getString('web_store_title');
900 a.href = localStrings.getString('web_store_url');
901 a.style.backgroundImage = url('chrome://theme/IDR_WEBSTORE_ICON_16');
902 a.className = 'item';
903 return a;
904 }
905 };
906 })();
907
908 // Enable drag and drop reordering of the app launcher.
909 var appDragAndDrop = new DragAndDropController(apps);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698