feat: Edit Playlist modal — name, description, public toggle, cover upload
- 'Bearbeiten' button in playlist-detail header opens modal
- Subsonic updatePlaylist API: name, comment, public synced
- Cover upload via POST /api/playlist/{id}/image (Navidrome REST, Basic Auth)
- Cover delete via DELETE /api/playlist/{id}/image
- Graceful toast if Navidrome version doesn't support cover upload
- Mobile-responsive layout (stacks cover + fields on small screens)
- Bump SW cache to ns-v31
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
dad2b0b730
commit
23d7c62045
4 files changed
+150
-2
No files matched your search
@@ -422,15 +422,21 @@ async function quickPlayPlaylist(pl){
|
||||
try{const r=await api('getPlaylist',`&id=${pl.id}`);const songs=r.playlist?.entry||[];playQueue(songs,0)}
|
||||
catch(e){toast('Fehler: '+e.message)}
|
||||
}
|
||||
let _currentPlaylist=null;
|
||||
|
||||
async function loadPlaylistDetail(pl){
|
||||
_currentPlaylist={...pl};
|
||||
document.getElementById('playlist-detail-name').textContent=pl.name;
|
||||
const cw=document.getElementById('playlist-cover');
|
||||
const cv=coverUrl(pl.coverArt,300);
|
||||
cw.innerHTML=cv?`<img src="${cv}" onerror="this.parentNode.innerHTML='<div class=\'no-art\'>🎶</div>'">`:'<div class="no-art">🎶</div>';
|
||||
document.getElementById('btn-edit-playlist').onclick=()=>openEditPlModal(_currentPlaylist);
|
||||
const sl=document.getElementById('playlist-songs');sl.innerHTML='<div class="spinner"></div>';
|
||||
try{
|
||||
const r=await api('getPlaylist',`&id=${pl.id}`);
|
||||
const songs=r.playlist?.entry||[];
|
||||
const full=r.playlist||{};
|
||||
_currentPlaylist={...pl,...full,id:pl.id};
|
||||
const songs=full.entry||[];
|
||||
const totalMin=Math.round(songs.reduce((s,t)=>s+(t.duration||0),0)/60);
|
||||
document.getElementById('playlist-detail-stats').textContent=`${songs.length} Titel · ${totalMin} Min.`;
|
||||
document.getElementById('btn-play-playlist').onclick=()=>playQueue(songs,0);
|
||||
@@ -442,6 +448,77 @@ async function loadPlaylistDetail(pl){
|
||||
}catch(e){sl.innerHTML=`<p style="color:var(--danger)">${e.message}</p>`}
|
||||
}
|
||||
|
||||
// ─── EDIT PLAYLIST ───
|
||||
function openEditPlModal(pl){
|
||||
const overlay=document.getElementById('edit-pl-overlay');
|
||||
document.getElementById('edit-pl-name').value=pl.name||'';
|
||||
document.getElementById('edit-pl-desc').value=pl.comment||'';
|
||||
const tog=document.getElementById('edit-pl-public-toggle');
|
||||
tog.classList.toggle('on',!!pl.public);
|
||||
const cv=coverUrl(pl.coverArt,300);
|
||||
document.getElementById('edit-pl-cover').innerHTML=cv
|
||||
?`<img src="${cv}" style="width:100%;height:100%;object-fit:cover">`
|
||||
:'<div class="no-art" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px">🎶</div>';
|
||||
overlay.classList.add('open');
|
||||
}
|
||||
function closeEditPlModal(){document.getElementById('edit-pl-overlay').classList.remove('open')}
|
||||
|
||||
async function saveEditPlaylist(){
|
||||
const pl=_currentPlaylist;if(!pl)return;
|
||||
const name=document.getElementById('edit-pl-name').value.trim();
|
||||
if(!name){toast('Name darf nicht leer sein');return;}
|
||||
const comment=document.getElementById('edit-pl-desc').value.trim();
|
||||
const pub=document.getElementById('edit-pl-public-toggle').classList.contains('on');
|
||||
const btn=document.querySelector('.edit-pl-btn-save');
|
||||
btn.disabled=true;btn.textContent='Speichern…';
|
||||
try{
|
||||
await api('updatePlaylist',`&playlistId=${pl.id}&name=${encodeURIComponent(name)}&comment=${encodeURIComponent(comment)}&public=${pub}`);
|
||||
_currentPlaylist={..._currentPlaylist,name,comment,public:pub};
|
||||
document.getElementById('playlist-detail-name').textContent=name;
|
||||
closeEditPlModal();
|
||||
toast('Playlist aktualisiert');
|
||||
}catch(e){toast('Fehler: '+e.message)}
|
||||
finally{btn.disabled=false;btn.textContent='Speichern';}
|
||||
}
|
||||
|
||||
function editPlUploadCover(){document.getElementById('edit-pl-file').click()}
|
||||
|
||||
async function editPlCoverSelected(input){
|
||||
const file=input.files?.[0];if(!file||!_currentPlaylist)return;
|
||||
input.value='';
|
||||
const preview=URL.createObjectURL(file);
|
||||
document.getElementById('edit-pl-cover').innerHTML=`<img src="${preview}" style="width:100%;height:100%;object-fit:cover">`;
|
||||
try{
|
||||
const form=new FormData();form.append('image',file);
|
||||
const auth='Basic '+btoa(`${cfg.user}:${cfg.pass}`);
|
||||
const res=await fetch(`${cfg.url}/api/playlist/${_currentPlaylist.id}/image`,{method:'POST',headers:{Authorization:auth},body:form});
|
||||
if(!res.ok)throw new Error(res.status);
|
||||
toast('Cover aktualisiert');
|
||||
// Refresh cover in detail view
|
||||
setTimeout(()=>{
|
||||
const cv=document.getElementById('playlist-cover');
|
||||
const img=cv?.querySelector('img');if(img)img.src=img.src.split('?')[0]+'?t='+Date.now();
|
||||
},500);
|
||||
}catch{
|
||||
toast('Cover-Upload nicht unterstützt (Navidrome-Version)');
|
||||
document.getElementById('edit-pl-cover').innerHTML=
|
||||
(coverUrl(_currentPlaylist.coverArt,300)
|
||||
?`<img src="${coverUrl(_currentPlaylist.coverArt,300)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
:'<div class="no-art" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px">🎶</div>');
|
||||
}
|
||||
}
|
||||
|
||||
async function editPlRemoveCover(){
|
||||
if(!_currentPlaylist)return;
|
||||
try{
|
||||
const auth='Basic '+btoa(`${cfg.user}:${cfg.pass}`);
|
||||
const res=await fetch(`${cfg.url}/api/playlist/${_currentPlaylist.id}/image`,{method:'DELETE',headers:{Authorization:auth}});
|
||||
if(!res.ok)throw new Error(res.status);
|
||||
toast('Cover entfernt');
|
||||
document.getElementById('edit-pl-cover').innerHTML='<div class="no-art" style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:36px">🎶</div>';
|
||||
}catch{toast('Entfernen nicht unterstützt (Navidrome-Version)')}
|
||||
}
|
||||
|
||||
// ─── RADIO MODE (endloser Shuffle aus der eigenen Bibliothek) ───
|
||||
let radioMode=false;
|
||||
function _applyRadioBtn(){
|
||||
|
||||
Reference in new issue
Block a user