OLD | NEW |
---|---|
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 var MAX_APPS_PER_ROW = []; | 5 var MAX_APPS_PER_ROW = []; |
6 MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4; | 6 MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4; |
7 MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6; | 7 MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6; |
8 | 8 |
9 // The URL prefix used in the app link 'ping' attributes. | 9 // The URL prefix used in the app link 'ping' attributes. |
10 var PING_APP_LAUNCH_PREFIX = 'record-app-launch'; | 10 var PING_APP_LAUNCH_PREFIX = 'record-app-launch'; |
(...skipping 27 matching lines...) Expand all Loading... | |
38 $('apps-create-shortcut-command-separator').style.display = | 38 $('apps-create-shortcut-command-separator').style.display = |
39 (data.disableCreateAppShortcut ? 'none' : ''); | 39 (data.disableCreateAppShortcut ? 'none' : ''); |
40 | 40 |
41 appsMiniview.textContent = ''; | 41 appsMiniview.textContent = ''; |
42 appsSectionContent.textContent = ''; | 42 appsSectionContent.textContent = ''; |
43 | 43 |
44 data.apps.sort(function(a,b) { | 44 data.apps.sort(function(a,b) { |
45 return a.app_launch_index - b.app_launch_index; | 45 return a.app_launch_index - b.app_launch_index; |
46 }); | 46 }); |
47 | 47 |
48 // Determines if the web store link should be detached and place in the | |
49 // top right of the screen. | |
50 apps.detachWebstoreEntry = | |
51 !apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode]; | |
52 | |
53 apps.data = data.apps; | |
54 if (!apps.detachWebstoreEntry) | |
55 apps.data.push('web-store-entry'); | |
56 | |
48 clearClosedMenu(apps.menu); | 57 clearClosedMenu(apps.menu); |
49 data.apps.forEach(function(app) { | 58 data.apps.forEach(function(app) { |
50 appsSectionContent.appendChild(apps.createElement(app)); | 59 appsSectionContent.appendChild(apps.createElement(app)); |
51 }); | 60 }); |
52 | 61 |
53 webStoreEntry = apps.createWebStoreElement(); | 62 webStoreEntry = apps.createWebStoreElement(); |
54 webStoreEntry.querySelector('a').setAttribute('ping', appsPromoPing); | 63 webStoreEntry.querySelector('a').setAttribute('ping', appsPromoPing); |
55 appsSectionContent.appendChild(webStoreEntry); | 64 appsSectionContent.appendChild(webStoreEntry); |
56 | 65 |
57 data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) { | 66 data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) { |
(...skipping 19 matching lines...) Expand all Loading... | |
77 if (apps.showPromo) | 86 if (apps.showPromo) |
78 document.documentElement.classList.add('apps-promo-visible'); | 87 document.documentElement.classList.add('apps-promo-visible'); |
79 else | 88 else |
80 document.documentElement.classList.remove('apps-promo-visible'); | 89 document.documentElement.classList.remove('apps-promo-visible'); |
81 | 90 |
82 var appsPromoLink = $('apps-promo-link'); | 91 var appsPromoLink = $('apps-promo-link'); |
83 if (appsPromoLink) | 92 if (appsPromoLink) |
84 appsPromoLink.setAttribute('ping', appsPromoPing); | 93 appsPromoLink.setAttribute('ping', appsPromoPing); |
85 maybeDoneLoading(); | 94 maybeDoneLoading(); |
86 | 95 |
96 // Disable the animations when the app launcher is being (re)initailized. | |
97 apps.layout({disableAnimations:true}); | |
98 | |
99 if (apps.detachWebstoreEntry) | |
100 webStoreEntry.classList.add('loner'); | |
101 else | |
102 webStoreEntry.classList.remove('loner'); | |
103 | |
87 if (isDoneLoading()) { | 104 if (isDoneLoading()) { |
88 if (!apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode]) | |
89 webStoreEntry.classList.add('loner'); | |
90 else | |
91 webStoreEntry.classList.remove('loner'); | |
92 | |
93 updateMiniviewClipping(appsMiniview); | 105 updateMiniviewClipping(appsMiniview); |
94 layoutSections(); | 106 layoutSections(); |
95 } | 107 } |
96 } | 108 } |
97 | 109 |
98 function appsPrefChangeCallback(data) { | 110 function appsPrefChangeCallback(data) { |
99 // Currently the only pref that is watched is the launch type. | 111 // Currently the only pref that is watched is the launch type. |
100 data.apps.forEach(function(app) { | 112 data.apps.forEach(function(app) { |
101 var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']'); | 113 var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']'); |
102 if (appLink) | 114 if (appLink) |
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
254 case 'apps-options-command': | 266 case 'apps-options-command': |
255 e.canExecute = currentApp && currentApp['options_url']; | 267 e.canExecute = currentApp && currentApp['options_url']; |
256 break; | 268 break; |
257 case 'apps-launch-command': | 269 case 'apps-launch-command': |
258 case 'apps-uninstall-command': | 270 case 'apps-uninstall-command': |
259 e.canExecute = true; | 271 e.canExecute = true; |
260 break; | 272 break; |
261 } | 273 } |
262 }); | 274 }); |
263 | 275 |
276 // Moves the element at position |from| in array |arr| to position |to|. | |
277 function arrayMove(arr, from, to) { | |
278 var element = arr.splice(from, 1); | |
279 arr.splice(to, 0, element[0]); | |
280 } | |
281 | |
282 // Converts a css pixel count ('53px') to the corresponding number (53). | |
283 function cssPxToNum(px) { | |
Aaron Boodman
2011/01/24 01:29:33
You can just use parseInt() instead of defining th
| |
284 return Number(px.substring(0, px.length-2)); | |
285 } | |
286 | |
264 return { | 287 return { |
265 loaded: false, | 288 loaded: false, |
266 | 289 |
267 menu: $('apps-menu'), | 290 menu: $('apps-menu'), |
268 | 291 |
269 showPromo: false, | 292 showPromo: false, |
270 | 293 |
294 detachWebstoreEntry: false, | |
295 | |
296 // The list of app ids, in order, of each app in the launcher. | |
297 data_: null, | |
298 get data() { return this.data_; }, | |
299 set data(data) { | |
300 this.data_ = data.map(function(app) { | |
301 return app.id; | |
302 }); | |
303 this.invalidate_(); | |
304 }, | |
305 | |
306 dirty_: true, | |
307 invalidate_: function() { | |
308 this.dirty_ = true; | |
309 }, | |
310 | |
311 visible_: true, | |
312 get visible() { | |
313 return this.visible_; | |
314 }, | |
315 set visible(visible) { | |
316 this.visible_ = visible; | |
317 this.invalidate_(); | |
318 }, | |
319 | |
320 // DragAndDropDelegate | |
321 | |
322 dragContainer: $('apps-content'), | |
323 transitionsDuration: 200, | |
324 | |
325 get dragItem() { return this.dragItem_; }, | |
326 set dragItem(dragItem) { | |
327 if (this.dragItem_ != dragItem) { | |
328 this.dragItem_ = dragItem; | |
329 this.invalidate_(); | |
330 } | |
331 }, | |
332 | |
333 // The dimensions of each item in the app launcher. This calculates the | |
334 // dimensions dynamically, so it should be called after creating the DOM. | |
335 dimensions_: null, | |
336 get dimensions() { | |
337 if (!this.dimensions_) { | |
Aaron Boodman
2011/01/24 01:29:33
Invert check and return early.
| |
338 var app = this.dragContainer.firstChild; | |
339 | |
340 var width = app.offsetWidth; | |
341 var height = app.offsetHeight; | |
342 | |
343 // If the apps haven't properly loaded yet, don't cache the result. | |
344 if (app.offsetWidth == 0 || app.offsetHeight == 0) | |
345 return {width:0, height:0}; | |
346 | |
347 var style = getComputedStyle(app); | |
348 | |
349 var marginWidth = | |
350 cssPxToNum(style.marginLeft) + cssPxToNum(style.marginRight); | |
351 var marginHeight = | |
352 cssPxToNum(style.marginTop) + cssPxToNum(style.marginBottom); | |
353 | |
354 var borderWidth = cssPxToNum(style.borderLeftWidth) + | |
355 cssPxToNum(style.borderRightWidth); | |
356 var borderHeight = cssPxToNum(style.borderTopWidth) + | |
357 cssPxToNum(style.borderBottomWidth); | |
358 | |
359 this.dimensions_ = { | |
360 width: width + marginWidth + borderWidth, | |
361 height: height + marginHeight + borderHeight | |
362 }; | |
363 } | |
364 return this.dimensions_; | |
365 }, | |
366 | |
367 // Gets the item under the mouse event |e|. Returns null if there is no | |
368 // item or if the item is not draggable. | |
369 getItem: function(e) { | |
370 var item = findAncestorByClass(e.target, 'app'); | |
371 | |
372 // You can't drag the web store launcher. | |
373 if (item.classList.contains('web-store-entry')) | |
374 return null; | |
375 | |
376 return item; | |
377 }, | |
378 | |
379 // Returns true if |coordinates| point to a valid drop location. The | |
380 // coordinates are relative to the drag container and the object should | |
381 // have the 'x' and 'y' properties set. | |
382 canDropOn: function(coordinates) { | |
383 var cols = MAX_APPS_PER_ROW[layoutMode]; | |
384 var rows = Math.ceil(this.data.length / cols); | |
385 | |
386 var bottom = rows * this.dimensions.height; | |
387 var right = cols * this.dimensions.width; | |
388 | |
389 if (coordinates.x > right || coordinates.x < 0 || | |
390 coordinates.y > bottom || coordinates.y < 0) | |
391 return false; | |
392 | |
393 var position = this.getIndexAt_(coordinates); | |
394 var appCount = this.data.length; | |
395 | |
396 if (!this.detachWebstoreEntry) | |
397 appCount--; | |
398 | |
399 return position >= 0 && position < appCount; | |
400 }, | |
401 | |
402 setDragPlaceholder: function(coordinates) { | |
403 var position = this.getIndexAt_(coordinates); | |
404 var appId = this.dragItem.querySelector('a').getAttribute('app-id'); | |
405 var current = this.data.indexOf(appId); | |
406 | |
407 if (current == position || current < 0) | |
408 return; | |
409 | |
410 arrayMove(this.data, current, position); | |
411 this.invalidate_(); | |
412 this.layout(); | |
413 }, | |
414 | |
415 getIndexAt_: function(coordinates) { | |
416 var x = coordinates.x; | |
417 var y = coordinates.y; | |
418 | |
419 var w = this.dimensions.width; | |
420 var h = this.dimensions.height; | |
421 | |
422 var availableWidth = this.dragContainer.offsetWidth; | |
423 | |
424 var row = Math.floor(y / h); | |
425 var col = Math.floor(x / w); | |
426 var index = Math.floor(availableWidth / w) * row + col; | |
427 | |
428 return index; | |
429 }, | |
430 | |
431 saveDrag: function() { | |
432 this.invalidate_(); | |
433 this.layout(); | |
434 | |
435 var appIds = this.data.filter(function(id) { | |
436 return id != 'web-store-entry'; | |
437 }); | |
438 | |
439 // Wait until the transitions are complete before notifying the browser. | |
440 // Otherwise, the apps will be re-rendered while still transitioning. | |
441 setTimeout(function() { | |
442 chrome.send('reorderApps', appIds); | |
443 }, this.transitionsDuration + 10); | |
444 }, | |
445 | |
446 layout: function(options) { | |
447 options = options || {}; | |
448 if (!this.dirty_ && options.force != true) | |
449 return; | |
450 | |
451 try { | |
452 var container = this.dragContainer; | |
453 if (options.disableAnimations) | |
454 container.setAttribute('launcher-animations', false); | |
455 var d0 = Date.now(); | |
456 this.layoutImpl_(); | |
457 this.dirty_ = false; | |
458 logEvent('apps.layout: ' + (Date.now() - d0)); | |
459 | |
460 } finally { | |
461 if (options.disableAnimations) { | |
462 // We need to re-enable animations asynchronously, so that the | |
463 // animations are still disabled for this layout update. | |
464 setTimeout(function() { | |
465 container.setAttribute('launcher-animations', true); | |
466 }, 0); | |
467 } | |
468 } | |
469 }, | |
470 | |
471 layoutImpl_: function() { | |
472 var apps = this.data; | |
473 var rects = this.getLayoutRects_(apps.length); | |
474 var appsContent = this.dragContainer; | |
475 | |
476 if (!this.visible) | |
477 return; | |
478 | |
479 for (var i = 0; i < apps.length; i++) { | |
480 var app = appsContent.querySelector('[app-id='+apps[i]+']').parentNode; | |
481 | |
482 // If the node is being dragged, don't try to place it in the grid. | |
483 if (app == this.dragItem) | |
484 continue; | |
485 | |
486 app.style.left = rects[i].left + 'px'; | |
487 app.style.top = rects[i].top + 'px'; | |
488 } | |
489 | |
490 // We need to set the container's height manually because the apps use | |
491 // absolute positioning. | |
492 var rows = Math.ceil(apps.length / MAX_APPS_PER_ROW[layoutMode]); | |
493 appsContent.style.height = (rows * this.dimensions.height) + 'px'; | |
494 }, | |
495 | |
496 getLayoutRects_: function(appCount) { | |
497 var availableWidth = this.dragContainer.offsetWidth; | |
498 var rtl = isRtl(); | |
499 var rects = []; | |
500 var w = this.dimensions.width; | |
501 var h = this.dimensions.height; | |
502 | |
503 for (var i = 0; i < appCount; i++) { | |
504 var row = Math.floor((w * i) / availableWidth); | |
505 var top = row * h; | |
506 var left = (w * i) % availableWidth; | |
507 | |
508 // Reflect the X axis if an RTL language is active. | |
509 if (rtl) | |
510 left = availableWidth - left - w; | |
511 rects[i] = {left: left, top: top, row: row}; | |
512 } | |
513 return rects; | |
514 }, | |
515 | |
271 createElement: function(app) { | 516 createElement: function(app) { |
272 var div = createElement(app); | 517 var div = createElement(app); |
273 var a = div.firstChild; | 518 var a = div.firstChild; |
274 | 519 |
275 a.onclick = handleClick; | 520 a.onclick = handleClick; |
276 a.setAttribute('ping', PING_APP_LAUNCH_PREFIX + '+' + this.showPromo); | 521 a.setAttribute('ping', PING_APP_LAUNCH_PREFIX + '+' + this.showPromo); |
277 a.style.backgroundImage = url(app['icon_big']); | 522 a.style.backgroundImage = url(app['icon_big']); |
278 if (hashParams['app-id'] == app['id']) { | 523 if (hashParams['app-id'] == app['id']) { |
279 div.setAttribute('new', 'new'); | 524 div.setAttribute('new', 'new'); |
280 // Delay changing the attribute a bit to let the page settle down a bit. | 525 // Delay changing the attribute a bit to let the page settle down a bit. |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
337 | 582 |
338 return a; | 583 return a; |
339 }, | 584 }, |
340 | 585 |
341 createWebStoreElement: function() { | 586 createWebStoreElement: function() { |
342 var elm = createElement({ | 587 var elm = createElement({ |
343 'id': 'web-store-entry', | 588 'id': 'web-store-entry', |
344 'name': localStrings.getString('web_store_title'), | 589 'name': localStrings.getString('web_store_title'), |
345 'launch_url': localStrings.getString('web_store_url') | 590 'launch_url': localStrings.getString('web_store_url') |
346 }); | 591 }); |
347 elm.setAttribute('app-id', 'web-store-entry'); | 592 elm.classList.add('web-store-entry'); |
348 return elm; | 593 return elm; |
349 }, | 594 }, |
350 | 595 |
351 createWebStoreMiniElement: function() { | 596 createWebStoreMiniElement: function() { |
352 var span = document.createElement('span'); | 597 var span = document.createElement('span'); |
353 span.appendChild(this.createWebStoreClosedMenuElement()); | 598 span.appendChild(this.createWebStoreClosedMenuElement()); |
354 return span; | 599 return span; |
355 }, | 600 }, |
356 | 601 |
357 createWebStoreClosedMenuElement: function() { | 602 createWebStoreClosedMenuElement: function() { |
358 var a = document.createElement('a'); | 603 var a = document.createElement('a'); |
359 a.textContent = localStrings.getString('web_store_title'); | 604 a.textContent = localStrings.getString('web_store_title'); |
360 a.href = localStrings.getString('web_store_url'); | 605 a.href = localStrings.getString('web_store_url'); |
361 a.style.backgroundImage = url('chrome://theme/IDR_PRODUCT_LOGO_16'); | 606 a.style.backgroundImage = url('chrome://theme/IDR_PRODUCT_LOGO_16'); |
362 a.className = 'item'; | 607 a.className = 'item'; |
363 return a; | 608 return a; |
364 } | 609 } |
365 }; | 610 }; |
366 })(); | 611 })(); |
612 | |
613 // Enable drag and drop reordering of the app launcher. | |
614 var appDragAndDrop = new DragAndDropController(apps); | |
OLD | NEW |