react-photo-album
Version:
Responsive photo gallery component for React
125 lines (124 loc) • 5.23 kB
JavaScript
import { ratio } from "../utils/index.js";
function computeShortestPath(graph, pathLength, startNode, endNode) {
const matrix = /* @__PURE__ */ new Map();
const queue = /* @__PURE__ */ new Set();
queue.add(startNode);
for (let length = 0; length < pathLength; length += 1) {
const currentQueue = [...queue.keys()];
queue.clear();
currentQueue.forEach((node) => {
const accumulatedWeight = length > 0 ? matrix.get(node)[length][1] : 0;
graph(node).forEach(([neighbor, weight]) => {
let paths = matrix.get(neighbor);
if (!paths) {
paths = [];
matrix.set(neighbor, paths);
}
const newWeight = accumulatedWeight + weight;
const nextPath = paths[length + 1];
if (!nextPath || nextPath[1] > newWeight && (nextPath[1] / newWeight > 1.0001 || node < nextPath[0])) {
paths[length + 1] = [node, newWeight];
}
if (length < pathLength - 1 && neighbor !== endNode) {
queue.add(neighbor);
}
});
});
}
return matrix;
}
function reconstructShortestPath(matrix, pathLength, endNode) {
const path = [endNode];
for (let node = endNode, length = pathLength; length > 0; length -= 1) {
[node] = matrix.get(node)[length];
path.push(node);
}
return path.reverse();
}
function findShortestPathLengthN(graph, pathLength, startNode, endNode) {
return reconstructShortestPath(computeShortestPath(graph, pathLength, startNode, endNode), pathLength, endNode);
}
function makeGetColumnNeighbors(photos, spacing, padding, targetColumnWidth, targetColumnHeight) {
return (node) => {
const results = [];
const cutOffHeight = targetColumnHeight * 1.5;
let height = targetColumnWidth / ratio(photos[node]) + 2 * padding;
for (let i = node + 1; i < photos.length + 1; i += 1) {
results.push([i, (targetColumnHeight - height) ** 2]);
if (height > cutOffHeight || i === photos.length) {
break;
}
height += targetColumnWidth / ratio(photos[i]) + spacing + 2 * padding;
}
return results;
};
}
function buildColumnsModel(path, photos, spacing, padding, containerWidth, columnsGaps, columnsRatios) {
const tracks = [];
const totalRatio = columnsRatios.reduce((total, columnRatio) => total + columnRatio, 0);
for (let i = 0; i < path.length - 1; i += 1) {
const column = photos.map((photo, index) => ({ photo, index })).slice(path[i], path[i + 1]);
const adjustedGaps = columnsRatios.reduce(
(total, columnRatio, index) => total + (columnsGaps[i] - columnsGaps[index]) * columnRatio,
0
);
const columnWidth = (containerWidth - (path.length - 2) * spacing - 2 * (path.length - 1) * padding - adjustedGaps) * columnsRatios[i] / totalRatio;
tracks.push({
photos: column.map(({ photo, index }) => ({
photo,
index,
width: columnWidth,
height: columnWidth / ratio(photo)
})),
variables: { adjustedGaps, columnRatio: columnsRatios[i] }
});
}
return { tracks, variables: { totalRatio } };
}
function computeColumnsModel(photos, spacing, padding, containerWidth, targetColumnWidth, columns) {
const columnsGaps = [];
const columnsRatios = [];
if (photos.length <= columns) {
const averageRatio = photos.length > 0 ? photos.reduce((acc, photo) => acc + ratio(photo), 0) / photos.length : 1;
for (let i = 0; i < columns; i += 1) {
columnsGaps[i] = 2 * padding;
columnsRatios[i] = i < photos.length ? ratio(photos[i]) : averageRatio;
}
return buildColumnsModel(
Array.from({ length: columns + 1 }, (_, index) => Math.min(index, photos.length)),
photos,
spacing,
padding,
containerWidth,
columnsGaps,
columnsRatios
);
}
const targetColumnHeight = (photos.reduce((acc, photo) => acc + targetColumnWidth / ratio(photo), 0) + spacing * (photos.length - columns) + 2 * padding * photos.length) / columns;
const getNeighbors = makeGetColumnNeighbors(photos, spacing, padding, targetColumnWidth, targetColumnHeight);
const path = findShortestPathLengthN(getNeighbors, columns, 0, photos.length);
for (let i = 0; i < path.length - 1; i += 1) {
const column = photos.slice(path[i], path[i + 1]);
columnsGaps[i] = spacing * (column.length - 1) + 2 * padding * column.length;
columnsRatios[i] = 1 / column.reduce((acc, photo) => acc + 1 / ratio(photo), 0);
}
return buildColumnsModel(path, photos, spacing, padding, containerWidth, columnsGaps, columnsRatios);
}
function computeColumnsLayout(photos, spacing, padding, containerWidth, columns) {
const targetColumnWidth = (containerWidth - spacing * (columns - 1) - 2 * padding * columns) / columns;
const { tracks, variables } = computeColumnsModel(
photos,
spacing,
padding,
containerWidth,
targetColumnWidth,
columns
);
if (tracks.some((track) => track.photos.some(({ width, height }) => width < 0 || height < 0))) {
return columns > 1 ? computeColumnsLayout(photos, spacing, padding, containerWidth, columns - 1) : void 0;
}
return { tracks, spacing, padding, containerWidth, variables: { columns, ...variables } };
}
export {
computeColumnsLayout as default
};