我正在尝试创建图库视图,我在某种程度上遵循Google相册示例,但是无法完美地创建所有代码。
这是我的current implementation (StackBlitz)。我怀疑我在兑现方面有错误的逻辑。
代码实际上已经完成,但是似乎执行的步骤比预期的要多。
ComputeGraph.ts
export function computeGraph<T extends object, K extends { width: number, height: number, original: T }>(FSFiles: K[], idealRowHeight: number, rowWidth: number): GraphCell<T>[][] {
if (!FSFiles.length) {
return [];
}
const createPossibleRow = rowGenerator<T>(rowWidth);
const simpleFiles: ReadonlyArray<GraphCell<T>> = FSFiles.map(file => {
// This should be 1, but if image was not resized to gallery optimal height, this will then be other and thus should affect badness
const transformRatio = idealRowHeight / file.height;
return {
badness: transformRatio,
height: idealRowHeight,
width: file.width * transformRatio,
original: file.original,
};
});
const possibleGraphRows = createPossibleRow(simpleFiles);
const graph = new Graph(possibleGraphRows);
{
let lastRow: GraphRow<T>;
// We can assert type, because if `.pop()` would return `undefined` it would not pass truthy condition.
while (lastRow = possibleGraphRows.pop() as GraphRow<T>) {
const remainingCells = simpleFiles.slice(simpleFiles.findIndex(file => {
return file.original == lastRow.cells[lastRow.cells.length - 1].original;
}) + 1);
const newPossibleRow = createPossibleRow(remainingCells);
graph.addNode(lastRow, newPossibleRow);
possibleGraphRows.push(...newPossibleRow.filter(row => !row.final));
}
}
return graph.solve().map(row => row.cells);
}
Graph.ts
export class Graph<T> {
/** parent-child dictionary */
private nodes: WeakMap<GraphRow<T>, GraphRow<T>[]> = new WeakMap();
private startingNodes: GraphRow<T>[];
constructor(startingNodes: GraphRow<T>[]) {
this.startingNodes = Array.from(startingNodes);
}
addNode(node: GraphRow<T>, children: GraphRow<T>[]) {
if (this.nodes.has(node)) {
console.error('how?', this.nodes.get(node)! == children);
}
this.nodes.set(node, children);
}
solve(): GraphRow<T>[] {
const toVisit = new Set<{ parent: GraphRow<T>, nodeToVisit: GraphRow<T> }>(this.startingNodes.map(parent => {
return this.nodes.get(parent)!.map(nodeToVisit => ({
nodeToVisit,
parent,
}));
}).flat());
const paths = new WeakMap<GraphRow<T>, GraphRow<T>[]>(this.startingNodes.map(node => [node, [node]] as [GraphRow<T>, [GraphRow<T>]]));
const endNodes = new Map<GraphRow<T>, number>();
for (const { parent, nodeToVisit } of toVisit) {
const path = [...paths.get(parent)!, nodeToVisit];
paths.set(nodeToVisit, path);
if (nodeToVisit.final) {
endNodes.set(nodeToVisit, path.reduce((totalBadness, node) => totalBadness + node.badness, 0));
} else {
const children = this.nodes.get(nodeToVisit)!;
children.forEach(node => {
toVisit.add({
parent: nodeToVisit,
nodeToVisit: node,
});
});
}
}
return Array.from(endNodes).reduce((bestCandidate, [node, badness]) => {
if (badness < bestCandidate.badness) {
bestCandidate.badness = badness;
bestCandidate.path = paths.get(node)!;
}
return bestCandidate;
}, {
path: [] as GraphRow<T>[],
badness: Infinity,
}).path;
}
}
界面(2个文件)
export interface GraphCell<T> {
badness: number;
height: number;
width: number;
original: T;
}
export interface GraphRow<T> {
readonly cells: GraphCell<T>[];
readonly width: number;
readonly badness: number;
complete: boolean;
final: boolean;
}
rowGenerator.ts
export function rowGenerator<T extends object>(rowWidth: number) {
const rowStore = new WeakMap<T, GraphRow<T>[]>();
/**
* Creates ***all*** acceptable rows, that begins with the same file.
*/
return function createPossibleRow(files: ReadonlyArray<GraphCell<T>>): GraphRow<T>[] {
const reversedFiles = Array.from(files).reverse();
let file = reversedFiles.pop();
//console.log(`Creating row(s) starting with file:`, file);
if (!file) {
return [];
}
{
if (rowStore.has(file.original)) {
return rowStore.get(file.original)!;
}
}
const result: GraphRow<T>[] = [];
const rowFiles = [];
let width = 0;
let complete = false;
let final = false;
rowStore.set(file.original, result);
while (file) {
rowFiles.push(file);
width += file.width;
if (width >= rowWidth) {
complete = true;
break;
}
file = reversedFiles.pop();
}
if (complete) {
if (rowFiles.length > 1) {
result.push(createRow(rowWidth, complete, final, rowFiles.slice(0, -1)));
}
} else {
final = true;
}
if (!reversedFiles.length) {
final = true;
}
result.push(createRow(rowWidth, complete, final, rowFiles));
return result;
};
}
function createRow<T>(rowWidth: number, complete: boolean, final: boolean, files: GraphCell<T>[]): GraphRow<T> {
const width = files.reduce((acc, file) => acc + file.width, 0);
const ratio = width / rowWidth;
return {
complete,
final,
width,
badness: files.reduce((acc, file) => acc + (file.badness * ratio), 0) / files.length,
cells: files.map(({ badness, original, ...dimensions }) => ({
badness,
original,
height: dimensions.height / ratio,
width: dimensions.width / ratio,
})),
};
}
有人可以帮助我找到我错过的东西吗?为什么会有这么多的递归调用?