From b919e55206cd2b5f4a2d225071b12b079dfd4263 Mon Sep 17 00:00:00 2001 From: TBNilles Date: Sun, 7 Jun 2026 15:56:22 -0400 Subject: [PATCH] fix(model-manager): replace window.confirm with in-app modal Browsers suppress native confirm() dialogs after repeated use (the "prevent this page from creating additional dialogs" checkbox), which silently broke deletes. Add a promise-based in-app confirmation modal and use it for gallery photo and installed-model deletes. Co-Authored-By: Claude Opus 4.8 --- model-manager/app/static/app.js | 31 +++++++++++++++++++++++++++-- model-manager/app/static/index.html | 11 ++++++++++ model-manager/app/static/style.css | 17 ++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/model-manager/app/static/app.js b/model-manager/app/static/app.js index 5f11e71..8c93017 100644 --- a/model-manager/app/static/app.js +++ b/model-manager/app/static/app.js @@ -32,6 +32,33 @@ function fmtDate(epoch) { return new Date(epoch * 1000).toLocaleString(); } +// In-app confirmation dialog (replaces window.confirm, which browsers can +// suppress after repeated use). Returns a Promise. +function confirmDialog(message, okLabel = "Delete") { + return new Promise((resolve) => { + const overlay = $("#confirmModal"); + $("#confirmMsg").textContent = message; + $("#confirmOk").textContent = okLabel; + overlay.hidden = false; + const done = (val) => { + overlay.hidden = true; + $("#confirmOk").onclick = null; + $("#confirmCancel").onclick = null; + overlay.onclick = null; + document.removeEventListener("keydown", onKey); + resolve(val); + }; + function onKey(e) { + if (e.key === "Escape") done(false); + if (e.key === "Enter") done(true); + } + $("#confirmOk").onclick = () => done(true); + $("#confirmCancel").onclick = () => done(false); + overlay.onclick = (e) => { if (e.target === overlay) done(false); }; + document.addEventListener("keydown", onKey); + }); +} + // ---- navigation ----------------------------------------------------------- function showView(name) { @@ -118,7 +145,7 @@ function renderModels(models) { } async function deleteModel(m) { - if (!confirm(`Delete ${m.name}?`)) return; + if (!(await confirmDialog(`Delete ${m.name}?`))) return; try { await api("/api/models", { method: "DELETE", @@ -307,7 +334,7 @@ function renderPhotoCard(p) { } async function deletePhoto(p, card) { - if (!confirm(`Permanently delete ${p.name}?`)) return; + if (!(await confirmDialog(`Permanently delete ${p.name}?`))) return; try { await api("/api/gallery", { method: "DELETE", diff --git a/model-manager/app/static/index.html b/model-manager/app/static/index.html index e933521..9d990b8 100644 --- a/model-manager/app/static/index.html +++ b/model-manager/app/static/index.html @@ -194,6 +194,17 @@ + + + diff --git a/model-manager/app/static/style.css b/model-manager/app/static/style.css index f3fef0f..1a1d07e 100644 --- a/model-manager/app/static/style.css +++ b/model-manager/app/static/style.css @@ -172,6 +172,23 @@ code { background: var(--bg-3); padding: 1px 5px; border-radius: 4px; font-size: .nav-ico { font-size: 18px; } } +/* ---- confirm modal ---- */ +.modal-overlay { + position: fixed; inset: 0; z-index: 200; + background: rgba(8,10,16,.7); + display: flex; align-items: center; justify-content: center; padding: 24px; +} +.modal-overlay[hidden] { display: none; } +.modal { + background: var(--bg-2); border: 1px solid var(--border); + border-radius: var(--radius); padding: 22px; max-width: 420px; width: 100%; + box-shadow: 0 12px 40px rgba(0,0,0,.5); +} +.modal p { margin: 0 0 20px; font-size: 15px; line-height: 1.45; word-break: break-word; } +.modal-actions { display: flex; justify-content: flex-end; gap: 10px; } +.btn.danger-solid { background: var(--red); border-color: var(--red); color: #fff; font-weight: 600; } +.btn.danger-solid:hover { background: #e0414f; } + /* ---- lightbox ---- */ .lightbox { position: fixed; inset: 0; z-index: 100;