mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
#182 shipped hydrateAuthImages() loading every thumbnail immediately, which regressed the content-library grid from lazy to eager (a fetch per thumbnail on render) and left the IntersectionObserver as dead code. Restore lazy-by-default (observe-only) so large grids only fetch thumbnails as they scroll into view, and add an { eager: true } opt-in for the transient pickers where every item is on screen and immediate load reads better: the device assign-content modal, the playlist add-item modal, and the widget content picker. Grids and inline lists (content library, playlist items, device playlist tab, directory logo/background) use the lazy default. Behavior for those pickers is unchanged; only the large grids revert to the lazy loading they had before #182. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
00e8300af7
commit
84ad89b06d
|
|
@ -73,8 +73,8 @@ export function isPlatformAdmin(user) {
|
|||
|
||||
// Lazy-load authenticated images. A plain <img> can't send the Bearer token,
|
||||
// and thumbnail/file endpoints require auth — a just-uploaded item's thumbnail
|
||||
// 403's without it. We fetch with the token and swap in an object URL.
|
||||
// IntersectionObserver keeps it lazy; the object URL is revoked after load.
|
||||
// 403's without it. We fetch with the token and swap in an object URL, revoked
|
||||
// after load.
|
||||
let _authImgObserver = null;
|
||||
export function loadAuthImage(img) {
|
||||
const url = img.dataset.authSrc;
|
||||
|
|
@ -89,13 +89,17 @@ export function loadAuthImage(img) {
|
|||
})
|
||||
.catch(() => { img.style.opacity = '0.25'; });
|
||||
}
|
||||
export function hydrateAuthImages(root) {
|
||||
// Hydrate <img data-auth-src> under `root`. Lazy by default: an
|
||||
// IntersectionObserver loads each thumbnail only as it scrolls into view, so a
|
||||
// large grid (content library, etc.) doesn't fire a fetch per thumbnail on
|
||||
// render. Pass { eager: true } for small, transient surfaces (pickers/modals)
|
||||
// where every item is on screen and immediate load reads better.
|
||||
export function hydrateAuthImages(root, { eager = false } = {}) {
|
||||
const imgs = root.querySelectorAll('img[data-auth-src]');
|
||||
if (!imgs.length) return;
|
||||
|
||||
// Load all images immediately; IntersectionObserver is used below
|
||||
// only for images that are off-screen (lazy loading).
|
||||
if (typeof IntersectionObserver === 'undefined') {
|
||||
// Eager path (opt-in) and the no-IntersectionObserver fallback both load now.
|
||||
if (eager || typeof IntersectionObserver === 'undefined') {
|
||||
imgs.forEach(loadAuthImage);
|
||||
return;
|
||||
}
|
||||
|
|
@ -105,8 +109,5 @@ export function hydrateAuthImages(root) {
|
|||
for (const e of entries) if (e.isIntersecting) { obs.unobserve(e.target); loadAuthImage(e.target); }
|
||||
}, { rootMargin: '300px' });
|
||||
}
|
||||
|
||||
// Load every image now — the observer will also fire for them but
|
||||
// loadAuthImage is idempotent (deletes data-auth-src on first call).
|
||||
imgs.forEach(img => { loadAuthImage(img); _authImgObserver.observe(img); });
|
||||
imgs.forEach(img => _authImgObserver.observe(img));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1454,7 +1454,7 @@ async function setupPlaylistActions(device) {
|
|||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
hydrateAuthImages(modal);
|
||||
hydrateAuthImages(modal, { eager: true });
|
||||
|
||||
// Tab switching
|
||||
modal.querySelectorAll('.assign-tab').forEach(tab => {
|
||||
|
|
|
|||
|
|
@ -687,7 +687,7 @@ async function showAddItemModal(playlistId, opts = {}) {
|
|||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
hydrateAuthImages(list);
|
||||
hydrateAuthImages(list, { eager: true });
|
||||
|
||||
list.querySelectorAll('.add-item-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function openContentPicker({ multiple = false, title } = {}) {
|
|||
</div>`;
|
||||
}).join('')
|
||||
}</div>`;
|
||||
hydrateAuthImages(list);
|
||||
hydrateAuthImages(list, { eager: true });
|
||||
list.querySelectorAll('[data-pick-id]').forEach(el => el.onclick = () => {
|
||||
const id = el.dataset.pickId;
|
||||
if (multiple) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue