Add the submission pipeline and the review loop
Steps 3 and 4 of the build order. A member can now upload a song, watch it convert, publish it, and review what everyone else has published. Pipeline: - ffprobe reads tags synchronously at submit so prefill never races typing; ffmpeg converts to Opus in the background, two at a time - ffmpeg succeeding is the validation — no container sniffing - publish moves the file inside the transaction, so a song row and its .ogg appear together or neither does - five submissions per rolling 24h, failures excluded Reviews and the reveal rule: - the queue is unreviewed songs only, oldest first, never your own - other people's reviews and the average are withheld in the query, not the template — a hidden average is never sent - 30 minutes to edit or delete your own review, enforced in the WHERE clause - deleting the last review unlocks the song for its submitter again The waiting page has one button: the metadata form autosaves after a pause in typing, and Julkaise submits it and publishes in the same request, so nothing is lost without JS. Genres store an English code and render a Finnish label.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
{{define "content"}}
|
||||
<h1>Lähetä kappale</h1>
|
||||
|
||||
{{with .Data.Error}}<p class="error">{{.}}</p>{{end}}
|
||||
|
||||
<form method="post" action="/submit" enctype="multipart/form-data" class="stack">
|
||||
<label class="dropzone" id="dropzone" for="audio">
|
||||
<input type="file" id="audio" name="audio" accept="audio/*" required>
|
||||
<span class="dz-title">Raahaa äänitiedosto tähän</span>
|
||||
<span class="muted small">tai valitse napsauttamalla</span>
|
||||
<span class="filename" id="filename"></span>
|
||||
</label>
|
||||
<button type="submit">Lähetä</button>
|
||||
</form>
|
||||
|
||||
{{with .Data.Submissions}}
|
||||
<section>
|
||||
<h2>Omat lähetykset</h2>
|
||||
<table>
|
||||
<thead><tr><th>Kappale</th><th>Tila</th><th>Lähetetty</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td><a href="/submit/{{.ID}}">{{if .Title}}{{.Title}}{{else}}(nimetön){{end}}</a></td>
|
||||
<td>{{.Label}}</td>
|
||||
<td class="nowrap">{{fidate .CreatedAt}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="muted small">Valmis lähetys odottaa Julkaise-painallusta — vasta se tuo kappaleen
|
||||
muiden nähtäville.</p>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<p class="muted">Enintään 50 MB ja 15 minuuttia. Tiedosto muunnetaan Opus-muotoon, ja pääset
|
||||
kirjoittamaan esittelyn odotellessa. Kappale julkaistaan vasta kun painat Julkaise.</p>
|
||||
|
||||
<script>
|
||||
// The native control can't be relabelled or styled, so it is hidden behind this one — which
|
||||
// still is the input, so keyboard focus and form submission work untouched.
|
||||
(function () {
|
||||
const zone = document.getElementById('dropzone')
|
||||
const input = document.getElementById('audio')
|
||||
const name = document.getElementById('filename')
|
||||
|
||||
const show = () => {
|
||||
name.textContent = input.files.length ? input.files[0].name : ''
|
||||
zone.classList.toggle('has-file', input.files.length > 0)
|
||||
}
|
||||
input.addEventListener('change', show)
|
||||
|
||||
for (const type of ['dragenter', 'dragover']) {
|
||||
zone.addEventListener(type, e => { e.preventDefault(); zone.classList.add('over') })
|
||||
}
|
||||
for (const type of ['dragleave', 'dragend', 'drop']) {
|
||||
zone.addEventListener(type, e => { e.preventDefault(); zone.classList.remove('over') })
|
||||
}
|
||||
zone.addEventListener('drop', e => {
|
||||
if (e.dataTransfer.files.length) {
|
||||
input.files = e.dataTransfer.files
|
||||
show()
|
||||
}
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user