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
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
119function onLoad() {
120  chrome.management.getAll(function(info) {
121    var appCount = 0;
122    for (var i = 0; i < info.length; i++) {
123      if (info[i].isApp) {
124        appCount++;
125      }
126    }
127    if (appCount == 0) {
128      $("search").style.display = "none";
129      $("appstore_link").style.display = "";
130      return;
131    }
132    completeList = info.sort(compareByName);
133    onSearchInput();
134  });
135}
136
137// Changes the selected app in the list.
138function changeSelection(newIndex) {
139  if (newIndex >= 0 && newIndex <= appList.length - 1) {
140    selectedIndex = newIndex;
141    reloadAppDisplay();
142
143    var selected = document.getElementsByClassName("app_selected")[0];
144    var rect = selected.getBoundingClientRect();
145    if (newIndex == 0) {
146      window.scrollTo(0, 0);
147    } else if (newIndex == appList.length - 1) {
148      window.scrollTo(0, document.height);
149    }  else if (rect.top < 0) {
150      window.scrollBy(0, rect.top);
151    } else if (rect.bottom > innerHeight) {
152      window.scrollBy(0, rect.bottom - innerHeight);
153    }
154  }
155}
156
157var keys = {
158  ENTER : 13,
159  ESCAPE : 27,
160  END : 35,
161  HOME : 36,
162  LEFT : 37,
163  UP : 38,
164  RIGHT : 39,
165  DOWN : 40
166};
167
168// Set up a key event handler that handles moving the selected app up/down,
169// hitting enter to launch the selected app, as well as auto-focusing the
170// search box as soon as you start typing.
171window.onkeydown = function(event) {
172  switch (event.keyCode) {
173    case keys.DOWN:
174      changeSelection(selectedIndex + 1);
175      break;
176    case keys.UP:
177      changeSelection(selectedIndex - 1);
178      break;
179    case keys.HOME:
180      changeSelection(0);
181      break;
182    case keys.END:
183      changeSelection(appList.length - 1);
184      break;
185    case keys.ENTER:
186      var app = appList[selectedIndex];
187      if (app) {
188        launchApp(app.id);
189      }
190      break;
191    default:
192      // Focus the search box and return true so you can just start typing even
193      // when the search box isn't focused.
194      $("search").focus();
195      return true;
196  }
197  return false;
198};
199