feat: add session-level API cache — skip spinners on home/albums/artists/playlists revisit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-06-06 11:45:22 +02:00
1 parent 08ca8303f3
commit d8908c69e9
2 files changed
+31 -9

No files matched your search

+21
View File
@@ -15,6 +15,27 @@ export async function api(method, extra='') {
if(r.status !== 'ok') throw new Error(r.error?.message || 'API Error'); if(r.status !== 'ok') throw new Error(r.error?.message || 'API Error');
return r; 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`); } 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. // Stable per-session cache so the same id+size always produces the same URL.
+10 -9
View File
@@ -1,6 +1,6 @@
import { S } from './state.js'; import { S } from './state.js';
import { esc, formatDur, toast } from './utils.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) // Circular imports — safe (only used inside function bodies)
import { playQueue, dlSongLocal, playPreview, toggleStar, parseArtists } from './player.js'; import { playQueue, dlSongLocal, playPreview, toggleStar, parseArtists } from './player.js';
import { showCtxMenu, openEditPlModal, openDlModal, closeSidebar, switchNavMode } from './ui.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) { export async function loadAlbumRow(id, method, extra) {
const c = document.getElementById(id); const c = document.getElementById(id);
if(!c) return; if(!c) return;
c.innerHTML = '<div class="spinner"></div>'; if (!apiIsCached(method, extra)) c.innerHTML = '<div class="spinner"></div>';
try { try {
const [r] = await Promise.all([ const [r] = await Promise.all([
api(method, extra), apiCached(method, extra),
S.cfg.showRomaji ? fetchRomajiIndex().catch(() => {}) : Promise.resolve(), S.cfg.showRomaji ? fetchRomajiIndex().catch(() => {}) : Promise.resolve(),
]); ]);
c.innerHTML = ''; c.innerHTML = '';
@@ -293,9 +293,9 @@ export async function loadAlbumRow(id, method, extra) {
// ─── ARTISTS ─── // ─── ARTISTS ───
export async function loadArtists() { export async function loadArtists() {
const g = document.getElementById('artists-grid'); const g = document.getElementById('artists-grid');
g.innerHTML = '<div class="spinner"></div>'; if (!apiIsCached('getArtists')) g.innerHTML = '<div class="spinner"></div>';
try { try {
const r = await api('getArtists'); const r = await apiCached('getArtists');
const all = (r.artists?.index || []).flatMap(i => i.artist || []); const all = (r.artists?.index || []).flatMap(i => i.artist || []);
if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {}); if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {});
g.innerHTML = ''; g.innerHTML = '';
@@ -327,9 +327,10 @@ export async function loadAlbums(type = _albumSort) {
document.querySelectorAll('#albums-sort-chips .sort-chip').forEach(c => document.querySelectorAll('#albums-sort-chips .sort-chip').forEach(c =>
c.classList.toggle('active', c.dataset.sort === type)); c.classList.toggle('active', c.dataset.sort === type));
const g = document.getElementById('albums-grid'); const g = document.getElementById('albums-grid');
g.innerHTML = '<div class="spinner"></div>'; const extra = `&type=${type}&size=500`;
if (!apiIsCached('getAlbumList2', extra)) g.innerHTML = '<div class="spinner"></div>';
try { try {
const r = await api('getAlbumList2', `&type=${type}&size=500`); const r = await apiCached('getAlbumList2', extra);
g.innerHTML = ''; g.innerHTML = '';
(r.albumList2?.album || []).forEach(a => g.appendChild(makeAlbumCard(a))); (r.albumList2?.album || []).forEach(a => g.appendChild(makeAlbumCard(a)));
} catch(e) { g.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; } } catch(e) { g.innerHTML = `<p style="color:var(--danger)">${e.message}</p>`; }
@@ -688,9 +689,9 @@ export async function loadAlbumDetail(album) {
// ─── PLAYLISTS ─── // ─── PLAYLISTS ───
export async function loadPlaylists() { export async function loadPlaylists() {
const g = document.getElementById('playlists-grid'); const g = document.getElementById('playlists-grid');
g.innerHTML = '<div class="spinner"></div>'; if (!apiIsCached('getPlaylists')) g.innerHTML = '<div class="spinner"></div>';
try { try {
const r = await api('getPlaylists'); const r = await apiCached('getPlaylists', '', 3 * 60 * 1000);
const lists = r.playlists?.playlist || []; const lists = r.playlists?.playlist || [];
g.innerHTML = ''; g.innerHTML = '';
if(!lists.length) { g.innerHTML = '<div class="empty-state"><div class="icon">🎶</div><p>Keine Playlists vorhanden</p></div>'; return; } if(!lists.length) { g.innerHTML = '<div class="empty-state"><div class="icon">🎶</div><p>Keine Playlists vorhanden</p></div>'; return; }