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:
1 parent
08ca8303f3
commit
d8908c69e9
2 files changed
+31
-9
No files matched your search
+21
@@ -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.
|
||||
|
||||
+10
-9
@@ -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 = '<div class="spinner"></div>';
|
||||
if (!apiIsCached(method, extra)) c.innerHTML = '<div class="spinner"></div>';
|
||||
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 = '<div class="spinner"></div>';
|
||||
if (!apiIsCached('getArtists')) g.innerHTML = '<div class="spinner"></div>';
|
||||
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 = '<div class="spinner"></div>';
|
||||
const extra = `&type=${type}&size=500`;
|
||||
if (!apiIsCached('getAlbumList2', extra)) g.innerHTML = '<div class="spinner"></div>';
|
||||
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 = `<p style="color:var(--danger)">${e.message}</p>`; }
|
||||
@@ -688,9 +689,9 @@ export async function loadAlbumDetail(album) {
|
||||
// ─── PLAYLISTS ───
|
||||
export async function loadPlaylists() {
|
||||
const g = document.getElementById('playlists-grid');
|
||||
g.innerHTML = '<div class="spinner"></div>';
|
||||
if (!apiIsCached('getPlaylists')) g.innerHTML = '<div class="spinner"></div>';
|
||||
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 = '<div class="empty-state"><div class="icon">🎶</div><p>Keine Playlists vorhanden</p></div>'; return; }
|
||||
|
||||
Reference in new issue
Block a user