Files
brk/website_next/explore/chain/older.js
T
2026-07-06 13:17:11 +02:00

176 lines
4.5 KiB
JavaScript

import { createPlaceholderCube, setConfirmedInterval } from "./block-cube.js";
import { olderRemaining, olderRunway } from "./scroll.js";
const OLDER_RESERVE_VIEWPORTS = 6;
/**
* @typedef {import("../../modules/brk-client/index.js").BlockInfoV1} Block
* @typedef {{ generation: number, startHeight: number, placeholders: HTMLElement[] }} OlderBatch
*/
/**
* @param {Object} args
* @param {HTMLElement} args.scrollElement
* @param {HTMLElement} args.blocksElement
* @param {number} args.batchSize
* @param {() => boolean} args.isActive
* @param {() => boolean} args.isHorizontal
* @param {(startHeight: number) => Promise<Block[]>} args.fetchBlocks
* @param {(block: Block) => HTMLButtonElement} args.createCube
* @param {() => boolean} args.isAborted
* @param {(error: unknown) => void} args.onError
*/
export function createOlderBlocks({
scrollElement,
blocksElement,
batchSize,
isActive,
isHorizontal,
fetchBlocks,
createCube,
isAborted,
onError,
}) {
let oldestHeight = Infinity;
let oldestReservedHeight = -1;
let hydrating = false;
let generation = 0;
/** @type {OlderBatch[]} */
const batches = [];
function reset() {
oldestHeight = Infinity;
oldestReservedHeight = -1;
hydrating = false;
generation++;
batches.length = 0;
}
/** @param {number} height */
function setOldestHeight(height) {
oldestHeight = height;
oldestReservedHeight = height;
}
/**
* @param {Element | null} anchor
* @param {number} count
*/
function prependPlaceholders(anchor, count) {
const fragment = document.createDocumentFragment();
const placeholders = /** @type {HTMLElement[]} */ ([]);
for (let i = 0; i < count; i++) {
const cube = createPlaceholderCube();
placeholders.push(cube);
fragment.append(cube);
}
blocksElement.insertBefore(fragment, anchor);
return placeholders;
}
function reserveBatch() {
if (!isActive() || oldestReservedHeight <= 0) return false;
const anchor = blocksElement.firstElementChild;
const count = Math.min(batchSize, oldestReservedHeight);
const startHeight = oldestReservedHeight - 1;
const placeholders = prependPlaceholders(anchor, count);
if (!placeholders.length) return false;
oldestReservedHeight -= placeholders.length;
batches.push({ generation, startHeight, placeholders });
void hydrateBatches();
return true;
}
/** @param {number} [delta] */
function reserve(delta = 0) {
if (!isActive() || oldestReservedHeight <= 0) return;
const horizontal = isHorizontal();
const runway =
olderRunway(scrollElement, horizontal, OLDER_RESERVE_VIEWPORTS) + delta;
let remaining = olderRemaining(scrollElement, horizontal);
while (remaining < runway) {
if (!reserveBatch()) return;
remaining = olderRemaining(scrollElement, horizontal);
}
}
async function hydrateBatches() {
if (hydrating) return;
const currentGeneration = generation;
hydrating = true;
try {
while (
isActive() &&
currentGeneration === generation &&
batches[0]?.generation === currentGeneration
) {
await hydrateBatch(batches[0]);
if (batches[0]?.generation === currentGeneration) batches.shift();
}
} finally {
if (currentGeneration === generation) hydrating = false;
}
}
/** @param {OlderBatch} batch */
async function hydrateBatch(batch) {
try {
const blocks = await fetchBlocks(batch.startHeight);
if (
batch.generation !== generation ||
!batch.placeholders.some((placeholder) => placeholder.isConnected)
) {
return;
}
const cubes = [...blocks].reverse().map(createCube);
for (let i = 0; i < batch.placeholders.length; i++) {
const cube = cubes[i];
if (cube) batch.placeholders[i].replaceWith(cube);
else batch.placeholders[i].remove();
}
for (const cube of cubes) setConfirmedInterval(cube);
const next = cubes.at(-1)?.nextElementSibling;
if (next instanceof HTMLElement) setConfirmedInterval(next);
if (blocks.length) {
oldestHeight = blocks[blocks.length - 1].height;
} else {
oldestReservedHeight = oldestHeight;
}
reserve();
} catch (error) {
if (isAborted() || batch.generation !== generation) return;
for (const placeholder of batch.placeholders) placeholder.remove();
oldestReservedHeight = oldestHeight;
onError(error);
}
}
return /** @type {const} */ ({
reserve,
reset,
setOldestHeight,
});
}