diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 7c63881..2c51212 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -7785,6 +7785,7 @@ test('info dialog size formatting preserves numeric, nested and label values', ( test('link/name controls place the add or search action before the input switch', () => { const renderSearchControls = eval(`(${extractMethod(searchPanelMethodsSource, 'renderSearchControls')})`); const dialog = { + getMissingModelDomKey() { return 'model-key'; }, getSearchState() { return { selectedSource: 'all', @@ -7838,6 +7839,42 @@ test('link/name controls place the add or search action before the input switch' assert.ok(modeIndex > inputIndex); }); +test('search progress stays with its model when switching a shared widget', async () => { + const refresh = eval(`(${extractMethod(searchPanelMethodsSource.replaceAll('workflowKey = this.getWorkflowScopedQueueKey()', "workflowKey = 'workflow'"), 'refreshSearchUiForMissingNow')})`); + const window = new Window(); + const root = window.document.createElement('div'); + window.document.body.append(root); + const first = { node_id: 232, widget_index: 0, original_path: 'first' }; + const second = { ...first, original_path: 'second' }; + const state = { activeSearchRunId: 'run-a' }; + const dialog = { + ...workflowIdentityMethods, + contentElement: root, + activeTab: 'missing', + getWorkflowScopedQueueKey: () => 'workflow', + getMissingSearchKey: model => model.original_path, + refreshMissingSourcesSummary() {}, + hasRenderableSearchState: () => true, + displaySearchResults(model, currentState, container) { container.textContent = model.original_path; }, + renderSearchButtonContent: text => text, + }; + const mount = model => { + dialog.selectedMissingModelKey = dialog.getMissingModelKey(model); + const key = dialog.getMissingModelDomKey(model); + root.innerHTML = `
`; + }; + mount(second); + refresh.call(dialog, first, state, { workflowKey: 'workflow' }); + assert.equal(root.querySelector('div').textContent, ''); + assert.equal(root.querySelector('button').disabled, false); + mount(first); + refresh.call(dialog, first, state, { workflowKey: 'workflow' }); + assert.equal(root.querySelector('div').textContent, 'first'); + assert.equal(root.querySelector('button').disabled, true); + assert.notEqual(dialog.getMissingModelDomKey(first), dialog.getMissingModelDomKey(second)); + await window.happyDOM.close(); +}); + test('search result refreshes preserve keyed progress and result DOM', () => { const displaySearchResults = extractMethod(resolveDownloadMethodsSource, 'displaySearchResults'); const patchSearchResultsContainer = extractMethod( diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index 89b73a9..0837fc5 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -2645,13 +2645,13 @@ export const resolveDownloadMethods = { } const isUrn = Boolean(missing.is_urn && !requestedSearchQuery && !searchSha256); - const resultsId = `search-results-${missing.node_id}-${missing.widget_index}`; + const resultsId = `search-results-${this.getMissingModelDomKey(missing)}`; const canUpdateCurrentWorkflow = workflowKey === this.getWorkflowScopedQueueKey(); const resultsDiv = canUpdateCurrentWorkflow ? this.contentElement?.querySelector(`#${resultsId}`) : null; const searchBtn = canUpdateCurrentWorkflow - ? this.contentElement?.querySelector(`#search-${missing.node_id}-${missing.widget_index}`) + ? this.contentElement?.querySelector(`#search-${this.getMissingModelDomKey(missing)}`) : null; let searchRunId = null; let completedSearchRun = false; @@ -3192,13 +3192,13 @@ export const resolveDownloadMethods = { wireDownloadSearchPanel(container, missing) { if (!container) return; - const searchResultsId = `search-results-${missing.node_id}-${missing.widget_index}`; + const searchResultsId = `search-results-${this.getMissingModelDomKey(missing)}`; const searchResultsDiv = container.querySelector(`#${searchResultsId}`); if (searchResultsDiv) { this.wireSearchDownloadButtons(searchResultsDiv, missing); } - const searchBtn = container.querySelector(`#search-${missing.node_id}-${missing.widget_index}`); + const searchBtn = container.querySelector(`#search-${this.getMissingModelDomKey(missing)}`); if (searchBtn && searchBtn.dataset.mlSearchBound !== 'true') { searchBtn.dataset.mlSearchBound = 'true'; searchBtn.addEventListener('click', () => { diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 9943921..33fca23 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -596,8 +596,9 @@ export const searchPanelMethods = { this.updateBatchFooterButtons?.(); if (!this.contentElement || this.activeTab !== 'missing') return; + if (this.selectedMissingModelKey && this.getMissingModelKey(missing) !== this.selectedMissingModelKey) return; - const resultsId = `search-results-${missing.node_id}-${missing.widget_index}`; + const resultsId = `search-results-${this.getMissingModelDomKey(missing)}`; const resultsDiv = this.contentElement.querySelector(`#${resultsId}`); if (resultsDiv && currentState && this.hasRenderableSearchState(currentState)) { resultsDiv.classList.remove('mr-is-hidden'); @@ -605,7 +606,7 @@ export const searchPanelMethods = { this.displaySearchResults(missing, currentState, resultsDiv); } - const searchBtn = this.contentElement.querySelector(`#search-${missing.node_id}-${missing.widget_index}`); + const searchBtn = this.contentElement.querySelector(`#search-${this.getMissingModelDomKey(missing)}`); if (searchBtn && currentState) { const isRunning = Boolean(currentState.activeSearchRunId); const hasSearchAttempt = this.hasRenderableSearchState(currentState); @@ -1915,7 +1916,7 @@ export const searchPanelMethods = { html += ``; const downloadSourceRow = this.getDownloadSourceTableRow(missing, downloadSource); - html += `
`; + html += `
`; html += this.renderSearchResultsTable(downloadSourceRow ? [downloadSourceRow] : []); html += `
`; return html; @@ -2082,8 +2083,8 @@ export const searchPanelMethods = { let html = `
`; html += `
`; - html += ``; - html += ``; html += `
`; diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index 9f83966..c402800 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -2260,7 +2260,7 @@ export const missingBrowserMethods = { } const state = this.searchResultCache.get(this.getMissingSearchKey(missing)); - const searchResultsDiv = container.querySelector(`#search-results-${missing.node_id}-${missing.widget_index}`); + const searchResultsDiv = container.querySelector(`#search-results-${this.getMissingModelDomKey(missing)}`); if (state && searchResultsDiv && this.hasRenderableSearchState(state)) { searchResultsDiv.classList.remove('mr-is-hidden'); searchResultsDiv.classList.add('mr-is-visible'); @@ -2732,7 +2732,7 @@ export const missingBrowserMethods = { }); html += this.renderDownloadTargetControls(missing, missing.category || 'checkpoints'); html += `
`; - html += `
`; + html += `
`; } else if (missing.is_urn) { html += `
`; html += `
Resolving CivitAI download for this URN...
`; @@ -2743,7 +2743,7 @@ export const missingBrowserMethods = { html += this.renderSearchControls(missing); html += this.renderDownloadTargetControls(missing, missing.category || 'checkpoints'); html += `
`; - html += `
`; + html += `
`; } // Progress container (for downloads). Use the complete model identity,