From 0a98c9e44fe68ed556ec61c0ed36c352d367c401 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 21 Jun 2026 03:51:24 +0200 Subject: [PATCH] feat: add suggest download subfolder button with lightbulb animation; normalize search button labels based on UI state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add grid layout and CSS for a new 'Suggest' column in the download target panel, including a lightbulb SVG icon button with a flash animation - Update download_target_methods.js to render the suggest button in the dedicated grid area and apply a ‘mr-is-suggesting’ animation on click - Adjust search button label logic so it shows 'Search Again' when there is a renderable search state in the panel, otherwise show 'Search Online' --- web/css/resolver-main.css | 99 +++++++++++++++++-- .../search/download_target_methods.js | 20 ++-- web/resolver/search/search_panel.js | 9 +- web/utils/icon_utils.js | 1 + 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 5dc6120..40a4f0f 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -589,6 +589,13 @@ font-size: 11px; font-weight: 700; } +.mr-missing-list-head > span { + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + text-align: center; +} .mr-missing-list { min-height: 0; overflow-y: auto; @@ -1586,6 +1593,32 @@ transform: rotate(360deg); } } +@keyframes mr-suggest-lightbulb-flash { + 0% { + color: var(--mr-text); + stroke: var(--mr-text); + filter: none; + transform: scale(1); + } + 35% { + color: #ffd84d; + stroke: #ffd84d; + filter: drop-shadow(0 0 7px rgba(255,216,77,0.75)); + transform: scale(1.12); + } + 70% { + color: #ffe680; + stroke: #ffe680; + filter: drop-shadow(0 0 4px rgba(255,216,77,0.45)); + transform: scale(1.04); + } + 100% { + color: var(--mr-text); + stroke: var(--mr-text); + filter: none; + transform: scale(1); + } +} .mr-refresh-spin-target, #model-resolver-modal .mr-refresh-spin-target { display: inline-flex; @@ -1853,7 +1886,7 @@ } .mr-search-source-bar { display: grid; - grid-template-columns: auto minmax(150px, 0.85fr) minmax(150px, 1fr); + grid-template-columns: 82px minmax(0, 0.85fr) minmax(0, 1fr); align-items: end; gap: 10px; margin-bottom: 0; @@ -5354,8 +5387,10 @@ } .mr-download-target-grid { display: grid; - grid-template-columns: minmax(120px, 33%) minmax(0, 1fr); - grid-template-rows: auto auto; + grid-template-columns: minmax(120px, 33%) minmax(0, 1fr) auto; + grid-template-areas: + "folder-label subfolder-label suggest-label" + "folder-control subfolder-control suggest-control"; gap: 8px 10px; align-items: start; } @@ -5366,7 +5401,13 @@ @media (max-width: 720px) { .mr-download-target-grid { grid-template-columns: 1fr; - grid-template-rows: auto; + grid-template-areas: + "folder-label" + "folder-control" + "subfolder-label" + "subfolder-control" + "suggest-label" + "suggest-control"; } } .mr-download-target-label { @@ -5379,6 +5420,22 @@ letter-spacing: 0.06em; line-height: 1.2; } +.mr-download-target-label-folder { + grid-area: folder-label; +} +.mr-download-target-label-subfolder { + grid-area: subfolder-label; +} +.mr-download-target-label-suggest { + grid-area: suggest-label; + justify-content: center; +} +.mr-download-target-folder-wrap { + grid-area: folder-control; +} +.mr-download-target-subfolder-wrap { + grid-area: subfolder-control; +} .mr-download-target-input, .mr-download-target-select { display: block; @@ -5432,13 +5489,43 @@ } .mr-download-suggest-btn, #model-resolver-modal .mr-download-suggest-btn { - min-width: 58px; + grid-area: suggest-control; + justify-self: center; + width: var(--mr-download-control-height, 36px); + min-width: var(--mr-download-control-height, 36px); height: var(--mr-download-control-height, 36px); min-height: var(--mr-download-control-height, 36px); - padding: 0 9px; + padding: 0; border-radius: var(--mr-missing-control-radius, 7px); font-size: 11px; line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; +} +.mr-download-suggest-icon { + width: 16px; + height: 16px; + flex: none; +} +.mr-download-suggest-btn.mr-is-suggesting, +.mr-download-suggest-btn.mr-is-suggesting:hover, +.mr-download-suggest-btn.mr-is-suggesting:focus, +#model-resolver-modal .mr-download-suggest-btn.mr-is-suggesting, +#model-resolver-modal .mr-download-suggest-btn.mr-is-suggesting:hover, +#model-resolver-modal .mr-download-suggest-btn.mr-is-suggesting:focus { + color: #ffd84d !important; +} +.mr-download-suggest-btn.mr-is-suggesting:disabled, +#model-resolver-modal .mr-download-suggest-btn.mr-is-suggesting:disabled { + opacity: 1; +} +.mr-download-suggest-btn.mr-is-suggesting .mr-download-suggest-icon, +#model-resolver-modal .mr-download-suggest-btn.mr-is-suggesting .mr-download-suggest-icon { + color: #ffd84d; + stroke: #ffd84d; + animation: mr-suggest-lightbulb-flash 650ms ease-out; + transform-origin: 50% 58%; } @media (max-width: 720px) { .mr-download-target-label { diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index 5f122d5..661568b 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -1444,19 +1444,18 @@ export const downloadTargetMethods = { let html = `
`; html += `
`; - html += ``; - html += ``; - html += `
`; + html += ``; + html += ``; + html += ``; + html += `
`; html += ``; html += `
`; html += `
`; - html += `
`; - html += `
`; + html += `
`; html += ``; - html += ``; - html += `
`; html += `
`; html += `
`; + html += ``; html += `
`; html += `
`; return html; @@ -1683,12 +1682,19 @@ export const downloadTargetMethods = { if (suggestBtn && suggestBtn.dataset.mlSuggestBound !== 'true') { suggestBtn.dataset.mlSuggestBound = 'true'; suggestBtn.addEventListener('click', async () => { + const suggestAnimationMs = 700; + suggestBtn.classList.remove('mr-is-suggesting'); + void suggestBtn.offsetWidth; + suggestBtn.classList.add('mr-is-suggesting'); suggestBtn.disabled = true; + const animationDone = new Promise((resolve) => window.setTimeout(resolve, suggestAnimationMs)); try { await this.forceSuggestedDownloadSubfolder(missing, categoryEl, subfolderEl); listEl.innerHTML = ''; listEl.style.display = 'none'; } finally { + await animationDone; + suggestBtn.classList.remove('mr-is-suggesting'); suggestBtn.disabled = false; } }); diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 72a4104..6f3a6da 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -549,10 +549,11 @@ export const searchPanelMethods = { const searchBtn = this.contentElement.querySelector(`#search-${missing.node_id}-${missing.widget_index}`); if (searchBtn && currentState) { const isRunning = Boolean(currentState.activeSearchRunId); + const hasSearchAttempt = this.hasRenderableSearchState(currentState); searchBtn.disabled = isRunning; searchBtn.innerHTML = isRunning ? this.renderSearchButtonContent('Searching...') - : this.renderSearchButtonContent('Search Again'); + : this.renderSearchButtonContent(hasSearchAttempt ? 'Search Again' : 'Search Online'); } }, @@ -1182,9 +1183,7 @@ export const searchPanelMethods = { const searchBtn = container?.querySelector?.(`#search-${missing.node_id}-${missing.widget_index}`); if (searchBtn) { searchBtn.disabled = false; - searchBtn.innerHTML = this.renderSearchButtonContent( - this.hasSearchResultsForMissing(missing) ? 'Search Again' : 'Search Online' - ); + searchBtn.innerHTML = this.renderSearchButtonContent('Search Online'); } } state.selectedBaseModel = nextBaseModel; @@ -1634,7 +1633,7 @@ export const searchPanelMethods = { const selectedBaseModel = state.selectedBaseModel || this.getDefaultSearchBaseModel(); const baseModelTooltip = this.getSearchBaseModelTooltip(missing); const buttonText = options.buttonText - || (this.hasSearchResultsForMissing(missing) ? 'Search Again' : 'Search Online'); + || (this.hasRenderableSearchState(state) ? 'Search Again' : 'Search Online'); let html = `
`; html += `
`; diff --git a/web/utils/icon_utils.js b/web/utils/icon_utils.js index e99258b..b4c3347 100644 --- a/web/utils/icon_utils.js +++ b/web/utils/icon_utils.js @@ -19,6 +19,7 @@ const SVG_ICONS = { civarchive: ``, loraManager: ``, star: ``, + lightbulb: ``, link: ``, globe: ``, eye: ``,