Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6998)

Unified Diff: chrome/browser/resources/ntp_search/tile_page.js

Issue 10823052: Refactoring the NTP. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
new file mode 100644
index 0000000000000000000000000000000000000000..ec6bcaaff75c26e85ca290d15afd3f4c0af05177
--- /dev/null
+++ b/chrome/browser/resources/ntp_search/tile_page.js
@@ -0,0 +1,1476 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('ntp', function() {
+ 'use strict';
+
+
+ function Tile(gridValues) {
+ throw 'Tile is a virtual class and is not supposed to be instantiated';
+ }
+
+ 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 = {
+ initialize: function(gridValues) {
+ // TODO: rename to tile
+ this.className = 'tile-content ' + gridValues.tileClassName;
+
+ // TODO set using CSS?
+ if (gridValues.reinforceStyles) {
+ var style = this.style;
+ style.width = gridValues.tileWidth + 'px';
+ style.height = gridValues.tileHeight + 'px';
+ style.borderWidth = gridValues.tileBorderWidth + 'px';
+ }
+ },
+
+ tearDown_: function () {
+ },
+ };
+
+ var TileGetters = {
+ 'tile': function() {
+ return findAncestorByClass(this, 'tile-cell');
+ },
+ 'index': function() {
+ return this.tile.index;
+ }
+ };
+
+
+
+ // We can't pass the currently dragging tile via dataTransfer because of
+ // http://crbug.com/31037
+ // TODO(xci) drag
+ var currentlyDraggingTile = null;
+ function getCurrentlyDraggingTile() {
+ return currentlyDraggingTile;
+ }
+ function setCurrentlyDraggingTile(tile) {
+ currentlyDraggingTile = tile;
+ if (tile)
+ ntp.enterRearrangeMode();
+ else
+ ntp.leaveRearrangeMode();
+ }
+
+ /**
+ * 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).
+ */
+ // TODO(xci) drag
+ function setCurrentDropEffect(dataTransfer, effect) {
+ dataTransfer.dropEffect = effect;
+ if (currentlyDraggingTile)
+ currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect;
+ }
+
+ /**
+ * Creates a new TileCell object. Tiles wrap content on a TilePage, providing
+ * some styling and drag functionality.
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ function TileCell(tile, gridValues) {
+ var tileCell = cr.doc.createElement('div');
+ tileCell.__proto__ = TileCell.prototype;
+ tileCell.initialize(tile, gridValues);
+
+ return tileCell;
+ }
+
+ TileCell.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ initialize: function(tile, gridValues) {
+ // 'real' as opposed to doppleganger.
+ // TODO: rename to tile-cell
+ this.className = 'tile-cell real';
+
+ if (gridValues.reinforceStyles) {
+ var style = this.style;
+ var borderWidth = 2 * gridValues.tileBorderWidth;
+ style.width = gridValues.tileWidth + borderWidth + 'px';
+ style.height = gridValues.tileHeight + borderWidth + 'px';
+ style.marginLeft = gridValues.tileHorMargin + 'px';
+ style.marginBottom = gridValues.tileVerMargin + 'px';
+ }
+
+ this.assign_(tile);
+ },
+
+ get index() {
+ return Array.prototype.indexOf.call(this.tilePage.tileElements_, this.firstChild);
+ },
+
+ get tilePage() {
+ return findAncestorByClass(this, 'tile-page');
+ },
+
+ assign_: function(tile) {
+ if (this.firstChild) {
+ this.replaceChild(tile, this.firstChild);
+ } else {
+ this.appendChild(tile);
+ }
+ },
+
+ tearDown_: function () {
+ },
+
+ /**
+ * The handler for dragstart events fired on |this|.
+ * @param {Event} e The event for the drag.
+ * @private
+ */
+ // TODO(xci) drag
+ 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);
+ },
+
+ /**
+ * The handler for drag events fired on |this|.
+ * @param {Event} e The event for the drag.
+ * @private
+ */
+ // TODO(xci) drag
+ 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);
+ },
+
+ /**
+ * The handler for dragend events fired on |this|.
+ * @param {Event} e The event for the drag.
+ * @private
+ */
+ // TODO(xci) drag
+ 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); // TODO(xci) function was deleted!
+
+ // 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;
+ },
+
+ /**
+ * 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.
+ */
+ // TODO(xci) drag
+ 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');
+ }
+
+ this.appendChild(clone);
+ this.doppleganger_ = clone;
+
+ if (isRTL())
+ x *= -1;
+
+ this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' +
+ y + 'px)';
+ },
+
+ /**
+ * Destroys the current doppleganger.
+ */
+ // TODO(xci) drag
+ clearDoppleganger: function() {
+ if (this.doppleganger_) {
+ this.removeChild(this.doppleganger_);
+ this.doppleganger_ = null;
+ }
+ },
+
+ /**
+ * Returns status of doppleganger.
+ * @return {boolean} True if there is a doppleganger showing for |this|.
+ */
+ // TODO(xci) drag
+ hasDoppleganger: function() {
+ return !!this.doppleganger_;
+ },
+
+ /**
+ * 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
+ */
+ // TODO(xci) drag
+ 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();
+ },
+
+ /**
+ * Called when the drag representation node is done migrating to its final
+ * resting spot.
+ * @param {Event} e The transition end event.
+ */
+ // TODO(xci) drag
+ onDragCloneTransitionEnd_: function(e) {
+ if (this.classList.contains('dragging') &&
+ (e.propertyName == 'left' || e.propertyName == 'top' ||
+ e.propertyName == '-webkit-transform')) {
+ this.finalizeDrag_();
+ }
+ },
+
+ /**
+ * Called when an app is removed from Chrome. Animates its disappearance.
+ * @param {boolean=} opt_animate Whether the animation should be animated.
+ */
+ doRemove: function(opt_animate) {
+ if (opt_animate)
+ 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);
+ },
+ };
+
+ /**
+ * 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() {
+ var el = cr.doc.createElement('div');
+ el.__proto__ = TilePage.prototype;
+ el.initialize();
+
+ return el;
+ }
+
+ TilePage.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ // The grid values should be defined by each TilePage subclass.
+ gridValues_: {
+ tileWidth: 0,
+ tileHeight: 0,
+ tileHorMargin: 0, // TODO margin with CSS / there's no margin in first col
+ tileVerMargin: 0,
+ tileBorderWidth: 0,
+ bottomPanelHorMargin: 0, // TODO it doesn't make sense having this here.
+
+ tileCount: 0, // TODO remove this dependency. rename it to maxTileCount.
+ tileClassName: '',
+ reinforceStyles: true,
+
+ // debug
+ slowFactor: 1,
+ debug: false
+ },
+
+ initialize: function() {
+ this.className = 'tile-page';
+
+ // This contains everything but the scrollbar.
+ this.content_ = this.ownerDocument.createElement('div');
+ this.content_.className = 'tile-page-content';
+ this.appendChild(this.content_);
+
+ // Div that holds the tiles.
+ this.tileGrid_ = this.ownerDocument.createElement('div');
+ this.tileGrid_.className = 'tile-grid';
+ //this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px'; // TODO(xci)
+ this.content_.appendChild(this.tileGrid_);
+
+ // TODO(xci) new!
+ this.tileGridContent_ = this.ownerDocument.createElement('div');
+ this.tileGridContent_.className = 'tile-grid-content';
+ this.tileGrid_.appendChild(this.tileGridContent_);
+
+ // TODO(xci) new!
+ this.tileGrid_.addEventListener('webkitTransitionEnd',
+ this.onTileGridAnimationEnd_.bind(this));
+
+ // Ordered list of our tiles.
+ //this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real');
+ this.tileElements_ = [];
+
+ // 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.
+ // TODO(xci)
+ /*
+ this.focusableElements_ =
+ this.tileGrid_.getElementsByClassName('focusable');
+ */
+
+ this.eventTracker = new EventTracker();
+ this.eventTracker.add(window, 'resize', this.onResize_.bind(this));
+ this.eventTracker.add(window, 'keydown', this.onKeyDown_.bind(this)); // TODO(xci) new
+
+ // TODO(xci)
+ /*
+ this.addEventListener('DOMNodeInsertedIntoDocument',
+ this.onNodeInsertedIntoDocument_);
+
+ this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, 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 notification content of this tile (if any, otherwise null).
+ * @type {!HTMLElement}
+ */
+ get notification() {
+ return this.topMargin_.nextElementSibling.id == 'notification-container' ?
+ this.topMargin_.nextElementSibling : null;
+ },
+ /**
+ * The notification content of this tile (if any, otherwise null).
+ * @type {!HTMLElement}
+ */
+ 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.positionNotification_();
+ },
+
+ /**
+ * Removes the tilePage from the DOM and cleans up event handlers.
+ */
+ remove: function() {
+ // This checks arguments.length as most remove functions have a boolean
+ // |opt_animate| argument, but that's not necesarilly applicable to
+ // removing a tilePage. Selecting a different card in an animated way and
+ // deleting the card afterward is probably a better choice.
+ assert(typeof arguments[0] != 'boolean',
+ 'This function takes no |opt_animate| argument.');
+ this.tearDown_();
+ this.parentNode.removeChild(this);
+ },
+
+ /**
+ * Cleans up resources that are no longer needed after this TilePage
+ * instance is removed from the DOM.
+ * @private
+ */
+ tearDown_: function() {
+ this.eventTracker.removeAll();
+ },
+
+ /**
+ * 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
+ */
+ // TODO(xci)
+ /*
+ 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
+ */
+ // TODO(xci)
+ /*
+ addTileAt: function(tileElement, index, animate) {
+ this.calculateLayoutValues_();
+ this.fireAddedEvent(wrapperDiv, index, animate);
+ },
+ */
+
+ /**
+ * Notify interested subscribers that a tile has been removed from this
+ * page.
+ * @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.
+ */
+ fireAddedEvent: function(tile, index, wasAnimated) {
+ var e = document.createEvent('Event');
+ e.initEvent('tilePage:tile_added', true, true);
+ e.addedIndex = index;
+ e.addedTile = tile;
+ e.wasAnimated = wasAnimated;
+ this.dispatchEvent(e);
+ },
+
+ /**
+ * Removes the given tile and animates the repositioning of the other tiles.
+ * @param {boolean=} opt_animate Whether the removal should be animated.
+ * @param {boolean=} opt_dontNotify Whether a page should be removed if the
+ * last tile is removed from it.
+ */
+ removeTile: function(tile, opt_animate, opt_dontNotify) {
+ if (opt_animate)
+ this.classList.add('animating-tile-page');
+ var index = tile.index;
+ tile.parentNode.removeChild(tile);
+ this.calculateLayoutValues_();
+ this.cleanupDrag();
+
+ if (!opt_dontNotify)
+ this.fireRemovedEvent(tile, index, !!opt_animate);
+ },
+
+ /**
+ * Notify interested subscribers that a tile has been removed from this
+ * page.
+ * @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.
+ */
+ fireRemovedEvent: function(tile, oldIndex, wasAnimated) {
+ var e = document.createEvent('Event');
+ e.initEvent('tilePage:tile_removed', true, true);
+ e.removedIndex = oldIndex;
+ e.removedTile = tile;
+ e.wasAnimated = wasAnimated;
+ this.dispatchEvent(e);
+ },
+
+ /**
+ * Removes all tiles from the page.
+ */
+ removeAllTiles: function() {
+ // TODO(xci) dispatch individual tearDown functions
+ this.tileGrid_.innerHTML = '';
+ },
+
+ /**
+ * Called when the page is selected (in the card selector).
+ * @param {Event} e A custom cardselected event.
+ * @private
+ */
+ handleCardSelection_: function(e) {
jeremycho_google 2012/07/31 03:09:16 This doesn't seem to get called when switching to
pedrosimonetti2 2012/08/03 18:14:01 Done. In the newly revised code this method is bei
+ this.tabIndex = 1;
+
+ // When we are selected, we re-calculate the layout values. (See comment
+ // in doDrop.)
+ this.calculateLayoutValues_();
+ },
+
+ /**
+ * Called when the page loses selection (in the card selector).
+ * @param {Event} e A custom carddeselected event.
+ * @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"]');
+ },
+
+ /**
+ * Makes some calculations for tile layout. These change depending on
+ * height, width, and the number of tiles.
+ * TODO(estade): optimize calls to this function. Do nothing if the page is
+ * hidden, but call before being shown.
+ * @private
+ */
+ calculateLayoutValues_: function() {
+
+ // We need to update the top margin as well.
+ this.updateTopMargin_();
+
+ // TODO(pedrosimonetti): when do we really need to send this message?
+ this.firePageLayoutEvent_();
+ },
+
+ /**
+ * Dispatches the custom pagelayout event.
+ * @private
+ */
+ firePageLayoutEvent_: function() {
+ cr.dispatchSimpleEvent(this, 'pagelayout', true, true);
+ },
+
+
+ /**
+ * 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
+ */
+ // TODO(xci) drag
+ 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;
+ },
+
+ /**
+ * 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
+ */
+ // TODO(xci) drag
+ 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;
+ },
+
+ // TODO(xci) delete (used by drag and drop)
+ updateTopMargin_: function() {
+ return;
+ },
+
+ /**
+ * Position the notification if there's one showing.
+ */
+ positionNotification_: function() {
+ if (this.notification && !this.notification.hidden) {
+ this.notification.style.margin =
+ -this.notification.offsetHeight + 'px ' +
+ this.layoutValues_.leftMargin + 'px 0';
+ }
+ },
+
+ /**
+ * Handles final setup that can only happen after |this| is inserted into
+ * the page.
+ * @private
+ */
+ onNodeInsertedIntoDocument_: function(e) {
+ this.calculateLayoutValues_();
+ },
+
+ /**
+ * Places an element at the bottom of the content div. Used in bare-minimum
+ * mode to hold #footer.
+ * @param {HTMLElement} footerNode The node to append to content.
+ */
+ appendFooter: function(footerNode) {
+ this.footerNode_ = footerNode;
+ this.content_.appendChild(footerNode);
+ },
+
+ /**
+ * Scrolls the page in response to an mousewheel event, although the event
+ * may have been triggered on a different element. Return true if the
+ * event triggered scrolling, and false otherwise.
+ * This is called explicitly, which allows a consistent experience whether
+ * the user scrolls on the page or on the page switcher, because this
+ * function provides a common conversion factor between wheel delta and
+ * scroll delta.
+ * @param {Event} e The mousewheel event.
+ */
+ handleMouseWheel: function(e) {
+ if (e.wheelDeltaY == 0)
+ return false;
+
+ this.content_.scrollTop -= e.wheelDeltaY / 3;
+ return true;
+ },
+
+ /** Dragging **/
+
+ // TODO(xci) drag
+ get isCurrentDragTarget() {
+ return this.dragWrapper_.isCurrentDragTarget;
+ },
+
+ /**
+ * Thunk for dragleave events fired on |tileGrid_|.
+ * @param {Event} e A MouseEvent for the drag.
+ */
+ // TODO(xci) drag
+ doDragLeave: function(e) {
+ this.cleanupDrag();
+ },
+
+ /**
+ * Performs all actions necessary when a drag enters the tile page.
+ * @param {Event} e A mouseover event for the drag enter.
+ */
+ // TODO(xci) drag
+ 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_)
+ // TODO(xci) this function does nothing now!
+ this.updateTopMargin_();
+
+ this.doDragOver(e);
+ },
+
+ /**
+ * 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.
+ */
+ // TODO(xci) drag
+ 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);
+ },
+
+ /**
+ * Performs all actions necessary when the user completes a drop.
+ * @param {Event} e A mouseover event for the drag drop.
+ */
+ // TODO(xci) drag
+ 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_);
+ } else {
+ var originalPage = currentlyDraggingTile ?
+ currentlyDraggingTile.tilePage : null;
+ this.addDragData(e.dataTransfer, adjustedIndex);
+ if (originalPage)
+ originalPage.cleanupDrag();
+ }
+
+ // 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.classList.remove('animating-tile-page');
+ this.cleanupDrag();
+ },
+
+ /**
+ * Appends the currently dragged tile to the end of the page. Called
+ * from outside the page, e.g. when dropping on a nav dot.
+ */
+ // TODO(xci) drag
+ appendDraggingTile: function() {
+ var originalPage = currentlyDraggingTile.tilePage;
+ if (originalPage == this)
+ return;
+
+ this.addDragData(null, this.tileElements_.length);
+ if (originalPage)
+ originalPage.cleanupDrag();
+ },
+
+ /**
+ * Makes sure all the tiles are in the right place after a drag is over.
+ */
+ // TODO(xci) drag
+ cleanupDrag: function() {
+ this.repositionTiles_(currentlyDraggingTile);
+ // Remove the drag mask.
+ this.updateMask_();
+ },
+
+ /**
+ * Reposition all the tiles (possibly ignoring one).
+ * @param {?Node} ignoreNode An optional node to ignore.
+ * @private
+ */
+ // TODO(xci) drag
+ repositionTiles_: function(ignoreNode) {
+ for (var i = 0; i < this.tileElements_.length; i++) {
+ if (!ignoreNode || ignoreNode !== this.tileElements_[i])
+ ;//this.positionTile_(i); TODO(xci) this function was deleted!
+ }
+ },
+
+ /**
+ * Updates the visual indicator for the drop location for the active drag.
+ * @param {Event} e A MouseEvent for the drag.
+ * @private
+ */
+ // TODO(xci) drag
+ 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); TODO(xci) function was deleted!
+ }
+ 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.
+ */
+ // TODO(xci) drag
+ shouldAcceptDrag: function(e) {
+ return false;
+ },
+
+ /**
+ * 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.
+ */
+ // TODO(xci) drag
+ addDragData: function(dataTransfer, index) {
+ assert(false);
+ },
+
+ /**
+ * 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.
+ */
+ tileMoved: function(draggedTile, prevIndex) {
+ },
+
+ /**
+ * Sets the drop effect on |dataTransfer| to the desired value (e.g.
+ * 'copy').
+ * @param {Object} dataTransfer The drag event dataTransfer object.
+ */
+ // TODO(xci) drag
+ setDropEffect: function(dataTransfer) {
+ assert(false);
+ },
+
+
+ // #########################################################################
+ // XCI - Extended Chrome Instant
+ // #########################################################################
+
+
+ // properties
+ // -------------------------------------------------------------------------
+ colCount: 5,
+ rowCount: 2,
+
+ numOfVisibleRows: 0,
+ animatingColCount: 5, // TODO initialize
+
+ // TODO move to layout?
+ pageOffset: 0,
+
+ appendTile: function(tile) {
+ this.tileElements_.push(tile);
+ this.renderGrid_();
+ },
+
+ addTileAt: function(tile) {
+ this.appendTile(tile);
+ },
+
+ // internal helpers
+ // -------------------------------------------------------------------------
+
+ // TODO move to layout?
+ getNumOfVisibleRows: function() {
+ return this.numOfVisibleRows;
+ },
+
+ getTileRequiredWidth_: function() {
+ var grid = this.gridValues_;
+ return grid.tileWidth + 2 * grid.tileBorderWidth + grid.tileHorMargin;
+ },
+
+ getColCountForWidth_: function(width) {
+ var availableWidth = width + this.gridValues_.tileHorMargin;
+ var requiredWidth = this.getTileRequiredWidth_();
+ var colCount = Math.floor(availableWidth / requiredWidth);
+ return colCount;
+ },
+
+ getWidthForColCount_: function(colCount) {
+ var requiredWidth = this.getTileRequiredWidth_();
+ var width = colCount * requiredWidth - this.gridValues_.tileHorMargin;
+ return width;
+ },
+
+ getBottomPanelWidth_: function() {
+ var windowWidth = cr.doc.documentElement.clientWidth;
+ var width;
+ if (windowWidth >= 948) {
+ width = 748;
+ } else if (windowWidth >= 500) {
+ width = windowWidth - 2 * this.gridValues_.bottomPanelHorMargin;
+ } else if (windowWidth >= 300) {
+ // TODO(pedrosimonetti): check math and document
+ width = Math.floor(((windowWidth - 300) / 200) * 100 + 200);
+ } else {
+ width = 200;
+ }
+ return width;
+ },
+
+ getAvailableColCount_: function() {
+ return this.getColCountForWidth_(this.getBottomPanelWidth_());
+ },
+
+ // rendering
+ // -------------------------------------------------------------------------
+
+ renderGrid_: function(colCount, tileElements) {
+ //if (window.xc > 5) debugger;
+ //window.xc = window.xc || 1, console.log('renderGrid' + window.xc++);
+ colCount = colCount || this.colCount;
+
+ var tileGridContent = this.tileGridContent_;
+
+ tileElements = tileElements || this.tileElements_;
+
+ var tileCount = tileElements.length;
+ var rowCount = Math.ceil(tileCount / colCount);
+
+ var tileRows = tileGridContent.getElementsByClassName('tile-row');
+ var tileRow;
+ var tileRowTiles;
+ var tileCell;
+ var tileElement;
+ var maxColCount;
+
+ var numOfVisibleRows = this.getNumOfVisibleRows();
+ var pageOffset = this.pageOffset;
+
+ for (var tile = 0, row = 0; row < rowCount; row++) {
+ // Get the current tile row.
+ tileRow = tileRows[row];
+
+ // Create tile row if there's no one yet.
+ if (!tileRow) {
+ tileRow = cr.doc.createElement('div');
+ tileRow.className = 'tile-row tile-row-' + row;// TODO do we need id?
+ tileGridContent.appendChild(tileRow);
+ }
+
+ // Adjust row visibility.
+ var rowVisible = (row >= pageOffset &&
+ row <= (pageOffset + numOfVisibleRows - 1));
+ tileRow.classList[rowVisible ? 'remove' : 'add']('hide-row');
+
+ // The tiles inside the current row.
+ tileRowTiles = tileRow.childNodes;
+
+ // Remove excessive columns from a particular tile row.
+ maxColCount = Math.min(colCount, tileCount - tile);
+ while (tileRowTiles.length > maxColCount) {
+ tileRow.removeChild(tileRow.lastElementChild);
+ }
+
+ // For each column in the current row.
+ for (var col = 0; /*tile < tileCount &&*/ col < colCount; col++, tile++) {
+
+ if (tileRowTiles[col]) {
+ tileCell = tileRowTiles[col];
+ } else {
+ var span = cr.doc.createElement('span');
+ tileCell = new TileCell(span, this.gridValues_);
+ }
+
+ // Reset column class.
+ this.resetTileCol_(tileCell, col);
+
+ // Render Tiles.
+ if (tile < tileCount) {
+ tileCell.classList.remove('filler');
+ tileElement = tileElements[tile];
+ if (tileCell.firstChild) {
+ if (tileElement != tileCell.firstChild) {
+ tileCell.replaceChild(tileElement, tileCell.firstChild);
+ }
+ } else {
+ tileCell.appendChild(tileElement);
+ }
+ } else {
+ // TODO create filler and method to
+ if (!tileCell.classList.contains('filler')) {
+ tileCell.classList.add('filler');
+ tileElement = cr.doc.createElement('span');
+ 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;
+ },
+
+ layout_: function() {
+ var pageList = $('page-list');
+ var panel = this.content_;
+ var menu = $('page-list-menu');
+ var tileGrid = this.tileGrid_;
+ var tileGridContent = this.tileGridContent_;
+ var tileRows = tileGridContent.getElementsByClassName('tile-row');
+
+ tileGridContent.classList.add('animate-tile');
+
+ var bottomPanelWidth = this.getBottomPanelWidth_();
+ var colCount = this.getColCountForWidth_(bottomPanelWidth);
+ var lastColCount = this.colCount;
+ var animatingColCount = this.animatingColCount;
+
+ var windowHeight = cr.doc.documentElement.clientHeight;
+
+ // TODO better handling of height state
+ // changeVisibleRows
+ // TODO need to call paginate when height changes
+ var numOfVisibleRows = this.numOfVisibleRows;
+ if (windowHeight > 500) {
+ numOfVisibleRows = 2;
+ } else {
+ numOfVisibleRows = 1;
+ }
+
+ if (numOfVisibleRows != this.numOfVisibleRows) {
+ this.numOfVisibleRows = numOfVisibleRows;
+ this.paginate(null, true);
+ pageList.style.height = (107 * numOfVisibleRows) + 'px';
+ //tileGrid.style.height = (107 * numOfVisibleRows) + 'px';
+ }
+
+ // changeVisibleCols
+ if (colCount != animatingColCount) {
+
+ var newWidth = this.getWidthForColCount_(colCount);
+ if (colCount > animatingColCount) {
+
+ // TODO actual size check
+ if (colCount != lastColCount) {
+ this.renderGrid_(colCount);
+ //this.renderTiles_(colCount);
+ }
+
+ this.showTileCols_(animatingColCount, false);
+
+ var self = this;
+ setTimeout((function(animatingColCount){
+ return function(){
+ self.showTileCols_(animatingColCount, true);
+ }
+ })(animatingColCount), 0);
+
+ } else {
+ this.showTileCols_(colCount, false);
+ }
+
+ tileGrid.style.width = newWidth + 'px';
+ menu.style.width = newWidth + 'px';
+
+ // TODO: listen animateEnd
+ var self = this;
+ this.onTileGridAnimationEndHandler_ = function(){
+ if (colCount < lastColCount) {
+ self.renderGrid_(colCount);
+ //self.renderTiles_(colCount);
+ } else {
+ self.showTileCols_(0, true);
+ }
+ };
+
+ this.paginate();
+
+ }
+
+ panel.style.width = bottomPanelWidth + 'px';
+
+ this.animatingColCount = colCount;
+ },
+
+ // animation helpers
+ // -------------------------------------------------------------------------
+
+ // TODO make it local?
+ showTileCols_: function(col, show) {
+ var prop = show ? 'remove' : 'add';
+ var max = 10; // TODO(pedrosimonetti) const?
+ var tileGridContent = this.tileGridContent_;
+ for (var i = col; i < max; i++) {
+ tileGridContent.classList[prop]('hide-col-' + i);
+ }
+ },
+
+ // TODO make it local?
+ resetTileCol_: function(tileCell, col) {
+ var max = 10;
+ for (var i = 0; i < max; i++) {
+ if (i != col) {
+ tileCell.classList.remove('tile-col-' + i);
+ }
+ }
+ tileCell.classList.add('tile-col-' + col);
+ },
+
+ // pagination
+ // -------------------------------------------------------------------------
+
+ paginate: function(pageOffset, force) {
+ var numOfVisibleRows = this.getNumOfVisibleRows();
+ var pageOffset = typeof pageOffset == 'number' ?
+ pageOffset : this.pageOffset;
+
+ pageOffset = Math.max(0, pageOffset);
+ pageOffset = Math.min(this.rowCount - numOfVisibleRows, pageOffset);
+
+ if (pageOffset != this.pageOffset || force) {
+ var rows = this.tileGridContent_.getElementsByClassName('tile-row');
+ for (var i = 0, l = rows.length; i < l; i++) {
+ var row = rows[i];
+ if (i >= pageOffset && i <= (pageOffset + numOfVisibleRows - 1)) {
+ row.classList.remove('hide-row');
+ } else {
+ row.classList.add('hide-row');
+ }
+ }
+
+ this.pageOffset = pageOffset;
+ this.tileGridContent_.style.webkitTransform = 'translate3d(0, ' +
+ (-pageOffset * 106)+ 'px, 0)';
+ }
+ },
+
+ // event handlers
+ // -------------------------------------------------------------------------
+
+ onResize_: function() {
+ this.layout_();
+ },
+
+ onTileGridAnimationEnd_: function() {
+ // TODO figure out how to cleanup each kind animation properly
+ //console.log('AnimationEnd', event.target.className, event.target, event);
+ //console.log('-----------------------------------------------------------');
+ if (event.target == this.tileGrid_ &&
+ this.onTileGridAnimationEndHandler_) {
+ if (this.tileGridContent_.classList.contains('animate-tile')) {
+ this.onTileGridAnimationEndHandler_();
+ //this.tileGridContent_.classList.remove('animate-tile')
+ }
+ }
+ },
+
+ onKeyDown_: function(e) {
+ var pageOffset = this.pageOffset;
+
+ var keyCode = e.keyCode;
+ if (keyCode == 40 /* down */ ) {
+ pageOffset++;
+ } else if (keyCode == 38 /* up */ ) {
+ pageOffset--;
+ }
+
+ if (pageOffset != this.pageOffset) {
+ this.paginate(pageOffset);
+ }
+ }
+
+ };
+
+
+ var duration = 200;
+ var defaultDuration = 200;
+ var animatedProperties = [
+ '#card-slider-frame .tile-grid',
+ '#card-slider-frame .tile-grid-content',
+ '#card-slider-frame .tile-row',
+ '.animate-tile .tile-cell',
+ '.debug .animate-tile .tile-cell'
+ ];
+
+ function adjustAnimationTiming(slownessFactor, selectors) {
+ slownessFactor = slownessFactor || 1;
+ duration = defaultDuration * slownessFactor;
+ for (var i = 0, l = selectors.length; i < l; i++) {
+ var selector = selectors[i];
+ var rule = findCSSRule(selector);
+ if (rule) {
+ rule.style.webkitTransitionDuration = duration + 'ms';
+ } else {
+ throw 'Could not find the CSS rule "' + selector + '"';
+ }
+ }
+ }
+
+ function findCSSRule(selectorText){
+ var rules = document.styleSheets[0].rules;
+ for (var i = 0, l = rules.length; i < l; i++) {
+ var rule = rules[i];
+ if (rule.selectorText == selectorText)
+ {
+ return rule;
+ }
+ }
+ }
+
+ function changeSlowFactor(el) {
+ if (el.value)
+ adjustAnimationTiming(el.value-0, animatedProperties);
+ }
+
+ function changeDebugMode(el) {
+ var prop = el.checked ? 'add' : 'remove';
+ document.body.classList[prop]('debug');
+ }
+
+ return {
+ // TODO(xci) drag
+ //getCurrentlyDraggingTile2: getCurrentlyDraggingTile,
+ //setCurrentDropEffect2: setCurrentDropEffect,
+ Tile2: Tile,
+ TilePage2: TilePage,
+ };
+});

Powered by Google App Engine
This is Rietveld 408576698