feat: romaji artist names, Apple Music autofill, romaji index invalidation
- Artist grid and detail header now show romaji names when toggle is on. Builds a Map<artistId, artistRomaji> from the existing song romaji index so no extra API call is needed. - Apple Music autofill button added to edit modal next to MusicBrainz. Reuses the existing lookupItunesTrackMeta backend function via a new GET /api/itunes/lookup endpoint — fills album, year, genre, track#, and cover art. - Romaji index is invalidated after every successful download so newly added songs appear with correct sort tags without requiring a page reload. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
212491aa89
commit
b7afa19f70
6 files changed
+79
-9
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { S } from './state.js';
|
||||
import { esc, formatDur, toast } from './utils.js';
|
||||
import { api, coverUrl, ndAuth, fetchRomajiIndex, romajiConvert, romajiLookup } from './api.js';
|
||||
import { api, coverUrl, ndAuth, fetchRomajiIndex, romajiConvert, romajiLookup, invalidateRomajiIndex } from './api.js';
|
||||
import { THEMES, applyTheme, getCurrentThemeId } from './themes.js';
|
||||
// Circular imports — safe (only used inside function bodies)
|
||||
import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon, nativeInsertAt, refreshDisplayMode } from './player.js';
|
||||
@@ -444,6 +444,36 @@ export async function autofillMusicBrainz() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function autofillItunes() {
|
||||
if (!S.cfg.backendUrl) { toast('⚠ Kein Backend konfiguriert'); return; }
|
||||
const s = S.infoSong;
|
||||
if (!s) return;
|
||||
const artist = document.getElementById('ie-artist').value || s.artist || '';
|
||||
const title = document.getElementById('ie-title').value || s.title || '';
|
||||
const status = document.getElementById('info-edit-status');
|
||||
status.textContent = '🔍 Apple Music…';
|
||||
try {
|
||||
const r = await fetch(`${S.cfg.backendUrl}/api/itunes/lookup?artist=${encodeURIComponent(artist)}&title=${encodeURIComponent(title)}`);
|
||||
const d = await r.json();
|
||||
if (!d.found) { status.textContent = '❌ Kein Treffer auf iTunes'; return; }
|
||||
if (d.album) document.getElementById('ie-album').value = d.album;
|
||||
if (d.genre) document.getElementById('ie-genre').value = d.genre;
|
||||
if (d.year) document.getElementById('ie-year').value = d.year;
|
||||
if (d.trackNum) document.getElementById('ie-track').value = d.trackNum;
|
||||
if (d.totalTracks) document.getElementById('ie-total').value = d.totalTracks;
|
||||
if (d.coverUrl) {
|
||||
S._infoArtData = { url: d.coverUrl };
|
||||
document.getElementById('info-edit-art-preview').innerHTML =
|
||||
`<img src="${d.coverUrl}" style="width:100%;height:100%;object-fit:cover" onerror="this.parentNode.innerHTML='<div class=\\'no-art\\' style=\\'width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:28px\\'>🎵</div>'">`;
|
||||
status.textContent = '✅ Metadaten + Cover von Apple Music';
|
||||
} else {
|
||||
status.textContent = '✅ Metadaten von Apple Music (kein Cover)';
|
||||
}
|
||||
} catch (e) {
|
||||
status.textContent = '❌ Fehler: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveMetadata() {
|
||||
if (!S.cfg.backendUrl) { toast('⚠ Kein Backend konfiguriert'); return; }
|
||||
const s = S.infoSong;
|
||||
@@ -890,6 +920,7 @@ async function pollJob(jobId) {
|
||||
});
|
||||
if (d.status === 'done') {
|
||||
toast(d.skipped ? `⏭ "${title}" bereits auf dem Server` : `✅ ${title} gespeichert!`);
|
||||
if (!d.skipped) invalidateRomajiIndex();
|
||||
}
|
||||
}
|
||||
} catch { updateJob(jobId, 'error', 'Status-Abfrage fehlgeschlagen'); }
|
||||
|
||||
Reference in new issue
Block a user