feat(web): refresh the current view after deleting a song or playlist
Deletes left the removed item visible because the list loaders are cached (getPlaylists ~3 min): deletePlaylist navigated to a still-cached list, and deleteSong only reloaded playlists. Add refreshCurrentView() — drops the API cache and re-runs the active nav entry's loader (playlists, playlist/album/artist detail, starred, home, …). deleteSong calls it after the delete+rescan delay; deletePlaylist invalidates getPlaylists before navigating. Bump SW cache to ns-v46. Also record the post-1.2 work in the 1.2 changelog (importer Enter, search single-song + radio, NowPlaying order, official-audio artist-metadata match, JP-katakana album artist fix, year back-fill, delete refresh). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
966b318d9e
commit
d98978a6c7
5 files changed
+36
-8
No files matched your search
@@ -61,16 +61,23 @@ Follow-up to 1.1: web history navigation, correct download tagging for mixed-art
|
||||
|
||||
### Added
|
||||
- **Android** — Directional bottom-nav tab transitions: the slide follows the tapped tab's position (a tab to the right slides in from the right, one to the left from the left) instead of a fixed, sometimes-random-feeling direction. Applied across all four enter/exit/pop transitions; detail-screen push/pop is unchanged
|
||||
- **Web** — Pressing **Enter** in the importer link field starts the import (same as the Importieren button)
|
||||
|
||||
### Fixed
|
||||
- **Web** — Browser / mouse **Forward** navigation now works (previously only Back did): `popstate` applies the target history entry (`nsIdx`) for both directions, and the back handler no longer `pushState()`s over the browser's forward stack. In-app back buttons route through `history.back()` so the browser and app stacks stay in sync
|
||||
- **Web** — Deleting a song or playlist refreshes the current view immediately (drops the cached list) instead of leaving the removed item visible for up to ~3 minutes
|
||||
- **Web** — Album detail header showed the **first track's** artist (e.g. "TWICE" on a soundtrack) instead of the album artist; it now reads the album's own album-artist fields from `getAlbum` (matches the Android app)
|
||||
- **Web** — Metadata autofill (iTunes / MusicBrainz) normalizes the trailing `" & "` in a multi-artist credit ("A, B & C" → "A; B; C"), but only when the value is already a list, so band names like "Simon & Garfunkel" stay intact
|
||||
- **Android** — Erkunden (Apple) overlay back no longer *sometimes* jumped to Home: the root-level back callback is re-asserted on every navigation, so it can't lose back-priority to the NavHost after an album drill-down or tab switch
|
||||
- **Backend (download)** — Mixed-artist albums search **per track**: the YTM/YouTube query and channel filter use each track's own performer, so soundtrack tracks by other artists (TWICE's "Strategy", MeloMance's "Love, Maybe") are found instead of failing with "Nicht eindeutig gefunden"
|
||||
- **Backend (download)** — `album_artist` is derived from iTunes' album-level `collectionArtistName` (identical across the album) instead of the request's artist — which could be the lead single's performer, so a whole KPop soundtrack was tagged `album_artist = TWICE`. Applies to album and artist downloads; each track still gets its own per-track artist tag
|
||||
- **Backend (download)** — Official audio is matched by yt-dlp's `artist` metadata, not just the channel name, so uploads on label channels (BANGTANTV, HYBE, "… - Topic") win over fan reuploads — e.g. BTS "Pied Piper" now grabs the official track. (`channel_is_verified` was rejected as a signal: lyric videos and fan MVs are also on verified channels)
|
||||
- **Backend (download)** — Western artists are no longer tagged with the Japanese store's katakana album-artist ("DJスネイク" for DJ Snake): the JP-first iTunes lookup now keeps Japanese names only for Japanese songs and uses the US/English names otherwise (affects the `album_artist` tag and the on-disk folder)
|
||||
- **Backend (download)** — Release year is back-filled from iTunes on single-track downloads where the source omitted it (the year tag was previously left empty)
|
||||
|
||||
### Changed
|
||||
- **Web + Android** — Playing a song from search results queues only that song; Radio mode (if on) fills the rest — closer to Spotify. Album / playlist play still queues the whole list
|
||||
- **Android** — NowPlaying track info is ordered title → album → artists, matching the web player
|
||||
- **Web** — NowPlaying favorite star is vertically centered with the whole title / album / artist block instead of pinned to the title line
|
||||
|
||||
---
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'ns-v45';
|
||||
const CACHE = 'ns-v46';
|
||||
const PRECACHE = ['/', 'index.html', 'manifest.json', 'icon.svg'];
|
||||
const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js'];
|
||||
// Auth token params stripped from cache keys so artwork survives across sessions/token refreshes.
|
||||
|
||||
+4
-6
@@ -2,7 +2,7 @@ import { S } from '../state.js';
|
||||
import { esc, formatDur, toast } from '../utils.js';
|
||||
import { api, coverUrl, fetchRomajiIndex, romajiLookup, getRomajiOverrides, invalidateRomajiIndex, invalidateApiCache } from '../api.js';
|
||||
// Circular — safe (only used inside function bodies)
|
||||
import { loadPlaylists, loadPlaylistDetail } from '../views/views.js';
|
||||
import { loadPlaylists, loadPlaylistDetail, refreshCurrentView } from '../views/views.js';
|
||||
import { showConfirm } from './modals.js';
|
||||
|
||||
// ─── SONG INFO MODAL ───
|
||||
@@ -312,11 +312,9 @@ export async function deleteSong() {
|
||||
if (!r.ok) throw new Error(d.error || `HTTP ${r.status}`);
|
||||
toast(`🗑 "${s.title}" gelöscht`);
|
||||
closeInfoModal();
|
||||
setTimeout(() => {
|
||||
loadPlaylists();
|
||||
if (document.getElementById('page-playlist-detail')?.classList.contains('active') && S._currentPlaylist)
|
||||
loadPlaylistDetail(S._currentPlaylist);
|
||||
}, 3000);
|
||||
// Backend delete + Navidrome rescan takes a moment; then refresh whatever view is open (album,
|
||||
// artist, playlist, starred, …) so the removed song disappears without a manual reload.
|
||||
setTimeout(() => refreshCurrentView(), 3000);
|
||||
} catch (e) {
|
||||
status.textContent = '❌ Fehler: ' + e.message;
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import { S } from '../state.js';
|
||||
import { esc, toast } from '../utils.js';
|
||||
import { api, coverUrl, ndAuth } from '../api.js';
|
||||
import { api, coverUrl, ndAuth, invalidateApiCache } from '../api.js';
|
||||
// Circular — safe (only used inside function bodies)
|
||||
import { playQueue, dlSongLocal, nativeInsertAt, renderQueue } from '../player/player.js';
|
||||
import { navigate, loadPlaylists, loadPlaylistDetail } from '../views/views.js';
|
||||
@@ -235,6 +235,7 @@ export async function deletePlaylist() {
|
||||
await api('deletePlaylist', `&id=${pl.id}`);
|
||||
closeEditPlModal();
|
||||
toast(`🗑 "${pl.name}" gelöscht`);
|
||||
invalidateApiCache('getPlaylists'); // drop the 3-min cache so the list reloads without the deleted playlist
|
||||
navigate('playlists');
|
||||
} catch(e) { toast('Fehler beim Löschen: ' + e.message); }
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { loadHomeData, loadAlbumRow, loadArtists, loadAlbums, loadGenreDetail }
|
||||
import { loadArtistDetail, loadArtistAllSongs, loadAlbumDetail, loadItunesAlbumDetail, loadPartialAlbumDetail } from './detail.js';
|
||||
import { loadPlaylists, loadPlaylistDetail, loadStarred, loadRandom } from './playlists.js';
|
||||
import { loadNewReleases, loadRecommendations } from './discover.js';
|
||||
import { invalidateApiCache } from '../api.js';
|
||||
|
||||
// ─── NAVIGATION HISTORY ───
|
||||
export function _navSave() {
|
||||
@@ -36,6 +37,27 @@ export function closeHeaderSearch() {
|
||||
clearTimeout(S._headerSearchTimer);
|
||||
}
|
||||
|
||||
// Force-refresh the active view after a mutating action (delete a song / playlist, etc.): drop the
|
||||
// cached API responses so the reload is fresh, then re-run the current nav entry's loader.
|
||||
export function refreshCurrentView() {
|
||||
invalidateApiCache();
|
||||
const entry = S._navStack[S._navCursor];
|
||||
if (!entry) return;
|
||||
const { page, data } = entry;
|
||||
switch (page) {
|
||||
case 'home': loadHomeData(true); break;
|
||||
case 'albums': loadAlbums(); break;
|
||||
case 'artists': loadArtists(); break;
|
||||
case 'starred': loadStarred(); break;
|
||||
case 'random': loadRandom(); break;
|
||||
case 'playlists': loadPlaylists(); break;
|
||||
case 'playlist-detail': if (data) loadPlaylistDetail(data); break;
|
||||
case 'album-detail': if (data) loadAlbumDetail(data); break;
|
||||
case 'artist-detail': if (data) loadArtistDetail(data); break;
|
||||
case 'genre-detail': if (data) loadGenreDetail(data); break;
|
||||
}
|
||||
}
|
||||
|
||||
export function navigate(page, data=null) {
|
||||
closeHeaderSearch();
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
|
||||
Reference in new issue
Block a user