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 provides functionality to update the current grid to a new michael@0: * set of pinned and blocked sites. It adds, moves and removes sites. michael@0: */ michael@0: let gUpdater = { michael@0: /** michael@0: * Updates the current grid according to its pinned and blocked sites. michael@0: * This removes old, moves existing and creates new sites to fill gaps. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: updateGrid: function Updater_updateGrid(aCallback) { michael@0: let links = gLinks.getLinks().slice(0, gGrid.cells.length); michael@0: michael@0: // Find all sites that remain in the grid. michael@0: let sites = this._findRemainingSites(links); michael@0: michael@0: let self = this; michael@0: michael@0: // Remove sites that are no longer in the grid. michael@0: this._removeLegacySites(sites, function () { michael@0: // Freeze all site positions so that we can move their DOM nodes around michael@0: // without any visual impact. michael@0: self._freezeSitePositions(sites); michael@0: michael@0: // Move the sites' DOM nodes to their new position in the DOM. This will michael@0: // have no visual effect as all the sites have been frozen and will michael@0: // remain in their current position. michael@0: self._moveSiteNodes(sites); michael@0: michael@0: // Now it's time to animate the sites actually moving to their new michael@0: // positions. michael@0: self._rearrangeSites(sites, function () { michael@0: // Try to fill empty cells and finish. michael@0: self._fillEmptyCells(links, aCallback); michael@0: michael@0: // Update other pages that might be open to keep them synced. michael@0: gAllPages.update(gPage); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Takes an array of links and tries to correlate them to sites contained in michael@0: * the current grid. If no corresponding site can be found (i.e. the link is michael@0: * new and a site will be created) then just set it to null. michael@0: * @param aLinks The array of links to find sites for. michael@0: * @return Array of sites mapped to the given links (can contain null values). michael@0: */ michael@0: _findRemainingSites: function Updater_findRemainingSites(aLinks) { michael@0: let map = {}; michael@0: michael@0: // Create a map to easily retrieve the site for a given URL. michael@0: gGrid.sites.forEach(function (aSite) { michael@0: if (aSite) michael@0: map[aSite.url] = aSite; michael@0: }); michael@0: michael@0: // Map each link to its corresponding site, if any. michael@0: return aLinks.map(function (aLink) { michael@0: return aLink && (aLink.url in map) && map[aLink.url]; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Freezes the given sites' positions. michael@0: * @param aSites The array of sites to freeze. michael@0: */ michael@0: _freezeSitePositions: function Updater_freezeSitePositions(aSites) { michael@0: aSites.forEach(function (aSite) { michael@0: if (aSite) michael@0: gTransformation.freezeSitePosition(aSite); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Moves the given sites' DOM nodes to their new positions. michael@0: * @param aSites The array of sites to move. michael@0: */ michael@0: _moveSiteNodes: function Updater_moveSiteNodes(aSites) { michael@0: let cells = gGrid.cells; michael@0: michael@0: // Truncate the given array of sites to not have more sites than cells. michael@0: // This can happen when the user drags a bookmark (or any other new kind michael@0: // of link) onto the grid. michael@0: let sites = aSites.slice(0, cells.length); michael@0: michael@0: sites.forEach(function (aSite, aIndex) { michael@0: let cell = cells[aIndex]; michael@0: let cellSite = cell.site; michael@0: michael@0: // The site's position didn't change. michael@0: if (!aSite || cellSite != aSite) { michael@0: let cellNode = cell.node; michael@0: michael@0: // Empty the cell if necessary. michael@0: if (cellSite) michael@0: cellNode.removeChild(cellSite.node); michael@0: michael@0: // Put the new site in place, if any. michael@0: if (aSite) michael@0: cellNode.appendChild(aSite.node); michael@0: } michael@0: }, this); michael@0: }, michael@0: michael@0: /** michael@0: * Rearranges the given sites and slides them to their new positions. michael@0: * @param aSites The array of sites to re-arrange. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: _rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) { michael@0: let options = {callback: aCallback, unfreeze: true}; michael@0: gTransformation.rearrangeSites(aSites, options); michael@0: }, michael@0: michael@0: /** michael@0: * Removes all sites from the grid that are not in the given links array or michael@0: * exceed the grid. michael@0: * @param aSites The array of sites remaining in the grid. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: _removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) { michael@0: let batch = []; michael@0: michael@0: // Delete sites that were removed from the grid. michael@0: gGrid.sites.forEach(function (aSite) { michael@0: // The site must be valid and not in the current grid. michael@0: if (!aSite || aSites.indexOf(aSite) != -1) michael@0: return; michael@0: michael@0: let deferred = Promise.defer(); michael@0: batch.push(deferred.promise); michael@0: michael@0: // Fade out the to-be-removed site. michael@0: gTransformation.hideSite(aSite, function () { michael@0: let node = aSite.node; michael@0: michael@0: // Remove the site from the DOM. michael@0: node.parentNode.removeChild(node); michael@0: deferred.resolve(); michael@0: }); michael@0: }); michael@0: michael@0: Promise.all(batch).then(aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * Tries to fill empty cells with new links if available. michael@0: * @param aLinks The array of links. michael@0: * @param aCallback The callback to call when finished. michael@0: */ michael@0: _fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) { michael@0: let {cells, sites} = gGrid; michael@0: let batch = []; michael@0: michael@0: // Find empty cells and fill them. michael@0: sites.forEach(function (aSite, aIndex) { michael@0: if (aSite || !aLinks[aIndex]) michael@0: return; michael@0: michael@0: let deferred = Promise.defer(); michael@0: batch.push(deferred.promise); michael@0: michael@0: // Create the new site and fade it in. michael@0: let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]); michael@0: michael@0: // Set the site's initial opacity to zero. michael@0: site.node.style.opacity = 0; michael@0: michael@0: // Flush all style changes for the dynamically inserted site to make michael@0: // the fade-in transition work. michael@0: window.getComputedStyle(site.node).opacity; michael@0: gTransformation.showSite(site, function () deferred.resolve()); michael@0: }); michael@0: michael@0: Promise.all(batch).then(aCallback); michael@0: } michael@0: };