(() => { const el = (id) => document.getElementById(id); const t = (key, vars) => window.I18n?.t(key, vars) ?? key; const listEl = el("soundList"); const emptyEl = el("soundListEmpty"); const createBtnEl = el("soundCreateBtn"); const dialogEl = el("soundEditDialog"); const formEl = el("soundEditForm"); const titleEl = el("soundEditTitle"); const nameEl = el("soundEditName"); const descEl = el("soundEditDescription"); const enabledEl = el("soundEditEnabled"); const fileMetaEl = el("soundEditFileMeta"); const uploadInputEl = el("soundEditUploadInput"); const uploadBtnEl = el("soundEditUploadBtn"); const playBtnEl = el("soundEditPlayBtn"); const deleteBtnEl = el("soundEditDeleteBtn"); const store = { sounds: [], editingId: null, previewAudio: null, }; function canWrite() { if (!window.AuthApp?.canWrite) return true; return window.AuthApp.canWrite("integrations"); } function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } async function apiJson(url, opts = {}) { if (window.AuthApp && !window.AuthApp.isReady()) { throw new Error("not authenticated"); } const res = await fetch(url, { credentials: "include", ...opts }); const text = await res.text(); let data = null; try { data = text ? JSON.parse(text) : null; } catch { data = null; } if (!res.ok) { const msg = (data && data.error) || text || res.statusText; throw new Error(msg); } return data; } async function refreshSounds() { const data = await apiJson("/api/sounds"); store.sounds = Array.isArray(data.sounds) ? data.sounds : []; } function formatDuration(ms) { if (!Number.isFinite(Number(ms))) return "—"; const sec = Math.round(Number(ms) / 100) / 10; return `${sec}s`; } function renderList() { if (!listEl) return; listEl.innerHTML = ""; if (emptyEl) emptyEl.hidden = store.sounds.length > 0; store.sounds.forEach((sound) => { const row = document.createElement("div"); row.className = "missionListItem soundListItem"; const hasFile = !!sound.file_name; row.innerHTML = `
${escapeHtml(sound.name || sound.id)}
${sound.enabled === false ? t("common.disabled") : t("common.enabled")} · ${hasFile ? escapeHtml(sound.file_name) : t("sounds.noFile")} ${sound.duration_ms != null ? ` · ${formatDuration(sound.duration_ms)}` : ""}
`; listEl.appendChild(row); }); listEl.querySelectorAll(".soundEditBtn").forEach((btn) => { btn.addEventListener("click", () => openDialog(btn.dataset.id)); }); listEl.querySelectorAll(".soundPlayBtn").forEach((btn) => { btn.addEventListener("click", () => playSound(btn.dataset.id)); }); } function stopPreview() { if (store.previewAudio) { store.previewAudio.pause(); store.previewAudio = null; } } function playSound(id) { stopPreview(); const audio = new Audio(`/api/sounds/${encodeURIComponent(id)}/file`); store.previewAudio = audio; audio.play().catch(() => alert(t("sounds.playFailed"))); } function updateFileMeta(sound) { if (!fileMetaEl) return; if (sound?.file_name) { fileMetaEl.textContent = t("sounds.fileMeta", { name: sound.file_name, duration: formatDuration(sound.duration_ms), }); } else { fileMetaEl.textContent = t("sounds.noFile"); } if (playBtnEl) playBtnEl.disabled = !sound?.file_name; } function openDialog(id = null) { store.editingId = id; const existing = id ? store.sounds.find((s) => s.id === id) : null; if (titleEl) { titleEl.textContent = existing ? t("sounds.editTitle") : t("sounds.createTitle"); } if (nameEl) nameEl.value = existing?.name || ""; if (descEl) descEl.value = existing?.description || ""; if (enabledEl) enabledEl.checked = existing?.enabled !== false; updateFileMeta(existing); if (deleteBtnEl) deleteBtnEl.hidden = !existing || !canWrite(); if (uploadBtnEl) uploadBtnEl.disabled = !canWrite(); if (nameEl) nameEl.readOnly = !canWrite(); if (descEl) descEl.readOnly = !canWrite(); if (enabledEl) enabledEl.disabled = !canWrite(); dialogEl?.showModal(); } async function saveDialog() { if (!canWrite()) return; const name = nameEl?.value.trim() || ""; if (!name) { alert(t("sounds.nameRequired")); return; } const payload = { name, description: descEl?.value.trim() || "", enabled: enabledEl?.checked !== false, }; try { if (store.editingId) { await apiJson(`/api/sounds/${encodeURIComponent(store.editingId)}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); } else { const created = await apiJson("/api/sounds", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); store.editingId = created.id; } await refreshSounds(); renderList(); const updated = store.sounds.find((s) => s.id === store.editingId); updateFileMeta(updated); if (!uploadInputEl?.files?.length) { dialogEl?.close(); } } catch (e) { alert(e.message); } } async function uploadFile() { if (!canWrite() || !store.editingId) return; const file = uploadInputEl?.files?.[0]; if (!file) return; const form = new FormData(); form.append("file", file); try { await apiJson(`/api/sounds/${encodeURIComponent(store.editingId)}/file`, { method: "POST", body: form, }); uploadInputEl.value = ""; await refreshSounds(); renderList(); updateFileMeta(store.sounds.find((s) => s.id === store.editingId)); } catch (e) { alert(e.message); } } async function deleteSound() { if (!canWrite() || !store.editingId) return; if (!confirm(t("sounds.deleteConfirm"))) return; try { await apiJson(`/api/sounds/${encodeURIComponent(store.editingId)}`, { method: "DELETE" }); dialogEl?.close(); store.editingId = null; await refreshSounds(); renderList(); } catch (e) { alert(e.message); } } function bindEvents() { createBtnEl?.addEventListener("click", () => { if (!canWrite()) return; openDialog(null); }); formEl?.addEventListener("submit", (evt) => { evt.preventDefault(); saveDialog(); }); el("soundEditCancelBtn")?.addEventListener("click", () => { stopPreview(); dialogEl?.close(); }); dialogEl?.addEventListener("cancel", (evt) => { evt.preventDefault(); stopPreview(); dialogEl?.close(); }); uploadBtnEl?.addEventListener("click", () => uploadInputEl?.click()); uploadInputEl?.addEventListener("change", () => { saveDialog().then(() => uploadFile()); }); playBtnEl?.addEventListener("click", () => { if (store.editingId) playSound(store.editingId); }); deleteBtnEl?.addEventListener("click", () => deleteSound()); } async function onPageShow() { stopPreview(); if (createBtnEl) createBtnEl.hidden = !canWrite(); try { await refreshSounds(); renderList(); } catch (e) { if (emptyEl) { emptyEl.hidden = false; emptyEl.textContent = e.message; } } } function onPageHide() { stopPreview(); dialogEl?.close(); } function getSounds() { return JSON.parse(JSON.stringify(store.sounds)); } bindEvents(); window.SoundsApp = { onPageShow, onPageHide, getSounds, refreshSounds, }; })();