initial commit
This commit is contained in:
248
themes/gallery/assets/js/justified-layout/index.js
Normal file
248
themes/gallery/assets/js/justified-layout/index.js
Normal file
@@ -0,0 +1,248 @@
|
||||
/*!
|
||||
* Copyright 2019 SmugMug, Inc.
|
||||
* Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
|
||||
* @license
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var Row = require('./row');
|
||||
|
||||
/**
|
||||
* Create a new, empty row.
|
||||
*
|
||||
* @method createNewRow
|
||||
* @param layoutConfig {Object} The layout configuration
|
||||
* @param layoutData {Object} The current state of the layout
|
||||
* @return A new, empty row of the type specified by this layout.
|
||||
*/
|
||||
|
||||
function createNewRow(layoutConfig, layoutData) {
|
||||
|
||||
var isBreakoutRow;
|
||||
|
||||
// Work out if this is a full width breakout row
|
||||
if (layoutConfig.fullWidthBreakoutRowCadence !== false) {
|
||||
if (((layoutData._rows.length + 1) % layoutConfig.fullWidthBreakoutRowCadence) === 0) {
|
||||
isBreakoutRow = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new Row({
|
||||
top: layoutData._containerHeight,
|
||||
left: layoutConfig.containerPadding.left,
|
||||
width: layoutConfig.containerWidth - layoutConfig.containerPadding.left - layoutConfig.containerPadding.right,
|
||||
spacing: layoutConfig.boxSpacing.horizontal,
|
||||
targetRowHeight: layoutConfig.targetRowHeight,
|
||||
targetRowHeightTolerance: layoutConfig.targetRowHeightTolerance,
|
||||
edgeCaseMinRowHeight: 0.5 * layoutConfig.targetRowHeight,
|
||||
edgeCaseMaxRowHeight: 2 * layoutConfig.targetRowHeight,
|
||||
rightToLeft: false,
|
||||
isBreakoutRow: isBreakoutRow,
|
||||
widowLayoutStyle: layoutConfig.widowLayoutStyle
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a completed row to the layout.
|
||||
* Note: the row must have already been completed.
|
||||
*
|
||||
* @method addRow
|
||||
* @param layoutConfig {Object} The layout configuration
|
||||
* @param layoutData {Object} The current state of the layout
|
||||
* @param row {Row} The row to add.
|
||||
* @return {Array} Each item added to the row.
|
||||
*/
|
||||
|
||||
function addRow(layoutConfig, layoutData, row) {
|
||||
|
||||
layoutData._rows.push(row);
|
||||
layoutData._layoutItems = layoutData._layoutItems.concat(row.getItems());
|
||||
|
||||
// Increment the container height
|
||||
layoutData._containerHeight += row.height + layoutConfig.boxSpacing.vertical;
|
||||
|
||||
return row.items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the current layout for all items in the list that require layout.
|
||||
* "Layout" means geometry: position within container and size
|
||||
*
|
||||
* @method computeLayout
|
||||
* @param layoutConfig {Object} The layout configuration
|
||||
* @param layoutData {Object} The current state of the layout
|
||||
* @param itemLayoutData {Array} Array of items to lay out, with data required to lay out each item
|
||||
* @return {Object} The newly-calculated layout, containing the new container height, and lists of layout items
|
||||
*/
|
||||
|
||||
function computeLayout(layoutConfig, layoutData, itemLayoutData) {
|
||||
|
||||
var laidOutItems = [],
|
||||
itemAdded,
|
||||
currentRow,
|
||||
nextToLastRowHeight;
|
||||
|
||||
// Apply forced aspect ratio if specified, and set a flag.
|
||||
if (layoutConfig.forceAspectRatio) {
|
||||
itemLayoutData.forEach(function (itemData) {
|
||||
itemData.forcedAspectRatio = true;
|
||||
itemData.aspectRatio = layoutConfig.forceAspectRatio;
|
||||
});
|
||||
}
|
||||
|
||||
// Loop through the items
|
||||
itemLayoutData.some(function (itemData, i) {
|
||||
|
||||
if (isNaN(itemData.aspectRatio)) {
|
||||
throw new Error("Item " + i + " has an invalid aspect ratio");
|
||||
}
|
||||
|
||||
// If not currently building up a row, make a new one.
|
||||
if (!currentRow) {
|
||||
currentRow = createNewRow(layoutConfig, layoutData);
|
||||
}
|
||||
|
||||
// Attempt to add item to the current row.
|
||||
itemAdded = currentRow.addItem(itemData);
|
||||
|
||||
if (currentRow.isLayoutComplete()) {
|
||||
|
||||
// Row is filled; add it and start a new one
|
||||
laidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));
|
||||
|
||||
if (layoutData._rows.length >= layoutConfig.maxNumRows) {
|
||||
currentRow = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
currentRow = createNewRow(layoutConfig, layoutData);
|
||||
|
||||
// Item was rejected; add it to its own row
|
||||
if (!itemAdded) {
|
||||
|
||||
itemAdded = currentRow.addItem(itemData);
|
||||
|
||||
if (currentRow.isLayoutComplete()) {
|
||||
|
||||
// If the rejected item fills a row on its own, add the row and start another new one
|
||||
laidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));
|
||||
if (layoutData._rows.length >= layoutConfig.maxNumRows) {
|
||||
currentRow = null;
|
||||
return true;
|
||||
}
|
||||
currentRow = createNewRow(layoutConfig, layoutData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Handle any leftover content (orphans) depending on where they lie
|
||||
// in this layout update, and in the total content set.
|
||||
if (currentRow && currentRow.getItems().length && layoutConfig.showWidows) {
|
||||
|
||||
// Last page of all content or orphan suppression is suppressed; lay out orphans.
|
||||
if (layoutData._rows.length) {
|
||||
|
||||
// Only Match previous row's height if it exists and it isn't a breakout row
|
||||
if (layoutData._rows[layoutData._rows.length - 1].isBreakoutRow) {
|
||||
nextToLastRowHeight = layoutData._rows[layoutData._rows.length - 1].targetRowHeight;
|
||||
} else {
|
||||
nextToLastRowHeight = layoutData._rows[layoutData._rows.length - 1].height;
|
||||
}
|
||||
|
||||
currentRow.forceComplete(false, nextToLastRowHeight);
|
||||
|
||||
} else {
|
||||
|
||||
// ...else use target height if there is no other row height to reference.
|
||||
currentRow.forceComplete(false);
|
||||
|
||||
}
|
||||
|
||||
laidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));
|
||||
layoutConfig._widowCount = currentRow.getItems().length;
|
||||
|
||||
}
|
||||
|
||||
// We need to clean up the bottom container padding
|
||||
// First remove the height added for box spacing
|
||||
layoutData._containerHeight = layoutData._containerHeight - layoutConfig.boxSpacing.vertical;
|
||||
// Then add our bottom container padding
|
||||
layoutData._containerHeight = layoutData._containerHeight + layoutConfig.containerPadding.bottom;
|
||||
|
||||
return {
|
||||
containerHeight: layoutData._containerHeight,
|
||||
widowCount: layoutConfig._widowCount,
|
||||
boxes: layoutData._layoutItems
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a bunch of box data and config. Returns
|
||||
* geometry to lay them out in a justified view.
|
||||
*
|
||||
* @method covertSizesToAspectRatios
|
||||
* @param sizes {Array} Array of objects with widths and heights
|
||||
* @return {Array} A list of aspect ratios
|
||||
*/
|
||||
|
||||
module.exports = function (input, config) {
|
||||
var layoutConfig = {};
|
||||
var layoutData = {};
|
||||
|
||||
// Defaults
|
||||
var defaults = {
|
||||
containerWidth: 1060,
|
||||
containerPadding: 10,
|
||||
boxSpacing: 10,
|
||||
targetRowHeight: 320,
|
||||
targetRowHeightTolerance: 0.25,
|
||||
maxNumRows: Number.POSITIVE_INFINITY,
|
||||
forceAspectRatio: false,
|
||||
showWidows: true,
|
||||
fullWidthBreakoutRowCadence: false,
|
||||
widowLayoutStyle: 'left'
|
||||
};
|
||||
|
||||
var containerPadding = {};
|
||||
var boxSpacing = {};
|
||||
|
||||
config = config || {};
|
||||
|
||||
// Merge defaults and config passed in
|
||||
layoutConfig = Object.assign(defaults, config);
|
||||
|
||||
// Sort out padding and spacing values
|
||||
containerPadding.top = (!isNaN(parseFloat(layoutConfig.containerPadding.top))) ? layoutConfig.containerPadding.top : layoutConfig.containerPadding;
|
||||
containerPadding.right = (!isNaN(parseFloat(layoutConfig.containerPadding.right))) ? layoutConfig.containerPadding.right : layoutConfig.containerPadding;
|
||||
containerPadding.bottom = (!isNaN(parseFloat(layoutConfig.containerPadding.bottom))) ? layoutConfig.containerPadding.bottom : layoutConfig.containerPadding;
|
||||
containerPadding.left = (!isNaN(parseFloat(layoutConfig.containerPadding.left))) ? layoutConfig.containerPadding.left : layoutConfig.containerPadding;
|
||||
boxSpacing.horizontal = (!isNaN(parseFloat(layoutConfig.boxSpacing.horizontal))) ? layoutConfig.boxSpacing.horizontal : layoutConfig.boxSpacing;
|
||||
boxSpacing.vertical = (!isNaN(parseFloat(layoutConfig.boxSpacing.vertical))) ? layoutConfig.boxSpacing.vertical : layoutConfig.boxSpacing;
|
||||
|
||||
layoutConfig.containerPadding = containerPadding;
|
||||
layoutConfig.boxSpacing = boxSpacing;
|
||||
|
||||
// Local
|
||||
layoutData._layoutItems = [];
|
||||
layoutData._awakeItems = [];
|
||||
layoutData._inViewportItems = [];
|
||||
layoutData._leadingOrphans = [];
|
||||
layoutData._trailingOrphans = [];
|
||||
layoutData._containerHeight = layoutConfig.containerPadding.top;
|
||||
layoutData._rows = [];
|
||||
layoutData._orphans = [];
|
||||
layoutConfig._widowCount = 0;
|
||||
|
||||
// Convert widths and heights to aspect ratios if we need to
|
||||
return computeLayout(layoutConfig, layoutData, input.map(function (item) {
|
||||
if (item.width && item.height) {
|
||||
return { aspectRatio: item.width / item.height };
|
||||
} else {
|
||||
return { aspectRatio: item };
|
||||
}
|
||||
}));
|
||||
};
|
||||
Reference in New Issue
Block a user