apps.js revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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
5var MAX_APPS_PER_ROW = [];
6MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4;
7MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6;
8
9function getAppsCallback(data) {
10  logEvent('received apps');
11  var appsSection = $('apps');
12  var appsSectionContent = $('apps-content');
13  var appsMiniview = appsSection.getElementsByClassName('miniview')[0];
14  var appsPromo = $('apps-promo');
15  var webStoreEntry;
16
17  appsMiniview.textContent = '';
18  appsSectionContent.textContent = '';
19
20  data.apps.sort(function(a,b) {
21    return a.app_launch_index - b.app_launch_index
22  });
23
24  clearClosedMenu(apps.menu);
25  if (data.apps.length == 0 && !data.showLauncher) {
26    appsSection.classList.add('disabled');
27    layoutSections();
28  } else {
29    data.apps.forEach(function(app) {
30      appsSectionContent.appendChild(apps.createElement(app));
31    });
32
33    webStoreEntry = apps.createWebStoreElement();
34    appsSectionContent.appendChild(webStoreEntry);
35
36    data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) {
37      appsMiniview.appendChild(apps.createMiniviewElement(app));
38      addClosedMenuEntryWithLink(apps.menu, apps.createClosedMenuElement(app));
39    });
40
41    if (!(shownSections & MINIMIZED_APPS)) {
42      appsSection.classList.remove('disabled');
43    }
44  }
45  addClosedMenuFooter(apps.menu, 'apps', MINIMIZED_APPS, Section.APPS);
46
47  apps.loaded = true;
48  if (data.showPromo)
49    document.documentElement.classList.add('apps-promo-visible');
50  else
51    document.documentElement.classList.remove('apps-promo-visible');
52  maybeDoneLoading();
53
54  if (data.apps.length > 0 && isDoneLoading()) {
55    if (!data.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode])
56      webStoreEntry.classList.add('loner');
57    else
58      webStoreEntry.classList.remove('loner');
59
60    updateMiniviewClipping(appsMiniview);
61    layoutSections();
62  }
63}
64
65function appsPrefChangeCallback(data) {
66  // Currently the only pref that is watched is the launch type.
67  data.apps.forEach(function(app) {
68    var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']');
69    if (appLink)
70      appLink.setAttribute('launch-type', app['launch_type']);
71  });
72}
73
74var apps = (function() {
75
76  function createElement(app) {
77    var div = document.createElement('div');
78    div.className = 'app';
79
80    var a = div.appendChild(document.createElement('a'));
81    a.setAttribute('app-id', app['id']);
82    a.setAttribute('launch-type', app['launch_type']);
83    a.xtitle = a.textContent = app['name'];
84    a.href = app['launch_url'];
85
86    return div;
87  }
88
89  function createContextMenu(app) {
90    var menu = new cr.ui.Menu;
91    var button = document.createElement(button);
92  }
93
94  function launchApp(appId) {
95    var appsSection = $('apps');
96    var expanded = !appsSection.classList.contains('hidden');
97    var element = document.querySelector(
98        (expanded ? '.maxiview' : '.miniview') + ' a[app-id=' + appId + ']');
99
100    // TODO(arv): Handle zoom?
101    var rect = element.getBoundingClientRect();
102    var cs = getComputedStyle(element);
103    var size = cs.backgroundSize.split(/\s+/);  // background-size has the
104                                                // format '123px 456px'.
105
106    var width = parseInt(size[0], 10);
107    var height = parseInt(size[1], 10);
108
109    var top, left;
110    if (expanded) {
111      // We are using background-position-x 50%.
112      top = rect.top + parseInt(cs.backgroundPositionY, 10);
113      left = rect.left + ((rect.width - width) >> 1);  // Integer divide by 2.
114
115    } else {
116      // We are using background-position-y 50%.
117      top = rect.top + ((rect.height - width) >> 1);  // Integer divide by 2.
118      if (getComputedStyle(element).direction == 'rtl')
119        left = rect.left + rect.width - width;
120      else
121        left = rect.left;
122    }
123
124    chrome.send('launchApp', [appId,
125                              String(left), String(top),
126                              String(width), String(height)]);
127  }
128
129  /**
130   * @this {!HTMLAnchorElement}
131   */
132  function handleClick(e) {
133    var appId = e.currentTarget.getAttribute('app-id');
134    launchApp(appId);
135    return false;
136  }
137
138  // Keep in sync with LaunchType in extension_prefs.h
139  var LaunchType = {
140    LAUNCH_PINNED: 0,
141    LAUNCH_REGULAR: 1,
142    LAUNCH_FULLSCREEN: 2
143  };
144
145  // Keep in sync with LaunchContainer in extension.h
146  var LaunchContainer = {
147    LAUNCH_WINDOW: 0,
148    LAUNCH_PANEL: 1,
149    LAUNCH_TAB: 2
150  };
151
152  var currentApp;
153
154  function addContextMenu(el, app) {
155    el.addEventListener('contextmenu', cr.ui.contextMenuHandler);
156    el.addEventListener('keydown', cr.ui.contextMenuHandler);
157    el.addEventListener('keyup', cr.ui.contextMenuHandler);
158
159    Object.defineProperty(el, 'contextMenu', {
160      get: function() {
161        currentApp = app;
162
163        $('apps-launch-command').label = app['name'];
164        $('apps-options-command').canExecuteChange();
165
166        var appLinkSel = '.app a[app-id=' + app['id'] + ']';
167        var launchType =
168            el.querySelector(appLinkSel).getAttribute('launch-type');
169
170        var launchContainer = app['launch_container'];
171        var isPanel = launchContainer == LaunchContainer.LAUNCH_PANEL;
172
173        // Update the commands related to the launch type.
174        var launchTypeIds = ['apps-launch-type-pinned',
175                             'apps-launch-type-regular',
176                             'apps-launch-type-fullscreen'];
177        launchTypeIds.forEach(function(id) {
178          var command = $(id);
179          command.disabled = isPanel;
180          command.checked = !isPanel &&
181              launchType == command.getAttribute('launch-type');
182        });
183
184        return $('app-context-menu');
185      }
186    });
187  }
188
189  document.addEventListener('command', function(e) {
190    if (!currentApp)
191      return;
192
193    var commandId = e.command.id;
194    switch (commandId) {
195      case 'apps-options-command':
196        window.location = currentApp['options_url'];
197        break;
198      case 'apps-launch-command':
199        launchApp(currentApp['id']);
200        break;
201      case 'apps-uninstall-command':
202        chrome.send('uninstallApp', [currentApp['id']]);
203        break;
204      case 'apps-launch-type-pinned':
205      case 'apps-launch-type-regular':
206      case 'apps-launch-type-fullscreen':
207        chrome.send('setLaunchType',
208            [currentApp['id'], e.command.getAttribute('launch-type')]);
209        break;
210    }
211  });
212
213  document.addEventListener('canExecute', function(e) {
214    switch (e.command.id) {
215      case 'apps-options-command':
216        e.canExecute = currentApp && currentApp['options_url'];
217        break;
218      case 'apps-launch-command':
219      case 'apps-uninstall-command':
220        e.canExecute = true;
221        break;
222    }
223  });
224
225  return {
226    loaded: false,
227
228    menu: $('apps-menu'),
229
230    createElement: function(app) {
231      var div = createElement(app);
232      var a = div.firstChild;
233
234      a.onclick = handleClick;
235      a.style.backgroundImage = url(app['icon_big']);
236      if (hashParams['app-id'] == app['id']) {
237        div.setAttribute('new', 'new');
238        // Delay changing the attribute a bit to let the page settle down a bit.
239        setTimeout(function() {
240          // Make sure the new icon is scrolled into view.
241          document.body.scrollTop = document.body.scrollHeight;
242
243          // This will trigger the 'bounce' animation defined in apps.css.
244          div.setAttribute('new', 'installed');
245        }, 500);
246        div.addEventListener('webkitAnimationEnd', function(e) {
247          div.removeAttribute('new');
248
249          // If we get new data (eg because something installs in another tab,
250          // or because we uninstall something here), don't run the install
251          // animation again.
252          document.documentElement.setAttribute("install-animation-enabled",
253                                                "false");
254        });
255      }
256
257      var settingsButton = div.appendChild(new cr.ui.ContextMenuButton);
258      settingsButton.className = 'app-settings';
259      settingsButton.title = localStrings.getString('appsettings');
260
261      addContextMenu(div, app);
262
263      return div;
264    },
265
266    createMiniviewElement: function(app) {
267      var span = document.createElement('span');
268      var a = span.appendChild(document.createElement('a'));
269
270      a.setAttribute('app-id', app['id']);
271      a.textContent = app['name'];
272      a.href = app['launch_url'];
273      a.onclick = handleClick;
274      a.style.backgroundImage = url(app['icon_small']);
275      a.className = 'item';
276      span.appendChild(a);
277
278      addContextMenu(span, app);
279
280      return span;
281    },
282
283    createClosedMenuElement: function(app) {
284      var a = document.createElement('a');
285      a.setAttribute('app-id', app['id']);
286      a.textContent = app['name'];
287      a.href = app['launch_url'];
288      a.onclick = handleClick;
289      a.style.backgroundImage = url(app['icon_small']);
290      a.className = 'item';
291      return a;
292    },
293
294    createWebStoreElement: function() {
295      var elm = createElement({
296        'id': 'web-store-entry',
297        'name': localStrings.getString('web_store_title'),
298        'launch_url': localStrings.getString('web_store_url')
299      });
300      elm.setAttribute('app-id', 'web-store-entry');
301      return elm;
302    }
303  };
304})();
305