michael@0: #ifdef 0 michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #endif michael@0: michael@0: /** michael@0: * This singleton allows to transform the grid by repositioning a site's node michael@0: * in the DOM and by showing or hiding the node. It additionally provides michael@0: * convenience methods to work with a site's DOM node. michael@0: */ michael@0: let gTransformation = { michael@0: /** michael@0: * Returns the width of the left and top border of a cell. We need to take it michael@0: * into account when measuring and comparing site and cell positions. michael@0: */ michael@0: get _cellBorderWidths() { michael@0: let cstyle = window.getComputedStyle(gGrid.cells[0].node, null); michael@0: let widths = { michael@0: left: parseInt(cstyle.getPropertyValue("border-left-width")), michael@0: top: parseInt(cstyle.getPropertyValue("border-top-width")) michael@0: }; michael@0: michael@0: // Cache this value, overwrite the getter. michael@0: Object.defineProperty(this, "_cellBorderWidths", michael@0: {value: widths, enumerable: true}); michael@0: michael@0: return widths; michael@0: }, michael@0: michael@0: /** michael@0: * Gets a DOM node's position. michael@0: * @param aNode The DOM node. michael@0: * @return A Rect instance with the position. michael@0: */ michael@0: getNodePosition: function Transformation_getNodePosition(aNode) { michael@0: let {left, top, width, height} = aNode.getBoundingClientRect(); michael@0: return new Rect(left + scrollX, top + scrollY, width, height); michael@0: }, michael@0: michael@0: /** michael@0: * Fades a given node from zero to full opacity. michael@0: * @param aNode The node to fade. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) { michael@0: this._setNodeOpacity(aNode, 1, function () { michael@0: // Clear the style property. michael@0: aNode.style.opacity = ""; michael@0: michael@0: if (aCallback) michael@0: aCallback(); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Fades a given node from full to zero opacity. michael@0: * @param aNode The node to fade. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) { michael@0: this._setNodeOpacity(aNode, 0, aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * Fades a given site from zero to full opacity. michael@0: * @param aSite The site to fade. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: showSite: function Transformation_showSite(aSite, aCallback) { michael@0: this.fadeNodeIn(aSite.node, aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * Fades a given site from full to zero opacity. michael@0: * @param aSite The site to fade. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: hideSite: function Transformation_hideSite(aSite, aCallback) { michael@0: this.fadeNodeOut(aSite.node, aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * Allows to set a site's position. michael@0: * @param aSite The site to re-position. michael@0: * @param aPosition The desired position for the given site. michael@0: */ michael@0: setSitePosition: function Transformation_setSitePosition(aSite, aPosition) { michael@0: let style = aSite.node.style; michael@0: let {top, left} = aPosition; michael@0: michael@0: style.top = top + "px"; michael@0: style.left = left + "px"; michael@0: }, michael@0: michael@0: /** michael@0: * Freezes a site in its current position by positioning it absolute. michael@0: * @param aSite The site to freeze. michael@0: */ michael@0: freezeSitePosition: function Transformation_freezeSitePosition(aSite) { michael@0: if (this._isFrozen(aSite)) michael@0: return; michael@0: michael@0: let style = aSite.node.style; michael@0: let comp = getComputedStyle(aSite.node, null); michael@0: style.width = comp.getPropertyValue("width") michael@0: style.height = comp.getPropertyValue("height"); michael@0: michael@0: aSite.node.setAttribute("frozen", "true"); michael@0: this.setSitePosition(aSite, this.getNodePosition(aSite.node)); michael@0: }, michael@0: michael@0: /** michael@0: * Unfreezes a site by removing its absolute positioning. michael@0: * @param aSite The site to unfreeze. michael@0: */ michael@0: unfreezeSitePosition: function Transformation_unfreezeSitePosition(aSite) { michael@0: if (!this._isFrozen(aSite)) michael@0: return; michael@0: michael@0: let style = aSite.node.style; michael@0: style.left = style.top = style.width = style.height = ""; michael@0: aSite.node.removeAttribute("frozen"); michael@0: }, michael@0: michael@0: /** michael@0: * Slides the given site to the target node's position. michael@0: * @param aSite The site to move. michael@0: * @param aTarget The slide target. michael@0: * @param aOptions Set of options (see below). michael@0: * unfreeze - unfreeze the site after sliding michael@0: * callback - the callback to call when finished michael@0: */ michael@0: slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) { michael@0: let currentPosition = this.getNodePosition(aSite.node); michael@0: let targetPosition = this.getNodePosition(aTarget.node) michael@0: let callback = aOptions && aOptions.callback; michael@0: michael@0: let self = this; michael@0: michael@0: function finish() { michael@0: if (aOptions && aOptions.unfreeze) michael@0: self.unfreezeSitePosition(aSite); michael@0: michael@0: if (callback) michael@0: callback(); michael@0: } michael@0: michael@0: // We need to take the width of a cell's border into account. michael@0: targetPosition.left += this._cellBorderWidths.left; michael@0: targetPosition.top += this._cellBorderWidths.top; michael@0: michael@0: // Nothing to do here if the positions already match. michael@0: if (currentPosition.left == targetPosition.left && michael@0: currentPosition.top == targetPosition.top) { michael@0: finish(); michael@0: } else { michael@0: this.setSitePosition(aSite, targetPosition); michael@0: this._whenTransitionEnded(aSite.node, ["left", "top"], finish); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Rearranges a given array of sites and moves them to their new positions or michael@0: * fades in/out new/removed sites. michael@0: * @param aSites An array of sites to rearrange. michael@0: * @param aOptions Set of options (see below). michael@0: * unfreeze - unfreeze the site after rearranging michael@0: * callback - the callback to call when finished michael@0: */ michael@0: rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) { michael@0: let batch = []; michael@0: let cells = gGrid.cells; michael@0: let callback = aOptions && aOptions.callback; michael@0: let unfreeze = aOptions && aOptions.unfreeze; michael@0: michael@0: aSites.forEach(function (aSite, aIndex) { michael@0: // Do not re-arrange empty cells or the dragged site. michael@0: if (!aSite || aSite == gDrag.draggedSite) michael@0: return; michael@0: michael@0: let deferred = Promise.defer(); michael@0: batch.push(deferred.promise); michael@0: let cb = deferred.resolve; michael@0: michael@0: if (!cells[aIndex]) michael@0: // The site disappeared from the grid, hide it. michael@0: this.hideSite(aSite, cb); michael@0: else if (this._getNodeOpacity(aSite.node) != 1) michael@0: // The site disappeared before but is now back, show it. michael@0: this.showSite(aSite, cb); michael@0: else michael@0: // The site's position has changed, move it around. michael@0: this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb}); michael@0: }, this); michael@0: michael@0: if (callback) { michael@0: Promise.all(batch).then(callback); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Listens for the 'transitionend' event on a given node and calls the given michael@0: * callback. michael@0: * @param aNode The node that is transitioned. michael@0: * @param aProperties The properties we'll wait to be transitioned. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: _whenTransitionEnded: michael@0: function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) { michael@0: michael@0: let props = new Set(aProperties); michael@0: aNode.addEventListener("transitionend", function onEnd(e) { michael@0: if (props.has(e.propertyName)) { michael@0: aNode.removeEventListener("transitionend", onEnd); michael@0: aCallback(); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Gets a given node's opacity value. michael@0: * @param aNode The node to get the opacity value from. michael@0: * @return The node's opacity value. michael@0: */ michael@0: _getNodeOpacity: function Transformation_getNodeOpacity(aNode) { michael@0: let cstyle = window.getComputedStyle(aNode, null); michael@0: return cstyle.getPropertyValue("opacity"); michael@0: }, michael@0: michael@0: /** michael@0: * Sets a given node's opacity. michael@0: * @param aNode The node to set the opacity value for. michael@0: * @param aOpacity The opacity value to set. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: _setNodeOpacity: michael@0: function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) { michael@0: michael@0: if (this._getNodeOpacity(aNode) == aOpacity) { michael@0: if (aCallback) michael@0: aCallback(); michael@0: } else { michael@0: if (aCallback) { michael@0: this._whenTransitionEnded(aNode, ["opacity"], aCallback); michael@0: } michael@0: michael@0: aNode.style.opacity = aOpacity; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Moves a site to the cell with the given index. michael@0: * @param aSite The site to move. michael@0: * @param aIndex The target cell's index. michael@0: * @param aOptions Options that are directly passed to slideSiteTo(). michael@0: */ michael@0: _moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) { michael@0: this.freezeSitePosition(aSite); michael@0: this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions); michael@0: }, michael@0: michael@0: /** michael@0: * Checks whether a site is currently frozen. michael@0: * @param aSite The site to check. michael@0: * @return Whether the given site is frozen. michael@0: */ michael@0: _isFrozen: function Transformation_isFrozen(aSite) { michael@0: return aSite.node.hasAttribute("frozen"); michael@0: } michael@0: };