Frontend perf: pdf.js smart preload, admin poll cleanup, list render hoisting
Three targeted frontend fixes: 1. pdf.js smart preload (thumbnail.js, resourceIcon.js). The first PDF thumbnail of a session stalled 1-2s on the lazy import of the ~1.3 MB pdf.js stack. buildResourceIcon() now fires thumbnail.preloadPdf() the moment a PDF row enters the DOM, warming both the module (~300 KB, via the now promise-memoized getPdfjsLib, shared with real users) and the worker script (~1 MB, via a cache-priming fetch — pdf.js only requests it on first getDocument). Only folders that actually contain PDFs pay the download; idempotent after first call, resets on failure so transient offline retries. 2. Admin migration poll cleanup (admin.js). The 2s setInterval kept hitting the API and updating hidden DOM after leaving the Storage tab, and polled a failing endpoint forever after session expiry (!resp.ok returned without clearing). New stopMigrationPolling() helper, invoked on tab switch away from Storage, on non-running status, and on failed polls; tab re-entry re-arms via loadStorage(). 3. resourceList.js render hoisting. Per-row i18n.t() type-cell lookups and the fully item-invariant _renderCustomActions() HTML were recomputed for every row; they now resolve once per batch via _buildItemLabels() (per-category labels memoized, rebuilt each batch so locale switches keep working). _findLaneByKey() swaps the container-wide attribute querySelector for an O(1) _lanes Map kept in sync at the only lane create/wipe sites. https://claude.ai/code/session_0193Hff42gaA962wThxMGSd1
This commit is contained in:
@@ -151,6 +151,10 @@ function switchTab(name, el) {
|
||||
}
|
||||
|
||||
activeTabName = name;
|
||||
// The migration auto-poll only makes sense while the Storage tab is
|
||||
// visible — without this it would keep hitting the API every 2 s
|
||||
// (and updating hidden DOM) for as long as a migration runs.
|
||||
if (name !== 'storage') stopMigrationPolling();
|
||||
if (name === 'users') loadUsers();
|
||||
if (name === 'dashboard') loadDashboard();
|
||||
if (name === 'storage') loadStorage();
|
||||
@@ -908,6 +912,22 @@ async function testStorageConnection() {
|
||||
/** @type {ReturnType<typeof setInterval> | null} */
|
||||
let migrationPollTimer = null;
|
||||
|
||||
/**
|
||||
* Stop the 2 s migration auto-poll if it is armed.
|
||||
*
|
||||
* Called when the poll observes a non-running status, when a poll request
|
||||
* fails (an expired admin session would otherwise be retried every 2 s
|
||||
* forever), and when the user leaves the Storage tab. Re-entering the tab
|
||||
* re-arms it via `loadStorage()` → `loadMigrationStatus()` while a
|
||||
* migration is running.
|
||||
*/
|
||||
function stopMigrationPolling() {
|
||||
if (migrationPollTimer) {
|
||||
clearInterval(migrationPollTimer);
|
||||
migrationPollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} msg
|
||||
* @param {string} type
|
||||
@@ -979,7 +999,12 @@ async function loadMigrationStatus() {
|
||||
headers: headers(),
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
if (!resp.ok) return;
|
||||
if (!resp.ok) {
|
||||
// Don't keep hammering a failing endpoint (e.g. expired session);
|
||||
// any migration button or tab re-entry re-arms the poll.
|
||||
stopMigrationPolling();
|
||||
return;
|
||||
}
|
||||
const m = await resp.json();
|
||||
updateMigrationUI(m);
|
||||
|
||||
@@ -988,9 +1013,8 @@ async function loadMigrationStatus() {
|
||||
if (!migrationPollTimer) {
|
||||
migrationPollTimer = setInterval(loadMigrationStatus, 2000);
|
||||
}
|
||||
} else if (migrationPollTimer) {
|
||||
clearInterval(migrationPollTimer);
|
||||
migrationPollTimer = null;
|
||||
} else {
|
||||
stopMigrationPolling();
|
||||
}
|
||||
} catch (_e) {
|
||||
/* ignore */
|
||||
|
||||
Reference in New Issue
Block a user