summaryrefslogtreecommitdiffstats
path: root/application/palemoon/base/content/newtab/grid.js
diff options
context:
space:
mode:
Diffstat (limited to 'application/palemoon/base/content/newtab/grid.js')
-rw-r--r--application/palemoon/base/content/newtab/grid.js217
1 files changed, 154 insertions, 63 deletions
diff --git a/application/palemoon/base/content/newtab/grid.js b/application/palemoon/base/content/newtab/grid.js
index fbeb7bd27..be5a57c4b 100644
--- a/application/palemoon/base/content/newtab/grid.js
+++ b/application/palemoon/base/content/newtab/grid.js
@@ -5,6 +5,12 @@
#endif
/**
+ * Define various fixed dimensions
+ */
+const GRID_BOTTOM_EXTRA = 7; // title's line-height extends 7px past the margin
+const GRID_WIDTH_EXTRA = 1; // provide 1px buffer to allow for rounding error
+
+/**
* This singleton represents the grid that contains all sites.
*/
var gGrid = {
@@ -12,6 +18,7 @@ var gGrid = {
* The DOM node of the grid.
*/
_node: null,
+ _gridDefaultContent: null,
get node() { return this._node; },
/**
@@ -22,7 +29,7 @@ var gGrid = {
/**
* All cells contained in the grid.
*/
- _cells: null,
+ _cells: [],
get cells() { return this._cells; },
/**
@@ -31,7 +38,10 @@ var gGrid = {
get sites() { return [for (cell of this.cells) cell.site]; },
// Tells whether the grid has already been initialized.
- get ready() { return !!this._node; },
+ get ready() { return !!this._ready; },
+
+ // Returns whether the page has finished loading yet.
+ get isDocumentLoaded() { return document.readyState == "complete"; },
/**
* Initializes the grid.
@@ -39,8 +49,26 @@ var gGrid = {
*/
init: function Grid_init() {
this._node = document.getElementById("newtab-grid");
+ this._gridDefaultContent = this._node.lastChild;
this._createSiteFragment();
- this._render();
+
+ gLinks.populateCache(() => {
+ this._refreshGrid();
+ this._ready = true;
+
+ // If fetching links took longer than loading the page itself then
+ // we need to resize the grid as that was blocked until now.
+ // We also want to resize now if the page was already loaded when
+ // initializing the grid (the user toggled the page).
+ this._resizeGrid();
+
+ addEventListener("resize", this);
+ });
+
+ // Resize the grid as soon as the page loads.
+ if (!this.isDocumentLoaded) {
+ addEventListener("load", this);
+ }
},
/**
@@ -56,25 +84,15 @@ var gGrid = {
},
/**
- * Refreshes the grid and re-creates all sites.
+ * Handles all grid events.
*/
- refresh: function Grid_refresh() {
- let cells = this.cells;
- if (!cells) {
- return;
+ handleEvent: function Grid_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load":
+ case "resize":
+ this._resizeGrid();
+ break;
}
-
- // Remove all sites.
- cells.forEach(function (cell) {
- let node = cell.node;
- let child = node.firstElementChild;
-
- if (child)
- node.removeChild(child);
- }, this);
-
- // Render the grid again.
- this._render();
},
/**
@@ -92,34 +110,62 @@ var gGrid = {
},
/**
- * Creates the newtab grid.
+ * Renders and resizes the gird. _resizeGrid() call is needed to ensure
+ * that scrollbar disappears when the bottom row becomes empty following
+ * the block action, or tile display is turmed off via cog menu
+ */
+
+ refresh() {
+ this._refreshGrid();
+ this._resizeGrid();
+ },
+
+ /**
+ * Renders the grid, including cells and sites.
*/
- _renderGrid: function Grid_renderGrid() {
- let row = document.createElementNS(HTML_NAMESPACE, "div");
+ _refreshGrid() {
let cell = document.createElementNS(HTML_NAMESPACE, "div");
- row.classList.add("newtab-row");
cell.classList.add("newtab-cell");
- // Clear the grid
- this._node.innerHTML = "";
-
- // Creates the structure of one row
- for (let i = 0; i < gGridPrefs.gridColumns; i++) {
- row.appendChild(cell.cloneNode(true));
+ // Creates all the cells up to the maximum
+ let fragment = document.createDocumentFragment();
+ for (let i = 0; i < gGridPrefs.gridColumns * gGridPrefs.gridRows; i++) {
+ fragment.appendChild(cell.cloneNode(true));
}
- // Creates the grid
- for (let j = 0; j < gGridPrefs.gridRows; j++) {
- this._node.appendChild(row.cloneNode(true));
+
+ // Create cells.
+ let cells = Array.from(fragment.childNodes, (cell) => new Cell(this, cell));
+
+ // Fetch links.
+ let links = gLinks.getLinks();
+
+ // Create sites.
+ let numLinks = Math.min(links.length, cells.length);
+ let hasHistoryTiles = false;
+ for (let i = 0; i < numLinks; i++) {
+ if (links[i]) {
+ this.createSite(links[i], cells[i]);
+ if (links[i].type == "history") {
+ hasHistoryTiles = true;
+ }
+ }
}
- // (Re-)initialize all cells.
- let cellElements = this.node.querySelectorAll(".newtab-cell");
- // Tycho: this._cells = [new Cell(this, cell) for (cell of cellElements)];
- this._cells = [];
-
- for (let cellItem of cellElements) {
- this._cells.push(new Cell(this, cellItem));
+ this._cells = cells;
+ while (this._gridDefaultContent.nextSibling) {
+ this._gridDefaultContent.nextSibling.remove();
}
+ this._node.appendChild(fragment);
+ },
+
+ /**
+ * Calculate the height for a number of rows up to the maximum rows
+ * @param rows Number of rows defaulting to the max
+ */
+ _computeHeight: function Grid_computeHeight(aRows) {
+ let {gridRows} = gGridPrefs;
+ aRows = aRows === undefined ? gridRows : Math.min(gridRows, aRows);
+ return aRows * this._cellHeight + GRID_BOTTOM_EXTRA;
},
/**
@@ -133,7 +179,8 @@ var gGrid = {
// Create the site's inner HTML code.
site.innerHTML =
'<a class="newtab-link">' +
- ' <span class="newtab-thumbnail"/>' +
+ ' <span class="newtab-thumbnail placeholder"/>' +
+ ' <span class="newtab-thumbnail thumbnail"/>' +
' <span class="newtab-title"/>' +
'</a>' +
'<input type="button" title="' + newTabString("pin") + '"' +
@@ -146,36 +193,80 @@ var gGrid = {
},
/**
- * Renders the sites, creates all sites and puts them into their cells.
+ * Test a tile at a given position for being pinned or history
+ * @param position Position in sites array
*/
- _renderSites: function Grid_renderSites() {
- let cells = this.cells;
- // Put sites into the cells.
- let links = gLinks.getLinks();
- let length = Math.min(links.length, cells.length);
-
- for (let i = 0; i < length; i++) {
- if (links[i])
- this.createSite(links[i], cells[i]);
- }
+ _isHistoricalTile: function Grid_isHistoricalTile(aPos) {
+ let site = this.sites[aPos];
+ return site && (site.isPinned() || site.link && site.link.type == "history");
},
/**
- * Renders the grid.
+ * Make sure the correct number of rows and columns are visible
*/
- _render: function Grid_render() {
- if (this._shouldRenderGrid()) {
- this._renderGrid();
+ _resizeGrid: function Grid_resizeGrid() {
+ // If we're somehow called before the page has finished loading,
+ // let's bail out to avoid caching zero heights and widths.
+ // We'll be called again when DOMContentLoaded fires.
+ // Same goes for the grid if that's not ready yet.
+ if (!this.isDocumentLoaded || !this._ready) {
+ return;
}
- this._renderSites();
- },
+ // Save the cell's computed height/width including margin and border
+ if (this._cellHeight === undefined) {
+ let refCell = document.querySelector(".newtab-cell");
+ let style = getComputedStyle(refCell);
+ this._cellHeight = refCell.offsetHeight +
+ parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ this._cellWidth = refCell.offsetWidth +
+ parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+ }
+
+ let searchContainer = document.querySelector("#searchContainer");
+ // Save search-container margin height
+ if (this._searchContainerMargin === undefined) {
+ let style = getComputedStyle(searchContainer);
+ this._searchContainerMargin = parseFloat(style.marginBottom) +
+ parseFloat(style.marginTop);
+ }
+
+ // Find the number of rows we can place into view port
+ let availHeight = document.documentElement.clientHeight -
+ searchContainer.offsetHeight - this._searchContainerMargin;
+ let visibleRows = Math.floor(availHeight / this._cellHeight);
+
+ // Find the number of columns that fit into view port
+ let maxGridWidth = gGridPrefs.gridColumns * this._cellWidth + GRID_WIDTH_EXTRA;
+ // available width is current grid width, but no greater than maxGridWidth
+ let availWidth = Math.min(document.querySelector("#newtab-grid").clientWidth,
+ maxGridWidth);
+ // finally get the number of columns we can fit into view port
+ let gridColumns = Math.floor(availWidth / this._cellWidth);
+ // walk sites backwords until a pinned or history tile is found or visibleRows reached
+ let tileIndex = Math.min(gGridPrefs.gridRows * gridColumns, this.sites.length) - 1;
+ while (tileIndex >= visibleRows * gridColumns) {
+ if (this._isHistoricalTile(tileIndex)) {
+ break;
+ }
+ tileIndex--;
+ }
- _shouldRenderGrid : function Grid_shouldRenderGrid() {
- let rowsLength = this._node.querySelectorAll(".newtab-row").length;
- let cellsLength = this._node.querySelectorAll(".newtab-cell").length;
+ // Compute the actual number of grid rows we will display (potentially
+ // with a scroll bar). tileIndex now points to a historical tile with
+ // heighest index or to the last index of the visible row, if none found
+ // Dividing tileIndex by number of tiles in a column gives the rows
+ let gridRows = Math.floor(tileIndex / gridColumns) + 1;
- return (rowsLength != gGridPrefs.gridRows ||
- cellsLength != (gGridPrefs.gridRows * gGridPrefs.gridColumns));
+ // we need to set grid width, for otherwise the scrollbar may shrink
+ // the grid when shown and cause grid layout to be different from
+ // what being computed above. This, in turn, may cause scrollbar shown
+ // for directory tiles, and introduce jitter when grid width is aligned
+ // exactly on the column boundary
+ this._node.style.width = gridColumns * this._cellWidth + "px";
+ this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
+ GRID_WIDTH_EXTRA + "px";
+ this._node.style.height = this._computeHeight() + "px";
+ this._node.style.maxHeight = this._computeHeight(gridRows) + "px";
}
};