const $ = id => document.getElementById(id); const E = {}; [ "adminStatus", "loginButton", "weekAdminList", "weekForm", "weekHeading", "weekTitle", "weekProject", "weekOutput", "saveWeekButton", "newWeekFile", "weekFiles", "weekFileLabel", "weekFileContent", "demoTotal", "newDemoButton", "demoClassSelect", "demoStudentSelect", "demoProjectSelect", "demoPickerSummary", "demoForm", "demoHeading", "demoClass", "demoStudent", "demoProject", "newDemoFile", "demoFiles", "demoFileLabel", "demoFileContent", "demoAssets", "saveDemoButton", "deleteDemoButton", "adminLoginDialog", "adminLoginForm", "adminPassword", "adminLoginError", "toast", "classroomTotal", "newClassroomButton", "classroomList", "classroomHeading", "deleteClassroomButton", "saveClassroomButton", "classroomCode", "classroomName", "backupWeek", "backupSubmissionsButton", "adminStudentForm", "adminStudentNames", "adminAddStudentsButton", "adminStudentTotal", "adminStudentList", "submissionHeading", "submissionTotal", "submissionList", "submissionProject", "submissionMeta", "deleteSubmissionButton", "submissionFiles", "submissionCode" ].forEach(id => E[id] = $(id)); let password = ""; let state = { course: { weeks: [] }, demo_projects: [], classrooms: { courses: [], submissions: [] } }; let selectedWeek = ""; let weekFiles = {}; let weekFile = ""; let originalDemo = null; let demoFiles = {}; let demoFile = ""; let demoAssets = []; let selectedClassroom = ""; let selectedClassroomStudent = ""; let editingClassroomStudent = ""; let selectedSubmission = null; let submissionFiles = {}; let submissionFile = ""; const esc = value => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """); function toast(message) { E.toast.textContent = message; E.toast.classList.add("visible"); setTimeout(() => E.toast.classList.remove("visible"), 2600); } async function request(path, data = {}, method = "POST") { const response = await fetch(path, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password, ...data }) }); if (!response.ok) { let message = "操作失敗"; try { message = (await response.json()).detail || message; } catch (_) {} throw Error(message); } return response.json(); } function setBusy(button, busy, label) { if (!button.dataset.label) button.dataset.label = button.textContent; button.disabled = busy; button.textContent = busy ? label : button.dataset.label; } document.querySelectorAll(".admin-tab").forEach(button => button.onclick = () => { document.querySelectorAll(".admin-tab").forEach(item => item.classList.toggle("active", item === button)); document.querySelectorAll(".admin-view").forEach(view => view.classList.toggle("active", view.id === button.dataset.view)); }); async function login() { state = await request("/api/admin/state"); E.adminStatus.textContent = `已登入 · ${state.course.title || "課程管理"}`; renderWeeks(); renderDemoPicker(); E.backupWeek.innerHTML = state.course.weeks.map(week => ``).join(""); E.backupWeek.value = "week02"; renderClassrooms(); if (state.course.weeks.length) await selectWeek(state.course.weeks[0].id); } E.adminLoginForm.onsubmit = async event => { event.preventDefault(); password = E.adminPassword.value; const button = E.adminLoginForm.querySelector("button[type=submit]"); setBusy(button, true, "登入中…"); E.adminLoginError.textContent = ""; try { await login(); E.adminLoginDialog.close(); } catch (error) { password = ""; E.adminLoginError.textContent = error.message; } finally { setBusy(button, false); } }; E.loginButton.onclick = () => E.adminLoginDialog.showModal(); function renderWeeks() { E.weekAdminList.innerHTML = state.course.weeks.map(week => ` `).join(""); E.weekAdminList.querySelectorAll("[data-week]").forEach(button => button.onclick = () => selectWeek(button.dataset.week)); } async function selectWeek(weekId) { saveWeekFileBuffer(); selectedWeek = weekId; renderWeeks(); const data = await request("/api/admin/week", { week_id: weekId }); E.weekHeading.textContent = `Lesson ${data.week.lesson ?? data.week.week}|${data.week.title}`; E.weekTitle.value = data.week.title || ""; E.weekProject.value = data.week.project || ""; E.weekOutput.value = data.week.output || ""; weekFiles = data.files || {}; weekFile = Object.keys(weekFiles)[0] || ""; renderWeekFiles(); } function saveWeekFileBuffer() { if (weekFile) weekFiles[weekFile] = E.weekFileContent.value; } function renderWeekFiles() { E.weekFiles.innerHTML = Object.keys(weekFiles).sort().map(path => `
`).join("") || '
此 Lesson 目前沒有套版。學生會看到「尚未發布套版」。
'; E.weekFiles.querySelectorAll("[data-open]").forEach(button => button.onclick = () => { saveWeekFileBuffer(); weekFile = button.dataset.open; renderWeekFiles(); }); E.weekFiles.querySelectorAll("[data-remove]").forEach(button => button.onclick = () => { const path = button.dataset.remove; delete weekFiles[path]; if (weekFile === path) weekFile = Object.keys(weekFiles)[0] || ""; renderWeekFiles(); }); E.weekFileLabel.textContent = weekFile || "尚未選擇檔案"; E.weekFileContent.disabled = !weekFile; E.weekFileContent.value = weekFile ? weekFiles[weekFile] : ""; } function askFileName(existing) { const path = (prompt("檔案名稱(例如 main.py 或 data/case.json):") || "").trim().replaceAll("\\", "/"); if (!/^[\w\-\u4e00-\u9fff/]+\.(py|html|css|js|json|txt|csv|md|toml|yaml|yml)$/i.test(path) || path.includes("..")) { if (path) toast("檔名或副檔名不符合規則"); return ""; } if (path in existing) { toast("檔案已存在"); return ""; } return path; } E.newWeekFile.onclick = () => { saveWeekFileBuffer(); const path = askFileName(weekFiles); if (!path) return; weekFiles[path] = path.endsWith(".py") ? "# Lesson Python 套版\n" : ""; weekFile = path; renderWeekFiles(); }; E.weekForm.onsubmit = async event => { event.preventDefault(); saveWeekFileBuffer(); setBusy(E.saveWeekButton, true, "儲存中…"); try { await request("/api/admin/week/save", { week_id: selectedWeek, title: E.weekTitle.value, project: E.weekProject.value, output: E.weekOutput.value, files: weekFiles }); const week = state.course.weeks.find(item => item.id === selectedWeek); Object.assign(week, { title: E.weekTitle.value.trim(), project: E.weekProject.value.trim(), output: E.weekOutput.value.trim(), template: Object.keys(weekFiles).length > 0 }); E.weekHeading.textContent = `Lesson ${week.lesson ?? week.week}|${week.title}`; renderWeeks(); toast("課程內容已儲存,Space 正在重新建置"); } catch (error) { toast(error.message); } finally { setBusy(E.saveWeekButton, false); } }; function demoKey(item) { return `${item.class_name}\n${item.student_name}\n${item.project_name}`; } const sortedUnique = values => [...new Set(values)].sort((a, b) => a.localeCompare(b, "zh-Hant")); function optionList(values, placeholder) { return `` + values.map(value => ``).join(""); } function renderDemoPicker(selection = null) { const classes = sortedUnique(state.demo_projects.map(item => item.class_name)); E.demoTotal.textContent = `${classes.length} 門課程 · ${state.demo_projects.length} 個專題`; E.demoClassSelect.innerHTML = optionList(classes, "請選擇課程"); E.demoClassSelect.value = selection?.class_name || ""; renderStudentPicker(selection?.student_name || "", selection?.project_name || ""); } function renderStudentPicker(studentName = "", projectName = "") { const className = E.demoClassSelect.value; const students = sortedUnique(state.demo_projects.filter(item => item.class_name === className).map(item => item.student_name)); E.demoStudentSelect.innerHTML = optionList(students, className ? "請選擇學生" : "請先選課程"); E.demoStudentSelect.disabled = !className; E.demoStudentSelect.value = students.includes(studentName) ? studentName : ""; renderProjectPicker(projectName); } function renderProjectPicker(projectName = "") { const className = E.demoClassSelect.value; const studentName = E.demoStudentSelect.value; const projects = state.demo_projects.filter(item => item.class_name === className && item.student_name === studentName); E.demoProjectSelect.innerHTML = optionList(projects.map(item => item.project_name), studentName ? "請選擇專題" : "請先選學生"); E.demoProjectSelect.disabled = !studentName; E.demoProjectSelect.value = projects.some(item => item.project_name === projectName) ? projectName : ""; if (!E.demoProjectSelect.value) E.demoPickerSummary.innerHTML = `${studentName ? "請選擇專題" : className ? "請選擇學生" : "尚未選擇專題"}

選好三個層級後,右側會載入可編輯的程式檔案。

`; } function resetDemoEditor() { originalDemo = null; demoFiles = {}; demoAssets = []; demoFile = ""; E.demoHeading.textContent = "選擇一個 Demo 專題"; [E.demoClass, E.demoStudent, E.demoProject].forEach(input => input.value = ""); enableDemoForm(false); renderDemoFiles(); } E.demoClassSelect.onchange = () => { resetDemoEditor(); renderStudentPicker(); }; E.demoStudentSelect.onchange = () => { resetDemoEditor(); renderProjectPicker(); }; E.demoProjectSelect.onchange = () => { const item = state.demo_projects.find(project => project.class_name === E.demoClassSelect.value && project.student_name === E.demoStudentSelect.value && project.project_name === E.demoProjectSelect.value); if (item) selectDemo(item); }; function enableDemoForm(enabled) { [E.demoClass, E.demoStudent, E.demoProject, E.newDemoFile, E.saveDemoButton].forEach(element => element.disabled = !enabled); E.deleteDemoButton.disabled = !enabled || !originalDemo; } async function selectDemo(item) { saveDemoFileBuffer(); originalDemo = { class_name: item.class_name, student_name: item.student_name, project_name: item.project_name }; const data = await request("/api/admin/demo/project", { project: originalDemo }); E.demoClass.value = item.class_name; E.demoStudent.value = item.student_name; E.demoProject.value = item.project_name; E.demoHeading.textContent = item.project_name; demoFiles = data.files || {}; demoAssets = data.assets || []; demoFile = Object.keys(demoFiles)[0] || ""; E.demoPickerSummary.innerHTML = `${esc(item.project_name)}

${esc(item.class_name)} → ${esc(item.student_name)} → ${esc(item.type)}

`; enableDemoForm(true); renderDemoFiles(); } function saveDemoFileBuffer() { if (demoFile) demoFiles[demoFile] = E.demoFileContent.value; } function renderDemoFiles() { E.demoFiles.innerHTML = Object.keys(demoFiles).sort().map(path => `
`).join("") || '
尚無文字程式檔案。
'; E.demoFiles.querySelectorAll("[data-open]").forEach(button => button.onclick = () => { saveDemoFileBuffer(); demoFile = button.dataset.open; renderDemoFiles(); }); E.demoFiles.querySelectorAll("[data-remove]").forEach(button => button.onclick = () => { const path = button.dataset.remove; delete demoFiles[path]; if (demoFile === path) demoFile = Object.keys(demoFiles)[0] || ""; renderDemoFiles(); }); E.demoFileLabel.textContent = demoFile || "尚未選擇檔案"; E.demoFileContent.disabled = !demoFile; E.demoFileContent.value = demoFile ? demoFiles[demoFile] : ""; E.demoAssets.textContent = demoAssets.length ? `保留 ${demoAssets.length} 個非文字資產:${demoAssets.join("、")}` : "沒有圖片或音效資產。"; } E.newDemoButton.onclick = () => { const pickedClass = E.demoClassSelect.value; const pickedStudent = E.demoStudentSelect.value; originalDemo = null; demoFiles = { "main.py": "" }; demoAssets = []; demoFile = "main.py"; E.demoClass.value = pickedClass || ""; E.demoStudent.value = pickedStudent || ""; E.demoProject.value = ""; E.demoHeading.textContent = "建立新 Demo 專題"; enableDemoForm(true); E.demoPickerSummary.innerHTML = `建立新專題

會建立在右側填寫的課程與學生之下。

`; renderDemoFiles(); E.demoStudent.focus(); }; E.newDemoFile.onclick = () => { saveDemoFileBuffer(); const path = askFileName(demoFiles); if (!path) return; demoFiles[path] = ""; demoFile = path; renderDemoFiles(); }; E.demoForm.onsubmit = async event => { event.preventDefault(); saveDemoFileBuffer(); setBusy(E.saveDemoButton, true, "儲存中…"); try { const data = await request("/api/admin/demo/save", { original: originalDemo, class_name: E.demoClass.value, student_name: E.demoStudent.value, project_name: E.demoProject.value, files: demoFiles }); if (originalDemo) state.demo_projects = state.demo_projects.filter(item => demoKey(item) !== demoKey(originalDemo)); originalDemo = data.project; state.demo_projects.push({ ...data.project, type: "main.py" in demoFiles ? "python" : "index.html" in demoFiles ? "html" : "external" }); E.demoHeading.textContent = data.project.project_name; renderDemoPicker(originalDemo); E.demoPickerSummary.innerHTML = `${esc(data.project.project_name)}

${esc(data.project.class_name)} → ${esc(data.project.student_name)}

`; enableDemoForm(true); toast("Demo 專題已儲存,Space 正在重新建置"); } catch (error) { toast(error.message); } finally { setBusy(E.saveDemoButton, false); } }; E.deleteDemoButton.onclick = async () => { if (!originalDemo || !confirm(`永久刪除 Demo 專題「${originalDemo.project_name}」?\n\n這會移除程式、圖片與音效,無法從管理頁復原。`)) return; setBusy(E.deleteDemoButton, true, "刪除中…"); try { await request("/api/admin/demo/delete", { project: originalDemo }); state.demo_projects = state.demo_projects.filter(item => demoKey(item) !== demoKey(originalDemo)); originalDemo = null; demoFiles = {}; demoAssets = []; demoFile = ""; E.demoHeading.textContent = "選擇一個 Demo 專題"; [E.demoClass, E.demoStudent, E.demoProject].forEach(input => input.value = ""); enableDemoForm(false); renderDemoPicker(); renderDemoFiles(); toast("Demo 專題已刪除,Space 正在重新建置"); } catch (error) { toast(error.message); enableDemoForm(true); } }; async function refreshClassrooms(classCode = selectedClassroom, studentId = selectedClassroomStudent) { const next = await request("/api/admin/state"); state.classrooms = next.classrooms; selectedClassroom = state.classrooms.courses.some(item => item.class_code === classCode) ? classCode : (state.classrooms.courses[0]?.class_code || ""); selectedClassroomStudent = studentId; renderClassrooms(); } function currentClassroom() { return state.classrooms.courses.find(item => item.class_code === selectedClassroom); } function renderClassrooms() { const courses = state.classrooms.courses || []; if (selectedClassroom !== "__new__" && !courses.some(item => item.class_code === selectedClassroom)) selectedClassroom = courses[0]?.class_code || ""; E.classroomTotal.textContent = `${courses.length} 個班級`; E.classroomList.innerHTML = courses.map(item => { const submissions = state.classrooms.submissions.filter(entry => entry.class_code === item.class_code).length; return ``; }).join("") || '
尚未建立班級。
'; E.classroomList.querySelectorAll("[data-classroom]").forEach(button => button.onclick = () => { selectedClassroom = button.dataset.classroom; selectedClassroomStudent = ""; editingClassroomStudent = ""; selectedSubmission = null; renderClassrooms(); }); renderClassroomDetail(); } function renderClassroomDetail() { const course = currentClassroom(); const enabled = Boolean(course); E.classroomHeading.textContent = course ? course.course_name : (selectedClassroom === "__new__" ? "建立新班級" : "選擇班級"); E.classroomCode.disabled = selectedClassroom !== "__new__"; E.classroomName.disabled = !enabled && selectedClassroom !== "__new__"; E.saveClassroomButton.disabled = !enabled && selectedClassroom !== "__new__"; E.deleteClassroomButton.disabled = !enabled; E.saveClassroomButton.textContent = selectedClassroom === "__new__" ? "建立班級" : "儲存班級名稱"; E.saveClassroomButton.dataset.label = E.saveClassroomButton.textContent; [E.backupWeek, E.backupSubmissionsButton, E.adminStudentNames, E.adminAddStudentsButton].forEach(element => element.disabled = !enabled); if (selectedClassroom === "__new__") { E.classroomCode.value = ""; E.classroomName.value = ""; } else { E.classroomCode.value = course?.class_code || ""; E.classroomName.value = course?.course_name || ""; } renderClassroomStudents(); } function renderClassroomStudents() { const members = currentClassroom()?.students || []; if (selectedClassroomStudent && !members.some(item => item.student_id === selectedClassroomStudent)) selectedClassroomStudent = ""; if (editingClassroomStudent && !members.some(item => item.student_id === editingClassroomStudent)) editingClassroomStudent = ""; E.adminStudentTotal.textContent = `${members.length} 人`; E.adminStudentList.innerHTML = members.length ? `` + members.map(member => { if (member.student_id === editingClassroomStudent) return `
`; return `
`; }).join("") : '
這個班級尚無學生。
'; E.adminStudentList.querySelectorAll("[data-student]").forEach(button => button.onclick = () => { selectedClassroomStudent = button.dataset.student; selectedSubmission = null; renderClassroomStudents(); }); E.adminStudentList.querySelectorAll("[data-remove-student]").forEach(button => button.onclick = () => removeClassroomStudent(button.dataset.removeStudent)); E.adminStudentList.querySelectorAll("[data-edit-student]").forEach(button => button.onclick = () => { editingClassroomStudent = button.dataset.editStudent; renderClassroomStudents(); const input = E.adminStudentList.querySelector('[data-student-edit-form] input[name="display_name"]'); input?.focus(); input?.select(); }); E.adminStudentList.querySelectorAll("[data-cancel-student-edit]").forEach(button => button.onclick = () => { editingClassroomStudent = ""; renderClassroomStudents(); }); E.adminStudentList.querySelectorAll("[data-student-edit-form]").forEach(form => form.onsubmit = event => renameClassroomStudent(event, form)); renderSubmissions(); } function visibleSubmissions() { return state.classrooms.submissions.filter(item => item.class_code === selectedClassroom && (!selectedClassroomStudent || item.student_id === selectedClassroomStudent)); } function renderSubmissions() { const entries = visibleSubmissions(); const student = currentClassroom()?.students.find(item => item.student_id === selectedClassroomStudent); E.submissionHeading.textContent = student ? `${student.display_name}的提交` : "已提交內容"; E.submissionTotal.textContent = `${entries.length} 份`; E.submissionList.innerHTML = entries.map((item, index) => ``).join("") || '
目前沒有已提交內容。
'; E.submissionList.querySelectorAll("[data-submission]").forEach(button => button.onclick = () => selectSubmission(entries[Number(button.dataset.submission)])); if (!selectedSubmission || !entries.some(item => item.student_id === selectedSubmission.student_id && item.week_id === selectedSubmission.week_id)) resetSubmissionDetail(); } async function selectSubmission(item) { selectedSubmission = item; const data = await request("/api/admin/classroom/submission", item); submissionFiles = data.submission.files || {}; submissionFile = Object.keys(submissionFiles)[0] || ""; E.submissionProject.textContent = item.project_name; E.submissionMeta.textContent = `${item.display_name} · ${item.week_id} · ${new Date(item.submitted_at).toLocaleString("zh-TW")}`; E.deleteSubmissionButton.disabled = false; renderSubmissionFiles(); renderSubmissions(); } function renderSubmissionFiles() { E.submissionFiles.innerHTML = Object.keys(submissionFiles).map(path => ``).join(""); E.submissionFiles.querySelectorAll("[data-submission-file]").forEach(button => button.onclick = () => { submissionFile = button.dataset.submissionFile; renderSubmissionFiles(); }); E.submissionCode.textContent = submissionFile ? submissionFiles[submissionFile] : "這份提交沒有可顯示的檔案。"; } function resetSubmissionDetail() { selectedSubmission = null; submissionFiles = {}; submissionFile = ""; E.submissionProject.textContent = "尚未選擇提交"; E.submissionMeta.textContent = ""; E.submissionFiles.innerHTML = ""; E.submissionCode.textContent = "選擇一份提交查看內容。"; E.deleteSubmissionButton.disabled = true; } E.newClassroomButton.onclick = () => { selectedClassroom = "__new__"; selectedClassroomStudent = ""; editingClassroomStudent = ""; resetSubmissionDetail(); renderClassrooms(); E.classroomCode.focus(); }; E.saveClassroomButton.onclick = async () => { const code = E.classroomCode.value.trim(); const name = E.classroomName.value.trim(); if (!code || !name) return toast("請輸入班級代碼與名稱"); setBusy(E.saveClassroomButton, true, "儲存中…"); try { const method = selectedClassroom === "__new__" ? "POST" : "PUT"; const data = await request("/api/classroom/classes", { class_code: code, course_name: name }, method); selectedClassroom = code; await refreshClassrooms(code); toast(data.warning || "班級已儲存,Space 正在重新建置"); } catch (error) { toast(error.message); } finally { setBusy(E.saveClassroomButton, false); } }; E.deleteClassroomButton.onclick = async () => { const course = currentClassroom(); if (!course || !confirm(`刪除班級「${course.course_name}」?\n\n學生名單、教室進度與提交封存都會被移除。`)) return; setBusy(E.deleteClassroomButton, true, "刪除中…"); try { const data = await request("/api/classroom/classes", { class_code: course.class_code }, "DELETE"); selectedClassroom = ""; await refreshClassrooms(); toast(data.warning || "班級已刪除"); } catch (error) { toast(error.message); } finally { setBusy(E.deleteClassroomButton, false); } }; E.adminStudentForm.onsubmit = async event => { event.preventDefault(); const names = E.adminStudentNames.value.split(/[,,\n]+/).map(value => value.trim()).filter(Boolean); if (!names.length) return toast("請輸入學生姓名"); setBusy(E.adminAddStudentsButton, true, "加入中…"); try { const data = await request("/api/classroom/students", { class_code: selectedClassroom, display_names: names }); E.adminStudentNames.value = ""; await refreshClassrooms(selectedClassroom); toast(data.warning || `已加入 ${data.created.length} 位學生`); } catch (error) { toast(error.message); } finally { setBusy(E.adminAddStudentsButton, false); } }; async function renameClassroomStudent(event, form) { event.preventDefault(); const studentId = form.dataset.studentEditForm; const displayName = form.querySelector('[name="display_name"]').value.trim(); const errorLabel = form.querySelector(".student-edit-error"); if (!displayName) { errorLabel.textContent = "請輸入學生姓名"; return; } const button = form.querySelector('button[type="submit"]'); const previousSubmission = selectedSubmission; setBusy(button, true, "儲存中…"); errorLabel.textContent = ""; try { const data = await request("/api/classroom/students", { class_code: selectedClassroom, student_id: studentId, display_name: displayName }, "PUT"); editingClassroomStudent = ""; await refreshClassrooms(selectedClassroom, studentId); if (previousSubmission?.student_id === studentId) { const updated = visibleSubmissions().find(item => item.student_id === studentId && item.week_id === previousSubmission.week_id); if (updated) await selectSubmission(updated); else resetSubmissionDetail(); } toast(data.warning || "學生姓名已更新;既有作品與進度已保留"); } catch (error) { errorLabel.textContent = error.message; } finally { setBusy(button, false); } } async function removeClassroomStudent(studentId) { const member = currentClassroom()?.students.find(item => item.student_id === studentId); if (!member || !confirm(`從這個班級刪除「${member.display_name}」?\n\n教室進度與提交封存也會被移除。`)) return; try { const data = await request("/api/classroom/students", { class_code: selectedClassroom, student_id: studentId }, "DELETE"); selectedClassroomStudent = ""; await refreshClassrooms(selectedClassroom); toast(data.warning || "學生已刪除"); } catch (error) { toast(error.message); } } E.backupSubmissionsButton.onclick = async () => { setBusy(E.backupSubmissionsButton, true, "備份中…"); try { const data = await request("/api/admin/classroom/backup", { class_code: selectedClassroom, week_id: E.backupWeek.value }); toast(`已備份 ${data.count} 份提交,Space 正在重新建置`); } catch (error) { toast(error.message); } finally { setBusy(E.backupSubmissionsButton, false); } }; E.deleteSubmissionButton.onclick = async () => { if (!selectedSubmission || !confirm(`刪除 ${selectedSubmission.display_name} 的 ${selectedSubmission.week_id} 正式提交?`)) return; setBusy(E.deleteSubmissionButton, true, "刪除中…"); try { await request("/api/admin/classroom/submission/delete", selectedSubmission); resetSubmissionDetail(); await refreshClassrooms(selectedClassroom, selectedClassroomStudent); toast("已刪除提交"); } catch (error) { toast(error.message); } finally { setBusy(E.deleteSubmissionButton, false); } }; E.adminLoginDialog.showModal();