react-photo-album
Version:
Responsive photo gallery component for React
150 lines (149 loc) • 4.99 kB
JavaScript
import { round, ratio } from "../utils/index.js";
function rankingFunctionComparator(rank) {
return (a, b) => rank(b) - rank(a);
}
function MinHeap(comparator) {
let n = 0;
const heap = [];
const greater = (i, j) => comparator(heap[i], heap[j]) < 0;
const swap = (i, j) => {
const temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
};
const swim = (i) => {
let k = i;
let k2 = Math.floor(k / 2);
while (k > 1 && greater(k2, k)) {
swap(k2, k);
k = k2;
k2 = Math.floor(k / 2);
}
};
const sink = (i) => {
let k = i;
let k2 = k * 2;
while (k2 <= n) {
if (k2 < n && greater(k2, k2 + 1)) k2 += 1;
if (!greater(k, k2)) break;
swap(k, k2);
k = k2;
k2 = k * 2;
}
};
const push = (element) => {
n += 1;
heap[n] = element;
swim(n);
};
const pop = () => {
if (n === 0) return void 0;
swap(1, n);
n -= 1;
const max = heap.pop();
sink(1);
return max;
};
const size = () => n;
return { push, pop, size };
}
function buildPrecedentsMap(graph, startNode, endNode) {
const precedentsMap = /* @__PURE__ */ new Map();
const visited = /* @__PURE__ */ new Set();
const storedShortestPaths = /* @__PURE__ */ new Map();
storedShortestPaths.set(startNode, 0);
const queue = MinHeap(rankingFunctionComparator((el) => el[1]));
queue.push([startNode, 0]);
while (queue.size() > 0) {
const [id, weight] = queue.pop();
if (!visited.has(id)) {
const neighboringNodes = graph(id);
visited.add(id);
neighboringNodes.forEach((neighborWeight, neighbor) => {
const newWeight = weight + neighborWeight;
const currentId = precedentsMap.get(neighbor);
const currentWeight = storedShortestPaths.get(neighbor);
if (currentWeight === void 0 || currentWeight > newWeight && (currentWeight / newWeight > 1.005 || currentId !== void 0 && currentId < id)) {
storedShortestPaths.set(neighbor, newWeight);
queue.push([neighbor, newWeight]);
precedentsMap.set(neighbor, id);
}
});
}
}
return storedShortestPaths.has(endNode) ? precedentsMap : void 0;
}
function getPathFromPrecedentsMap(precedentsMap, endNode) {
if (!precedentsMap) return void 0;
const nodes = [];
for (let node = endNode; node !== void 0; node = precedentsMap.get(node)) {
nodes.push(node);
}
return nodes.reverse();
}
function findShortestPath(graph, startNode, endNode) {
return getPathFromPrecedentsMap(buildPrecedentsMap(graph, startNode, endNode), endNode);
}
function findIdealNodeSearch(photos, containerWidth, targetRowHeight, minPhotos) {
return round(containerWidth / targetRowHeight / Math.min(...photos.map((photo) => ratio(photo)))) + (minPhotos || 0) + 2;
}
function getCommonHeight(photos, containerWidth, spacing, padding) {
return (containerWidth - (photos.length - 1) * spacing - 2 * padding * photos.length) / photos.reduce((acc, photo) => acc + ratio(photo), 0);
}
function cost(photos, i, j, width, spacing, padding, targetRowHeight) {
const row = photos.slice(i, j);
const commonHeight = getCommonHeight(row, width, spacing, padding);
return commonHeight > 0 ? (commonHeight - targetRowHeight) ** 2 * row.length : void 0;
}
function makeGetRowNeighbors(photos, spacing, padding, containerWidth, targetRowHeight, limitNodeSearch, minPhotos, maxPhotos) {
return (node) => {
const results = /* @__PURE__ */ new Map();
results.set(node, 0);
const startOffset = minPhotos || 1;
const endOffset = Math.min(limitNodeSearch, maxPhotos || Infinity);
for (let i = node + startOffset; i < photos.length + 1; i += 1) {
if (i - node > endOffset) break;
const currentCost = cost(photos, node, i, containerWidth, spacing, padding, targetRowHeight);
if (currentCost === void 0) break;
results.set(i, currentCost);
}
return results;
};
}
function computeRowsLayout(photos, spacing, padding, containerWidth, targetRowHeight, minPhotos, maxPhotos) {
const limitNodeSearch = findIdealNodeSearch(photos, containerWidth, targetRowHeight, minPhotos);
const getNeighbors = makeGetRowNeighbors(
photos,
spacing,
padding,
containerWidth,
targetRowHeight,
limitNodeSearch,
minPhotos,
maxPhotos
);
const path = findShortestPath(getNeighbors, 0, photos.length);
if (!path) return void 0;
const tracks = [];
for (let i = 1; i < path.length; i += 1) {
const row = photos.map((photo, index) => ({ photo, index })).slice(path[i - 1], path[i]);
const height = getCommonHeight(
row.map(({ photo }) => photo),
containerWidth,
spacing,
padding
);
tracks.push({
photos: row.map(({ photo, index }) => ({
photo,
index,
width: height * ratio(photo),
height
}))
});
}
return { spacing, padding, containerWidth, tracks, horizontal: true };
}
export {
computeRowsLayout as default
};