aboutsummaryrefslogtreecommitdiffstats
path: root/npm_assets/node_modules/justified-layout
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:50 -0500
committerLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:50 -0500
commit475d074fd74425efbe783fad08f97f2df0c4909f (patch)
tree2acdae53999b3c74b716efa4edb5b40311fa356a /npm_assets/node_modules/justified-layout
parentcd502d52787f666fff3254d7d7e7578930c813c2 (diff)
parent3a0d66f07b112b6d2bdc2b57bbf717a89a351ce6 (diff)
Update upstream source from tag 'upstream/8.1.2'
Update to upstream version '8.1.2' with Debian dir e5e966a9e6010ef70618dc9a61558fa4db35aceb
Diffstat (limited to 'npm_assets/node_modules/justified-layout')
-rw-r--r--npm_assets/node_modules/justified-layout/LICENSE20
-rw-r--r--npm_assets/node_modules/justified-layout/README.md66
-rw-r--r--npm_assets/node_modules/justified-layout/demo.html80
-rw-r--r--npm_assets/node_modules/justified-layout/dist/justified-layout.js767
-rw-r--r--npm_assets/node_modules/justified-layout/dist/justified-layout.min.js11
-rw-r--r--npm_assets/node_modules/justified-layout/lib/index.js249
-rw-r--r--npm_assets/node_modules/justified-layout/lib/row.js335
-rw-r--r--npm_assets/node_modules/justified-layout/package.json74
8 files changed, 1602 insertions, 0 deletions
diff --git a/npm_assets/node_modules/justified-layout/LICENSE b/npm_assets/node_modules/justified-layout/LICENSE
new file mode 100644
index 0000000..bf65506
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+Copyright 2019 SmugMug, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/npm_assets/node_modules/justified-layout/README.md b/npm_assets/node_modules/justified-layout/README.md
new file mode 100644
index 0000000..67671f2
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/README.md
@@ -0,0 +1,66 @@
+# Flickr's Justified Layout
+
+[![Build Status](https://travis-ci.org/flickr/justified-layout.svg?branch=master)](https://travis-ci.org/flickr/justified-layout) [![Coverage Status](https://coveralls.io/repos/github/flickr/justified-layout/badge.svg?branch=master)](https://coveralls.io/github/flickr/justified-layout?branch=master)
+
+Pass in box sizes and get back sizes and coordinates for a nice justified layout like that seen all
+over Flickr. The <a href="https://www.flickr.com/explore">explore page</a> is a great example. Here's
+another example using the `fullWidthBreakoutRowCadence` option on Flickr's
+<a href="https://www.flickr.com/photos/dataichi/albums/72157650151574962">album page</a>.
+
+It converts this:
+
+```js
+[0.5, 1.5, 1, 1.8, 0.4, 0.7, 0.9, 1.1, 1.7, 2, 2.1]
+```
+
+Into this:
+
+```js
+{
+ "containerHeight": 1269,
+ "widowCount": 0,
+ "boxes": [
+ {
+ "aspectRatio": 0.5,
+ "top": 10,
+ "width": 170,
+ "height": 340,
+ "left": 10
+ },
+ {
+ "aspectRatio": 1.5,
+ "top": 10,
+ "width": 510,
+ "height": 340,
+ "left": 190
+ },
+ ...
+ ]
+}
+```
+
+Which gives you everything you need to make something like this:
+
+![Demonstration](https://cloud.githubusercontent.com/assets/43693/14033849/f5cffb58-f1da-11e5-9763-dce7e90835e1.png)
+
+
+## Install
+
+`npm install justified-layout`
+
+
+## Easy Usage
+
+```js
+var layoutGeometry = require('justified-layout')([1.33, 1, 0.65] [, config])
+```
+
+
+## Full Documentation and Examples
+
+Find it here: http://flickr.github.io/justified-layout/
+
+
+## License
+
+Open Source Licensed under the MIT license. \ No newline at end of file
diff --git a/npm_assets/node_modules/justified-layout/demo.html b/npm_assets/node_modules/justified-layout/demo.html
new file mode 100644
index 0000000..bf500df
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/demo.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+
+<head>
+ <title>Justified Layout Demo</title>
+ <script type="text/javascript" src="dist/justified-layout.js"></script>
+ <script type="text/javascript">
+
+ document.addEventListener('DOMContentLoaded', function() {
+
+ var justifiedLayout = require('justified-layout');
+
+ var demos = [
+ {
+ sizes: [0.5, 1.5, 1, 1.8, 0.4, 0.7, 0.9, 1.1, 1.7, 2, 2.1],
+ className: "various",
+ config: {}
+ },
+ {
+ sizes: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
+ className: "squares",
+ config: {}
+ },
+ {
+ sizes: [1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1.5, 1.73, 1.1, 0.5, 1],
+ className: "breakout",
+ config: {
+ "fullWidthBreakoutRowCadence": 3
+ }
+ }
+ ];
+
+ // Loop through the demos and put them in the page
+ demos.forEach(function (demo) {
+
+ var section = document.createElement("section");
+ section.innerHTML = `
+ <h2>Input: <code>[${demo.sizes.join(", ")}]</code></h2>
+ <div class="justified"></div>
+ `;
+ document.body.appendChild(section);
+
+ var geometry = justifiedLayout(demo.sizes, demo.config);
+
+ console.log("geometry", geometry);
+ var boxes = geometry.boxes.map(function (box) {
+ return `<div class="box" style="width: ${box.width}px; height: ${box.height}px; top: ${box.top}px; left: ${box.left}px"></div>`;
+ }).join('\n')
+
+ section.querySelector('.justified').innerHTML = boxes;
+ section.querySelector('.justified').style.height = geometry.containerHeight + "px";
+
+ });
+
+ }, false);
+
+ </script>
+
+ <style type="text/css" media="screen">
+
+ .justified {
+ position: relative;
+ background: seagreen;
+ width: 1060px;
+ }
+
+ .box {
+ position: absolute;
+ background: yellowgreen;
+ }
+
+ </style>
+
+</head>
+
+<body>
+
+ <h1>justified-layout demo</h1>
+
+</body>
+
diff --git a/npm_assets/node_modules/justified-layout/dist/justified-layout.js b/npm_assets/node_modules/justified-layout/dist/justified-layout.js
new file mode 100644
index 0000000..e0e272a
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/dist/justified-layout.js
@@ -0,0 +1,767 @@
+require=(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+/*!
+ * Copyright 2019 SmugMug, Inc.
+ * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
+ * @license
+ */
+
+var merge = require('merge');
+
+/**
+ * Row
+ * Wrapper for each row in a justified layout.
+ * Stores relevant values and provides methods for calculating layout of individual rows.
+ *
+ * @param {Object} layoutConfig - The same as that passed
+ * @param {Object} Initialization parameters. The following are all required:
+ * @param params.top {Number} Top of row, relative to container
+ * @param params.left {Number} Left side of row relative to container (equal to container left padding)
+ * @param params.width {Number} Width of row, not including container padding
+ * @param params.spacing {Number} Horizontal spacing between items
+ * @param params.targetRowHeight {Number} Layout algorithm will aim for this row height
+ * @param params.targetRowHeightTolerance {Number} Row heights may vary +/- (`targetRowHeight` x `targetRowHeightTolerance`)
+ * @param params.edgeCaseMinRowHeight {Number} Absolute minimum row height for edge cases that cannot be resolved within tolerance.
+ * @param params.edgeCaseMaxRowHeight {Number} Absolute maximum row height for edge cases that cannot be resolved within tolerance.
+ * @param params.isBreakoutRow {Boolean} Is this row in particular one of those breakout rows? Always false if it's not that kind of photo list
+ * @param params.widowLayoutStyle {String} If widows are visible, how should they be laid out?
+ * @constructor
+ */
+
+var Row = module.exports = function (params) {
+
+ // Top of row, relative to container
+ this.top = params.top;
+
+ // Left side of row relative to container (equal to container left padding)
+ this.left = params.left;
+
+ // Width of row, not including container padding
+ this.width = params.width;
+
+ // Horizontal spacing between items
+ this.spacing = params.spacing;
+
+ // Row height calculation values
+ this.targetRowHeight = params.targetRowHeight;
+ this.targetRowHeightTolerance = params.targetRowHeightTolerance;
+ this.minAspectRatio = this.width / params.targetRowHeight * (1 - params.targetRowHeightTolerance);
+ this.maxAspectRatio = this.width / params.targetRowHeight * (1 + params.targetRowHeightTolerance);
+
+ // Edge case row height minimum/maximum
+ this.edgeCaseMinRowHeight = params.edgeCaseMinRowHeight;
+ this.edgeCaseMaxRowHeight = params.edgeCaseMaxRowHeight;
+
+ // Widow layout direction
+ this.widowLayoutStyle = params.widowLayoutStyle;
+
+ // Full width breakout rows
+ this.isBreakoutRow = params.isBreakoutRow;
+
+ // Store layout data for each item in row
+ this.items = [];
+
+ // Height remains at 0 until it's been calculated
+ this.height = 0;
+
+};
+
+Row.prototype = {
+
+ /**
+ * Attempt to add a single item to the row.
+ * This is the heart of the justified algorithm.
+ * This method is direction-agnostic; it deals only with sizes, not positions.
+ *
+ * If the item fits in the row, without pushing row height beyond min/max tolerance,
+ * the item is added and the method returns true.
+ *
+ * If the item leaves row height too high, there may be room to scale it down and add another item.
+ * In this case, the item is added and the method returns true, but the row is incomplete.
+ *
+ * If the item leaves row height too short, there are too many items to fit within tolerance.
+ * The method will either accept or reject the new item, favoring the resulting row height closest to within tolerance.
+ * If the item is rejected, left/right padding will be required to fit the row height within tolerance;
+ * if the item is accepted, top/bottom cropping will be required to fit the row height within tolerance.
+ *
+ * @method addItem
+ * @param itemData {Object} Item layout data, containing item aspect ratio.
+ * @return {Boolean} True if successfully added; false if rejected.
+ */
+
+ addItem: function (itemData) {
+
+ var newItems = this.items.concat(itemData),
+ // Calculate aspect ratios for items only; exclude spacing
+ rowWidthWithoutSpacing = this.width - (newItems.length - 1) * this.spacing,
+ newAspectRatio = newItems.reduce(function (sum, item) {
+ return sum + item.aspectRatio;
+ }, 0),
+ targetAspectRatio = rowWidthWithoutSpacing / this.targetRowHeight,
+ previousRowWidthWithoutSpacing,
+ previousAspectRatio,
+ previousTargetAspectRatio;
+
+ // Handle big full-width breakout photos if we're doing them
+ if (this.isBreakoutRow) {
+ // Only do it if there's no other items in this row
+ if (this.items.length === 0) {
+ // Only go full width if this photo is a square or landscape
+ if (itemData.aspectRatio >= 1) {
+ // Close out the row with a full width photo
+ this.items.push(itemData);
+ this.completeLayout(rowWidthWithoutSpacing / itemData.aspectRatio, 'justify');
+ return true;
+ }
+ }
+ }
+
+ if (newAspectRatio < this.minAspectRatio) {
+
+ // New aspect ratio is too narrow / scaled row height is too tall.
+ // Accept this item and leave row open for more items.
+
+ this.items.push(merge(itemData));
+ return true;
+
+ } else if (newAspectRatio > this.maxAspectRatio) {
+
+ // New aspect ratio is too wide / scaled row height will be too short.
+ // Accept item if the resulting aspect ratio is closer to target than it would be without the item.
+ // NOTE: Any row that falls into this block will require cropping/padding on individual items.
+
+ if (this.items.length === 0) {
+
+ // When there are no existing items, force acceptance of the new item and complete the layout.
+ // This is the pano special case.
+ this.items.push(merge(itemData));
+ this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
+ return true;
+
+ }
+
+ // Calculate width/aspect ratio for row before adding new item
+ previousRowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing;
+ previousAspectRatio = this.items.reduce(function (sum, item) {
+ return sum + item.aspectRatio;
+ }, 0);
+ previousTargetAspectRatio = previousRowWidthWithoutSpacing / this.targetRowHeight;
+
+ if (Math.abs(newAspectRatio - targetAspectRatio) > Math.abs(previousAspectRatio - previousTargetAspectRatio)) {
+
+ // Row with new item is us farther away from target than row without; complete layout and reject item.
+ this.completeLayout(previousRowWidthWithoutSpacing / previousAspectRatio, 'justify');
+ return false;
+
+ } else {
+
+ // Row with new item is us closer to target than row without;
+ // accept the new item and complete the row layout.
+ this.items.push(merge(itemData));
+ this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
+ return true;
+
+ }
+
+ } else {
+
+ // New aspect ratio / scaled row height is within tolerance;
+ // accept the new item and complete the row layout.
+ this.items.push(merge(itemData));
+ this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
+ return true;
+
+ }
+
+ },
+
+ /**
+ * Check if a row has completed its layout.
+ *
+ * @method isLayoutComplete
+ * @return {Boolean} True if complete; false if not.
+ */
+
+ isLayoutComplete: function () {
+ return this.height > 0;
+ },
+
+ /**
+ * Set row height and compute item geometry from that height.
+ * Will justify items within the row unless instructed not to.
+ *
+ * @method completeLayout
+ * @param newHeight {Number} Set row height to this value.
+ * @param widowLayoutStyle {String} How should widows display? Supported: left | justify | center
+ */
+
+ completeLayout: function (newHeight, widowLayoutStyle) {
+
+ var itemWidthSum = this.left,
+ rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
+ clampedToNativeRatio,
+ clampedHeight,
+ errorWidthPerItem,
+ roundedCumulativeErrors,
+ singleItemGeometry,
+ centerOffset;
+
+ // Justify unless explicitly specified otherwise.
+ if (typeof widowLayoutStyle === 'undefined' || ['justify', 'center', 'left'].indexOf(widowLayoutStyle) < 0) {
+ widowLayoutStyle = 'left';
+ }
+
+ // Clamp row height to edge case minimum/maximum.
+ clampedHeight = Math.max(this.edgeCaseMinRowHeight, Math.min(newHeight, this.edgeCaseMaxRowHeight));
+
+ if (newHeight !== clampedHeight) {
+
+ // If row height was clamped, the resulting row/item aspect ratio will be off,
+ // so force it to fit the width (recalculate aspectRatio to match clamped height).
+ // NOTE: this will result in cropping/padding commensurate to the amount of clamping.
+ this.height = clampedHeight;
+ clampedToNativeRatio = (rowWidthWithoutSpacing / clampedHeight) / (rowWidthWithoutSpacing / newHeight);
+
+ } else {
+
+ // If not clamped, leave ratio at 1.0.
+ this.height = newHeight;
+ clampedToNativeRatio = 1.0;
+
+ }
+
+ // Compute item geometry based on newHeight.
+ this.items.forEach(function (item) {
+
+ item.top = this.top;
+ item.width = item.aspectRatio * this.height * clampedToNativeRatio;
+ item.height = this.height;
+
+ // Left-to-right.
+ // TODO right to left
+ // item.left = this.width - itemWidthSum - item.width;
+ item.left = itemWidthSum;
+
+ // Increment width.
+ itemWidthSum += item.width + this.spacing;
+
+ }, this);
+
+ // If specified, ensure items fill row and distribute error
+ // caused by rounding width and height across all items.
+ if (widowLayoutStyle === 'justify') {
+
+ itemWidthSum -= (this.spacing + this.left);
+
+ errorWidthPerItem = (itemWidthSum - this.width) / this.items.length;
+ roundedCumulativeErrors = this.items.map(function (item, i) {
+ return Math.round((i + 1) * errorWidthPerItem);
+ });
+
+
+ if (this.items.length === 1) {
+
+ // For rows with only one item, adjust item width to fill row.
+ singleItemGeometry = this.items[0];
+ singleItemGeometry.width -= Math.round(errorWidthPerItem);
+
+ } else {
+
+ // For rows with multiple items, adjust item width and shift items to fill the row,
+ // while maintaining equal spacing between items in the row.
+ this.items.forEach(function (item, i) {
+ if (i > 0) {
+ item.left -= roundedCumulativeErrors[i - 1];
+ item.width -= (roundedCumulativeErrors[i] - roundedCumulativeErrors[i - 1]);
+ } else {
+ item.width -= roundedCumulativeErrors[i];
+ }
+ });
+
+ }
+
+ } else if (widowLayoutStyle === 'center') {
+
+ // Center widows
+ centerOffset = (this.width - itemWidthSum) / 2;
+
+ this.items.forEach(function (item) {
+ item.left += centerOffset + this.spacing;
+ }, this);
+
+ }
+
+ },
+
+ /**
+ * Force completion of row layout with current items.
+ *
+ * @method forceComplete
+ * @param fitToWidth {Boolean} Stretch current items to fill the row width.
+ * This will likely result in padding.
+ * @param fitToWidth {Number}
+ */
+
+ forceComplete: function (fitToWidth, rowHeight) {
+
+ // TODO Handle fitting to width
+ // var rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
+ // currentAspectRatio = this.items.reduce(function (sum, item) {
+ // return sum + item.aspectRatio;
+ // }, 0);
+
+ if (typeof rowHeight === 'number') {
+
+ this.completeLayout(rowHeight, this.widowLayoutStyle);
+
+ } else {
+
+ // Complete using target row height.
+ this.completeLayout(this.targetRowHeight, this.widowLayoutStyle);
+ }
+
+ },
+
+ /**
+ * Return layout data for items within row.
+ * Note: returns actual list, not a copy.
+ *
+ * @method getItems
+ * @return Layout data for items within row.
+ */
+
+ getItems: function () {
+ return this.items;
+ }
+
+};
+
+},{"merge":2}],2:[function(require,module,exports){
+/*!
+ * @name JavaScript/NodeJS Merge v1.2.1
+ * @author yeikos
+ * @repository https://github.com/yeikos/js.merge
+
+ * Copyright 2014 yeikos - MIT license
+ * https://raw.github.com/yeikos/js.merge/master/LICENSE
+ */
+
+;(function(isNode) {
+
+ /**
+ * Merge one or more objects
+ * @param bool? clone
+ * @param mixed,... arguments
+ * @return object
+ */
+
+ var Public = function(clone) {
+
+ return merge(clone === true, false, arguments);
+
+ }, publicName = 'merge';
+
+ /**
+ * Merge two or more objects recursively
+ * @param bool? clone
+ * @param mixed,... arguments
+ * @return object
+ */
+
+ Public.recursive = function(clone) {
+
+ return merge(clone === true, true, arguments);
+
+ };
+
+ /**
+ * Clone the input removing any reference
+ * @param mixed input
+ * @return mixed
+ */
+
+ Public.clone = function(input) {
+
+ var output = input,
+ type = typeOf(input),
+ index, size;
+
+ if (type === 'array') {
+
+ output = [];
+ size = input.length;
+
+ for (index=0;index<size;++index)
+
+ output[index] = Public.clone(input[index]);
+
+ } else if (type === 'object') {
+
+ output = {};
+
+ for (index in input)
+
+ output[index] = Public.clone(input[index]);
+
+ }
+
+ return output;
+
+ };
+
+ /**
+ * Merge two objects recursively
+ * @param mixed input
+ * @param mixed extend
+ * @return mixed
+ */
+
+ function merge_recursive(base, extend) {
+
+ if (typeOf(base) !== 'object')
+
+ return extend;
+
+ for (var key in extend) {
+
+ if (typeOf(base[key]) === 'object' && typeOf(extend[key]) === 'object') {
+
+ base[key] = merge_recursive(base[key], extend[key]);
+
+ } else {
+
+ base[key] = extend[key];
+
+ }
+
+ }
+
+ return base;
+
+ }
+
+ /**
+ * Merge two or more objects
+ * @param bool clone
+ * @param bool recursive
+ * @param array argv
+ * @return object
+ */
+
+ function merge(clone, recursive, argv) {
+
+ var result = argv[0],
+ size = argv.length;
+
+ if (clone || typeOf(result) !== 'object')
+
+ result = {};
+
+ for (var index=0;index<size;++index) {
+
+ var item = argv[index],
+
+ type = typeOf(item);
+
+ if (type !== 'object') continue;
+
+ for (var key in item) {
+
+ if (key === '__proto__') continue;
+
+ var sitem = clone ? Public.clone(item[key]) : item[key];
+
+ if (recursive) {
+
+ result[key] = merge_recursive(result[key], sitem);
+
+ } else {
+
+ result[key] = sitem;
+
+ }
+
+ }
+
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Get type of variable
+ * @param mixed input
+ * @return string
+ *
+ * @see http://jsperf.com/typeofvar
+ */
+
+ function typeOf(input) {
+
+ return ({}).toString.call(input).slice(8, -1).toLowerCase();
+
+ }
+
+ if (isNode) {
+
+ module.exports = Public;
+
+ } else {
+
+ window[publicName] = Public;
+
+ }
+
+})(typeof module === 'object' && module && typeof module.exports === 'object' && module.exports);
+},{}],"justified-layout":[function(require,module,exports){
+/*!
+ * 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 merge = require('merge'),
+ 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 = merge(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 };
+ }
+ }));
+};
+
+},{"./row":1,"merge":2}]},{},[]);
diff --git a/npm_assets/node_modules/justified-layout/dist/justified-layout.min.js b/npm_assets/node_modules/justified-layout/dist/justified-layout.min.js
new file mode 100644
index 0000000..b7c42ab
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/dist/justified-layout.min.js
@@ -0,0 +1,11 @@
+require=function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r}()({1:[function(require,module,exports){/*!
+ * Copyright 2019 SmugMug, Inc.
+ * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
+ * @license
+ */
+var merge=require("merge");var Row=module.exports=function(params){this.top=params.top;this.left=params.left;this.width=params.width;this.spacing=params.spacing;this.targetRowHeight=params.targetRowHeight;this.targetRowHeightTolerance=params.targetRowHeightTolerance;this.minAspectRatio=this.width/params.targetRowHeight*(1-params.targetRowHeightTolerance);this.maxAspectRatio=this.width/params.targetRowHeight*(1+params.targetRowHeightTolerance);this.edgeCaseMinRowHeight=params.edgeCaseMinRowHeight;this.edgeCaseMaxRowHeight=params.edgeCaseMaxRowHeight;this.widowLayoutStyle=params.widowLayoutStyle;this.isBreakoutRow=params.isBreakoutRow;this.items=[];this.height=0};Row.prototype={addItem:function(itemData){var newItems=this.items.concat(itemData),rowWidthWithoutSpacing=this.width-(newItems.length-1)*this.spacing,newAspectRatio=newItems.reduce(function(sum,item){return sum+item.aspectRatio},0),targetAspectRatio=rowWidthWithoutSpacing/this.targetRowHeight,previousRowWidthWithoutSpacing,previousAspectRatio,previousTargetAspectRatio;if(this.isBreakoutRow){if(this.items.length===0){if(itemData.aspectRatio>=1){this.items.push(itemData);this.completeLayout(rowWidthWithoutSpacing/itemData.aspectRatio,"justify");return true}}}if(newAspectRatio<this.minAspectRatio){this.items.push(merge(itemData));return true}else if(newAspectRatio>this.maxAspectRatio){if(this.items.length===0){this.items.push(merge(itemData));this.completeLayout(rowWidthWithoutSpacing/newAspectRatio,"justify");return true}previousRowWidthWithoutSpacing=this.width-(this.items.length-1)*this.spacing;previousAspectRatio=this.items.reduce(function(sum,item){return sum+item.aspectRatio},0);previousTargetAspectRatio=previousRowWidthWithoutSpacing/this.targetRowHeight;if(Math.abs(newAspectRatio-targetAspectRatio)>Math.abs(previousAspectRatio-previousTargetAspectRatio)){this.completeLayout(previousRowWidthWithoutSpacing/previousAspectRatio,"justify");return false}else{this.items.push(merge(itemData));this.completeLayout(rowWidthWithoutSpacing/newAspectRatio,"justify");return true}}else{this.items.push(merge(itemData));this.completeLayout(rowWidthWithoutSpacing/newAspectRatio,"justify");return true}},isLayoutComplete:function(){return this.height>0},completeLayout:function(newHeight,widowLayoutStyle){var itemWidthSum=this.left,rowWidthWithoutSpacing=this.width-(this.items.length-1)*this.spacing,clampedToNativeRatio,clampedHeight,errorWidthPerItem,roundedCumulativeErrors,singleItemGeometry,centerOffset;if(typeof widowLayoutStyle==="undefined"||["justify","center","left"].indexOf(widowLayoutStyle)<0){widowLayoutStyle="left"}clampedHeight=Math.max(this.edgeCaseMinRowHeight,Math.min(newHeight,this.edgeCaseMaxRowHeight));if(newHeight!==clampedHeight){this.height=clampedHeight;clampedToNativeRatio=rowWidthWithoutSpacing/clampedHeight/(rowWidthWithoutSpacing/newHeight)}else{this.height=newHeight;clampedToNativeRatio=1}this.items.forEach(function(item){item.top=this.top;item.width=item.aspectRatio*this.height*clampedToNativeRatio;item.height=this.height;item.left=itemWidthSum;itemWidthSum+=item.width+this.spacing},this);if(widowLayoutStyle==="justify"){itemWidthSum-=this.spacing+this.left;errorWidthPerItem=(itemWidthSum-this.width)/this.items.length;roundedCumulativeErrors=this.items.map(function(item,i){return Math.round((i+1)*errorWidthPerItem)});if(this.items.length===1){singleItemGeometry=this.items[0];singleItemGeometry.width-=Math.round(errorWidthPerItem)}else{this.items.forEach(function(item,i){if(i>0){item.left-=roundedCumulativeErrors[i-1];item.width-=roundedCumulativeErrors[i]-roundedCumulativeErrors[i-1]}else{item.width-=roundedCumulativeErrors[i]}})}}else if(widowLayoutStyle==="center"){centerOffset=(this.width-itemWidthSum)/2;this.items.forEach(function(item){item.left+=centerOffset+this.spacing},this)}},forceComplete:function(fitToWidth,rowHeight){if(typeof rowHeight==="number"){this.completeLayout(rowHeight,this.widowLayoutStyle)}else{this.completeLayout(this.targetRowHeight,this.widowLayoutStyle)}},getItems:function(){return this.items}}},{merge:2}],2:[function(require,module,exports){(function(isNode){var Public=function(clone){return merge(clone===true,false,arguments)},publicName="merge";Public.recursive=function(clone){return merge(clone===true,true,arguments)};Public.clone=function(input){var output=input,type=typeOf(input),index,size;if(type==="array"){output=[];size=input.length;for(index=0;index<size;++index)output[index]=Public.clone(input[index])}else if(type==="object"){output={};for(index in input)output[index]=Public.clone(input[index])}return output};function merge_recursive(base,extend){if(typeOf(base)!=="object")return extend;for(var key in extend){if(typeOf(base[key])==="object"&&typeOf(extend[key])==="object"){base[key]=merge_recursive(base[key],extend[key])}else{base[key]=extend[key]}}return base}function merge(clone,recursive,argv){var result=argv[0],size=argv.length;if(clone||typeOf(result)!=="object")result={};for(var index=0;index<size;++index){var item=argv[index],type=typeOf(item);if(type!=="object")continue;for(var key in item){if(key==="__proto__")continue;var sitem=clone?Public.clone(item[key]):item[key];if(recursive){result[key]=merge_recursive(result[key],sitem)}else{result[key]=sitem}}}return result}function typeOf(input){return{}.toString.call(input).slice(8,-1).toLowerCase()}if(isNode){module.exports=Public}else{window[publicName]=Public}})(typeof module==="object"&&module&&typeof module.exports==="object"&&module.exports)},{}],"justified-layout":[function(require,module,exports){/*!
+ * 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 merge=require("merge"),Row=require("./row");function createNewRow(layoutConfig,layoutData){var isBreakoutRow;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:.5*layoutConfig.targetRowHeight,edgeCaseMaxRowHeight:2*layoutConfig.targetRowHeight,rightToLeft:false,isBreakoutRow:isBreakoutRow,widowLayoutStyle:layoutConfig.widowLayoutStyle})}function addRow(layoutConfig,layoutData,row){layoutData._rows.push(row);layoutData._layoutItems=layoutData._layoutItems.concat(row.getItems());layoutData._containerHeight+=row.height+layoutConfig.boxSpacing.vertical;return row.items}function computeLayout(layoutConfig,layoutData,itemLayoutData){var laidOutItems=[],itemAdded,currentRow,nextToLastRowHeight;if(layoutConfig.forceAspectRatio){itemLayoutData.forEach(function(itemData){itemData.forcedAspectRatio=true;itemData.aspectRatio=layoutConfig.forceAspectRatio})}itemLayoutData.some(function(itemData,i){if(isNaN(itemData.aspectRatio)){throw new Error("Item "+i+" has an invalid aspect ratio")}if(!currentRow){currentRow=createNewRow(layoutConfig,layoutData)}itemAdded=currentRow.addItem(itemData);if(currentRow.isLayoutComplete()){laidOutItems=laidOutItems.concat(addRow(layoutConfig,layoutData,currentRow));if(layoutData._rows.length>=layoutConfig.maxNumRows){currentRow=null;return true}currentRow=createNewRow(layoutConfig,layoutData);if(!itemAdded){itemAdded=currentRow.addItem(itemData);if(currentRow.isLayoutComplete()){laidOutItems=laidOutItems.concat(addRow(layoutConfig,layoutData,currentRow));if(layoutData._rows.length>=layoutConfig.maxNumRows){currentRow=null;return true}currentRow=createNewRow(layoutConfig,layoutData)}}}});if(currentRow&&currentRow.getItems().length&&layoutConfig.showWidows){if(layoutData._rows.length){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{currentRow.forceComplete(false)}laidOutItems=laidOutItems.concat(addRow(layoutConfig,layoutData,currentRow));layoutConfig._widowCount=currentRow.getItems().length}layoutData._containerHeight=layoutData._containerHeight-layoutConfig.boxSpacing.vertical;layoutData._containerHeight=layoutData._containerHeight+layoutConfig.containerPadding.bottom;return{containerHeight:layoutData._containerHeight,widowCount:layoutConfig._widowCount,boxes:layoutData._layoutItems}}module.exports=function(input,config){var layoutConfig={};var layoutData={};var defaults={containerWidth:1060,containerPadding:10,boxSpacing:10,targetRowHeight:320,targetRowHeightTolerance:.25,maxNumRows:Number.POSITIVE_INFINITY,forceAspectRatio:false,showWidows:true,fullWidthBreakoutRowCadence:false,widowLayoutStyle:"left"};var containerPadding={};var boxSpacing={};config=config||{};layoutConfig=merge(defaults,config);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;layoutData._layoutItems=[];layoutData._awakeItems=[];layoutData._inViewportItems=[];layoutData._leadingOrphans=[];layoutData._trailingOrphans=[];layoutData._containerHeight=layoutConfig.containerPadding.top;layoutData._rows=[];layoutData._orphans=[];layoutConfig._widowCount=0;return computeLayout(layoutConfig,layoutData,input.map(function(item){if(item.width&&item.height){return{aspectRatio:item.width/item.height}}else{return{aspectRatio:item}}}))}},{"./row":1,merge:2}]},{},[]); \ No newline at end of file
diff --git a/npm_assets/node_modules/justified-layout/lib/index.js b/npm_assets/node_modules/justified-layout/lib/index.js
new file mode 100644
index 0000000..f74bcf2
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/lib/index.js
@@ -0,0 +1,249 @@
+/*!
+ * 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 merge = require('merge'),
+ 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 = merge(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 };
+ }
+ }));
+};
diff --git a/npm_assets/node_modules/justified-layout/lib/row.js b/npm_assets/node_modules/justified-layout/lib/row.js
new file mode 100644
index 0000000..ea37923
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/lib/row.js
@@ -0,0 +1,335 @@
+/*!
+ * Copyright 2019 SmugMug, Inc.
+ * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
+ * @license
+ */
+
+var merge = require('merge');
+
+/**
+ * Row
+ * Wrapper for each row in a justified layout.
+ * Stores relevant values and provides methods for calculating layout of individual rows.
+ *
+ * @param {Object} layoutConfig - The same as that passed
+ * @param {Object} Initialization parameters. The following are all required:
+ * @param params.top {Number} Top of row, relative to container
+ * @param params.left {Number} Left side of row relative to container (equal to container left padding)
+ * @param params.width {Number} Width of row, not including container padding
+ * @param params.spacing {Number} Horizontal spacing between items
+ * @param params.targetRowHeight {Number} Layout algorithm will aim for this row height
+ * @param params.targetRowHeightTolerance {Number} Row heights may vary +/- (`targetRowHeight` x `targetRowHeightTolerance`)
+ * @param params.edgeCaseMinRowHeight {Number} Absolute minimum row height for edge cases that cannot be resolved within tolerance.
+ * @param params.edgeCaseMaxRowHeight {Number} Absolute maximum row height for edge cases that cannot be resolved within tolerance.
+ * @param params.isBreakoutRow {Boolean} Is this row in particular one of those breakout rows? Always false if it's not that kind of photo list
+ * @param params.widowLayoutStyle {String} If widows are visible, how should they be laid out?
+ * @constructor
+ */
+
+var Row = module.exports = function (params) {
+
+ // Top of row, relative to container
+ this.top = params.top;
+
+ // Left side of row relative to container (equal to container left padding)
+ this.left = params.left;
+
+ // Width of row, not including container padding
+ this.width = params.width;
+
+ // Horizontal spacing between items
+ this.spacing = params.spacing;
+
+ // Row height calculation values
+ this.targetRowHeight = params.targetRowHeight;
+ this.targetRowHeightTolerance = params.targetRowHeightTolerance;
+ this.minAspectRatio = this.width / params.targetRowHeight * (1 - params.targetRowHeightTolerance);
+ this.maxAspectRatio = this.width / params.targetRowHeight * (1 + params.targetRowHeightTolerance);
+
+ // Edge case row height minimum/maximum
+ this.edgeCaseMinRowHeight = params.edgeCaseMinRowHeight;
+ this.edgeCaseMaxRowHeight = params.edgeCaseMaxRowHeight;
+
+ // Widow layout direction
+ this.widowLayoutStyle = params.widowLayoutStyle;
+
+ // Full width breakout rows
+ this.isBreakoutRow = params.isBreakoutRow;
+
+ // Store layout data for each item in row
+ this.items = [];
+
+ // Height remains at 0 until it's been calculated
+ this.height = 0;
+
+};
+
+Row.prototype = {
+
+ /**
+ * Attempt to add a single item to the row.
+ * This is the heart of the justified algorithm.
+ * This method is direction-agnostic; it deals only with sizes, not positions.
+ *
+ * If the item fits in the row, without pushing row height beyond min/max tolerance,
+ * the item is added and the method returns true.
+ *
+ * If the item leaves row height too high, there may be room to scale it down and add another item.
+ * In this case, the item is added and the method returns true, but the row is incomplete.
+ *
+ * If the item leaves row height too short, there are too many items to fit within tolerance.
+ * The method will either accept or reject the new item, favoring the resulting row height closest to within tolerance.
+ * If the item is rejected, left/right padding will be required to fit the row height within tolerance;
+ * if the item is accepted, top/bottom cropping will be required to fit the row height within tolerance.
+ *
+ * @method addItem
+ * @param itemData {Object} Item layout data, containing item aspect ratio.
+ * @return {Boolean} True if successfully added; false if rejected.
+ */
+
+ addItem: function (itemData) {
+
+ var newItems = this.items.concat(itemData),
+ // Calculate aspect ratios for items only; exclude spacing
+ rowWidthWithoutSpacing = this.width - (newItems.length - 1) * this.spacing,
+ newAspectRatio = newItems.reduce(function (sum, item) {
+ return sum + item.aspectRatio;
+ }, 0),
+ targetAspectRatio = rowWidthWithoutSpacing / this.targetRowHeight,
+ previousRowWidthWithoutSpacing,
+ previousAspectRatio,
+ previousTargetAspectRatio;
+
+ // Handle big full-width breakout photos if we're doing them
+ if (this.isBreakoutRow) {
+ // Only do it if there's no other items in this row
+ if (this.items.length === 0) {
+ // Only go full width if this photo is a square or landscape
+ if (itemData.aspectRatio >= 1) {
+ // Close out the row with a full width photo
+ this.items.push(itemData);
+ this.completeLayout(rowWidthWithoutSpacing / itemData.aspectRatio, 'justify');
+ return true;
+ }
+ }
+ }
+
+ if (newAspectRatio < this.minAspectRatio) {
+
+ // New aspect ratio is too narrow / scaled row height is too tall.
+ // Accept this item and leave row open for more items.
+
+ this.items.push(merge(itemData));
+ return true;
+
+ } else if (newAspectRatio > this.maxAspectRatio) {
+
+ // New aspect ratio is too wide / scaled row height will be too short.
+ // Accept item if the resulting aspect ratio is closer to target than it would be without the item.
+ // NOTE: Any row that falls into this block will require cropping/padding on individual items.
+
+ if (this.items.length === 0) {
+
+ // When there are no existing items, force acceptance of the new item and complete the layout.
+ // This is the pano special case.
+ this.items.push(merge(itemData));
+ this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
+ return true;
+
+ }
+
+ // Calculate width/aspect ratio for row before adding new item
+ previousRowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing;
+ previousAspectRatio = this.items.reduce(function (sum, item) {
+ return sum + item.aspectRatio;
+ }, 0);
+ previousTargetAspectRatio = previousRowWidthWithoutSpacing / this.targetRowHeight;
+
+ if (Math.abs(newAspectRatio - targetAspectRatio) > Math.abs(previousAspectRatio - previousTargetAspectRatio)) {
+
+ // Row with new item is us farther away from target than row without; complete layout and reject item.
+ this.completeLayout(previousRowWidthWithoutSpacing / previousAspectRatio, 'justify');
+ return false;
+
+ } else {
+
+ // Row with new item is us closer to target than row without;
+ // accept the new item and complete the row layout.
+ this.items.push(merge(itemData));
+ this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
+ return true;
+
+ }
+
+ } else {
+
+ // New aspect ratio / scaled row height is within tolerance;
+ // accept the new item and complete the row layout.
+ this.items.push(merge(itemData));
+ this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
+ return true;
+
+ }
+
+ },
+
+ /**
+ * Check if a row has completed its layout.
+ *
+ * @method isLayoutComplete
+ * @return {Boolean} True if complete; false if not.
+ */
+
+ isLayoutComplete: function () {
+ return this.height > 0;
+ },
+
+ /**
+ * Set row height and compute item geometry from that height.
+ * Will justify items within the row unless instructed not to.
+ *
+ * @method completeLayout
+ * @param newHeight {Number} Set row height to this value.
+ * @param widowLayoutStyle {String} How should widows display? Supported: left | justify | center
+ */
+
+ completeLayout: function (newHeight, widowLayoutStyle) {
+
+ var itemWidthSum = this.left,
+ rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
+ clampedToNativeRatio,
+ clampedHeight,
+ errorWidthPerItem,
+ roundedCumulativeErrors,
+ singleItemGeometry,
+ centerOffset;
+
+ // Justify unless explicitly specified otherwise.
+ if (typeof widowLayoutStyle === 'undefined' || ['justify', 'center', 'left'].indexOf(widowLayoutStyle) < 0) {
+ widowLayoutStyle = 'left';
+ }
+
+ // Clamp row height to edge case minimum/maximum.
+ clampedHeight = Math.max(this.edgeCaseMinRowHeight, Math.min(newHeight, this.edgeCaseMaxRowHeight));
+
+ if (newHeight !== clampedHeight) {
+
+ // If row height was clamped, the resulting row/item aspect ratio will be off,
+ // so force it to fit the width (recalculate aspectRatio to match clamped height).
+ // NOTE: this will result in cropping/padding commensurate to the amount of clamping.
+ this.height = clampedHeight;
+ clampedToNativeRatio = (rowWidthWithoutSpacing / clampedHeight) / (rowWidthWithoutSpacing / newHeight);
+
+ } else {
+
+ // If not clamped, leave ratio at 1.0.
+ this.height = newHeight;
+ clampedToNativeRatio = 1.0;
+
+ }
+
+ // Compute item geometry based on newHeight.
+ this.items.forEach(function (item) {
+
+ item.top = this.top;
+ item.width = item.aspectRatio * this.height * clampedToNativeRatio;
+ item.height = this.height;
+
+ // Left-to-right.
+ // TODO right to left
+ // item.left = this.width - itemWidthSum - item.width;
+ item.left = itemWidthSum;
+
+ // Increment width.
+ itemWidthSum += item.width + this.spacing;
+
+ }, this);
+
+ // If specified, ensure items fill row and distribute error
+ // caused by rounding width and height across all items.
+ if (widowLayoutStyle === 'justify') {
+
+ itemWidthSum -= (this.spacing + this.left);
+
+ errorWidthPerItem = (itemWidthSum - this.width) / this.items.length;
+ roundedCumulativeErrors = this.items.map(function (item, i) {
+ return Math.round((i + 1) * errorWidthPerItem);
+ });
+
+
+ if (this.items.length === 1) {
+
+ // For rows with only one item, adjust item width to fill row.
+ singleItemGeometry = this.items[0];
+ singleItemGeometry.width -= Math.round(errorWidthPerItem);
+
+ } else {
+
+ // For rows with multiple items, adjust item width and shift items to fill the row,
+ // while maintaining equal spacing between items in the row.
+ this.items.forEach(function (item, i) {
+ if (i > 0) {
+ item.left -= roundedCumulativeErrors[i - 1];
+ item.width -= (roundedCumulativeErrors[i] - roundedCumulativeErrors[i - 1]);
+ } else {
+ item.width -= roundedCumulativeErrors[i];
+ }
+ });
+
+ }
+
+ } else if (widowLayoutStyle === 'center') {
+
+ // Center widows
+ centerOffset = (this.width - itemWidthSum) / 2;
+
+ this.items.forEach(function (item) {
+ item.left += centerOffset + this.spacing;
+ }, this);
+
+ }
+
+ },
+
+ /**
+ * Force completion of row layout with current items.
+ *
+ * @method forceComplete
+ * @param fitToWidth {Boolean} Stretch current items to fill the row width.
+ * This will likely result in padding.
+ * @param fitToWidth {Number}
+ */
+
+ forceComplete: function (fitToWidth, rowHeight) {
+
+ // TODO Handle fitting to width
+ // var rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
+ // currentAspectRatio = this.items.reduce(function (sum, item) {
+ // return sum + item.aspectRatio;
+ // }, 0);
+
+ if (typeof rowHeight === 'number') {
+
+ this.completeLayout(rowHeight, this.widowLayoutStyle);
+
+ } else {
+
+ // Complete using target row height.
+ this.completeLayout(this.targetRowHeight, this.widowLayoutStyle);
+ }
+
+ },
+
+ /**
+ * Return layout data for items within row.
+ * Note: returns actual list, not a copy.
+ *
+ * @method getItems
+ * @return Layout data for items within row.
+ */
+
+ getItems: function () {
+ return this.items;
+ }
+
+};
diff --git a/npm_assets/node_modules/justified-layout/package.json b/npm_assets/node_modules/justified-layout/package.json
new file mode 100644
index 0000000..e761ddd
--- /dev/null
+++ b/npm_assets/node_modules/justified-layout/package.json
@@ -0,0 +1,74 @@
+{
+ "_from": "justified-layout@^3.0.0",
+ "_id": "justified-layout@3.0.0",
+ "_inBundle": false,
+ "_integrity": "sha512-xki5bVJ84HokIV47mfHdmWB56zFrQKbtrU5KHA5GoatOnRwQWGOvNtBlbW8dU0yIa3pNmCPuacuuMRPvM9p5mg==",
+ "_location": "/justified-layout",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "range",
+ "registry": true,
+ "raw": "justified-layout@^3.0.0",
+ "name": "justified-layout",
+ "escapedName": "justified-layout",
+ "rawSpec": "^3.0.0",
+ "saveSpec": null,
+ "fetchSpec": "^3.0.0"
+ },
+ "_requiredBy": [
+ "/"
+ ],
+ "_resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-3.0.0.tgz",
+ "_shasum": "f8643ac51d97cf43dd40ddf38601161968ff8165",
+ "_spec": "justified-layout@^3.0.0",
+ "_where": "/Users/kwpolska/git/nikola/npm_assets",
+ "bugs": {
+ "url": "https://github.com/flickr/justified-layout/issues"
+ },
+ "bundleDependencies": false,
+ "dependencies": {
+ "merge": "1.2.1"
+ },
+ "deprecated": false,
+ "description": "Pass in box sizes and get back sizes and coordinates for a justified layout",
+ "devDependencies": {
+ "browserify": "^16.2.3",
+ "coveralls": "^2.11.9",
+ "eslint": "^4.18.2",
+ "eslint-config-flickr": "^2.0.1",
+ "expect": "1.14.0",
+ "istanbul": "^0.4.2",
+ "mocha": "^6.1.4",
+ "uglify-js": "2.6.2"
+ },
+ "files": [
+ "demo.html",
+ "dist",
+ "lib"
+ ],
+ "homepage": "https://github.com/flickr/justified-layout#readme",
+ "keywords": [
+ "justify",
+ "grid",
+ "photos",
+ "layout",
+ "boxes"
+ ],
+ "license": "ISC",
+ "main": "./lib/index.js",
+ "name": "justified-layout",
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/flickr/justified-layout.git"
+ },
+ "scripts": {
+ "build": "npm run build-dist && npm run build-min",
+ "build-dist": "mkdir -p dist && browserify -r $npm_package_main:justified-layout > dist/justified-layout.js",
+ "build-min": "uglifyjs dist/justified-layout.js --comments -o dist/justified-layout.min.js",
+ "coverage": "cat ./coverage/lcov.info | coveralls",
+ "lint": "eslint .",
+ "prepublish": "npm run build",
+ "test": "istanbul test _mocha"
+ },
+ "version": "3.0.0"
+}