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
5function $(id) {
6  return document.getElementById(id);
7}
8
9// Returns the largest size icon, or a default icon, for the given |app|.
10function getIconURL(app) {
11  if (!app.icons || app.icons.length == 0) {
12    return chrome.extension.getURL('icon.png');
13  }
14  var largest = {size:0};
15  for (var i = 0; i < app.icons.length; i++) {
16    var icon = app.icons[i];
17    if (icon.size > largest.size) {
18      largest = icon;
19    }
20  }
21  return largest.url;
22}
23
24function launchApp(id) {
25  chrome.management.launchApp(id);
26  window.close(); // Only needed on OSX because of crbug.com/63594
27}
28
29// Adds DOM nodes for |app| into |appsDiv|.
30function addApp(appsDiv, app, selected) {
31  var div = document.createElement('div');
32  div.className = 'app' + (selected ? ' app_selected' : '');
33
34  div.onclick = function() {
35    launchApp(app.id);
36  };
37
38  var img = document.createElement('img');
39  img.src = getIconURL(app);
40  div.appendChild(img);
41
42  var title = document.createElement('span');
43  title.className = 'app_title';
44  title.innerText = app.name;
45  div.appendChild(title);
46
47  appsDiv.appendChild(div);
48}
49
50// The list of all apps & extensions.
51var completeList = [];
52
53// A filtered list of apps we actually want to show.
54var appList = [];
55
56// The index of an app in |appList| that should be highlighted.
57var selectedIndex = 0;
58
59function reloadAppDisplay() {
60  var appsDiv = $('apps');
61
62  // Empty the current content.
63  appsDiv.innerHTML = '';
64
65  for (var i = 0; i < appList.length; i++) {
66    var item = appList[i];
67    addApp(appsDiv, item, i == selectedIndex);
68  }
69}
70
71// Puts only enabled apps from completeList into appList.
72function rebuildAppList(filter) {
73  selectedIndex = 0;
74  appList = [];
75  for (var i = 0; i < completeList.length; i++){
76    var item = completeList[i];
77    // Skip extensions and disabled apps.
78    if (!item.isApp || !item.enabled) {
79      continue;
80    }
81    if (filter && item.name.toLowerCase().search(filter) < 0) {
82      continue;
83    }
84    appList.push(item);
85  }
86}
87
88// In order to keep the popup bubble from shrinking as your search narrows the
89// list of apps shown, we set an explicit width on the outermost div.
90var didSetExplicitWidth = false;
91
92function adjustWidthIfNeeded(filter) {
93  if (filter.length > 0 && !didSetExplicitWidth) {
94    // Set an explicit width, correcting for any scroll bar present.
95    var outer = $('outer');
96    var correction = window.innerWidth - document.documentElement.clientWidth;
97    var width = outer.offsetWidth;
98    $('spacer_dummy').style.width = width + correction + 'px';
99    didSetExplicitWidth = true;
100  }
101}
102
103// Shows the list of apps based on the search box contents.
104function onSearchInput() {
105  var filter = $('search').value;
106  adjustWidthIfNeeded(filter);
107  rebuildAppList(filter);
108  reloadAppDisplay();
109}
110
111function compare(a, b) {
112  return (a > b) ? 1 : (a == b ? 0 : -1);
113}
114
115function compareByName(app1, app2) {
116  return compare(app1.name.toLowerCase(), app2.name.toLowerCase());
117}
118
119// Changes the selected app in the list.
120function changeSelection(newIndex) {
121  if (newIndex >= 0 && newIndex <= appList.length - 1) {
122    selectedIndex = newIndex;
123    reloadAppDisplay();
124
125    var selected = document.getElementsByClassName('app_selected')[0];
126    var rect = selected.getBoundingClientRect();
127    if (newIndex == 0) {
128      window.scrollTo(0, 0);
129    } else if (newIndex == appList.length - 1) {
130      window.scrollTo(0, document.height);
131    }  else if (rect.top < 0) {
132      window.scrollBy(0, rect.top);
133    } else if (rect.bottom > innerHeight) {
134      window.scrollBy(0, rect.bottom - innerHeight);
135    }
136  }
137}
138
139var keys = {
140  ENTER : 13,
141  ESCAPE : 27,
142  END : 35,
143  HOME : 36,
144  LEFT : 37,
145  UP : 38,
146  RIGHT : 39,
147  DOWN : 40
148};
149
150// Set up a key event handler that handles moving the selected app up/down,
151// hitting enter to launch the selected app, as well as auto-focusing the
152// search box as soon as you start typing.
153window.onkeydown = function(event) {
154  switch (event.keyCode) {
155    case keys.DOWN:
156      changeSelection(selectedIndex + 1);
157      break;
158    case keys.UP:
159      changeSelection(selectedIndex - 1);
160      break;
161    case keys.HOME:
162      changeSelection(0);
163      break;
164    case keys.END:
165      changeSelection(appList.length - 1);
166      break;
167    case keys.ENTER:
168      var app = appList[selectedIndex];
169      if (app) {
170        launchApp(app.id);
171      }
172      break;
173    default:
174      // Focus the search box and return true so you can just start typing even
175      // when the search box isn't focused.
176      $('search').focus();
177      return true;
178  }
179  return false;
180};
181
182
183// Initalize the popup window.
184document.addEventListener('DOMContentLoaded', function () {
185  chrome.management.getAll(function(info) {
186    var appCount = 0;
187    for (var i = 0; i < info.length; i++) {
188      if (info[i].isApp) {
189        appCount++;
190      }
191    }
192    if (appCount == 0) {
193      $('search').style.display = 'none';
194      $('appstore_link').style.display = '';
195      return;
196    }
197    completeList = info.sort(compareByName);
198    onSearchInput();
199  });
200
201  $('search').addEventListener('input', onSearchInput);
202
203  // Opens the webstore in a new tab.
204  document.querySelector('#appstore_link button').addEventListener('click',
205      function () {
206        chrome.tabs.create({
207          'url':'https://chrome.google.com/webstore',
208          'selected':true
209        });
210        window.close();
211      });
212});
213
214