let me = null; let instruments = []; let songs = []; const expanded = new Set(); const editing = new Set(); async function loadInstruments() { instruments = await api.get('/api/instruments'); } async function loadSongs() { songs = await api.get('/api/songs'); renderSongs(); } function instrumentOptions() { return instruments.map((i) => ``).join(''); } function songThumbTemplate(song) { const thumb = song.youtube_url ? youtubeThumbnailUrl(song.youtube_url) : null; if (thumb) { return `
${escapeHtml(song.title)}
`; } return `
`; } function songLinksTemplate(song) { const links = []; if (song.youtube_url) { links.push(`► YouTube`); } if (song.spotify_url) { links.push(`♫ Spotify`); } return links.length ? `` : ''; } function songCardTemplate(song) { if (editing.has(song.id)) return editSongCardTemplate(song); const isOpen = expanded.has(song.id); return `
${songThumbTemplate(song)}
${escapeHtml(song.title)}
${escapeHtml(song.artist)}
${song.notes ? `

${escapeHtml(song.notes)}

` : ''} ${songLinksTemplate(song)}

Chargement…

`; } function editSongCardTemplate(song) { return `
`; } function tutorialCardTemplate(t) { const thumb = youtubeThumbnailUrl(t.url); if (thumb) { return `
${escapeHtml(t.label || t.instrument_name)}
${escapeHtml(t.instrument_name)}
${escapeHtml(t.label || 'Tuto')}
`; } return `
🔗
${escapeHtml(t.instrument_name)}
`; } function renderSongs() { const container = document.getElementById('songs-list'); renderList(container, songs, songCardTemplate, 'Aucun morceau au répertoire pour le moment.'); document.querySelectorAll('.toggle-tutorials').forEach((btn) => { btn.addEventListener('click', () => onToggleTutorials(parseInt(btn.dataset.id, 10))); }); document.querySelectorAll('.add-tutorial-form').forEach((form) => { form.addEventListener('submit', onAddTutorial); }); document.querySelectorAll('.edit-song').forEach((btn) => { btn.addEventListener('click', () => { editing.add(parseInt(btn.dataset.id, 10)); renderSongs(); }); }); document.querySelectorAll('.cancel-edit').forEach((btn) => { btn.addEventListener('click', () => { editing.delete(parseInt(btn.dataset.id, 10)); renderSongs(); }); }); document.querySelectorAll('.edit-song-form').forEach((form) => { form.addEventListener('submit', onSaveSong); }); document.querySelectorAll('.delete-song').forEach((btn) => { btn.addEventListener('click', () => onDeleteSong(parseInt(btn.dataset.id, 10))); }); } async function onToggleTutorials(songId) { if (expanded.has(songId)) { expanded.delete(songId); } else { expanded.add(songId); } renderSongs(); if (expanded.has(songId)) { await loadTutorials(songId); } } async function loadTutorials(songId) { try { const detail = await api.get(`/api/songs/${songId}`); const container = document.querySelector(`[data-tutorials-for="${songId}"]`); if (!container) return; if (!detail.tutorials.length) { container.innerHTML = '

Aucun lien pour le moment.

'; return; } container.innerHTML = detail.tutorials.map(tutorialCardTemplate).join(''); container.querySelectorAll('.remove-tutorial').forEach((btn) => { btn.addEventListener('click', () => onRemoveTutorial(parseInt(btn.dataset.songId, 10), parseInt(btn.dataset.id, 10))); }); } catch (err) { showError(err.message); } } async function onAddTutorial(e) { e.preventDefault(); const form = e.target; const songId = parseInt(form.dataset.songId, 10); const instrumentId = parseInt(form.instrumentId.value, 10); const url = form.url.value.trim(); const label = form.label.value.trim(); try { await api.post(`/api/songs/${songId}/tutorials`, { instrumentId, url, label }); form.reset(); const song = songs.find((s) => s.id === songId); if (song) song.tutorial_count += 1; await loadTutorials(songId); renderSongs(); } catch (err) { showError(err.message); } } async function onRemoveTutorial(songId, tutorialId) { try { await api.del(`/api/songs/${songId}/tutorials/${tutorialId}`); const song = songs.find((s) => s.id === songId); if (song) song.tutorial_count -= 1; await loadTutorials(songId); renderSongs(); } catch (err) { showError(err.message); } } async function onAddSong(e) { e.preventDefault(); const form = e.target; const title = form.title.value.trim(); const artist = form.artist.value.trim(); const notes = form.notes.value.trim(); const youtubeUrl = form.youtubeUrl.value.trim(); const spotifyUrl = form.spotifyUrl.value.trim(); try { await api.post('/api/songs', { title, artist, notes, youtubeUrl, spotifyUrl }); form.reset(); document.getElementById('song-links-status').textContent = ''; closeAutocompleteDropdown(); setAddSongPanelOpen(false); await loadSongs(); } catch (err) { showError(err.message); } } function setAddSongPanelOpen(open) { document.getElementById('add-song-panel').style.display = open ? 'block' : 'none'; document.getElementById('toggle-add-song').textContent = open ? 'Annuler' : '+ Ajouter un morceau'; } async function onSaveSong(e) { e.preventDefault(); const form = e.target; const id = parseInt(form.dataset.id, 10); const title = form.title.value.trim(); const artist = form.artist.value.trim(); const notes = form.notes.value.trim(); const youtubeUrl = form.youtubeUrl.value.trim(); const spotifyUrl = form.spotifyUrl.value.trim(); try { await api.patch(`/api/songs/${id}`, { title, artist, notes, youtubeUrl, spotifyUrl }); editing.delete(id); await loadSongs(); } catch (err) { showError(err.message); } } async function onDeleteSong(id) { try { await api.del(`/api/songs/${id}`); editing.delete(id); await loadSongs(); } catch (err) { showError(err.message); } } function showError(message) { document.getElementById('error').innerHTML = `
${escapeHtml(message)}
`; } let autocompleteTimer = null; let autocompleteCandidates = []; let autocompleteHighlight = -1; function candidateItemTemplate(c, index) { const img = c.artworkUrl ? `` : ``; return `
${img}
${escapeHtml(c.title)}
${escapeHtml(c.artist)}
`; } function renderAutocompleteDropdown() { const dropdown = document.getElementById('song-title-dropdown'); if (!autocompleteCandidates.length) { dropdown.style.display = 'none'; dropdown.innerHTML = ''; return; } dropdown.innerHTML = autocompleteCandidates.map(candidateItemTemplate).join(''); dropdown.style.display = 'block'; dropdown.querySelectorAll('.autocomplete-item').forEach((el) => { el.addEventListener('mousedown', (e) => { e.preventDefault(); selectCandidate(autocompleteCandidates[parseInt(el.dataset.index, 10)]); }); }); } function closeAutocompleteDropdown() { autocompleteCandidates = []; autocompleteHighlight = -1; renderAutocompleteDropdown(); } async function selectCandidate(candidate) { document.getElementById('song-title-input').value = candidate.title; document.getElementById('song-artist-input').value = candidate.artist; closeAutocompleteDropdown(); const statusEl = document.getElementById('song-links-status'); statusEl.textContent = 'Recherche des liens YouTube / Spotify…'; try { const links = await api.get( `/api/music-search/links?title=${encodeURIComponent(candidate.title)}&artist=${encodeURIComponent(candidate.artist)}` ); const youtubeInput = document.getElementById('song-youtube-input'); const spotifyInput = document.getElementById('song-spotify-input'); if (links.youtubeUrl && !youtubeInput.value) youtubeInput.value = links.youtubeUrl; if (links.spotifyUrl && !spotifyInput.value) spotifyInput.value = links.spotifyUrl; if (links.youtubeUrl && links.spotifyUrl) statusEl.textContent = 'Liens YouTube et Spotify trouvés automatiquement.'; else if (links.youtubeUrl) statusEl.textContent = 'Lien YouTube trouvé automatiquement. Aucun lien Spotify trouvé — à saisir manuellement si besoin.'; else if (links.spotifyUrl) statusEl.textContent = 'Lien Spotify trouvé automatiquement. Aucun lien YouTube trouvé — à saisir manuellement si besoin.'; else statusEl.textContent = 'Aucun lien trouvé automatiquement — vous pouvez les saisir manuellement.'; } catch (err) { statusEl.textContent = ''; } } function initTitleAutocomplete() { const input = document.getElementById('song-title-input'); input.addEventListener('input', () => { const query = input.value.trim(); clearTimeout(autocompleteTimer); if (query.length < 2) { closeAutocompleteDropdown(); return; } autocompleteTimer = setTimeout(async () => { try { autocompleteCandidates = await api.get(`/api/music-search?q=${encodeURIComponent(query)}`); autocompleteHighlight = -1; renderAutocompleteDropdown(); } catch (err) { closeAutocompleteDropdown(); } }, 300); }); input.addEventListener('keydown', (e) => { if (!autocompleteCandidates.length) return; if (e.key === 'ArrowDown') { e.preventDefault(); autocompleteHighlight = Math.min(autocompleteHighlight + 1, autocompleteCandidates.length - 1); renderAutocompleteDropdown(); } else if (e.key === 'ArrowUp') { e.preventDefault(); autocompleteHighlight = Math.max(autocompleteHighlight - 1, 0); renderAutocompleteDropdown(); } else if (e.key === 'Enter' && autocompleteHighlight >= 0) { e.preventDefault(); selectCandidate(autocompleteCandidates[autocompleteHighlight]); } else if (e.key === 'Escape') { closeAutocompleteDropdown(); } }); input.addEventListener('blur', () => { setTimeout(closeAutocompleteDropdown, 150); }); } (async function init() { me = await initNav('repertoire'); await loadInstruments(); document.getElementById('add-song-form').addEventListener('submit', onAddSong); document.getElementById('toggle-add-song').addEventListener('click', () => { const isOpen = document.getElementById('add-song-panel').style.display !== 'none'; setAddSongPanelOpen(!isOpen); }); initTitleAutocomplete(); await loadSongs(); })();