modals beforeClose promise

3clyp50 committed Jan 25, 2026 at 16:46 UTC a62dfe4c5172724e10557d68578d0ca29221086b
1 file changed +51 -27
webui/js/modals.js
+51 -27
@@ -99,15 +99,17 @@ function createModalElement(path) {
99 inner: newModal.querySelector(".modal-inner"),
100 styles: [],
101 scripts: [],
102 + beforeClose: null,
103 };
104 }
105
106 // Function to open modal with content from URL
106 -export function openModal(modalPath) {
107 +export function openModal(modalPath, beforeClose = null) {
108 return new Promise((resolve) => {
109 try {
110 // Create new modal instance
111 const modal = createModalElement(modalPath);
112 + modal.beforeClose = beforeClose;
113
114 new MutationObserver(
115 (_, o) =>
@@ -184,23 +186,42 @@ export function closeModal(modalPath = null) {
186
187 // Get the modal from stack at the found index
188 modal = modalStack[modalIndex];
187 - // Remove the modal from stack
188 - modalStack.splice(modalIndex, 1);
189 } else {
190 - // Just remove the last modal
191 - modal = modalStack.pop();
190 + // Just get the last modal (removal happens after beforeClose)
191 + modal = modalStack[modalStack.length - 1];
192 }
193
194 - // Remove modal-specific styles and scripts immediately
195 - modal.styles.forEach((styleId) => {
196 - document.querySelector(`[data-modal-style="${styleId}"]`)?.remove();
197 - });
198 - modal.scripts.forEach((scriptId) => {
199 - document.querySelector(`[data-modal-script="${scriptId}"]`)?.remove();
200 - });
194 + const canClose = async () => {
195 + if (!modal.beforeClose) return true;
196 + try {
197 + const result = await Promise.resolve(modal.beforeClose());
198 + return result !== false;
199 + } catch (error) {
200 + console.error("Error in beforeClose handler:", error);
201 + return true;
202 + }
203 + };
204 +
205 + return Promise.resolve(canClose()).then((shouldClose) => {
206 + if (!shouldClose) return false;
207 +
208 + if (modalPath) {
209 + // Remove the modal from stack after beforeClose check
210 + modalStack.splice(modalIndex, 1);
211 + } else {
212 + modalStack.pop();
213 + }
214 +
215 + // Remove modal-specific styles and scripts immediately
216 + modal.styles.forEach((styleId) => {
217 + document.querySelector(`[data-modal-style="${styleId}"]`)?.remove();
218 + });
219 + modal.scripts.forEach((scriptId) => {
220 + document.querySelector(`[data-modal-script="${scriptId}"]`)?.remove();
221 + });
222
202 - // First remove the show class to trigger the transition
203 - modal.element.classList.remove("show");
223 + // First remove the show class to trigger the transition
224 + modal.element.classList.remove("show");
225
226 // commented out to prevent race conditions
227
@@ -223,21 +244,24 @@ export function closeModal(modalPath = null) {
244 // }
245 // }, 500); // 500ms should be enough for the transition to complete
246
226 - // remove immediately
227 - if (modal.element.parentNode) {
228 - modal.element.parentNode.removeChild(modal.element);
229 - }
247 + // remove immediately
248 + if (modal.element.parentNode) {
249 + modal.element.parentNode.removeChild(modal.element);
250 + }
251
252
232 - // Handle backdrop visibility and body overflow
233 - if (modalStack.length === 0) {
234 - // Hide backdrop when no modals are left
235 - backdrop.style.display = "none";
236 - document.body.style.overflow = "";
237 - } else {
238 - // Update modal z-indexes
239 - updateModalZIndexes();
240 - }
253 + // Handle backdrop visibility and body overflow
254 + if (modalStack.length === 0) {
255 + // Hide backdrop when no modals are left
256 + backdrop.style.display = "none";
257 + document.body.style.overflow = "";
258 + } else {
259 + // Update modal z-indexes
260 + updateModalZIndexes();
261 + }
262 +
263 + return true;
264 + });
265 }
266
267 // Function to scroll to element by ID within the last modal