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:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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">`;
|
||||
|
||||
@@ -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>`,
|
||||
|
||||
Reference in New Issue
Block a user