Chromium Code Reviews| Index: chrome/browser/resources/ntp/apps.js |
| diff --git a/chrome/browser/resources/ntp/apps.js b/chrome/browser/resources/ntp/apps.js |
| index e61ebe0ea34a85798781e3f0628bddc5e0675a27..5426b03e4c0a8498f2376a00fdd3330793ed2daf 100644 |
| --- a/chrome/browser/resources/ntp/apps.js |
| +++ b/chrome/browser/resources/ntp/apps.js |
| @@ -45,6 +45,15 @@ function getAppsCallback(data) { |
| return a.app_launch_index - b.app_launch_index; |
| }); |
| + // Determines if the web store link should be detached and place in the |
| + // top right of the screen. |
| + apps.detachWebstoreEntry = |
| + !apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode]; |
| + |
| + apps.data = data.apps; |
| + if (!apps.detachWebstoreEntry) |
| + apps.data.push('web-store-entry'); |
| + |
| clearClosedMenu(apps.menu); |
| data.apps.forEach(function(app) { |
| appsSectionContent.appendChild(apps.createElement(app)); |
| @@ -84,12 +93,15 @@ function getAppsCallback(data) { |
| appsPromoLink.setAttribute('ping', appsPromoPing); |
| maybeDoneLoading(); |
| - if (isDoneLoading()) { |
| - if (!apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode]) |
| - webStoreEntry.classList.add('loner'); |
| - else |
| - webStoreEntry.classList.remove('loner'); |
| + // Disable the animations when the app launcher is being (re)initailized. |
| + apps.layout({disableAnimations:true}); |
| + |
| + if (apps.detachWebstoreEntry) |
| + webStoreEntry.classList.add('loner'); |
| + else |
| + webStoreEntry.classList.remove('loner'); |
| + if (isDoneLoading()) { |
| updateMiniviewClipping(appsMiniview); |
| layoutSections(); |
| } |
| @@ -261,6 +273,17 @@ var apps = (function() { |
| } |
| }); |
| + // Moves the element at position |from| in array |arr| to position |to|. |
| + function arrayMove(arr, from, to) { |
| + var element = arr.splice(from, 1); |
| + arr.splice(to, 0, element[0]); |
| + } |
| + |
| + // Converts a css pixel count ('53px') to the corresponding number (53). |
| + function cssPxToNum(px) { |
|
Aaron Boodman
2011/01/24 01:29:33
You can just use parseInt() instead of defining th
|
| + return Number(px.substring(0, px.length-2)); |
| + } |
| + |
| return { |
| loaded: false, |
| @@ -268,6 +291,228 @@ var apps = (function() { |
| showPromo: false, |
| + detachWebstoreEntry: false, |
| + |
| + // The list of app ids, in order, of each app in the launcher. |
| + data_: null, |
| + get data() { return this.data_; }, |
| + set data(data) { |
| + this.data_ = data.map(function(app) { |
| + return app.id; |
| + }); |
| + this.invalidate_(); |
| + }, |
| + |
| + dirty_: true, |
| + invalidate_: function() { |
| + this.dirty_ = true; |
| + }, |
| + |
| + visible_: true, |
| + get visible() { |
| + return this.visible_; |
| + }, |
| + set visible(visible) { |
| + this.visible_ = visible; |
| + this.invalidate_(); |
| + }, |
| + |
| + // DragAndDropDelegate |
| + |
| + dragContainer: $('apps-content'), |
| + transitionsDuration: 200, |
| + |
| + get dragItem() { return this.dragItem_; }, |
| + set dragItem(dragItem) { |
| + if (this.dragItem_ != dragItem) { |
| + this.dragItem_ = dragItem; |
| + this.invalidate_(); |
| + } |
| + }, |
| + |
| + // The dimensions of each item in the app launcher. This calculates the |
| + // dimensions dynamically, so it should be called after creating the DOM. |
| + dimensions_: null, |
| + get dimensions() { |
| + if (!this.dimensions_) { |
|
Aaron Boodman
2011/01/24 01:29:33
Invert check and return early.
|
| + var app = this.dragContainer.firstChild; |
| + |
| + var width = app.offsetWidth; |
| + var height = app.offsetHeight; |
| + |
| + // If the apps haven't properly loaded yet, don't cache the result. |
| + if (app.offsetWidth == 0 || app.offsetHeight == 0) |
| + return {width:0, height:0}; |
| + |
| + var style = getComputedStyle(app); |
| + |
| + var marginWidth = |
| + cssPxToNum(style.marginLeft) + cssPxToNum(style.marginRight); |
| + var marginHeight = |
| + cssPxToNum(style.marginTop) + cssPxToNum(style.marginBottom); |
| + |
| + var borderWidth = cssPxToNum(style.borderLeftWidth) + |
| + cssPxToNum(style.borderRightWidth); |
| + var borderHeight = cssPxToNum(style.borderTopWidth) + |
| + cssPxToNum(style.borderBottomWidth); |
| + |
| + this.dimensions_ = { |
| + width: width + marginWidth + borderWidth, |
| + height: height + marginHeight + borderHeight |
| + }; |
| + } |
| + return this.dimensions_; |
| + }, |
| + |
| + // Gets the item under the mouse event |e|. Returns null if there is no |
| + // item or if the item is not draggable. |
| + getItem: function(e) { |
| + var item = findAncestorByClass(e.target, 'app'); |
| + |
| + // You can't drag the web store launcher. |
| + if (item.classList.contains('web-store-entry')) |
| + return null; |
| + |
| + return item; |
| + }, |
| + |
| + // Returns true if |coordinates| point to a valid drop location. The |
| + // coordinates are relative to the drag container and the object should |
| + // have the 'x' and 'y' properties set. |
| + canDropOn: function(coordinates) { |
| + var cols = MAX_APPS_PER_ROW[layoutMode]; |
| + var rows = Math.ceil(this.data.length / cols); |
| + |
| + var bottom = rows * this.dimensions.height; |
| + var right = cols * this.dimensions.width; |
| + |
| + if (coordinates.x > right || coordinates.x < 0 || |
| + coordinates.y > bottom || coordinates.y < 0) |
| + return false; |
| + |
| + var position = this.getIndexAt_(coordinates); |
| + var appCount = this.data.length; |
| + |
| + if (!this.detachWebstoreEntry) |
| + appCount--; |
| + |
| + return position >= 0 && position < appCount; |
| + }, |
| + |
| + setDragPlaceholder: function(coordinates) { |
| + var position = this.getIndexAt_(coordinates); |
| + var appId = this.dragItem.querySelector('a').getAttribute('app-id'); |
| + var current = this.data.indexOf(appId); |
| + |
| + if (current == position || current < 0) |
| + return; |
| + |
| + arrayMove(this.data, current, position); |
| + this.invalidate_(); |
| + this.layout(); |
| + }, |
| + |
| + getIndexAt_: function(coordinates) { |
| + var x = coordinates.x; |
| + var y = coordinates.y; |
| + |
| + var w = this.dimensions.width; |
| + var h = this.dimensions.height; |
| + |
| + var availableWidth = this.dragContainer.offsetWidth; |
| + |
| + var row = Math.floor(y / h); |
| + var col = Math.floor(x / w); |
| + var index = Math.floor(availableWidth / w) * row + col; |
| + |
| + return index; |
| + }, |
| + |
| + saveDrag: function() { |
| + this.invalidate_(); |
| + this.layout(); |
| + |
| + var appIds = this.data.filter(function(id) { |
| + return id != 'web-store-entry'; |
| + }); |
| + |
| + // Wait until the transitions are complete before notifying the browser. |
| + // Otherwise, the apps will be re-rendered while still transitioning. |
| + setTimeout(function() { |
| + chrome.send('reorderApps', appIds); |
| + }, this.transitionsDuration + 10); |
| + }, |
| + |
| + layout: function(options) { |
| + options = options || {}; |
| + if (!this.dirty_ && options.force != true) |
| + return; |
| + |
| + try { |
| + var container = this.dragContainer; |
| + if (options.disableAnimations) |
| + container.setAttribute('launcher-animations', false); |
| + var d0 = Date.now(); |
| + this.layoutImpl_(); |
| + this.dirty_ = false; |
| + logEvent('apps.layout: ' + (Date.now() - d0)); |
| + |
| + } finally { |
| + if (options.disableAnimations) { |
| + // We need to re-enable animations asynchronously, so that the |
| + // animations are still disabled for this layout update. |
| + setTimeout(function() { |
| + container.setAttribute('launcher-animations', true); |
| + }, 0); |
| + } |
| + } |
| + }, |
| + |
| + layoutImpl_: function() { |
| + var apps = this.data; |
| + var rects = this.getLayoutRects_(apps.length); |
| + var appsContent = this.dragContainer; |
| + |
| + if (!this.visible) |
| + return; |
| + |
| + for (var i = 0; i < apps.length; i++) { |
| + var app = appsContent.querySelector('[app-id='+apps[i]+']').parentNode; |
| + |
| + // If the node is being dragged, don't try to place it in the grid. |
| + if (app == this.dragItem) |
| + continue; |
| + |
| + app.style.left = rects[i].left + 'px'; |
| + app.style.top = rects[i].top + 'px'; |
| + } |
| + |
| + // We need to set the container's height manually because the apps use |
| + // absolute positioning. |
| + var rows = Math.ceil(apps.length / MAX_APPS_PER_ROW[layoutMode]); |
| + appsContent.style.height = (rows * this.dimensions.height) + 'px'; |
| + }, |
| + |
| + getLayoutRects_: function(appCount) { |
| + var availableWidth = this.dragContainer.offsetWidth; |
| + var rtl = isRtl(); |
| + var rects = []; |
| + var w = this.dimensions.width; |
| + var h = this.dimensions.height; |
| + |
| + for (var i = 0; i < appCount; i++) { |
| + var row = Math.floor((w * i) / availableWidth); |
| + var top = row * h; |
| + var left = (w * i) % availableWidth; |
| + |
| + // Reflect the X axis if an RTL language is active. |
| + if (rtl) |
| + left = availableWidth - left - w; |
| + rects[i] = {left: left, top: top, row: row}; |
| + } |
| + return rects; |
| + }, |
| + |
| createElement: function(app) { |
| var div = createElement(app); |
| var a = div.firstChild; |
| @@ -344,7 +589,7 @@ var apps = (function() { |
| 'name': localStrings.getString('web_store_title'), |
| 'launch_url': localStrings.getString('web_store_url') |
| }); |
| - elm.setAttribute('app-id', 'web-store-entry'); |
| + elm.classList.add('web-store-entry'); |
| return elm; |
| }, |
| @@ -364,3 +609,6 @@ var apps = (function() { |
| } |
| }; |
| })(); |
| + |
| +// Enable drag and drop reordering of the app launcher. |
| +var appDragAndDrop = new DragAndDropController(apps); |