111 lines
3.3 KiB
HTML
111 lines
3.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Audio Uploader</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" />
|
|
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
|
<style>
|
|
body { font-family: sans-serif; padding: 2rem; width:500px; margin: 0 auto; }
|
|
.dropzn { border: 2px dashed #666; padding: 2rem; margin-bottom: 2rem; }
|
|
#playlist { padding-left: 0; }
|
|
#playlist li { margin-bottom: 0.5rem; background: #eee; padding: 0.5rem; border-radius: 4px; cursor: grab; display: flex; justify-content: space-between; align-items: center; }
|
|
#playlist li span { width: 85%; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Upload Audio</h1>
|
|
<form action="http://localhost:3000/upload" class="dropzn" id="audio-dropzone">
|
|
<div class="dz-message">Drop audio files here or click to upload</div>
|
|
</form>
|
|
|
|
<h2>Playlist</h2>
|
|
<ul id="playlist"></ul>
|
|
|
|
<script>
|
|
Dropzone.autoDiscover = false;
|
|
|
|
new Dropzone("#audio-dropzone", {
|
|
url: "http://localhost:3000/upload",
|
|
method: "post",
|
|
paramName: "file", // matches Multer field
|
|
maxFilesize: 20,
|
|
acceptedFiles: "audio/*",
|
|
init: function () {
|
|
this.on("sending", function(file, xhr, formData) {
|
|
const title = prompt("Enter title for: " + file.name);
|
|
formData.append("title", title || file.name);
|
|
const token = btoa("admin:supersecret");
|
|
xhr.setRequestHeader("Authorization", "Basic " + token);
|
|
});
|
|
this.on("success", function() {
|
|
setTimeout(loadPlaylist, 500);
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
async function fetchPlaylist() {
|
|
const res = await fetch("http://localhost:3000/api/playlist");
|
|
return await res.json();
|
|
}
|
|
|
|
async function loadPlaylist() {
|
|
const list = document.getElementById("playlist");
|
|
list.innerHTML = "";
|
|
const tracks = await fetchPlaylist();
|
|
tracks.forEach(track => {
|
|
const li = document.createElement("li");
|
|
li.setAttribute("data-url", track.url);
|
|
li.innerHTML = `
|
|
🎵 <span>${track.title}</span>
|
|
<button onclick="deleteTrack('${track.url}')">X </button>
|
|
`;
|
|
list.appendChild(li);
|
|
});
|
|
}
|
|
|
|
async function deleteTrack(url) {
|
|
const filename = url.split("/").pop();
|
|
const res = await fetch("http://localhost:3000/delete", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Basic " + btoa("admin:supersecret")
|
|
},
|
|
body: JSON.stringify({ filename })
|
|
});
|
|
if (res.ok) loadPlaylist();
|
|
}
|
|
|
|
function saveNewOrder() {
|
|
const items = document.querySelectorAll("#playlist li");
|
|
const newOrder = Array.from(items).map(li => ({
|
|
title: li.querySelector("span").textContent.trim(),
|
|
url: li.getAttribute("data-url")
|
|
}));
|
|
|
|
fetch("http://localhost:3000/reorder", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "Basic " + btoa("admin:supersecret")
|
|
},
|
|
body: JSON.stringify({ playlist: newOrder })
|
|
});
|
|
}
|
|
|
|
new Sortable(document.getElementById("playlist"), {
|
|
animation: 150,
|
|
onEnd: saveNewOrder
|
|
});
|
|
|
|
loadPlaylist();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|