saving
This commit is contained in:
parent
d9ece80418
commit
0a67dc864e
|
|
@ -0,0 +1,145 @@
|
|||
(function() {
|
||||
const M = window.MusicRoom;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const uploadBtn = M.$("#btn-upload");
|
||||
const fileInput = M.$("#file-input");
|
||||
const dropzone = M.$("#upload-dropzone");
|
||||
const libraryPanel = M.$("#library-panel");
|
||||
const progressEl = M.$("#upload-progress");
|
||||
const progressBar = progressEl?.querySelector(".upload-progress-bar");
|
||||
const progressText = progressEl?.querySelector(".upload-progress-text");
|
||||
|
||||
if (!uploadBtn || !fileInput || !dropzone) return;
|
||||
|
||||
// Click upload button opens file dialog
|
||||
uploadBtn.onclick = () => {
|
||||
if (!M.currentUser) {
|
||||
M.showToast("Sign in to upload");
|
||||
return;
|
||||
}
|
||||
fileInput.click();
|
||||
};
|
||||
|
||||
// File input change
|
||||
fileInput.onchange = () => {
|
||||
if (fileInput.files.length > 0) {
|
||||
uploadFiles(fileInput.files);
|
||||
fileInput.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
// Drag and drop on library panel
|
||||
let dragCounter = 0;
|
||||
|
||||
libraryPanel.ondragenter = (e) => {
|
||||
if (!M.currentUser) return;
|
||||
if (!e.dataTransfer.types.includes("Files")) return;
|
||||
e.preventDefault();
|
||||
dragCounter++;
|
||||
dropzone.classList.remove("hidden");
|
||||
};
|
||||
|
||||
libraryPanel.ondragleave = (e) => {
|
||||
e.preventDefault();
|
||||
dragCounter--;
|
||||
if (dragCounter === 0) {
|
||||
dropzone.classList.add("hidden");
|
||||
}
|
||||
};
|
||||
|
||||
libraryPanel.ondragover = (e) => {
|
||||
if (!M.currentUser) return;
|
||||
if (!e.dataTransfer.types.includes("Files")) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
};
|
||||
|
||||
libraryPanel.ondrop = (e) => {
|
||||
e.preventDefault();
|
||||
dragCounter = 0;
|
||||
dropzone.classList.add("hidden");
|
||||
|
||||
if (!M.currentUser) {
|
||||
M.showToast("Sign in to upload");
|
||||
return;
|
||||
}
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
uploadFiles(files);
|
||||
}
|
||||
};
|
||||
|
||||
function showProgress(current, total, filename) {
|
||||
if (!progressEl) return;
|
||||
const pct = Math.round((current / total) * 100);
|
||||
progressBar.style.width = pct + "%";
|
||||
progressText.textContent = `Uploading ${current}/${total}: ${filename}`;
|
||||
progressEl.classList.remove("hidden");
|
||||
uploadBtn.classList.add("hidden");
|
||||
}
|
||||
|
||||
function hideProgress() {
|
||||
if (!progressEl) return;
|
||||
progressEl.classList.add("hidden");
|
||||
uploadBtn.classList.remove("hidden");
|
||||
}
|
||||
|
||||
async function uploadFiles(files) {
|
||||
const validExts = [".mp3", ".ogg", ".flac", ".wav", ".m4a", ".aac", ".opus", ".wma", ".mp4"];
|
||||
const audioFiles = [...files].filter(f => {
|
||||
const ext = f.name.toLowerCase().match(/\.[^.]+$/)?.[0];
|
||||
return ext && validExts.includes(ext);
|
||||
});
|
||||
|
||||
if (audioFiles.length === 0) {
|
||||
M.showToast("No valid audio files");
|
||||
return;
|
||||
}
|
||||
|
||||
let uploaded = 0;
|
||||
let failed = 0;
|
||||
let duplicates = 0;
|
||||
const total = audioFiles.length;
|
||||
|
||||
for (let i = 0; i < audioFiles.length; i++) {
|
||||
const file = audioFiles[i];
|
||||
showProgress(i + 1, total, file.name);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const res = await fetch("/api/upload", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
uploaded++;
|
||||
} else if (res.status === 409) {
|
||||
// File already exists
|
||||
M.showToast(`Already uploaded: ${file.name}`, "warning");
|
||||
duplicates++;
|
||||
} else {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
console.error(`Upload failed for ${file.name}:`, err.error || res.status);
|
||||
failed++;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Upload error for ${file.name}:`, e);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
hideProgress();
|
||||
|
||||
if (uploaded > 0) {
|
||||
M.showToast(`Uploaded ${uploaded} track${uploaded > 1 ? 's' : ''}${failed > 0 ? `, ${failed} failed` : ''}`);
|
||||
} else {
|
||||
M.showToast(`Upload failed`);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
16
server.ts
16
server.ts
|
|
@ -45,7 +45,21 @@ interface Config {
|
|||
}
|
||||
|
||||
const CONFIG_PATH = join(import.meta.dir, "config.json");
|
||||
const config: Config = await file(CONFIG_PATH).json();
|
||||
|
||||
const DEFAULT_CONFIG: Config = {
|
||||
port: 3001,
|
||||
musicDir: "./music",
|
||||
allowGuests: true
|
||||
};
|
||||
|
||||
// Create default config if missing
|
||||
const configFile = file(CONFIG_PATH);
|
||||
if (!(await configFile.exists())) {
|
||||
console.log("[Config] Creating default config.json...");
|
||||
await Bun.write(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
||||
}
|
||||
|
||||
const config: Config = await configFile.json();
|
||||
|
||||
const MUSIC_DIR = resolve(import.meta.dir, config.musicDir);
|
||||
const PUBLIC_DIR = join(import.meta.dir, "public");
|
||||
|
|
|
|||
Loading…
Reference in New Issue