From f22ab5ea5f4e0a93ee781cfeded99450fd1e61ae Mon Sep 17 00:00:00 2001 From: Fisto Date: Fri, 18 Sep 2026 16:48:50 +0200 Subject: [PATCH] Preload the next page in page mode --- CURRENT.md | 3 ++- poc/public/media.js | 62 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/CURRENT.md b/CURRENT.md index 48d1196..ae68397 100644 --- a/CURRENT.md +++ b/CURRENT.md @@ -45,7 +45,8 @@ mobile draft at 390px. number / total / Next, persists the page in localStorage, and scrolls to the next page one at a time with a 2 s cooldown after each load. Same filter + page is stable while the archive is unchanged. Infinite mode keeps the stream buffer; prefetch, buffer fill, and the buffer are - disabled in page mode. + disabled in page mode. Page mode pre-loads the next page's JSON and preview images, so + jumping one page renders instantly. - [x] C56 Recovery completed. First run after the C55 fix recovered 1,117 of 1,240 Discord-CDN previews (90%); the last 123 were confirmed genuinely gone (refresh + direct fetch and `/stream` both fail) and are marked `unavailable after recovery` so later runs skip them. The recovery diff --git a/poc/public/media.js b/poc/public/media.js index 0c6a9f7..65d385f 100644 --- a/poc/public/media.js +++ b/poc/public/media.js @@ -36,6 +36,7 @@ pageMode: localStorage.getItem("vk-media-page-mode") === "page" ? "page" : "infinite", page: Math.max(1, Number(localStorage.getItem("vk-media-page")) || 1), pageCooldownUntil: 0, + pagePrefetch: null, draft: null, prefetched: null, prefetchPromise: null, @@ -414,9 +415,17 @@ } try { - const items = await window.VK.api.get( - `/api/media?limit=${state.pageSize}&offset=${state.offset}&${filterQuery()}` - ); + const prefetched = + reset && state.pageMode === "page" && state.pagePrefetch?.page === state.page + ? state.pagePrefetch + : null; + if (prefetched) state.pagePrefetch = null; + + const items = + prefetched?.items ?? + (await window.VK.api.get( + `/api/media?limit=${state.pageSize}&offset=${state.offset}&${filterQuery()}` + )); if (reset) { for (const item of items) state.itemKeys.add(`${item.scrapeId}:${item.itemId}`); @@ -442,13 +451,44 @@ } else { state.pageCooldownUntil = Date.now() + 2000; renderToolbar(); + void prefetchNext(); } } /** Warm the next page's JSON and preview images before the user reaches them. */ + function warmImages(items) { + for (const item of items) { + const src = item.thumbUrl || item.localUrl; + if (!src) continue; + const image = new Image(); + image.src = src; + } + } + + /** + * Warm the next slice in both modes. Infinite appends it when the user reaches the bottom; + * page mode keeps it ready so jumping one page renders instantly with its previews cached. + */ async function prefetchNext() { - if (state.pageMode !== "infinite") return; - if (!state.loaded || !state.more || state.prefetched || state.prefetchPromise || state.loading) return; + if (!state.loaded || state.loading) return; + + if (state.pageMode === "page") { + const page = state.page + 1; + if (!hasNextPage() || state.pagePrefetch?.page === page) return; + + try { + const items = await window.VK.api.get( + `/api/media?limit=${state.pageSize}&offset=${(page - 1) * state.pageSize}&${filterQuery()}` + ); + state.pagePrefetch = { page, items }; + warmImages(items); + } catch { + // best effort + } + return; + } + + if (!state.more || state.prefetched || state.prefetchPromise) return; state.prefetchPromise = window.VK.api .get(`/api/media?limit=${state.pageSize}&offset=${state.offset}&${filterQuery()}`) @@ -459,12 +499,7 @@ } state.prefetched = items; - for (const item of items) { - const src = item.thumbUrl || item.localUrl; - if (!src) continue; - const image = new Image(); - image.src = src; - } + warmImages(items); }) .catch(() => { // Preloading is best effort; the scroll handler falls back to a request. @@ -564,6 +599,7 @@ function setPageMode(mode) { if (state.pageMode === mode) return; state.pageMode = mode; + state.pagePrefetch = null; localStorage.setItem("vk-media-page-mode", mode); state.prefetched = null; state.page = 1; @@ -573,6 +609,7 @@ } function gotoPage(page) { + state.pagePrefetch = null; const count = pageCount(); const wanted = Math.max(1, Math.round(Number(page) || 1)); state.page = count ? Math.min(count, wanted) : wanted; @@ -1476,6 +1513,7 @@ function applyDraft() { state.page = 1; + state.pagePrefetch = null; savePage(); state.kind = state.draft.kind; state.filters = { @@ -1996,6 +2034,7 @@ state.more = false; state.page = 1; state.pageCooldownUntil = 0; + state.pagePrefetch = null; savePage(); state.prefetched = null; state.items = []; @@ -2042,6 +2081,7 @@ overlay.querySelector("#media-close").addEventListener("click", hide); overlay.querySelector("#media-refresh").addEventListener("click", () => { state.completed.clear(); + state.pagePrefetch = null; void loadScrapes(); if (state.view === "media") void loadMedia(true); if (state.view === "bot") {