From d8908c69e9b52267a896661c48158fafcf8b32b5 Mon Sep 17 00:00:00 2001 From: Honey Date: Sat, 6 Jun 2026 11:45:22 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20add=20session-level=20API=20cache=20?= =?UTF-8?q?=E2=80=94=20skip=20spinners=20on=20home/albums/artists/playlist?= =?UTF-8?q?s=20revisit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/api.js | 21 +++++++++++++++++++++ src/views.js | 19 ++++++++++--------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/api.js b/src/api.js index 8c56f2f..771e354 100644 --- a/src/api.js +++ b/src/api.js @@ -15,6 +15,27 @@ export async function api(method, extra='') { if(r.status !== 'ok') throw new Error(r.error?.message || 'API Error'); return r; } + +// ─── API CACHE ─── +const _apiCache = new Map(); // key → { data, ts } +const _CACHE_TTL = 5 * 60 * 1000; // 5 min default + +export function apiIsCached(method, extra = '') { + const hit = _apiCache.get(method + extra); + return !!(hit && Date.now() - hit.ts < _CACHE_TTL); +} +export async function apiCached(method, extra = '', ttl = _CACHE_TTL) { + const key = method + extra; + const hit = _apiCache.get(key); + if (hit && Date.now() - hit.ts < ttl) return hit.data; + const data = await api(method, extra); + _apiCache.set(key, { data, ts: Date.now() }); + return data; +} +export function invalidateApiCache(method, extra) { + if (method) _apiCache.delete(method + (extra || '')); + else _apiCache.clear(); +} export function streamUrl(id) { return apiUrl('stream', `&id=${id}&format=raw`); } // Stable per-session cache so the same id+size always produces the same URL. diff --git a/src/views.js b/src/views.js index 247590f..56aa93b 100644 --- a/src/views.js +++ b/src/views.js @@ -1,6 +1,6 @@ import { S } from './state.js'; import { esc, formatDur, toast } from './utils.js'; -import { api, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, itunesTopCharts, itunesTopSongs } from './api.js'; +import { api, apiCached, apiIsCached, coverUrl, itunesSearch, itunesLookupAlbum, itunesLookupArtistSongs, itunesLookupArtistAlbums, itunesTopCharts, itunesTopSongs } from './api.js'; // Circular imports — safe (only used inside function bodies) import { playQueue, dlSongLocal, playPreview, toggleStar, parseArtists } from './player.js'; import { showCtxMenu, openEditPlModal, openDlModal, closeSidebar, switchNavMode } from './ui.js'; @@ -279,10 +279,10 @@ async function loadGenreDetail(g) { export async function loadAlbumRow(id, method, extra) { const c = document.getElementById(id); if(!c) return; - c.innerHTML = '
'; + if (!apiIsCached(method, extra)) c.innerHTML = '
'; try { const [r] = await Promise.all([ - api(method, extra), + apiCached(method, extra), S.cfg.showRomaji ? fetchRomajiIndex().catch(() => {}) : Promise.resolve(), ]); c.innerHTML = ''; @@ -293,9 +293,9 @@ export async function loadAlbumRow(id, method, extra) { // ─── ARTISTS ─── export async function loadArtists() { const g = document.getElementById('artists-grid'); - g.innerHTML = '
'; + if (!apiIsCached('getArtists')) g.innerHTML = '
'; try { - const r = await api('getArtists'); + const r = await apiCached('getArtists'); const all = (r.artists?.index || []).flatMap(i => i.artist || []); if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {}); g.innerHTML = ''; @@ -327,9 +327,10 @@ export async function loadAlbums(type = _albumSort) { document.querySelectorAll('#albums-sort-chips .sort-chip').forEach(c => c.classList.toggle('active', c.dataset.sort === type)); const g = document.getElementById('albums-grid'); - g.innerHTML = '
'; + const extra = `&type=${type}&size=500`; + if (!apiIsCached('getAlbumList2', extra)) g.innerHTML = '
'; try { - const r = await api('getAlbumList2', `&type=${type}&size=500`); + const r = await apiCached('getAlbumList2', extra); g.innerHTML = ''; (r.albumList2?.album || []).forEach(a => g.appendChild(makeAlbumCard(a))); } catch(e) { g.innerHTML = `

${e.message}

`; } @@ -688,9 +689,9 @@ export async function loadAlbumDetail(album) { // ─── PLAYLISTS ─── export async function loadPlaylists() { const g = document.getElementById('playlists-grid'); - g.innerHTML = '
'; + if (!apiIsCached('getPlaylists')) g.innerHTML = '
'; try { - const r = await api('getPlaylists'); + const r = await apiCached('getPlaylists', '', 3 * 60 * 1000); const lists = r.playlists?.playlist || []; g.innerHTML = ''; if(!lists.length) { g.innerHTML = '
🎶

Keine Playlists vorhanden

'; return; }