mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
update: user can add musics, concerts and all
This commit is contained in:
parent
0683108c8e
commit
692b19a846
7 changed files with 92 additions and 42 deletions
|
|
@ -281,6 +281,37 @@ h3 { font-size: 1rem; margin: 0 0 0.5rem; }
|
|||
|
||||
.song-thumb:hover a.play-overlay { opacity: 1; }
|
||||
|
||||
/* ---------- Tutorial cards (embedded links per instrument) ---------- */
|
||||
|
||||
.tutorial-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 0.7rem;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.tutorial-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.tutorial-thumb {
|
||||
width: 100%;
|
||||
height: 84px;
|
||||
}
|
||||
|
||||
.tutorial-meta { font-size: 0.8rem; }
|
||||
|
||||
.tutorial-label {
|
||||
margin-top: 0.2rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tutorial-label a { color: var(--accent); text-decoration: none; }
|
||||
|
||||
.song-body {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<p class="page-subtitle">Les morceaux travaillés par le groupe, avec liens d'écoute et tutos par instrument.</p>
|
||||
<div id="error"></div>
|
||||
|
||||
<div id="admin-add-song" class="panel" style="display:none">
|
||||
<div class="panel">
|
||||
<h2 style="margin-top:0">Ajouter un morceau</h2>
|
||||
<form id="add-song-form" class="stacked-form">
|
||||
<label>Titre <input name="title" required></label>
|
||||
|
|
|
|||
|
|
@ -62,13 +62,12 @@ function songCardTemplate(song) {
|
|||
<button class="secondary icon-btn toggle-tutorials" data-id="${song.id}">
|
||||
${isOpen ? 'Masquer les tutos' : `Tutos (${song.tutorial_count})`}
|
||||
</button>
|
||||
${me && me.isAdmin ? `<button class="secondary icon-btn edit-song" data-id="${song.id}">Modifier</button>` : ''}
|
||||
<button class="secondary icon-btn edit-song" data-id="${song.id}">Modifier</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tutorials-panel panel" style="${isOpen ? '' : 'display:none'}">
|
||||
<div class="tag-list" data-tutorials-for="${song.id}"><p class="empty">Chargement…</p></div>
|
||||
${me && me.isAdmin ? `
|
||||
<div class="tutorial-grid" data-tutorials-for="${song.id}"><p class="empty">Chargement…</p></div>
|
||||
<form class="inline-form add-tutorial-form" data-song-id="${song.id}">
|
||||
<label>Instrument
|
||||
<select name="instrumentId" required>${instrumentOptions()}</select>
|
||||
|
|
@ -76,7 +75,7 @@ function songCardTemplate(song) {
|
|||
<label>Lien <input name="url" type="url" required placeholder="https://..."></label>
|
||||
<label>Libellé <input name="label" placeholder="ex: tuto solo"></label>
|
||||
<button type="submit">Ajouter le tuto</button>
|
||||
</form>` : ''}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -101,6 +100,35 @@ function editSongCardTemplate(song) {
|
|||
`;
|
||||
}
|
||||
|
||||
function tutorialCardTemplate(t) {
|
||||
const thumb = youtubeThumbnailUrl(t.url);
|
||||
if (thumb) {
|
||||
return `
|
||||
<div class="tutorial-card" data-tutorial-id="${t.id}">
|
||||
<div class="song-thumb tutorial-thumb">
|
||||
<img src="${thumb}" alt="${escapeHtml(t.label || t.instrument_name)}" loading="lazy">
|
||||
<a class="play-overlay" href="${escapeHtml(t.url)}" target="_blank" rel="noopener" title="Regarder sur YouTube">►</a>
|
||||
</div>
|
||||
<div class="tutorial-meta">
|
||||
<span class="tag">${escapeHtml(t.instrument_name)}</span>
|
||||
<div class="tutorial-label">${escapeHtml(t.label || 'Tuto')}</div>
|
||||
</div>
|
||||
<button class="secondary icon-btn remove-tutorial" data-song-id="${t.song_id}" data-id="${t.id}">Retirer</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<div class="tutorial-card" data-tutorial-id="${t.id}">
|
||||
<div class="song-thumb tutorial-thumb placeholder">🔗</div>
|
||||
<div class="tutorial-meta">
|
||||
<span class="tag">${escapeHtml(t.instrument_name)}</span>
|
||||
<div class="tutorial-label"><a href="${escapeHtml(t.url)}" target="_blank" rel="noopener">${escapeHtml(t.label || t.url)}</a></div>
|
||||
</div>
|
||||
<button class="secondary icon-btn remove-tutorial" data-song-id="${t.song_id}" data-id="${t.id}">Retirer</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSongs() {
|
||||
const container = document.getElementById('songs-list');
|
||||
renderList(container, songs, songCardTemplate, 'Aucun morceau au répertoire pour le moment.');
|
||||
|
|
@ -152,11 +180,10 @@ async function loadTutorials(songId) {
|
|||
container.innerHTML = '<p class="empty">Aucun lien pour le moment.</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = detail.tutorials
|
||||
.map(
|
||||
(t) => `<span class="tag">${escapeHtml(t.instrument_name)}: <a href="${escapeHtml(t.url)}" target="_blank" rel="noopener">${escapeHtml(t.label || t.url)}</a></span>`
|
||||
)
|
||||
.join('');
|
||||
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);
|
||||
}
|
||||
|
|
@ -181,6 +208,18 @@ async function onAddTutorial(e) {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
@ -233,9 +272,6 @@ function showError(message) {
|
|||
(async function init() {
|
||||
me = await initNav('repertoire');
|
||||
await loadInstruments();
|
||||
if (me.isAdmin) {
|
||||
document.getElementById('admin-add-song').style.display = 'block';
|
||||
document.getElementById('add-song-form').addEventListener('submit', onAddSong);
|
||||
}
|
||||
document.getElementById('add-song-form').addEventListener('submit', onAddSong);
|
||||
await loadSongs();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -30,17 +30,15 @@ function readOnlyView() {
|
|||
? `<ol class="setlist">${encore.map((s) => `<li><span class="row-title">${escapeHtml(s.title)}</span> <span class="row-artist">— ${escapeHtml(s.artist)}</span>${s.note ? `<span class="note">${escapeHtml(s.note)}</span>` : ''}</li>`).join('')}</ol>`
|
||||
: '<p class="empty">Aucun morceau de rappel prévu.</p>'}
|
||||
</div>
|
||||
${me.isAdmin ? '<button id="edit-btn" class="secondary" style="margin-top:1rem">Modifier la setlist</button>' : ''}
|
||||
<button id="edit-btn" class="secondary" style="margin-top:1rem">Modifier la setlist</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderReadOnly() {
|
||||
const container = document.getElementById('content');
|
||||
if (!setlist) {
|
||||
container.innerHTML = me.isAdmin
|
||||
? `<p class="empty">Aucun concert à venir.</p>${metaFormTemplate()}`
|
||||
: '<p class="empty">Aucun concert à venir pour le moment.</p>';
|
||||
if (me.isAdmin) attachMetaFormHandler();
|
||||
container.innerHTML = `<p class="empty">Aucun concert à venir.</p>${metaFormTemplate()}`;
|
||||
attachMetaFormHandler();
|
||||
return;
|
||||
}
|
||||
container.innerHTML = readOnlyView();
|
||||
|
|
@ -249,9 +247,7 @@ function showError(message) {
|
|||
|
||||
(async function init() {
|
||||
me = await initNav('setlist');
|
||||
if (me.isAdmin) {
|
||||
allSongs = await api.get('/api/songs');
|
||||
}
|
||||
allSongs = await api.get('/api/songs');
|
||||
setlist = await api.get('/api/setlists/next');
|
||||
renderReadOnly();
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue