Files
Oxicloud/static/js/components/resourceIcon.js
T
Claude ecbdaee19e 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
2026-06-11 09:58:46 +00:00

69 lines
2.2 KiB
JavaScript

/**
* resourceIcon — shared resource icon builder.
*
* Returns a `.file-icon` element identical to the one in resourceList:
* • Folders: `.file-icon.folder-icon` with CSS tab (no visible <i>)
* • Files: `.file-icon.{specialClass}` + optional thumbnail <img> + <i>
*
* CSS lives in fileType.css (folder/file type colours) and resourceList.css
* (base size in grid/list context). Consumer views add their own size overrides.
*/
import { thumbnail } from '../features/thumbnail.js';
/** @import {FileItem, FolderItem} from '../core/types.js' */
/**
* @param {FileItem|FolderItem} item
* @param {'file'|'folder'} resourceType
* @returns {HTMLElement}
*/
function buildResourceIcon(item, resourceType) {
const el = document.createElement('div');
if (resourceType === 'folder') {
el.className = 'file-icon folder-icon';
const i = document.createElement('i');
i.className = 'fas fa-folder';
el.appendChild(i);
return el;
}
const file = /** @type {FileItem} */ (item);
const iconClass = file.icon_class || 'fas fa-file';
const iconSpecialClass = file.icon_special_class || '';
el.className = `file-icon${iconSpecialClass ? ` ${iconSpecialClass}` : ''}`;
const canThumbnail = thumbnail?.canHandle(file) ?? false;
if (canThumbnail) {
// A PDF just entered the list: warm up the pdf.js stack (~1.3 MB)
// in the background now, so a thumbnail cache-miss below doesn't
// stall its first render on the library download. Idempotent.
if (file.mime_type === 'application/pdf') {
thumbnail.preloadPdf();
}
const img = document.createElement('img');
img.className = 'file-thumb';
img.src = `/api/files/${file.id}/thumbnail/icon`;
img.loading = 'lazy';
img.alt = '';
img.addEventListener('error', () => {
img.classList.add('hidden');
thumbnail?.queueGenerate(file, (dataUrl) => {
img.src = dataUrl;
img.classList.remove('hidden');
});
});
el.appendChild(img);
}
const i = document.createElement('i');
i.className = iconClass;
el.appendChild(i);
return el;
}
export { buildResourceIcon };