UNPKG

react-photo-album

Version:

Responsive photo gallery component for React

125 lines (124 loc) 5.23 kB
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 };