Add per-item Apply button to queued resolution selected bar

Refactor queue_methods.js to split applyPendingResolutions into
applyQueuedByKey (per-item) and applyPendingResolutionList (shared
implementation). Add an Apply button alongside the Remove button in
the selected resolution bar, wired to applyQueuedByKey with loading
state. Update CSS for .mr-selected-actions layout and .mr-btn-primary
styling in the detail pane.
This commit is contained in:
Dariusz L
2026-06-17 01:17:25 +02:00
parent 40b1108bd2
commit fa2002fc2a
4 changed files with 88 additions and 10 deletions
+15 -1
View File
@@ -5390,11 +5390,15 @@
font-size: 11px;
white-space: nowrap;
}
#model-resolver-modal .mr-missing-detail-pane .mr-selected-actions {
gap: 8px;
margin-left: auto;
}
#model-resolver-modal .mr-missing-detail-pane .model-resolver-selected .mr-btn {
min-height: var(--mr-missing-search-button-height, 34px);
height: var(--mr-missing-search-button-height, 34px);
padding: 0 12px;
margin-left: auto;
margin-left: 0;
background: var(--mr-missing-control-bg, rgba(255,255,255,0.035));
border-color: var(--mr-missing-control-border, rgba(255,255,255,0.105));
border-radius: var(--mr-missing-control-radius, 7px);
@@ -5408,6 +5412,16 @@
border-color: var(--mr-missing-control-border-hover, rgba(255,255,255,0.17));
color: var(--mr-text);
}
#model-resolver-modal .mr-missing-detail-pane .model-resolver-selected .mr-btn-primary {
background: rgba(47, 128, 237, 0.18);
border-color: rgba(47, 128, 237, 0.34);
color: #dbeafe;
}
#model-resolver-modal .mr-missing-detail-pane .model-resolver-selected .mr-btn-primary:hover:not(:disabled) {
background: rgba(47, 128, 237, 0.26);
border-color: rgba(47, 128, 237, 0.48);
color: #ffffff;
}
#model-resolver-modal .mr-missing-detail-pane .mr-search-progress-list-compact {
gap: 7px;
margin: 10px 0 8px;
+8
View File
@@ -523,6 +523,14 @@
text-overflow: ellipsis;
}
.mr-selected-actions {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: var(--mr-spacing-8);
margin-left: auto;
}
.mr-inline-note-link {
cursor: pointer;
color: var(--mr-accent-blue);
+64 -8
View File
@@ -343,17 +343,28 @@ export const queueMethods = {
// Build selected bar content
const label = selection.resolved_model?.relative_path || selection.resolved_model?.filename || selection.resolved_path || '';
const resolveBtnId = `selected-remove-${nodeId}-${widgetIndex}`;
const applyBtnId = `selected-apply-${nodeId}-${widgetIndex}`;
const removeBtnId = `selected-remove-${nodeId}-${widgetIndex}`;
selectedBar.innerHTML = `<div class="mr-selected-bar-inner">`;
selectedBar.innerHTML += `<span class="mr-selected-label">✓ Selected:</span>`;
selectedBar.innerHTML += `<code class="mr-selected-code">${label}</code>`;
selectedBar.innerHTML += `<button id="${resolveBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm">Remove</button>`;
selectedBar.innerHTML += `<span class="mr-selected-actions">`;
selectedBar.innerHTML += `<button id="${applyBtnId}" class="mr-btn mr-btn-primary mr-btn-sm" data-tooltip="Apply this selected local match to the workflow.">Apply</button>`;
selectedBar.innerHTML += `<button id="${removeBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm">Remove</button>`;
selectedBar.innerHTML += `</span>`;
selectedBar.innerHTML += `</div>`;
selectedBar.style.display = 'block';
const applyBtn = selectedBar.querySelector(`#${applyBtnId}`);
if (applyBtn) {
applyBtn.addEventListener('click', () => {
this.applyQueuedByKey(key, applyBtn);
});
}
// Wire remove button - use key-based removal
const removeBtn = selectedBar.querySelector(`#${resolveBtnId}`);
const removeBtn = selectedBar.querySelector(`#${removeBtnId}`);
if (removeBtn) {
removeBtn.addEventListener('click', () => {
this.removeQueuedByKey(key);
@@ -725,10 +736,39 @@ export const queueMethods = {
},
/**
* Apply all pending resolutions in batch
* Apply one queued resolution from the selected bar.
*/
async applyPendingResolutions() {
const list = this.pendingResolutions || [];
async applyQueuedByKey(key, button = null) {
if (!key || !this.pendingIndex.has(key)) {
this.showNotification('Selected match is no longer queued.', 'error');
return;
}
const idx = this.pendingIndex.get(key);
const selection = this.pendingResolutions?.[idx];
if (!selection) {
this.showNotification('Selected match is no longer queued.', 'error');
return;
}
if (button) {
button.disabled = true;
button.classList.add('mr-btn-is-disabled');
button.textContent = 'Applying...';
}
try {
await this.applyPendingResolutionList([selection], { clearAll: false });
} finally {
if (button?.isConnected) {
button.disabled = false;
button.classList.remove('mr-btn-is-disabled');
button.textContent = 'Apply';
}
}
},
async applyPendingResolutionList(list, { clearAll = false } = {}) {
if (!list.length) {
this.showNotification('No selections queued', 'error');
return;
@@ -760,11 +800,19 @@ export const queueMethods = {
const optimisticData = this.getOptimisticAnalysisDataAfterApply(appliedSelections);
await this.updateWorkflowInComfyUI(data.workflow);
this.pendingResolutions = [];
this.pendingIndex = new Map();
const appliedKeys = new Set(appliedSelections.map(selection => this.getResolutionQueueKey(selection)));
const remainingSelections = clearAll
? []
: (this.pendingResolutions || []).filter(selection => !appliedKeys.has(this.getResolutionQueueKey(selection)));
this.pendingResolutions = remainingSelections;
this.rebuildPendingIndex();
this.savePendingQueueForActiveWorkflow();
this.syncWorkflowScopedQueue?.(data.workflow);
this.pendingResolutions = remainingSelections;
this.rebuildPendingIndex();
this.savePendingQueueForActiveWorkflow();
this.updateApplyPendingButton();
this.updateQueuePanel();
@@ -785,6 +833,14 @@ export const queueMethods = {
}
},
/**
* Apply all pending resolutions in batch
*/
async applyPendingResolutions() {
const list = this.pendingResolutions || [];
await this.applyPendingResolutionList(list, { clearAll: true });
},
updateApplyPendingButton() {
if (!this.applyPendingBtn) return;
const count = this.pendingResolutions?.length || 0;