style(css+js): apply biome formatter

This commit is contained in:
Edouard Vanbelle
2026-04-11 18:20:43 +02:00
parent d32a32c30a
commit c7eeb734d7
6 changed files with 222 additions and 100 deletions
+11 -5
View File
@@ -106,7 +106,9 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transform 0.15s, box-shadow 0.15s; transition:
transform 0.15s,
box-shadow 0.15s;
box-shadow: 0 2px 8px var(--color-accent-shadow, rgba(255, 94, 58, 0.25)); box-shadow: 0 2px 8px var(--color-accent-shadow, rgba(255, 94, 58, 0.25));
} }
@@ -426,7 +428,9 @@
} }
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to {
transform: rotate(360deg);
}
} }
/* Dark theme */ /* Dark theme */
@@ -757,7 +761,7 @@
} }
.player-queue::after { .player-queue::after {
content: ''; content: "";
position: absolute; position: absolute;
bottom: -8px; bottom: -8px;
right: 60px; right: 60px;
@@ -916,7 +920,7 @@
} }
.player-btn.repeat-one i::after { .player-btn.repeat-one i::after {
content: '1'; content: "1";
font-size: 8px; font-size: 8px;
position: absolute; position: absolute;
top: 0; top: 0;
@@ -1076,7 +1080,9 @@
padding: 4px 6px; padding: 4px 6px;
border-radius: 4px; border-radius: 4px;
opacity: 0; opacity: 0;
transition: opacity 0.15s, color 0.15s; transition:
opacity 0.15s,
color 0.15s;
font-size: 0.85rem; font-size: 0.85rem;
} }
.music-track:hover .music-track-remove-btn { .music-track:hover .music-track-remove-btn {
+1 -1
View File
@@ -334,7 +334,7 @@ function switchToMusicSection() {
// Hide breadcrumb // Hide breadcrumb
const breadcrumb = document.querySelector('.breadcrumb'); const breadcrumb = document.querySelector('.breadcrumb');
breadcrumb?.classList.add("hidden"); breadcrumb?.classList.add('hidden');
// Hide file containers // Hide file containers
toggleFileContainer(false); toggleFileContainer(false);
+4 -4
View File
@@ -1122,7 +1122,7 @@ const contextMenus = {
}, },
_renderPlaylistSelect(container, playlists) { _renderPlaylistSelect(container, playlists) {
const t = (key, fallback) => window.i18n ? window.i18n.t(key, fallback) : fallback; const t = (key, fallback) => (window.i18n ? window.i18n.t(key, fallback) : fallback);
container.innerHTML = ''; container.innerHTML = '';
@@ -1131,7 +1131,7 @@ const contextMenus = {
return; return;
} }
playlists.forEach(playlist => { playlists.forEach((playlist) => {
const item = document.createElement('div'); const item = document.createElement('div');
item.className = 'folder-select-item'; item.className = 'folder-select-item';
item.dataset.id = playlist.id; item.dataset.id = playlist.id;
@@ -1142,7 +1142,7 @@ const contextMenus = {
`; `;
item.addEventListener('click', () => { item.addEventListener('click', () => {
container.querySelectorAll('.folder-select-item').forEach(el => el.classList.remove('selected')); container.querySelectorAll('.folder-select-item').forEach((el) => el.classList.remove('selected'));
item.classList.add('selected'); item.classList.add('selected');
this._selectedPlaylistId = playlist.id; this._selectedPlaylistId = playlist.id;
const addBtn = document.getElementById('playlist-add-btn'); const addBtn = document.getElementById('playlist-add-btn');
@@ -1170,7 +1170,7 @@ const contextMenus = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...getCsrfHeaders() ...getCsrfHeaders()
}, },
body: JSON.stringify({ file_ids: files.map(f => f.id) }) body: JSON.stringify({ file_ids: files.map((f) => f.id) })
}); });
if (!resp.ok) { if (!resp.ok) {
+174 -58
View File
@@ -191,7 +191,9 @@ const musicView = {
return; return;
} }
listEl.innerHTML = this.playlists.map(p => ` listEl.innerHTML = this.playlists
.map(
(p) => `
<div class="music-playlist-item" data-id="${p.id}"> <div class="music-playlist-item" data-id="${p.id}">
<div class="music-playlist-icon"> <div class="music-playlist-icon">
<i class="fas fa-music"></i> <i class="fas fa-music"></i>
@@ -201,9 +203,11 @@ const musicView = {
<span class="music-playlist-item-count">${p.track_count || 0} ${t('music.tracks', 'tracks')}</span> <span class="music-playlist-item-count">${p.track_count || 0} ${t('music.tracks', 'tracks')}</span>
</div> </div>
</div> </div>
`).join(''); `
)
.join('');
listEl.querySelectorAll('.music-playlist-item').forEach(item => { listEl.querySelectorAll('.music-playlist-item').forEach((item) => {
item.addEventListener('click', () => { item.addEventListener('click', () => {
const id = item.dataset.id; const id = item.dataset.id;
this._selectPlaylist(id); this._selectPlaylist(id);
@@ -264,7 +268,7 @@ const musicView = {
}, },
async _selectPlaylist(playlistId) { async _selectPlaylist(playlistId) {
const playlist = this.playlists.find(p => p.id === playlistId); const playlist = this.playlists.find((p) => p.id === playlistId);
if (!playlist) return; if (!playlist) return;
this.currentPlaylist = playlist; this.currentPlaylist = playlist;
@@ -301,12 +305,12 @@ const musicView = {
} }
const togglePublicBtn = document.getElementById('music-toggle-public-btn'); const togglePublicBtn = document.getElementById('music-toggle-public-btn');
if (togglePublicBtn) { if (togglePublicBtn) {
const t2 = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t2 = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
togglePublicBtn.title = playlist.is_public ? t2('music.make_private', 'Make private') : t2('music.make_public', 'Make public'); togglePublicBtn.title = playlist.is_public ? t2('music.make_private', 'Make private') : t2('music.make_public', 'Make public');
togglePublicBtn.classList.toggle('active', playlist.is_public); togglePublicBtn.classList.toggle('active', playlist.is_public);
} }
document.querySelectorAll('.music-playlist-item').forEach(item => { document.querySelectorAll('.music-playlist-item').forEach((item) => {
item.classList.toggle('active', item.dataset.id === playlistId); item.classList.toggle('active', item.dataset.id === playlistId);
}); });
@@ -363,7 +367,9 @@ const musicView = {
<span class="music-track-col music-track-duration"><i class="far fa-clock"></i></span> <span class="music-track-col music-track-duration"><i class="far fa-clock"></i></span>
<span class="music-track-col music-track-actions"></span> <span class="music-track-col music-track-actions"></span>
</div> </div>
${this.currentTracks.map((track, idx) => ` ${this.currentTracks
.map(
(track, idx) => `
<div class="music-track ${musicPlayer.currentTrack?.id === track.id ? 'playing' : ''}" data-idx="${idx}" data-id="${track.id}" data-file-id="${track.file_id}" draggable="true"> <div class="music-track ${musicPlayer.currentTrack?.id === track.id ? 'playing' : ''}" data-idx="${idx}" data-id="${track.id}" data-file-id="${track.file_id}" draggable="true">
<span class="music-track-col music-track-drag"><i class="fas fa-grip-vertical"></i></span> <span class="music-track-col music-track-drag"><i class="fas fa-grip-vertical"></i></span>
<span class="music-track-col music-track-num"> <span class="music-track-col music-track-num">
@@ -381,15 +387,17 @@ const musicView = {
<button class="music-track-remove-btn" title="${t('music.remove', 'Remove')}"><i class="fas fa-times"></i></button> <button class="music-track-remove-btn" title="${t('music.remove', 'Remove')}"><i class="fas fa-times"></i></button>
</span> </span>
</div> </div>
`).join('')} `
)
.join('')}
`; `;
const self = this; const self = this;
trackListEl.querySelectorAll('.music-track').forEach(row => { trackListEl.querySelectorAll('.music-track').forEach((row) => {
row.addEventListener('click', () => { row.addEventListener('click', () => {
const idx = parseInt(row.dataset.idx); const idx = parseInt(row.dataset.idx);
// Toggle selection // Toggle selection
trackListEl.querySelectorAll('.music-track').forEach(r => { trackListEl.querySelectorAll('.music-track').forEach((r) => {
if (r !== row) r.classList.remove('selected'); if (r !== row) r.classList.remove('selected');
}); });
row.classList.toggle('selected'); row.classList.toggle('selected');
@@ -413,7 +421,7 @@ const musicView = {
}); });
row.addEventListener('dragend', () => { row.addEventListener('dragend', () => {
row.classList.remove('dragging'); row.classList.remove('dragging');
trackListEl.querySelectorAll('.music-track').forEach(r => r.classList.remove('drag-over')); trackListEl.querySelectorAll('.music-track').forEach((r) => r.classList.remove('drag-over'));
}); });
row.addEventListener('dragover', (e) => { row.addEventListener('dragover', (e) => {
e.preventDefault(); e.preventDefault();
@@ -517,12 +525,22 @@ const musicView = {
this._renderPlaylists(); this._renderPlaylists();
this._selectPlaylist(playlist.id); this._selectPlaylist(playlist.id);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.create_playlist', 'Create Playlist'), text: name }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.create_playlist', 'Create Playlist'),
text: name
});
} }
} catch (err) { } catch (err) {
console.error('Create playlist error:', err); console.error('Create playlist error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} finally { } finally {
if (createBtn) createBtn.disabled = false; if (createBtn) createBtn.disabled = false;
@@ -536,8 +554,11 @@ const musicView = {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const confirmed = await new Promise(resolve => { const confirmed = await new Promise((resolve) => {
if (!window.Modal) { resolve(confirm(t('music.confirm_delete', 'Delete this playlist?'))); return; } if (!window.Modal) {
resolve(confirm(t('music.confirm_delete', 'Delete this playlist?')));
return;
}
window.Modal.prompt({ window.Modal.prompt({
title: t('music.delete', 'Delete'), title: t('music.delete', 'Delete'),
label: t('music.confirm_delete', 'Delete this playlist?'), label: t('music.confirm_delete', 'Delete this playlist?'),
@@ -545,7 +566,7 @@ const musicView = {
value: this.currentPlaylist.name, value: this.currentPlaylist.name,
icon: 'fa-trash', icon: 'fa-trash',
confirmText: t('music.delete', 'Delete') confirmText: t('music.delete', 'Delete')
}).then(val => resolve(val !== null)); }).then((val) => resolve(val !== null));
}); });
if (!confirmed) return; if (!confirmed) return;
@@ -561,7 +582,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to delete playlist'); if (!resp.ok) throw new Error('Failed to delete playlist');
const deletedName = this.currentPlaylist.name; const deletedName = this.currentPlaylist.name;
this.playlists = this.playlists.filter(p => p.id !== this.currentPlaylist.id); this.playlists = this.playlists.filter((p) => p.id !== this.currentPlaylist.id);
this.currentPlaylist = null; this.currentPlaylist = null;
this.currentTracks = []; this.currentTracks = [];
this._renderPlaylists(); this._renderPlaylists();
@@ -571,7 +592,12 @@ const musicView = {
} catch (err) { } catch (err) {
console.error('Delete playlist error:', err); console.error('Delete playlist error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} finally { } finally {
if (deleteBtn) deleteBtn.disabled = false; if (deleteBtn) deleteBtn.disabled = false;
@@ -640,7 +666,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to update playlist'); if (!resp.ok) throw new Error('Failed to update playlist');
this.currentPlaylist.name = newName.trim(); this.currentPlaylist.name = newName.trim();
const idx = this.playlists.findIndex(p => p.id === this.currentPlaylist.id); const idx = this.playlists.findIndex((p) => p.id === this.currentPlaylist.id);
if (idx !== -1) this.playlists[idx].name = newName.trim(); if (idx !== -1) this.playlists[idx].name = newName.trim();
const nameEl = document.getElementById('music-playlist-name'); const nameEl = document.getElementById('music-playlist-name');
@@ -649,7 +675,12 @@ const musicView = {
} catch (err) { } catch (err) {
console.error('Edit playlist error:', err); console.error('Edit playlist error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}, },
@@ -681,12 +712,22 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to share playlist'); if (!resp.ok) throw new Error('Failed to share playlist');
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.share', 'Share'), text: t('music.added', 'Added!') }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.share', 'Share'),
text: t('music.added', 'Added!')
});
} }
} catch (err) { } catch (err) {
console.error('Share playlist error:', err); console.error('Share playlist error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}, },
@@ -747,12 +788,17 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to add tracks'); if (!resp.ok) throw new Error('Failed to add tracks');
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.add_tracks', 'Add Tracks'), text: `${fileIds.length} ${t('music.added_to_playlist', 'added to playlist')}` }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.add_tracks', 'Add Tracks'),
text: `${fileIds.length} ${t('music.added_to_playlist', 'added to playlist')}`
});
} }
await this._loadPlaylistTracks(this.currentPlaylist.id); await this._loadPlaylistTracks(this.currentPlaylist.id);
// Update track count // Update track count
const playlist = this.playlists.find(p => p.id === this.currentPlaylist.id); const playlist = this.playlists.find((p) => p.id === this.currentPlaylist.id);
if (playlist) { if (playlist) {
playlist.track_count = (playlist.track_count || 0) + fileIds.length; playlist.track_count = (playlist.track_count || 0) + fileIds.length;
this.currentPlaylist.track_count = playlist.track_count; this.currentPlaylist.track_count = playlist.track_count;
@@ -763,7 +809,12 @@ const musicView = {
} catch (err) { } catch (err) {
console.error('Add tracks error:', err); console.error('Add tracks error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: t('music.add_error', 'Could not add tracks to playlist') }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: t('music.add_error', 'Could not add tracks to playlist')
});
} }
} }
}); });
@@ -773,7 +824,7 @@ const musicView = {
async _removeTrackFromPlaylist(trackId, fileId) { async _removeTrackFromPlaylist(trackId, fileId) {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
try { try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, {
@@ -784,10 +835,15 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to remove track'); if (!resp.ok) throw new Error('Failed to remove track');
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.remove', 'Remove'), text: t('music.track_removed', 'Track removed') }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.remove', 'Remove'),
text: t('music.track_removed', 'Track removed')
});
} }
await this._loadPlaylistTracks(this.currentPlaylist.id); await this._loadPlaylistTracks(this.currentPlaylist.id);
const playlist = this.playlists.find(p => p.id === this.currentPlaylist.id); const playlist = this.playlists.find((p) => p.id === this.currentPlaylist.id);
if (playlist) { if (playlist) {
playlist.track_count = Math.max(0, (playlist.track_count || 1) - 1); playlist.track_count = Math.max(0, (playlist.track_count || 1) - 1);
this.currentPlaylist.track_count = playlist.track_count; this.currentPlaylist.track_count = playlist.track_count;
@@ -798,14 +854,19 @@ const musicView = {
} catch (err) { } catch (err) {
console.error('Remove track error:', err); console.error('Remove track error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}, },
async _reorderTrack(fromIdx, toIdx) { async _reorderTrack(fromIdx, toIdx) {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const tracks = [...this.currentTracks]; const tracks = [...this.currentTracks];
const [moved] = tracks.splice(fromIdx, 1); const [moved] = tracks.splice(fromIdx, 1);
@@ -813,7 +874,7 @@ const musicView = {
this.currentTracks = tracks; this.currentTracks = tracks;
this._renderTracks(); this._renderTracks();
const itemIds = tracks.map(tr => tr.id); const itemIds = tracks.map((tr) => tr.id);
try { try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/reorder`, { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/reorder`, {
method: 'PUT', method: 'PUT',
@@ -825,7 +886,12 @@ const musicView = {
} catch (err) { } catch (err) {
console.error('Reorder error:', err); console.error('Reorder error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
await this._loadPlaylistTracks(this.currentPlaylist.id); await this._loadPlaylistTracks(this.currentPlaylist.id);
} }
@@ -833,7 +899,7 @@ const musicView = {
async _showManageSharesDialog() { async _showManageSharesDialog() {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const existing = document.getElementById('music-shares-dialog'); const existing = document.getElementById('music-shares-dialog');
if (existing) existing.remove(); if (existing) existing.remove();
@@ -864,7 +930,9 @@ const musicView = {
document.body.appendChild(dialog); document.body.appendChild(dialog);
dialog.querySelector('.music-shares-close-btn').addEventListener('click', () => dialog.remove()); dialog.querySelector('.music-shares-close-btn').addEventListener('click', () => dialog.remove());
dialog.addEventListener('click', (e) => { if (e.target === dialog) dialog.remove(); }); dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.remove();
});
dialog.querySelector('#music-share-add-btn').addEventListener('click', async () => { dialog.querySelector('#music-share-add-btn').addEventListener('click', async () => {
const userInput = dialog.querySelector('#music-share-user-input'); const userInput = dialog.querySelector('#music-share-user-input');
@@ -884,11 +952,21 @@ const musicView = {
writeInput.checked = false; writeInput.checked = false;
this._loadSharesList(dialog); this._loadSharesList(dialog);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.share', 'Share'), text: t('music.added', 'Added!') }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.share', 'Share'),
text: t('music.added', 'Added!')
});
} }
} catch (err) { } catch (err) {
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}); });
@@ -898,7 +976,7 @@ const musicView = {
async _loadSharesList(dialog) { async _loadSharesList(dialog) {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const body = dialog.querySelector('.music-shares-body'); const body = dialog.querySelector('.music-shares-body');
if (!body) return; if (!body) return;
@@ -917,15 +995,19 @@ const musicView = {
return; return;
} }
body.innerHTML = shares.map(s => ` body.innerHTML = shares
.map(
(s) => `
<div class="music-share-item" data-user-id="${this._escapeHtml(s.user_id)}"> <div class="music-share-item" data-user-id="${this._escapeHtml(s.user_id)}">
<span class="music-share-user"><i class="fas fa-user"></i> ${this._escapeHtml(s.user_id)}</span> <span class="music-share-user"><i class="fas fa-user"></i> ${this._escapeHtml(s.user_id)}</span>
<span class="music-share-perm">${s.can_write ? t('music.can_write', 'Can edit') : t('music.read_only', 'Read only')}</span> <span class="music-share-perm">${s.can_write ? t('music.can_write', 'Can edit') : t('music.read_only', 'Read only')}</span>
<button class="music-share-remove-btn" title="${t('music.remove_share', 'Remove share')}"><i class="fas fa-times"></i></button> <button class="music-share-remove-btn" title="${t('music.remove_share', 'Remove share')}"><i class="fas fa-times"></i></button>
</div> </div>
`).join(''); `
)
.join('');
body.querySelectorAll('.music-share-remove-btn').forEach(btn => { body.querySelectorAll('.music-share-remove-btn').forEach((btn) => {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
const item = btn.closest('.music-share-item'); const item = btn.closest('.music-share-item');
const userId = item.dataset.userId; const userId = item.dataset.userId;
@@ -939,7 +1021,7 @@ const musicView = {
async _removeShare(userId, dialog) { async _removeShare(userId, dialog) {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
try { try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, {
@@ -951,14 +1033,19 @@ const musicView = {
this._loadSharesList(dialog); this._loadSharesList(dialog);
} catch (err) { } catch (err) {
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}, },
async _togglePublic() { async _togglePublic() {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const newValue = !this.currentPlaylist.is_public; const newValue = !this.currentPlaylist.is_public;
try { try {
@@ -971,7 +1058,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to update playlist'); if (!resp.ok) throw new Error('Failed to update playlist');
this.currentPlaylist.is_public = newValue; this.currentPlaylist.is_public = newValue;
const idx = this.playlists.findIndex(p => p.id === this.currentPlaylist.id); const idx = this.playlists.findIndex((p) => p.id === this.currentPlaylist.id);
if (idx !== -1) this.playlists[idx].is_public = newValue; if (idx !== -1) this.playlists[idx].is_public = newValue;
const badge = document.getElementById('music-public-badge'); const badge = document.getElementById('music-public-badge');
@@ -985,19 +1072,29 @@ const musicView = {
if (window.notifications) { if (window.notifications) {
const status = newValue ? t('music.public', 'Public') : t('music.private', 'Private'); const status = newValue ? t('music.public', 'Public') : t('music.private', 'Private');
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.toggle_public', 'Visibility'), text: status }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.toggle_public', 'Visibility'),
text: status
});
} }
} catch (err) { } catch (err) {
console.error('Toggle public error:', err); console.error('Toggle public error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}, },
async _showCoverPicker() { async _showCoverPicker() {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; const t = (key, fallback = '') => (typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key);
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'file'; input.type = 'file';
@@ -1035,7 +1132,7 @@ const musicView = {
if (!resp.ok) throw new Error('Failed to set cover'); if (!resp.ok) throw new Error('Failed to set cover');
this.currentPlaylist.cover_file_id = uploaded.id; this.currentPlaylist.cover_file_id = uploaded.id;
const plIdx = this.playlists.findIndex(p => p.id === this.currentPlaylist.id); const plIdx = this.playlists.findIndex((p) => p.id === this.currentPlaylist.id);
if (plIdx !== -1) this.playlists[plIdx].cover_file_id = uploaded.id; if (plIdx !== -1) this.playlists[plIdx].cover_file_id = uploaded.id;
const coverEl = document.getElementById('music-playlist-cover'); const coverEl = document.getElementById('music-playlist-cover');
@@ -1044,12 +1141,22 @@ const musicView = {
} }
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.set_cover', 'Set cover'), text: t('music.cover_updated', 'Cover updated') }); window.notifications.addNotification({
icon: 'fa-check-circle',
iconClass: 'upload',
title: t('music.set_cover', 'Set cover'),
text: t('music.cover_updated', 'Cover updated')
});
} }
} catch (err) { } catch (err) {
console.error('Cover upload error:', err); console.error('Cover upload error:', err);
if (window.notifications) { if (window.notifications) {
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: err.message }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: err.message
});
} }
} }
}); });
@@ -1265,7 +1372,7 @@ const musicPlayer = {
const fileId = this.currentTrack.file_id; const fileId = this.currentTrack.file_id;
this.audio.src = `/api/files/${fileId}`; this.audio.src = `/api/files/${fileId}`;
this.audio.play().catch(err => { this.audio.play().catch((err) => {
console.error('Playback error:', err); console.error('Playback error:', err);
}); });
this.isPlaying = true; this.isPlaying = true;
@@ -1470,7 +1577,12 @@ const musicPlayer = {
}; };
if (window.notifications) { if (window.notifications) {
const trackName = this.currentTrack?.title || this.currentTrack?.file_name || t('music.unknown_title', 'Unknown'); const trackName = this.currentTrack?.title || this.currentTrack?.file_name || t('music.unknown_title', 'Unknown');
window.notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', title: t('music.error', 'Error'), text: `${t('music.playback_error', 'Playback failed')}: ${trackName}` }); window.notifications.addNotification({
icon: 'fa-exclamation-circle',
iconClass: 'error',
title: t('music.error', 'Error'),
text: `${t('music.playback_error', 'Playback failed')}: ${trackName}`
});
} }
}, },
@@ -1497,7 +1609,7 @@ const musicPlayer = {
return typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key; return typeof i18n !== 'undefined' && i18n.t ? i18n.t(key) : fallback || key;
}; };
trackName.textContent = this.currentTrack trackName.textContent = this.currentTrack
? (this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown')) ? this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown')
: t('music.not_playing', 'Not playing'); : t('music.not_playing', 'Not playing');
} }
@@ -1506,7 +1618,7 @@ const musicPlayer = {
} }
if (musicView.currentTracks.length > 0) { if (musicView.currentTracks.length > 0) {
document.querySelectorAll('.music-track').forEach(row => { document.querySelectorAll('.music-track').forEach((row) => {
const idx = parseInt(row.dataset.idx); const idx = parseInt(row.dataset.idx);
row.classList.toggle('playing', idx === this.currentIndex && this.isPlaying); row.classList.toggle('playing', idx === this.currentIndex && this.isPlaying);
@@ -1562,7 +1674,9 @@ const musicPlayer = {
return; return;
} }
queueList.innerHTML = this.queue.map((track, idx) => ` queueList.innerHTML = this.queue
.map(
(track, idx) => `
<div class="player-queue-item ${idx === this.currentIndex ? 'active' : ''}" data-idx="${idx}"> <div class="player-queue-item ${idx === this.currentIndex ? 'active' : ''}" data-idx="${idx}">
<span class="queue-item-num">${idx + 1}</span> <span class="queue-item-num">${idx + 1}</span>
<span class="queue-item-info"> <span class="queue-item-info">
@@ -1574,9 +1688,11 @@ const musicPlayer = {
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
`).join(''); `
)
.join('');
queueList.querySelectorAll('.player-queue-item').forEach(item => { queueList.querySelectorAll('.player-queue-item').forEach((item) => {
item.addEventListener('click', (e) => { item.addEventListener('click', (e) => {
if (e.target.closest('.queue-item-remove')) return; if (e.target.closest('.queue-item-remove')) return;
const idx = parseInt(item.dataset.idx); const idx = parseInt(item.dataset.idx);
@@ -1584,7 +1700,7 @@ const musicPlayer = {
}); });
}); });
queueList.querySelectorAll('.queue-item-remove').forEach(btn => { queueList.querySelectorAll('.queue-item-remove').forEach((btn) => {
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
const idx = parseInt(btn.dataset.idx); const idx = parseInt(btn.dataset.idx);