Index: chrome/browser/resources/ntp_search/tile_page.js |
diff --git a/chrome/browser/resources/ntp_search/tile_page.js b/chrome/browser/resources/ntp_search/tile_page.js |
index 924600e3bf2a45a23914cfbc594aec9e53e85cb3..7499dc22747055c40115976377474c501bb31793 100644 |
--- a/chrome/browser/resources/ntp_search/tile_page.js |
+++ b/chrome/browser/resources/ntp_search/tile_page.js |
@@ -5,291 +5,138 @@ |
cr.define('ntp', function() { |
'use strict'; |
- // We can't pass the currently dragging tile via dataTransfer because of |
- // http://crbug.com/31037 |
- var currentlyDraggingTile = null; |
- function getCurrentlyDraggingTile() { |
- return currentlyDraggingTile; |
- } |
- function setCurrentlyDraggingTile(tile) { |
- currentlyDraggingTile = tile; |
- if (tile) |
- ntp.enterRearrangeMode(); |
- else |
- ntp.leaveRearrangeMode(); |
- } |
+ //---------------------------------------------------------------------------- |
+ // Tile |
+ //---------------------------------------------------------------------------- |
/** |
- * Changes the current dropEffect of a drag. This modifies the native cursor |
- * and serves as an indicator of what we should do at the end of the drag as |
- * well as give indication to the user if a drop would succeed if they let go. |
- * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event. |
- * @param {string} effect A drop effect to change to (i.e. copy, move, none). |
+ * A virtual Tile class. Each TilePage subclass should have its own Tile |
+ * subclass implemented too (e.g. MostVisitedPage contains MostVisited |
+ * tiles, and MostVisited is a Tile subclass). |
+ * @constructor |
+ * @param {Object} config TilePage configuration object. |
*/ |
- function setCurrentDropEffect(dataTransfer, effect) { |
- dataTransfer.dropEffect = effect; |
- if (currentlyDraggingTile) |
- currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect; |
+ function Tile(config) { |
+ console.error('Tile is a virtual class and is not supposed to be ' + |
+ 'instantiated'); |
} |
/** |
- * Creates a new Tile object. Tiles wrap content on a TilePage, providing |
- * some styling and drag functionality. |
- * @constructor |
- * @extends {HTMLDivElement} |
+ * Creates a Tile subclass. We need to use this function to create a Tile |
+ * subclass because a Tile must also subclass a HTMLElement (which can be |
+ * any HTMLElement), so we need to individually add methods and getters here. |
+ * @param {Object} Subclass The prototype object of the class we want to be |
+ * a Tile subclass. |
+ * @param {Object} The extended Subclass object. |
*/ |
- function Tile(contents) { |
- var tile = cr.doc.createElement('div'); |
- tile.__proto__ = Tile.prototype; |
- tile.initialize(contents); |
- |
- return tile; |
- } |
+ Tile.subclass = function(Subclass) { |
+ var Base = Tile.prototype; |
+ for (var name in Base) { |
+ if (!Subclass.hasOwnProperty(name)) |
+ Subclass[name] = Base[name]; |
+ } |
+ for (var name in TileGetters) { |
+ if (!Subclass.hasOwnProperty(name)) |
+ Subclass.__defineGetter__(name, TileGetters[name]); |
+ } |
+ return Subclass; |
+ }; |
Tile.prototype = { |
- __proto__: HTMLDivElement.prototype, |
- |
- initialize: function(contents) { |
- // 'real' as opposed to doppleganger. |
- this.className = 'tile real'; |
- this.appendChild(contents); |
- contents.tile = this; |
- |
- this.addEventListener('dragstart', this.onDragStart_); |
- this.addEventListener('drag', this.onDragMove_); |
- this.addEventListener('dragend', this.onDragEnd_); |
- |
- this.firstChild.addEventListener( |
- 'webkitAnimationEnd', this.onContentsAnimationEnd_.bind(this)); |
- |
- this.eventTracker = new EventTracker(); |
- }, |
- |
- get index() { |
- return Array.prototype.indexOf.call(this.tilePage.tileElements_, this); |
- }, |
- |
- get tilePage() { |
- return findAncestorByClass(this, 'tile-page'); |
- }, |
- |
- /** |
- * Position the tile at |x, y|, and store this as the grid location, i.e. |
- * where the tile 'belongs' when it's not being dragged. |
- * @param {number} x The x coordinate, in pixels. |
- * @param {number} y The y coordinate, in pixels. |
- */ |
- setGridPosition: function(x, y) { |
- this.gridX = x; |
- this.gridY = y; |
- this.moveTo(x, y); |
- }, |
- |
/** |
- * Position the tile at |x, y|. |
- * @param {number} x The x coordinate, in pixels. |
- * @param {number} y The y coordinate, in pixels. |
+ * Initializes a Tile. |
+ * @param {Object} config TilePage configuration object. |
*/ |
- moveTo: function(x, y) { |
- // left overrides right in LTR, and right takes precedence in RTL. |
- this.style.left = toCssPx(x); |
- this.style.right = toCssPx(x); |
- this.style.top = toCssPx(y); |
- }, |
- |
- /** |
- * The handler for dragstart events fired on |this|. |
- * @param {Event} e The event for the drag. |
- * @private |
- */ |
- onDragStart_: function(e) { |
- // The user may start dragging again during a previous drag's finishing |
- // animation. |
- if (this.classList.contains('dragging')) |
- this.finalizeDrag_(); |
- |
- setCurrentlyDraggingTile(this); |
- |
- e.dataTransfer.effectAllowed = 'copyMove'; |
- this.firstChild.setDragData(e.dataTransfer); |
- |
- // The drag clone is the node we use as a representation during the drag. |
- // It's attached to the top level document element so that it floats above |
- // image masks. |
- this.dragClone = this.cloneNode(true); |
- this.dragClone.style.right = ''; |
- this.dragClone.classList.add('drag-representation'); |
- $('card-slider-frame').appendChild(this.dragClone); |
- this.eventTracker.add(this.dragClone, 'webkitTransitionEnd', |
- this.onDragCloneTransitionEnd_.bind(this)); |
- |
- this.classList.add('dragging'); |
- // offsetLeft is mirrored in RTL. Un-mirror it. |
- var offsetLeft = isRTL() ? |
- this.parentNode.clientWidth - this.offsetLeft : |
- this.offsetLeft; |
- this.dragOffsetX = e.x - offsetLeft - this.parentNode.offsetLeft; |
- this.dragOffsetY = e.y - this.offsetTop - |
- // Unlike offsetTop, this value takes scroll position into account. |
- this.parentNode.getBoundingClientRect().top; |
- |
- this.onDragMove_(e); |
- }, |
+ initialize: function(config) { |
+ this.className = 'tile'; |
+ } |
+ }; |
+ var TileGetters = { |
/** |
- * The handler for drag events fired on |this|. |
- * @param {Event} e The event for the drag. |
- * @private |
+ * The TileCell associated to this Tile. |
+ * @type {TileCell} |
*/ |
- onDragMove_: function(e) { |
- if (e.view != window || (e.x == 0 && e.y == 0)) { |
- this.dragClone.hidden = true; |
- return; |
- } |
- |
- this.dragClone.hidden = false; |
- this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX); |
- this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY); |
+ 'tileCell': function() { |
+ return findAncestorByClass(this, 'tile-cell'); |
}, |
/** |
- * The handler for dragend events fired on |this|. |
- * @param {Event} e The event for the drag. |
- * @private |
+ * The index of the Tile. |
+ * @type {number} |
*/ |
- onDragEnd_: function(e) { |
- this.dragClone.hidden = false; |
- this.dragClone.classList.add('placing'); |
- |
- setCurrentlyDraggingTile(null); |
- |
- // tilePage will be null if we've already been removed. |
- var tilePage = this.tilePage; |
- if (tilePage) |
- tilePage.positionTile_(this.index); |
- |
- // Take an appropriate action with the drag clone. |
- if (this.landedOnTrash) { |
- this.dragClone.classList.add('deleting'); |
- } else if (tilePage) { |
- // TODO(dbeam): Until we fix dropEffect to the correct behavior it will |
- // differ on windows - crbug.com/39399. That's why we use the custom |
- // this.lastDropEffect instead of e.dataTransfer.dropEffect. |
- if (tilePage.selected && this.lastDropEffect != 'copy') { |
- // The drag clone can still be hidden from the last drag move event. |
- this.dragClone.hidden = false; |
- // The tile's contents may have moved following the respositioning; |
- // adjust for that. |
- var contentDiffX = this.dragClone.firstChild.offsetLeft - |
- this.firstChild.offsetLeft; |
- var contentDiffY = this.dragClone.firstChild.offsetTop - |
- this.firstChild.offsetTop; |
- this.dragClone.style.left = |
- toCssPx(this.gridX + this.parentNode.offsetLeft - |
- contentDiffX); |
- this.dragClone.style.top = |
- toCssPx(this.gridY + |
- this.parentNode.getBoundingClientRect().top - |
- contentDiffY); |
- } else if (this.dragClone.hidden) { |
- this.finalizeDrag_(); |
- } else { |
- // The CSS3 transitions spec intentionally leaves it up to individual |
- // user agents to determine when styles should be applied. On some |
- // platforms (at the moment, Windows), when you apply both classes |
- // immediately a transition may not occur correctly. That's why we're |
- // using a setTimeout here to queue adding the class until the |
- // previous class (currently: .placing) sets up a transition. |
- // http://dev.w3.org/csswg/css3-transitions/#starting |
- window.setTimeout(function() { |
- if (this.dragClone) |
- this.dragClone.classList.add('dropped-on-other-page'); |
- }.bind(this), 0); |
- } |
- } |
- |
- delete this.lastDropEffect; |
- this.landedOnTrash = false; |
- }, |
+ 'index': function() { |
+ assert(this.tileCell); |
+ return this.tileCell.index; |
+ } |
+ }; |
- /** |
- * Creates a clone of this node offset by the coordinates. Used for the |
- * dragging effect where a tile appears to float off one side of the grid |
- * and re-appear on the other. |
- * @param {number} x x-axis offset, in pixels. |
- * @param {number} y y-axis offset, in pixels. |
- */ |
- showDoppleganger: function(x, y) { |
- // We always have to clear the previous doppleganger to make sure we get |
- // style updates for the contents of this tile. |
- this.clearDoppleganger(); |
- |
- var clone = this.cloneNode(true); |
- clone.classList.remove('real'); |
- clone.classList.add('doppleganger'); |
- var clonelets = clone.querySelectorAll('.real'); |
- for (var i = 0; i < clonelets.length; i++) { |
- clonelets[i].classList.remove('real'); |
- } |
+ //---------------------------------------------------------------------------- |
+ // TileCell |
+ //---------------------------------------------------------------------------- |
- this.appendChild(clone); |
- this.doppleganger_ = clone; |
+ /** |
+ * Creates a new TileCell object. A TileCell represents a cell in the |
+ * TilePage's grid. A TilePage uses TileCells to position Tiles in the proper |
+ * place and to animate them individually. Each TileCell is associated to |
+ * one Tile at a time (or none if it is a filler object), and that association |
+ * might change when the grid is resized. When that happens, the grid is |
+ * updated and the Tiles are moved to the proper TileCell. We cannot move the |
+ * the TileCell itself during the resize because this transition is animated |
+ * with CSS and there's no way to stop CSS animations, and we really want to |
+ * animate with CSS to take advantage of hardware acceleration. |
+ * @constructor |
+ * @extends {HTMLDivElement} |
+ * @param {HTMLElement} tile Tile element that will be associated to the cell. |
+ * @param {Object} config TilePage configuration object. |
+ */ |
+ function TileCell(tile, config) { |
+ var tileCell = cr.doc.createElement('div'); |
+ tileCell.__proto__ = TileCell.prototype; |
+ tileCell.initialize(tile, config); |
- if (isRTL()) |
- x *= -1; |
+ return tileCell; |
+ } |
- this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' + |
- y + 'px)'; |
- }, |
+ TileCell.prototype = { |
+ __proto__: HTMLDivElement.prototype, |
/** |
- * Destroys the current doppleganger. |
+ * Initializes a TileCell. |
+ * @param {Tile} tile The Tile that will be assigned to this TileCell. |
+ * @param {Object} config TilePage configuration object. |
*/ |
- clearDoppleganger: function() { |
- if (this.doppleganger_) { |
- this.removeChild(this.doppleganger_); |
- this.doppleganger_ = null; |
- } |
+ initialize: function(tile, config) { |
+ this.className = 'tile-cell'; |
+ this.assign_(tile); |
}, |
/** |
- * Returns status of doppleganger. |
- * @return {boolean} True if there is a doppleganger showing for |this|. |
+ * The index of the TileCell. |
+ * @type {number} |
*/ |
- hasDoppleganger: function() { |
- return !!this.doppleganger_; |
+ get index() { |
+ return Array.prototype.indexOf.call(this.tilePage.tiles_, |
+ this.firstChild); |
}, |
/** |
- * Cleans up after the drag is over. This is either called when the |
- * drag representation finishes animating to the final position, or when |
- * the next drag starts (if the user starts a 2nd drag very quickly). |
- * @private |
+ * The TilePage associated to this TileCell. |
+ * @type {TilePage} |
*/ |
- finalizeDrag_: function() { |
- assert(this.classList.contains('dragging')); |
- |
- var clone = this.dragClone; |
- this.dragClone = null; |
- |
- clone.parentNode.removeChild(clone); |
- this.eventTracker.remove(clone, 'webkitTransitionEnd'); |
- this.classList.remove('dragging'); |
- if (this.firstChild.finalizeDrag) |
- this.firstChild.finalizeDrag(); |
+ get tilePage() { |
+ return findAncestorByClass(this, 'tile-page'); |
}, |
/** |
- * Called when the drag representation node is done migrating to its final |
- * resting spot. |
- * @param {Event} e The transition end event. |
+ * Assigns a Tile to the this TileCell. |
+ * @type {TilePage} |
*/ |
- onDragCloneTransitionEnd_: function(e) { |
- if (this.classList.contains('dragging') && |
- (e.propertyName == 'left' || e.propertyName == 'top' || |
- e.propertyName == '-webkit-transform')) { |
- this.finalizeDrag_(); |
- } |
+ assign_: function(tile) { |
+ if (this.firstChild) |
+ this.replaceChild(tile, this.firstChild); |
+ else |
+ this.appendChild(tile); |
}, |
/** |
@@ -301,206 +148,103 @@ cr.define('ntp', function() { |
this.firstChild.classList.add('removing-tile-contents'); |
else |
this.tilePage.removeTile(this, false); |
- }, |
- |
- /** |
- * Callback for the webkitAnimationEnd event on the tile's contents. |
- * @param {Event} e The event object. |
- */ |
- onContentsAnimationEnd_: function(e) { |
- if (this.firstChild.classList.contains('new-tile-contents')) |
- this.firstChild.classList.remove('new-tile-contents'); |
- if (this.firstChild.classList.contains('removing-tile-contents')) |
- this.tilePage.removeTile(this, true); |
- }, |
+ } |
}; |
- /** |
- * Gives the proportion of the row width that is devoted to a single icon. |
- * @param {number} rowTileCount The number of tiles in a row. |
- * @param {number} tileSpacingFraction The proportion of the tile width which |
- * will be used as spacing between tiles. |
- * @return {number} The ratio between icon width and row width. |
- */ |
- function tileWidthFraction(rowTileCount, tileSpacingFraction) { |
- return rowTileCount + (rowTileCount - 1) * tileSpacingFraction; |
- } |
- |
- /** |
- * Calculates an assortment of tile-related values for a grid with the |
- * given dimensions. |
- * @param {number} width The pixel width of the grid. |
- * @param {number} numRowTiles The number of tiles in a row. |
- * @param {number} tileSpacingFraction The proportion of the tile width which |
- * will be used as spacing between tiles. |
- * @return {Object} A mapping of pixel values. |
- */ |
- function tileValuesForGrid(width, numRowTiles, tileSpacingFraction) { |
- var tileWidth = width / tileWidthFraction(numRowTiles, tileSpacingFraction); |
- var offsetX = tileWidth * (1 + tileSpacingFraction); |
- var interTileSpacing = offsetX - tileWidth; |
- |
- return { |
- tileWidth: tileWidth, |
- offsetX: offsetX, |
- interTileSpacing: interTileSpacing, |
- }; |
- } |
- |
- // The smallest amount of horizontal blank space to display on the sides when |
- // displaying a wide arrangement. There is an additional 26px of margin from |
- // the tile page padding. |
- var MIN_WIDE_MARGIN = 18; |
+ //---------------------------------------------------------------------------- |
+ // TilePage |
+ //---------------------------------------------------------------------------- |
/** |
* Creates a new TilePage object. This object contains tiles and controls |
* their layout. |
- * @param {Object} gridValues Pixel values that define the size and layout |
- * of the tile grid. |
* @constructor |
* @extends {HTMLDivElement} |
*/ |
- function TilePage(gridValues) { |
+ function TilePage() { |
var el = cr.doc.createElement('div'); |
- el.gridValues_ = gridValues; |
el.__proto__ = TilePage.prototype; |
el.initialize(); |
return el; |
} |
- /** |
- * Takes a collection of grid layout pixel values and updates them with |
- * additional tiling values that are calculated from TilePage constants. |
- * @param {Object} grid The grid layout pixel values to update. |
- */ |
- TilePage.initGridValues = function(grid) { |
- // The amount of space we need to display a narrow grid (all narrow grids |
- // are this size). |
- grid.narrowWidth = |
- grid.minTileWidth * tileWidthFraction(grid.minColCount, |
- grid.tileSpacingFraction); |
- // The minimum amount of space we need to display a wide grid. |
- grid.minWideWidth = |
- grid.minTileWidth * tileWidthFraction(grid.maxColCount, |
- grid.tileSpacingFraction); |
- // The largest we will ever display a wide grid. |
- grid.maxWideWidth = |
- grid.maxTileWidth * tileWidthFraction(grid.maxColCount, |
- grid.tileSpacingFraction); |
- // Tile-related pixel values for the narrow display. |
- grid.narrowTileValues = tileValuesForGrid(grid.narrowWidth, |
- grid.minColCount, |
- grid.tileSpacingFraction); |
- // Tile-related pixel values for the minimum narrow display. |
- grid.wideTileValues = tileValuesForGrid(grid.minWideWidth, |
- grid.maxColCount, |
- grid.tileSpacingFraction); |
- }; |
- |
TilePage.prototype = { |
__proto__: HTMLDivElement.prototype, |
+ // The config object should be defined by each TilePage subclass. |
+ config_: { |
+ // The width of a cell. |
+ cellWidth: 0, |
+ // The start margin of a cell (left or right according to text direction). |
+ cellMarginStart: 0, |
+ // The border panel horizontal margin. |
+ bottomPanelHorizontalMargin: 0, |
+ // The height of the tile row. |
+ rowHeight: 0, |
+ // The maximum number of Tiles to be displayed. |
+ maxTileCount: 0 |
+ }, |
+ |
+ /** |
+ * Initializes a TilePage. |
+ */ |
initialize: function() { |
this.className = 'tile-page'; |
- // Div that acts as a custom scrollbar. The scrollbar has to live |
- // outside the content div so it doesn't flicker when scrolling (due to |
- // repainting after the scroll, then repainting again when moved in the |
- // onScroll handler). |scrollbar_| is only aesthetic, and it only |
- // represents the thumb. Actual events are still handled by the invisible |
- // native scrollbars. This div gives us more flexibility with the visuals. |
- this.scrollbar_ = this.ownerDocument.createElement('div'); |
- this.scrollbar_.className = 'tile-page-scrollbar'; |
- this.scrollbar_.hidden = true; |
- this.appendChild(this.scrollbar_); |
- |
- // This contains everything but the scrollbar. |
+ // The content defines the actual space a page has to display tiles. |
this.content_ = this.ownerDocument.createElement('div'); |
this.content_.className = 'tile-page-content'; |
this.appendChild(this.content_); |
- // Div that sets the vertical position of the tile grid. |
- this.topMargin_ = this.ownerDocument.createElement('div'); |
- this.topMargin_.className = 'top-margin'; |
- this.content_.appendChild(this.topMargin_); |
- |
- // Div that holds the tiles. |
+ // The div that defines the tile grid viewport. |
this.tileGrid_ = this.ownerDocument.createElement('div'); |
this.tileGrid_.className = 'tile-grid'; |
- this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px'; |
this.content_.appendChild(this.tileGrid_); |
- // Ordered list of our tiles. |
- this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real'); |
- // Ordered list of the elements which want to accept keyboard focus. These |
- // elements will not be a part of the normal tab order; the tile grid |
- // initially gets focused and then these elements can be focused via the |
- // arrow keys. |
- this.focusableElements_ = |
- this.tileGrid_.getElementsByClassName('focusable'); |
+ // The tile grid contents, which can be scrolled. |
+ this.tileGridContent_ = this.ownerDocument.createElement('div'); |
+ this.tileGridContent_.className = 'tile-grid-content'; |
+ this.tileGrid_.appendChild(this.tileGridContent_); |
- // These are properties used in updateTopMargin. |
- this.animatedTopMarginPx_ = 0; |
- this.topMarginPx_ = 0; |
+ // The list of Tile elements which is used to fill the TileGrid cells. |
+ this.tiles_ = []; |
+ |
+ // Event handlers. |
+ this.tileGrid_.addEventListener('webkitTransitionEnd', |
+ this.onTileGridAnimationEnd_.bind(this)); |
this.eventTracker = new EventTracker(); |
this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); |
- |
- this.addEventListener('DOMNodeInsertedIntoDocument', |
- this.onNodeInsertedIntoDocument_); |
- |
- this.content_.addEventListener('scroll', this.onScroll_.bind(this)); |
- |
- this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this); |
+ this.eventTracker.add(window, 'keyup', this.onKeyUp_.bind(this)); |
this.addEventListener('cardselected', this.handleCardSelection_); |
this.addEventListener('carddeselected', this.handleCardDeselection_); |
- this.addEventListener('focus', this.handleFocus_); |
- this.addEventListener('keydown', this.handleKeyDown_); |
- this.addEventListener('mousedown', this.handleMouseDown_); |
- |
- this.focusElementIndex_ = -1; |
- }, |
- |
- get tiles() { |
- return this.tileElements_; |
- }, |
- |
- get tileCount() { |
- return this.tileElements_.length; |
- }, |
- |
- get selected() { |
- return Array.prototype.indexOf.call(this.parentNode.children, this) == |
- ntp.getCardSlider().currentCard; |
}, |
/** |
- * The size of the margin (unused space) on the sides of the tile grid, in |
- * pixels. |
- * @type {number} |
+ * The list of Tile elements. |
+ * @type {Array<Tile>} |
*/ |
- get sideMargin() { |
- return this.layoutValues_.leftMargin; |
+ get tiles() { |
+ return this.tiles_; |
}, |
/** |
- * Returns the width of the scrollbar, in pixels, if it is active, or 0 |
- * otherwise. |
+ * The number of Tiles in this TilePage. |
* @type {number} |
*/ |
- get scrollbarWidth() { |
- return this.scrollbar_.hidden ? 0 : 13; |
+ get tileCount() { |
+ return this.tiles_.length; |
}, |
/** |
- * Returns any extra padding to insert to the bottom of a tile page. By |
- * default there is none, but subclasses can override. |
- * @type {number} |
+ * Whether or not this TilePage is selected. |
+ * @type {boolean} |
*/ |
- get extraBottomPadding() { |
- return 0; |
+ get selected() { |
+ return Array.prototype.indexOf.call(this.parentNode.children, this) == |
+ ntp.getCardSlider().currentCard; |
}, |
/** |
@@ -508,8 +252,8 @@ cr.define('ntp', function() { |
* @type {!HTMLElement} |
*/ |
get notification() { |
- return this.topMargin_.nextElementSibling.id == 'notification-container' ? |
- this.topMargin_.nextElementSibling : null; |
+ return this.content_.firstChild.id == 'notification-container' ? |
+ this.content_.firstChild : null; |
}, |
/** |
* The notification content of this tile (if any, otherwise null). |
@@ -518,23 +262,11 @@ cr.define('ntp', function() { |
set notification(node) { |
assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!'); |
// NOTE: Implicitly removes from DOM if |node| is inside it. |
- this.content_.insertBefore(node, this.topMargin_.nextElementSibling); |
+ this.content_.insertBefore(node, this.content_.firstChild); |
this.positionNotification_(); |
}, |
/** |
- * Fetches the size, in pixels, of the padding-top of the tile contents. |
- * @type {number} |
- */ |
- get contentPadding() { |
- if (typeof this.contentPadding_ == 'undefined') { |
- this.contentPadding_ = |
- parseInt(getComputedStyle(this.content_).paddingTop, 10); |
- } |
- return this.contentPadding_; |
- }, |
- |
- /** |
* Removes the tilePage from the DOM and cleans up event handlers. |
*/ |
remove: function() { |
@@ -558,47 +290,9 @@ cr.define('ntp', function() { |
}, |
/** |
- * Appends a tile to the end of the tile grid. |
- * @param {HTMLElement} tileElement The contents of the tile. |
- * @param {boolean} animate If true, the append will be animated. |
- * @protected |
- */ |
- appendTile: function(tileElement, animate) { |
- this.addTileAt(tileElement, this.tileElements_.length, animate); |
- }, |
- |
- /** |
- * Adds the given element to the tile grid. |
- * @param {Node} tileElement The tile object/node to insert. |
- * @param {number} index The location in the tile grid to insert it at. |
- * @param {boolean} animate If true, the tile in question will be |
- * animated (other tiles, if they must reposition, do not animate). |
- * @protected |
- */ |
- addTileAt: function(tileElement, index, animate) { |
- this.classList.remove('animating-tile-page'); |
- if (animate) |
- tileElement.classList.add('new-tile-contents'); |
- |
- // Make sure the index is positive and either in the the bounds of |
- // this.tileElements_ or at the end (meaning append). |
- assert(index >= 0 && index <= this.tileElements_.length); |
- |
- var wrapperDiv = new Tile(tileElement); |
- // If is out of the bounds of the tile element list, .insertBefore() will |
- // act just like appendChild(). |
- this.tileGrid_.insertBefore(wrapperDiv, this.tileElements_[index]); |
- this.calculateLayoutValues_(); |
- this.heightChanged_(); |
- |
- this.repositionTiles_(); |
- this.fireAddedEvent(wrapperDiv, index, animate); |
- }, |
- |
- /** |
* Notify interested subscribers that a tile has been removed from this |
- * page. |
- * @param {Tile} tile The newly added tile. |
+ * page. TODO(pedrosimonetti): Do we really need to fire this event? |
+ * @param {TileCell} tile The newly added tile. |
* @param {number} index The index of the tile that was added. |
* @param {boolean} wasAnimated Whether the removal was animated. |
*/ |
@@ -632,7 +326,7 @@ cr.define('ntp', function() { |
/** |
* Notify interested subscribers that a tile has been removed from this |
* page. |
- * @param {Tile} tile The tile that was removed. |
+ * @param {TileCell} tile The tile that was removed. |
* @param {number} oldIndex Where the tile was positioned before removal. |
* @param {boolean} wasAnimated Whether the removal was animated. |
*/ |
@@ -649,6 +343,7 @@ cr.define('ntp', function() { |
* Removes all tiles from the page. |
*/ |
removeAllTiles: function() { |
+ // TODO(pedrosimonetti): Dispatch individual tearDown functions. |
this.tileGrid_.innerHTML = ''; |
}, |
@@ -658,8 +353,6 @@ cr.define('ntp', function() { |
* @private |
*/ |
handleCardSelection_: function(e) { |
- this.tabIndex = 1; |
- |
// When we are selected, we re-calculate the layout values. (See comment |
// in doDrop.) |
this.calculateLayoutValues_(); |
@@ -671,122 +364,6 @@ cr.define('ntp', function() { |
* @private |
*/ |
handleCardDeselection_: function(e) { |
- this.tabIndex = -1; |
- if (this.currentFocusElement_) |
- this.currentFocusElement_.tabIndex = -1; |
- }, |
- |
- /** |
- * When we get focus, pass it on to the focus element. |
- * @param {Event} e The focus event. |
- * @private |
- */ |
- handleFocus_: function(e) { |
- if (this.focusableElements_.length == 0) |
- return; |
- |
- this.updateFocusElement_(); |
- }, |
- |
- /** |
- * Since we are doing custom focus handling, we have to manually |
- * set focusability on click (as well as keyboard nav above). |
- * @param {Event} e The focus event. |
- * @private |
- */ |
- handleMouseDown_: function(e) { |
- var focusable = findAncestorByClass(e.target, 'focusable'); |
- if (focusable) { |
- this.focusElementIndex_ = |
- Array.prototype.indexOf.call(this.focusableElements_, |
- focusable); |
- this.updateFocusElement_(); |
- } else { |
- // This prevents the tile page from getting focus when the user clicks |
- // inside the grid but outside of any tile. |
- e.preventDefault(); |
- } |
- }, |
- |
- /** |
- * Handle arrow key focus nav. |
- * @param {Event} e The focus event. |
- * @private |
- */ |
- handleKeyDown_: function(e) { |
- // We only handle up, down, left, right without control keys. |
- if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) |
- return; |
- |
- // Wrap the given index to |this.focusableElements_|. |
- var wrap = function(idx) { |
- return (idx + this.focusableElements_.length) % |
- this.focusableElements_.length; |
- }.bind(this); |
- |
- switch (e.keyIdentifier) { |
- case 'Right': |
- case 'Left': |
- var direction = e.keyIdentifier == 'Right' ? 1 : -1; |
- this.focusElementIndex_ = wrap(this.focusElementIndex_ + direction); |
- break; |
- case 'Up': |
- case 'Down': |
- // Look through all focusable elements. Find the first one that is |
- // in the same column. |
- var direction = e.keyIdentifier == 'Up' ? -1 : 1; |
- var currentIndex = |
- Array.prototype.indexOf.call(this.focusableElements_, |
- this.currentFocusElement_); |
- var newFocusIdx = wrap(currentIndex + direction); |
- var tile = this.currentFocusElement_.parentNode; |
- for (;; newFocusIdx = wrap(newFocusIdx + direction)) { |
- var newTile = this.focusableElements_[newFocusIdx].parentNode; |
- var rowTiles = this.layoutValues_.numRowTiles; |
- if ((newTile.index - tile.index) % rowTiles == 0) |
- break; |
- } |
- |
- this.focusElementIndex_ = newFocusIdx; |
- break; |
- |
- default: |
- return; |
- } |
- |
- this.updateFocusElement_(); |
- |
- e.preventDefault(); |
- e.stopPropagation(); |
- }, |
- |
- /** |
- * Focuses the element for |this.focusElementIndex_|. Makes the current |
- * focus element, if any, no longer eligible for focus. |
- * @private |
- */ |
- updateFocusElement_: function() { |
- this.focusElementIndex_ = Math.min(this.focusableElements_.length - 1, |
- this.focusElementIndex_); |
- this.focusElementIndex_ = Math.max(0, this.focusElementIndex_); |
- |
- var newFocusElement = this.focusableElements_[this.focusElementIndex_]; |
- var lastFocusElement = this.currentFocusElement_; |
- if (lastFocusElement && lastFocusElement != newFocusElement) |
- lastFocusElement.tabIndex = -1; |
- |
- newFocusElement.tabIndex = 1; |
- newFocusElement.focus(); |
- this.tabIndex = -1; |
- }, |
- |
- /** |
- * The current focus element is that element which is eligible for focus. |
- * @type {HTMLElement} The node. |
- * @private |
- */ |
- get currentFocusElement_() { |
- return this.querySelector('.focusable[tabindex="1"]'); |
}, |
/** |
@@ -797,40 +374,9 @@ cr.define('ntp', function() { |
* @private |
*/ |
calculateLayoutValues_: function() { |
- var grid = this.gridValues_; |
- var availableSpace = this.tileGrid_.clientWidth - 2 * MIN_WIDE_MARGIN; |
- var wide = availableSpace >= grid.minWideWidth; |
- var numRowTiles = wide ? grid.maxColCount : grid.minColCount; |
- |
- var effectiveGridWidth = wide ? |
- Math.min(Math.max(availableSpace, grid.minWideWidth), |
- grid.maxWideWidth) : |
- grid.narrowWidth; |
- var realTileValues = tileValuesForGrid(effectiveGridWidth, numRowTiles, |
- grid.tileSpacingFraction); |
- |
- // leftMargin centers the grid within the avaiable space. |
- var minMargin = wide ? MIN_WIDE_MARGIN : 0; |
- var leftMargin = |
- Math.max(minMargin, |
- (this.tileGrid_.clientWidth - effectiveGridWidth) / 2); |
- |
- var rowHeight = this.heightForWidth(realTileValues.tileWidth) + |
- realTileValues.interTileSpacing; |
- |
- this.layoutValues_ = { |
- colWidth: realTileValues.offsetX, |
- gridWidth: effectiveGridWidth, |
- leftMargin: leftMargin, |
- numRowTiles: numRowTiles, |
- rowHeight: rowHeight, |
- tileWidth: realTileValues.tileWidth, |
- wide: wide, |
- }; |
- |
- // We need to update the top margin as well. |
- this.updateTopMargin_(); |
+ this.layout_(); |
+ // TODO(pedrosimonetti): When do we really need to send this message? |
this.firePageLayoutEvent_(); |
}, |
@@ -843,250 +389,16 @@ cr.define('ntp', function() { |
}, |
/** |
- * @return {number} The amount of margin that should be animated (in pixels) |
- * for the current grid layout. |
- */ |
- getAnimatedLeftMargin_: function() { |
- if (this.layoutValues_.wide) |
- return 0; |
- |
- var grid = this.gridValues_; |
- return (grid.minWideWidth - MIN_WIDE_MARGIN - grid.narrowWidth) / 2; |
- }, |
- |
- /** |
- * Calculates the x/y coordinates for an element and moves it there. |
- * @param {number} index The index of the element to be positioned. |
- * @param {number} indexOffset If provided, this is added to |index| when |
- * positioning the tile. The effect is that the tile will be positioned |
- * in a non-default location. |
- * @private |
- */ |
- positionTile_: function(index, indexOffset) { |
- var grid = this.gridValues_; |
- var layout = this.layoutValues_; |
- |
- indexOffset = typeof indexOffset != 'undefined' ? indexOffset : 0; |
- // Add the offset _after_ the modulus division. We might want to show the |
- // tile off the side of the grid. |
- var col = index % layout.numRowTiles + indexOffset; |
- var row = Math.floor(index / layout.numRowTiles); |
- // Calculate the final on-screen position for the tile. |
- var realX = col * layout.colWidth + layout.leftMargin; |
- var realY = row * layout.rowHeight; |
- |
- // Calculate the portion of the tile's position that should be animated. |
- var animatedTileValues = layout.wide ? |
- grid.wideTileValues : grid.narrowTileValues; |
- // Animate the difference between three-wide and six-wide. |
- var animatedLeftMargin = this.getAnimatedLeftMargin_(); |
- var animatedX = col * animatedTileValues.offsetX + animatedLeftMargin; |
- var animatedY = row * (this.heightForWidth(animatedTileValues.tileWidth) + |
- animatedTileValues.interTileSpacing); |
- |
- var tile = this.tileElements_[index]; |
- tile.setGridPosition(animatedX, animatedY); |
- tile.firstChild.setBounds(layout.tileWidth, |
- realX - animatedX, |
- realY - animatedY); |
- |
- // This code calculates whether the tile needs to show a clone of itself |
- // wrapped around the other side of the tile grid. |
- var offTheRight = col == layout.numRowTiles || |
- (col == layout.numRowTiles - 1 && tile.hasDoppleganger()); |
- var offTheLeft = col == -1 || (col == 0 && tile.hasDoppleganger()); |
- if (this.isCurrentDragTarget && (offTheRight || offTheLeft)) { |
- var sign = offTheRight ? 1 : -1; |
- tile.showDoppleganger(-layout.numRowTiles * layout.colWidth * sign, |
- layout.rowHeight * sign); |
- } else { |
- tile.clearDoppleganger(); |
- } |
- |
- if (index == this.tileElements_.length - 1) { |
- this.tileGrid_.style.height = (realY + layout.rowHeight) + 'px'; |
- this.queueUpdateScrollbars_(); |
- } |
- }, |
- |
- /** |
- * Gets the index of the tile that should occupy coordinate (x, y). Note |
- * that this function doesn't care where the tiles actually are, and will |
- * return an index even for the space between two tiles. This function is |
- * effectively the inverse of |positionTile_|. |
- * @param {number} x The x coordinate, in pixels, relative to the left of |
- * |this|. |
- * @param {number} y The y coordinate, in pixels, relative to the top of |
- * |this|. |
- * @private |
- */ |
- getWouldBeIndexForPoint_: function(x, y) { |
- var grid = this.gridValues_; |
- var layout = this.layoutValues_; |
- |
- var gridClientRect = this.tileGrid_.getBoundingClientRect(); |
- var col = Math.floor((x - gridClientRect.left - layout.leftMargin) / |
- layout.colWidth); |
- if (col < 0 || col >= layout.numRowTiles) |
- return -1; |
- |
- if (isRTL()) |
- col = layout.numRowTiles - 1 - col; |
- |
- var row = Math.floor((y - gridClientRect.top) / layout.rowHeight); |
- return row * layout.numRowTiles + col; |
- }, |
- |
- /** |
- * Window resize event handler. Window resizes may trigger re-layouts. |
- * @param {Object} e The resize event. |
- */ |
- onResize_: function(e) { |
- if (this.lastWidth_ == this.clientWidth && |
- this.lastHeight_ == this.clientHeight) { |
- return; |
- } |
- |
- this.calculateLayoutValues_(); |
- |
- this.lastWidth_ = this.clientWidth; |
- this.lastHeight_ = this.clientHeight; |
- this.classList.add('animating-tile-page'); |
- this.heightChanged_(); |
- |
- this.positionNotification_(); |
- this.repositionTiles_(); |
- }, |
- |
- /** |
- * The tile grid has an image mask which fades at the edges. We only show |
- * the mask when there is an active drag; it obscures doppleganger tiles |
- * as they enter or exit the grid. |
- * @private |
- */ |
- updateMask_: function() { |
- if (!this.isCurrentDragTarget) { |
- this.tileGrid_.style.WebkitMaskBoxImage = ''; |
- return; |
- } |
- |
- var leftMargin = this.layoutValues_.leftMargin; |
- // The fade distance is the space between tiles. |
- var fadeDistance = (this.gridValues_.tileSpacingFraction * |
- this.layoutValues_.tileWidth); |
- fadeDistance = Math.min(leftMargin, fadeDistance); |
- // On Skia we don't use any fade because it works very poorly. See |
- // http://crbug.com/99373 |
- if (!cr.isMac) |
- fadeDistance = 1; |
- var gradient = |
- '-webkit-linear-gradient(left,' + |
- 'transparent, ' + |
- 'transparent ' + (leftMargin - fadeDistance) + 'px, ' + |
- 'black ' + leftMargin + 'px, ' + |
- 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' + |
- 'transparent ' + (this.tileGrid_.clientWidth - leftMargin + |
- fadeDistance) + 'px, ' + |
- 'transparent)'; |
- this.tileGrid_.style.WebkitMaskBoxImage = gradient; |
- }, |
- |
- updateTopMargin_: function() { |
- var layout = this.layoutValues_; |
- |
- // The top margin is set so that the vertical midpoint of the grid will |
- // be 1/3 down the page. |
- var numTiles = this.tileCount + |
- (this.isCurrentDragTarget && !this.withinPageDrag_ ? 1 : 0); |
- var numRows = Math.max(1, Math.ceil(numTiles / layout.numRowTiles)); |
- var usedHeight = layout.rowHeight * numRows; |
- var newMargin = document.documentElement.clientHeight / 3 - |
- usedHeight / 3 - this.contentPadding; |
- // The 'height' style attribute of topMargin is non-zero to work around |
- // webkit's collapsing margin behavior, so we have to factor that into |
- // our calculations here. |
- newMargin = Math.max(newMargin, 0) - this.topMargin_.offsetHeight; |
- |
- // |newMargin| is the final margin we actually want to show. However, |
- // part of that should be animated and part should not (for the same |
- // reason as with leftMargin). The approach is to consider differences |
- // when the layout changes from wide to narrow or vice versa as |
- // 'animatable'. These differences accumulate in animatedTopMarginPx_, |
- // while topMarginPx_ caches the real (total) margin. Either of these |
- // calculations may come out to be negative, so we use margins as the |
- // css property. |
- |
- if (typeof this.topMarginIsForWide_ == 'undefined') |
- this.topMarginIsForWide_ = layout.wide; |
- if (this.topMarginIsForWide_ != layout.wide) { |
- this.animatedTopMarginPx_ += newMargin - this.topMarginPx_; |
- this.topMargin_.style.marginBottom = toCssPx(this.animatedTopMarginPx_); |
- } |
- |
- this.topMarginIsForWide_ = layout.wide; |
- this.topMarginPx_ = newMargin; |
- this.topMargin_.style.marginTop = |
- toCssPx(this.topMarginPx_ - this.animatedTopMarginPx_); |
- }, |
- |
- /** |
* Position the notification if there's one showing. |
+ * TODO(pedrosimonetti): Fix the position of the notification. |
*/ |
positionNotification_: function() { |
- var notification = this.notification; |
- if (!notification || notification.hidden) |
- return; |
- |
- // Update the horizontal position. |
- var animatedLeftMargin = this.getAnimatedLeftMargin_(); |
- notification.style.WebkitMarginStart = animatedLeftMargin + 'px'; |
- var leftOffset = (this.layoutValues_.leftMargin - animatedLeftMargin) * |
- (isRTL() ? -1 : 1); |
- notification.style.WebkitTransform = 'translateX(' + leftOffset + 'px)'; |
- |
- // Update the allowable widths of the text. |
- var buttonWidth = notification.querySelector('button').offsetWidth + 8; |
- notification.querySelector('span').style.maxWidth = |
- this.layoutValues_.gridWidth - buttonWidth + 'px'; |
- |
- // This makes sure the text doesn't condense smaller than the narrow size |
- // of the grid (e.g. when a user makes the window really small). |
- notification.style.minWidth = |
- this.gridValues_.narrowWidth - buttonWidth + 'px'; |
- |
- // Update the top position. |
- notification.style.marginTop = -notification.offsetHeight + 'px'; |
- }, |
- |
- /** |
- * Handles final setup that can only happen after |this| is inserted into |
- * the page. |
- * @private |
- */ |
- onNodeInsertedIntoDocument_: function(e) { |
- this.calculateLayoutValues_(); |
- this.heightChanged_(); |
- }, |
- |
- /** |
- * Called when the height of |this| has changed: update the size of |
- * tileGrid. |
- * @private |
- */ |
- heightChanged_: function() { |
- // The tile grid will expand to the bottom footer, or enough to hold all |
- // the tiles, whichever is greater. It would be nicer if tilePage were |
- // a flex box, and the tile grid could be box-flex: 1, but this exposes a |
- // bug where repositioning tiles will cause the scroll position to reset. |
- this.tileGrid_.style.minHeight = (this.clientHeight - |
- this.tileGrid_.offsetTop - this.content_.offsetTop - |
- this.extraBottomPadding - |
- (this.footerNode_ ? this.footerNode_.clientHeight : 0)) + 'px'; |
}, |
/** |
* Places an element at the bottom of the content div. Used in bare-minimum |
* mode to hold #footer. |
+ * TODO(pedrosimonetti): Delete & make Footer non-required(shared/js/util). |
* @param {HTMLElement} footerNode The node to append to content. |
*/ |
appendFooter: function(footerNode) { |
@@ -1112,266 +424,395 @@ cr.define('ntp', function() { |
return true; |
}, |
+ // ######################################################################### |
+ // Extended Chrome Instant |
+ // ######################################################################### |
+ |
+ |
+ // properties |
+ // ------------------------------------------------------------------------- |
+ |
+ // The number of columns. |
+ colCount_: 5, |
+ // The number of rows. |
+ rowCount_: 2, |
+ // The number of visible rows. |
+ numOfVisibleRows_: 0, |
+ // The number of the last column being animated. |
+ // TODO(pedrosimonetti): How to handle initialization of this value? |
+ animatingColCount_: 5, |
+ // The index of the topmost row visible. |
+ // TODO(pedrosimonetti): Move to config_? |
+ pageOffset_: 0, |
+ |
/** |
- * Handler for the 'scroll' event on |content_|. |
- * @param {Event} e The scroll event. |
- * @private |
+ * Appends a tile to the end of the tile grid. |
+ * @param {Tile} tile The tile to be added. |
+ * @param {number} index The location in the tile grid to insert it at. |
+ * @protected |
*/ |
- onScroll_: function(e) { |
- this.queueUpdateScrollbars_(); |
+ appendTile: function(tile) { |
+ var index = this.tiles_.length; |
+ this.tiles_.push(tile); |
+ this.renderGrid_(); |
+ this.fireAddedEvent(tile, index); |
}, |
/** |
- * ID of scrollbar update timer. If 0, there's no scrollbar re-calc queued. |
- * @private |
+ * Adds the given element to the tile grid. |
+ * TODO(pedrosimonetti): If this is not being used, delete. |
+ * @param {Tile} tile The tile to be added. |
+ * @param {number} index The location in the tile grid to insert it at. |
+ * @protected |
*/ |
- scrollbarUpdate_: 0, |
+ addTileAt: function(tile, index) { |
+ this.tiles_.splice(index, 0, tile); |
+ this.renderGrid_(); |
+ this.fireAddedEvent(tile, index); |
+ }, |
+ |
+ // internal helpers |
+ // ------------------------------------------------------------------------- |
/** |
- * Queues an update on the custom scrollbar. Used for two reasons: first, |
- * coalescing of multiple updates, and second, because action like |
- * repositioning a tile can require a delay before they affect values |
- * like clientHeight. |
+ * Gets the required width for a Tile. |
* @private |
*/ |
- queueUpdateScrollbars_: function() { |
- if (this.scrollbarUpdate_) |
- return; |
- |
- this.scrollbarUpdate_ = window.setTimeout( |
- this.doUpdateScrollbars_.bind(this), 0); |
+ getTileRequiredWidth_: function() { |
+ var conf = this.config_; |
+ return conf.cellWidth + conf.cellMarginStart; |
}, |
/** |
- * Does the work of calculating the visibility, height and position of the |
- * scrollbar thumb (there is no track or buttons). |
+ * Gets the the maximum number of columns that can fit in a given width. |
+ * @param {number} width The width in pixels. |
* @private |
*/ |
- doUpdateScrollbars_: function() { |
- this.scrollbarUpdate_ = 0; |
- |
- var content = this.content_; |
- |
- // Adjust scroll-height to account for possible header-bar. |
- var adjustedScrollHeight = content.scrollHeight - content.offsetTop; |
- |
- if (adjustedScrollHeight <= content.clientHeight) { |
- this.scrollbar_.hidden = true; |
- return; |
- } else { |
- this.scrollbar_.hidden = false; |
- } |
- |
- var thumbTop = content.offsetTop + |
- content.scrollTop / adjustedScrollHeight * content.clientHeight; |
- var thumbHeight = content.clientHeight / adjustedScrollHeight * |
- this.clientHeight; |
- |
- this.scrollbar_.style.top = thumbTop + 'px'; |
- this.scrollbar_.style.height = thumbHeight + 'px'; |
- this.firePageLayoutEvent_(); |
+ getColCountForWidth_: function(width) { |
+ var availableWidth = width + this.config_.cellMarginStart; |
+ var requiredWidth = this.getTileRequiredWidth_(); |
+ var colCount = Math.floor(availableWidth / requiredWidth); |
+ return colCount; |
}, |
/** |
- * Get the height for a tile of a certain width. Override this function to |
- * get non-square tiles. |
- * @param {number} width The pixel width of a tile. |
- * @return {number} The height for |width|. |
+ * Gets the width for a given number of columns. |
+ * @param {number} colCount The number of columns. |
+ * @private |
*/ |
- heightForWidth: function(width) { |
+ getWidthForColCount_: function(colCount) { |
+ var requiredWidth = this.getTileRequiredWidth_(); |
+ var width = colCount * requiredWidth - this.config_.cellMarginStart; |
return width; |
}, |
- /** Dragging **/ |
- |
- get isCurrentDragTarget() { |
- return this.dragWrapper_.isCurrentDragTarget; |
- }, |
- |
/** |
- * Thunk for dragleave events fired on |tileGrid_|. |
- * @param {Event} e A MouseEvent for the drag. |
+ * Gets the bottom panel width. |
+ * @private |
*/ |
- doDragLeave: function(e) { |
- this.cleanupDrag(); |
+ getBottomPanelWidth_: function() { |
+ var windowWidth = cr.doc.documentElement.clientWidth; |
+ var width; |
+ // TODO(pedrosimonetti): Add constants? |
+ if (windowWidth >= 948) |
+ width = 748; |
+ else if (windowWidth >= 500) |
+ width = windowWidth - 2 * this.config_.bottomPanelHorizontalMargin; |
+ else if (windowWidth >= 300) |
+ // TODO(pedrosimonetti): Check specification. |
+ width = Math.floor(((windowWidth - 300) / 200) * 100 + 200); |
+ else |
+ width = 200; |
+ |
+ return width; |
}, |
/** |
- * Performs all actions necessary when a drag enters the tile page. |
- * @param {Event} e A mouseover event for the drag enter. |
+ * Gets the number of available columns. |
+ * @private |
*/ |
- doDragEnter: function(e) { |
- // Applies the mask so doppleganger tiles disappear into the fog. |
- this.updateMask_(); |
- |
- this.classList.add('animating-tile-page'); |
- this.withinPageDrag_ = this.contains(currentlyDraggingTile); |
- this.dragItemIndex_ = this.withinPageDrag_ ? |
- currentlyDraggingTile.index : this.tileElements_.length; |
- this.currentDropIndex_ = this.dragItemIndex_; |
- |
- // The new tile may change the number of rows, hence the top margin |
- // will change. |
- if (!this.withinPageDrag_) |
- this.updateTopMargin_(); |
- |
- this.doDragOver(e); |
+ getAvailableColCount_: function() { |
+ return this.getColCountForWidth_(this.getBottomPanelWidth_()); |
}, |
+ // rendering |
+ // ------------------------------------------------------------------------- |
+ |
/** |
- * Performs all actions necessary when the user moves the cursor during |
- * a drag over the tile page. |
- * @param {Event} e A mouseover event for the drag over. |
+ * Renders the grid. |
+ * @param {number} colCount The number of columns. |
+ * @private |
*/ |
- doDragOver: function(e) { |
- e.preventDefault(); |
- |
- this.setDropEffect(e.dataTransfer); |
- var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY); |
- if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length) |
- newDragIndex = this.dragItemIndex_; |
- this.updateDropIndicator_(newDragIndex); |
+ renderGrid_: function(colCount) { |
+ colCount = colCount || this.colCount_; |
+ |
+ var tileGridContent = this.tileGridContent_; |
+ var tiles = this.tiles_; |
+ var tileCount = tiles.length; |
+ |
+ var numOfVisibleRows = this.numOfVisibleRows_; |
+ var rowCount = Math.ceil(tileCount / colCount); |
+ rowCount = Math.max(rowCount, numOfVisibleRows); |
+ var tileRows = tileGridContent.getElementsByClassName('tile-row'); |
+ |
+ var pageOffset = this.pageOffset_; |
+ |
+ for (var tile = 0, row = 0; row < rowCount; row++) { |
+ var tileRow = tileRows[row]; |
+ |
+ // Create tile row if there's no one yet. |
+ if (!tileRow) { |
+ tileRow = cr.doc.createElement('div'); |
+ tileRow.className = 'tile-row'; |
+ tileGridContent.appendChild(tileRow); |
+ } |
+ |
+ // Adjust row visibility. |
+ var rowVisible = row >= pageOffset && |
+ row <= (pageOffset + numOfVisibleRows - 1); |
+ this.showTileRow_(tileRow, rowVisible); |
+ |
+ // The tiles inside the current row. |
+ var tileRowTiles = tileRow.childNodes; |
+ |
+ // Remove excessive columns from a particular tile row. |
+ var maxColCount = Math.min(colCount, tileCount - tile); |
+ maxColCount = Math.max(0, maxColCount); |
+ while (tileRowTiles.length > maxColCount) { |
+ tileRow.removeChild(tileRow.lastElementChild); |
+ } |
+ |
+ // For each column in the current row. |
+ for (var col = 0; col < colCount; col++, tile++) { |
+ var tileCell; |
+ var tileElement; |
+ if (tileRowTiles[col]) { |
+ tileCell = tileRowTiles[col]; |
+ } else { |
+ var span = cr.doc.createElement('span'); |
+ tileCell = new TileCell(span, this.config_); |
+ } |
+ |
+ // Render Tiles. |
+ if (tile < tileCount) { |
+ tileCell.classList.remove('filler'); |
+ tileElement = tiles[tile]; |
+ if (!tileCell.firstChild) |
+ tileCell.appendChild(tileElement); |
+ else if (tileElement != tileCell.firstChild) |
+ tileCell.replaceChild(tileElement, tileCell.firstChild); |
+ } else if (!tileCell.classList.contains('filler')) { |
+ tileCell.classList.add('filler'); |
+ tileElement = cr.doc.createElement('span'); |
+ tileElement.className = 'tile'; |
+ if (tileCell.firstChild) |
+ tileCell.replaceChild(tileElement, tileCell.firstChild); |
+ else |
+ tileCell.appendChild(tileElement); |
+ } |
+ |
+ if (!tileRowTiles[col]) |
+ tileRow.appendChild(tileCell); |
+ } |
+ } |
+ |
+ // Remove excessive tile rows from the tile grid. |
+ while (tileRows.length > rowCount) { |
+ tileGridContent.removeChild(tileGridContent.lastElementChild); |
+ } |
+ |
+ this.colCount_ = colCount; |
+ this.rowCount_ = rowCount; |
}, |
/** |
- * Performs all actions necessary when the user completes a drop. |
- * @param {Event} e A mouseover event for the drag drop. |
+ * Adjusts the layout of the tile page according to the current window size. |
+ * @param {boolean} force Forces the layout. |
+ * @private |
*/ |
- doDrop: function(e) { |
- e.stopPropagation(); |
- e.preventDefault(); |
- |
- var index = this.currentDropIndex_; |
- // Only change data if this was not a 'null drag'. |
- if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) { |
- var adjustedIndex = this.currentDropIndex_ + |
- (index > this.dragItemIndex_ ? 1 : 0); |
- if (this.withinPageDrag_) { |
- this.tileGrid_.insertBefore( |
- currentlyDraggingTile, |
- this.tileElements_[adjustedIndex]); |
- this.tileMoved(currentlyDraggingTile, this.dragItemIndex_); |
+ layout_: function(force) { |
+ if (force) { |
+ this.numOfVisibleRows_ = 0; |
+ this.animatingColCount_ = 0; |
+ } |
+ |
+ var bottomPanelWidth = this.getBottomPanelWidth_(); |
+ var colCount = this.getColCountForWidth_(bottomPanelWidth); |
+ var lastColCount = this.colCount_; |
+ var animatingColCount = this.animatingColCount_; |
+ |
+ var windowHeight = cr.doc.documentElement.clientHeight; |
+ |
+ this.tileGridContent_.classList.add('animate-tile'); |
+ |
+ // TODO(pedrosimonetti): Better handling of height state. |
+ // TODO(pedrosimonetti): Add constants? |
+ var numOfVisibleRows = windowHeight > 500 ? 2 : 1; |
+ if (numOfVisibleRows != this.numOfVisibleRows_) { |
+ this.numOfVisibleRows_ = numOfVisibleRows; |
+ this.paginate_(null, true); |
+ $('page-list').style.height = |
+ (this.config_.rowHeight * numOfVisibleRows) + 'px'; |
+ } |
+ |
+ // changeVisibleCols |
+ if (colCount != animatingColCount) { |
+ var newWidth = this.getWidthForColCount_(colCount); |
+ if (colCount > animatingColCount) { |
+ // TODO(pedrosimonetti): Do an actual size check. |
+ if (colCount != lastColCount) |
+ this.renderGrid_(colCount); |
+ |
+ this.showTileCols_(animatingColCount, false); |
+ |
+ var self = this; |
+ // We need to save the animatingColCount value in a closure otherwise |
+ // the animation of tiles won't work when expanding horizontally. |
+ // The problem happens because the layout_ method is called several |
+ // times when resizing the window, and the animatingColCount is saved |
+ // and restored each time a new column has to be animated. So, if we |
+ // don't save the value, by the time the showTileCols_ method is |
+ // called the animatingColCount is holding a new value, breaking |
+ // the animation. |
+ setTimeout((function(animatingColCount) { |
+ return function() { |
+ self.showTileCols_(animatingColCount, true); |
+ } |
+ })(animatingColCount), 0); |
} else { |
- var originalPage = currentlyDraggingTile ? |
- currentlyDraggingTile.tilePage : null; |
- this.addDragData(e.dataTransfer, adjustedIndex); |
- if (originalPage) |
- originalPage.cleanupDrag(); |
+ this.showTileCols_(colCount, false); |
} |
- // Dropping the icon may cause topMargin to change, but changing it |
- // now would cause everything to move (annoying), so we leave it |
- // alone. The top margin will be re-calculated next time the window is |
- // resized or the page is selected. |
+ this.tileGrid_.style.width = newWidth + 'px'; |
+ $('page-list-menu').style.width = newWidth + 'px'; |
+ |
+ var self = this; |
+ this.onTileGridAnimationEndHandler_ = function() { |
+ if (colCount < lastColCount) |
+ self.renderGrid_(colCount); |
+ else |
+ self.showTileCols_(0, true); |
+ }; |
+ |
+ this.paginate_(); |
} |
- this.classList.remove('animating-tile-page'); |
- this.cleanupDrag(); |
- }, |
+ this.content_.style.width = bottomPanelWidth + 'px'; |
- /** |
- * Appends the currently dragged tile to the end of the page. Called |
- * from outside the page, e.g. when dropping on a nav dot. |
- */ |
- appendDraggingTile: function() { |
- var originalPage = currentlyDraggingTile.tilePage; |
- if (originalPage == this) |
- return; |
- |
- this.addDragData(null, this.tileElements_.length); |
- if (originalPage) |
- originalPage.cleanupDrag(); |
+ this.animatingColCount_ = colCount; |
}, |
+ // animation helpers |
+ // ------------------------------------------------------------------------- |
+ |
/** |
- * Makes sure all the tiles are in the right place after a drag is over. |
+ * Animates the display of a row. TODO(pedrosimonetti): Make it local? |
+ * @param {HTMLElement} row The row element. |
+ * @param {boolean} show Whether or not to show the row. |
*/ |
- cleanupDrag: function() { |
- this.repositionTiles_(currentlyDraggingTile); |
- // Remove the drag mask. |
- this.updateMask_(); |
+ showTileRow_: function(row, show) { |
+ row.classList[show ? 'remove' : 'add']('hide-row'); |
}, |
/** |
- * Reposition all the tiles (possibly ignoring one). |
- * @param {?Node} ignoreNode An optional node to ignore. |
- * @private |
+ * Animates the display of columns. TODO(pedrosimonetti): Make it local? |
+ * @param {number} col The column number. |
+ * @param {boolean} show Whether or not to show the row. |
*/ |
- repositionTiles_: function(ignoreNode) { |
- for (var i = 0; i < this.tileElements_.length; i++) { |
- if (!ignoreNode || ignoreNode !== this.tileElements_[i]) |
- this.positionTile_(i); |
+ showTileCols_: function(col, show) { |
+ var prop = show ? 'remove' : 'add'; |
+ var max = 10; // TODO(pedrosimonetti): Add const? |
+ var tileGridContent = this.tileGridContent_; |
+ for (var i = col; i < max; i++) { |
+ tileGridContent.classList[prop]('hide-col-' + i); |
} |
}, |
+ // pagination |
+ // ------------------------------------------------------------------------- |
+ |
/** |
- * Updates the visual indicator for the drop location for the active drag. |
- * @param {Event} e A MouseEvent for the drag. |
- * @private |
+ * Resets the display of columns. |
+ * @param {number} pageOffset The index of the topmost row visible. |
+ * @param {boolean} force Forces the pagination. |
*/ |
- updateDropIndicator_: function(newDragIndex) { |
- var oldDragIndex = this.currentDropIndex_; |
- if (newDragIndex == oldDragIndex) |
- return; |
- |
- var repositionStart = Math.min(newDragIndex, oldDragIndex); |
- var repositionEnd = Math.max(newDragIndex, oldDragIndex); |
- |
- for (var i = repositionStart; i <= repositionEnd; i++) { |
- if (i == this.dragItemIndex_) |
- continue; |
- else if (i > this.dragItemIndex_) |
- var adjustment = i <= newDragIndex ? -1 : 0; |
- else |
- var adjustment = i >= newDragIndex ? 1 : 0; |
- |
- this.positionTile_(i, adjustment); |
+ paginate_: function(pageOffset, force) { |
+ var numOfVisibleRows = this.numOfVisibleRows_; |
+ var pageOffset = typeof pageOffset == 'number' ? |
+ pageOffset : this.pageOffset_; |
+ |
+ pageOffset = Math.min(this.rowCount_ - numOfVisibleRows, pageOffset); |
+ pageOffset = Math.max(0, pageOffset); |
+ |
+ if (pageOffset != this.pageOffset || force) { |
+ var rows = this.tileGridContent_.getElementsByClassName('tile-row'); |
+ for (var i = 0, length = rows.length; i < length; i++) { |
+ var row = rows[i]; |
+ var isRowVisible = i >= pageOffset && |
+ i <= (pageOffset + numOfVisibleRows - 1); |
+ this.showTileRow_(row, isRowVisible); |
+ } |
+ |
+ this.pageOffset_ = pageOffset; |
+ this.tileGridContent_.style.webkitTransform = |
+ 'translate3d(0,' + (-pageOffset * this.config_.rowHeight) + 'px,0)'; |
} |
- this.currentDropIndex_ = newDragIndex; |
}, |
- /** |
- * Checks if a page can accept a drag with the given data. |
- * @param {Event} e The drag event if the drag object. Implementations will |
- * likely want to check |e.dataTransfer|. |
- * @return {boolean} True if this page can handle the drag. |
- */ |
- shouldAcceptDrag: function(e) { |
- return false; |
- }, |
+ // event handlers |
+ // ------------------------------------------------------------------------- |
/** |
- * Called to accept a drag drop. Will not be called for in-page drops. |
- * @param {Object} dataTransfer The data transfer object that holds the drop |
- * data. This should only be used if currentlyDraggingTile is null. |
- * @param {number} index The tile index at which the drop occurred. |
+ * Handles the window resize event. |
*/ |
- addDragData: function(dataTransfer, index) { |
- assert(false); |
+ onResize_: function() { |
+ this.layout_(); |
}, |
/** |
- * Called when a tile has been moved (via dragging). Override this to make |
- * backend updates. |
- * @param {Node} draggedTile The tile that was dropped. |
- * @param {number} prevIndex The previous index of the tile. |
+ * Handles the end of the tile grid animation. |
*/ |
- tileMoved: function(draggedTile, prevIndex) { |
+ onTileGridAnimationEnd_: function() { |
+ // TODO(pedrosimonetti): Figure out how to cleanup each kind of |
+ // animation properly. |
+ if (event.target == this.tileGrid_ && |
+ this.onTileGridAnimationEndHandler_ && |
+ this.tileGridContent_.classList.contains('animate-tile')) { |
+ this.onTileGridAnimationEndHandler_(); |
+ } |
}, |
/** |
- * Sets the drop effect on |dataTransfer| to the desired value (e.g. |
- * 'copy'). |
- * @param {Object} dataTransfer The drag event dataTransfer object. |
+ * Handles the window keyup event. |
+ * @param {Event} e The keyboard event. |
*/ |
- setDropEffect: function(dataTransfer) { |
- assert(false); |
- }, |
+ onKeyUp_: function(e) { |
+ var pageOffset = this.pageOffset_; |
+ |
+ var keyCode = e.keyCode; |
+ if (keyCode == 40 /* down */) |
+ pageOffset++; |
+ else if (keyCode == 38 /* up */) |
+ pageOffset--; |
+ |
+ // Changes the pagination according to which arrow key was pressed. |
+ if (pageOffset != this.pageOffset_) |
+ this.paginate_(pageOffset); |
+ } |
}; |
+ /** |
+ * Shows a deprecate error. |
+ */ |
+ function deprecate() { |
+ console.error('This function is deprecated!'); |
+ } |
+ |
return { |
- getCurrentlyDraggingTile: getCurrentlyDraggingTile, |
- setCurrentDropEffect: setCurrentDropEffect, |
- TilePage: TilePage, |
+ // TODO(pedrosimonetti): Drag. Delete after porting the rest of the code. |
+ getCurrentlyDraggingTile2: deprecate, |
+ setCurrentDropEffect2: deprecate, |
+ Tile2: Tile, |
+ TilePage2: TilePage |
}; |
}); |