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');
|
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
@@ -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; }
|
||||||
|
|||||||
Reference in new issue
Block a user