diff --git a/web/resolver/actions/selection_methods.js b/web/resolver/actions/selection_methods.js index 8caca7d..7dc9261 100644 --- a/web/resolver/actions/selection_methods.js +++ b/web/resolver/actions/selection_methods.js @@ -5,7 +5,7 @@ export const selectionMethods = { handleOutsideClick(e) { // Close if click is on the backdrop (not on the dialog itself) if (e.target === this.backdrop) { - this.close(); + this.requestClose(); } }, diff --git a/web/resolver/model_resolver.js b/web/resolver/model_resolver.js index 1cadb8d..489e286 100644 --- a/web/resolver/model_resolver.js +++ b/web/resolver/model_resolver.js @@ -794,9 +794,16 @@ export class ModelResolver { const button = target?.closest(`.${this.sidebarTabId}-tab-button`); if (!(button instanceof HTMLElement)) return; if (!this.dialog?.isVisible()) return; + if (this.dialog.pendingDockToSidebar) return; const wasDocked = this.dialog.docked; - this.dialog.close({ collapseSidebar: false }); + const didClose = this.dialog.requestClose({ collapseSidebar: false }); + if (didClose === false) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation?.(); + return; + } if (!wasDocked) { event.preventDefault(); @@ -823,7 +830,7 @@ export class ModelResolver { if (this.dialog.shouldOpenFromSidebarFloating()) { if (this.dialog.isVisible() && !this.dialog.docked) { - this.dialog.close(); + if (this.dialog.requestClose() === false) return; this.dialog.closeComfySidebar(element); return; } diff --git a/web/resolver/shell/dialog_shell_methods.js b/web/resolver/shell/dialog_shell_methods.js index 03845d2..8fe3b82 100644 --- a/web/resolver/shell/dialog_shell_methods.js +++ b/web/resolver/shell/dialog_shell_methods.js @@ -83,7 +83,7 @@ export const dialogShellMethods = { className: "mr-window-btn mr-window-btn--close", innerHTML: getSvgIcon('x', 'currentColor', 'mr-window-btn-icon'), ariaLabel: "Close Model Resolver", - onclick: () => this.close() + onclick: () => this.requestClose() }) ]) ]) diff --git a/web/resolver/shell/lifecycle_graph_methods.js b/web/resolver/shell/lifecycle_graph_methods.js index a970944..6f2d0a1 100644 --- a/web/resolver/shell/lifecycle_graph_methods.js +++ b/web/resolver/shell/lifecycle_graph_methods.js @@ -2,16 +2,18 @@ import { app } from "../../../../../scripts/app.js"; import { safeStorage } from "../utils/html_utils.js"; export const lifecycleGraphMethods = { async show(workflow = null) { + const preserveCurrentView = this.isVisible(); this.undockToFloating({ persist: false }); - await this.showContent(workflow, { restoreFullscreen: true }); + await this.showContent(workflow, { restoreFullscreen: true, preserveCurrentView }); }, async showDocked(container, workflow = null) { + const preserveCurrentView = this.isVisible(); this.dockTo(container); - await this.showContent(workflow, { restoreFullscreen: false }); + await this.showContent(workflow, { restoreFullscreen: false, preserveCurrentView }); }, - async showContent(workflow = null, { restoreFullscreen = true } = {}) { + async showContent(workflow = null, { restoreFullscreen = true, preserveCurrentView = false } = {}) { this.backdrop.style.display = "none"; this.element.style.display = "flex"; void this.loadFooterVersion(); @@ -41,12 +43,16 @@ export const lifecycleGraphMethods = { // Attach drag handle event listener (only once) this.attachDragHandleIfNeeded(); - this.activeTab = this.restoreActiveTab(); + if (!preserveCurrentView) { + this.activeTab = this.restoreActiveTab(); + } this.updateTabButtonStates(); this.updateQueueVisibility(); this.syncWorkflowScopedQueue(workflow || this.getCurrentWorkflow()); await this.restoreActiveDownloadsFromBackend?.(); + if (preserveCurrentView) return; + if (this.activeTab === 'missing') { await this.loadWorkflowData(workflow); } else if (this.activeTab === 'loaded') { diff --git a/web/resolver/views/options_methods.js b/web/resolver/views/options_methods.js index 7d9e193..e1adbc0 100644 --- a/web/resolver/views/options_methods.js +++ b/web/resolver/views/options_methods.js @@ -4,6 +4,20 @@ import { escapeHtml, pollBackgroundTask, safeStorage } from "../utils/html_utils const SETTINGS_MAP = []; export const optionsMethods = { + confirmDiscardOptionsChanges() { + if (this.activeTab !== 'options') return true; + + const status = this.contentElement?.querySelector('#mr-options-status'); + if (!status?.classList.contains('is-dirty')) return true; + + return window.confirm('You have unsaved options. Are you sure you want to leave without saving?'); + }, + + requestClose(...args) { + if (!this.confirmDiscardOptionsChanges()) return false; + return this.close(...args); + }, + buildLocalModelContext(item = {}, contextSource = '', extraFields = {}) { const modelLabel = item.relative_path || item.filename || item.model_path || 'Model'; const modelPath = item.model_path || ''; diff --git a/web/resolver/views/tabs_loaded_methods.js b/web/resolver/views/tabs_loaded_methods.js index 8e6c6f9..650a508 100644 --- a/web/resolver/views/tabs_loaded_methods.js +++ b/web/resolver/views/tabs_loaded_methods.js @@ -37,6 +37,13 @@ export const tabsLoadedMethods = { switchTab(tab, { force = false } = {}) { const nextTab = this.getValidTab(tab); const nextTabButton = this.getTabButton(nextTab); + if ( + this.activeTab === 'options' + && nextTab !== 'options' + && !this.confirmDiscardOptionsChanges() + ) { + return null; + } if (!force && nextTab === this.activeTab && nextTabButton?.classList.contains('mr-tab-active')) { this.hideTooltip(); this.updateQueueVisibility();