feat: add suggest download subfolder button with lightbulb animation; normalize search button labels based on UI state

- 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'
This commit is contained in:
Dariusz L
2026-06-21 03:51:24 +02:00
parent 24fed2302f
commit 0a98c9e44f
4 changed files with 111 additions and 18 deletions
+93 -6
View File
@@ -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 {
+13 -7
View File
@@ -1444,19 +1444,18 @@ export const downloadTargetMethods = {
let html = `<div class="mr-download-target">`;
html += `<div class="mr-download-target-grid">`;
html += `<label class="mr-download-target-label" for="${selectId}">Folder</label>`;
html += `<label class="mr-download-target-label" for="${subfolderId}">Subfolder (optional)</label>`;
html += `<div class="mr-download-target-wrap">`;
html += `<label class="mr-download-target-label mr-download-target-label-folder" for="${selectId}">Folder</label>`;
html += `<label class="mr-download-target-label mr-download-target-label-subfolder" for="${subfolderId}">Subfolder (optional)</label>`;
html += `<label class="mr-download-target-label mr-download-target-label-suggest" for="${suggestId}">Suggest</label>`;
html += `<div class="mr-download-target-wrap mr-download-target-folder-wrap">`;
html += `<input id="${selectId}" class="mr-download-target-input mr-download-target-select" type="text" autocomplete="off" data-value="${this.escapeHtml(selectedCategory)}" value="${this.escapeHtml(this.getCategoryDisplayName(selectedCategory))}">`;
html += `<div id="${categoryListId}" class="mr-download-target-list"></div>`;
html += `</div>`;
html += `<div class="mr-download-target-wrap">`;
html += `<div class="mr-download-subfolder-control">`;
html += `<div class="mr-download-target-wrap mr-download-target-subfolder-wrap">`;
html += `<input id="${subfolderId}" class="mr-download-target-input" type="text" placeholder="e.g. ponyxl\\styles" autocomplete="off" value="${this.escapeHtml(selectedSubfolder)}" data-base-directory="${this.escapeHtml(selectedSubfolderBaseDirectory)}">`;
html += `<button id="${suggestId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-download-suggest-btn" type="button" data-tooltip="Apply suggested subfolder">Suggest</button>`;
html += `</div>`;
html += `<div id="${subfolderListId}" class="mr-download-target-list"></div>`;
html += `</div>`;
html += `<button id="${suggestId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-download-suggest-btn" type="button" data-tooltip="Apply suggested subfolder" aria-label="Apply suggested subfolder">${getSvgIcon('lightbulb', 'currentColor', 'mr-download-suggest-icon')}</button>`;
html += `</div>`;
html += `</div>`;
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;
}
});
+4 -5
View File
@@ -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 = `<div id="${searchSourcesId}" class="mr-search-source-bar">`;
html += `<div class="mr-search-source-picker mr-search-button-picker">`;
+1
View File
@@ -19,6 +19,7 @@ const SVG_ICONS = {
civarchive: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"></path><path d="M12 22V12"></path><polyline points="3.29 7 12 12 20.71 7"></polyline><path d="m7.5 4.27 9 5.15"></path></svg>`,
loraManager: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" aria-hidden="true"><rect width="400" height="400" fill="#d3e3e6"></rect><path fill="#1e406e" d="M127 101h55v132h62l41 55H127z"></path></svg>`,
star: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="{color}" stroke="{color}" stroke-width="1.6" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.5l2.55 5.16l5.7.83l-4.13 4.02l.98 5.68L12 16.52l-5.1 2.67l.98-5.68L3.75 9.49l5.7-.83L12 3.5z"></path></svg>`,
lightbulb: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"></path><path d="M9 18h6"></path><path d="M10 22h4"></path></svg>`,
link: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l2-2a5 5 0 0 0-7.07-7.07l-1.14 1.14"></path><path d="M14 11a5 5 0 0 0-7.07 0l-2 2A5 5 0 0 0 12 20.07l1.14-1.14"></path></svg>`,
globe: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"></path><path d="M2 12h20"></path></svg>`,
eye: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"></path><circle cx="12" cy="12" r="3"></circle></svg>`,