mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-29 00:41:27 -03:00
feat(recipes): improve recipe LoRA status indicators and missing-badge affordance (#1076)
- Recipe card: compact status pill with state icon + available/total fraction (e.g. "2/3"), pinned to the footer bottom-right like model card actions; status is encoded by icon + color, never color alone - Recipe modal: "N missing" is now a real <button> with a persistent border, leading download icon, focus-visible ring and aria-label; clicking opens the download-missing flow - Fix context menu missing-LoRA detection selector after badge refactor - i18n: add recipes.status/loraStatus keys with translations for all 10 locales, and fill pending rate-limit translations
This commit is contained in:
@@ -37,8 +37,7 @@ export class RecipeContextMenu extends BaseContextMenu {
|
||||
|
||||
if (recipeId && missingLorasItem) {
|
||||
// Check if this card has missing LoRAs
|
||||
const loraCountElement = card.querySelector('.lora-count');
|
||||
const hasMissingLoras = loraCountElement && loraCountElement.classList.contains('missing');
|
||||
const hasMissingLoras = Boolean(card.querySelector('.lora-count.missing'));
|
||||
|
||||
// Show/hide the download missing LoRAs option based on missing status
|
||||
if (hasMissingLoras) {
|
||||
|
||||
@@ -45,6 +45,13 @@ class RecipeCard {
|
||||
const missingLorasCount = loras.filter(lora => !lora.inLibrary && !lora.isDeleted).length;
|
||||
const allLorasAvailable = missingLorasCount === 0 && lorasCount > 0;
|
||||
|
||||
// Compact status pill: state icon + available/total fraction.
|
||||
// Icon switches by state so status never relies on color alone.
|
||||
const availableLorasCount = lorasCount - missingLorasCount;
|
||||
const loraCountStateClass = missingLorasCount > 0 ? 'missing' : (allLorasAvailable ? 'ready' : '');
|
||||
const loraCountIcon = missingLorasCount > 0 ? 'fa-exclamation-triangle' : (allLorasAvailable ? 'fa-check' : 'fa-layer-group');
|
||||
const loraCountLabel = lorasCount > 0 ? `${availableLorasCount}/${lorasCount}` : `${lorasCount}`;
|
||||
|
||||
// Ensure file_url exists, fallback to API URL if needed
|
||||
let previewUrl = this.recipe.file_url;
|
||||
if (!previewUrl) {
|
||||
@@ -128,9 +135,8 @@ class RecipeCard {
|
||||
<span class="model-name">${this.recipe.title}</span>
|
||||
</div>
|
||||
${!isDuplicatesMode ? `
|
||||
<div class="lora-count ${allLorasAvailable ? 'ready' : (lorasCount > 0 ? 'missing' : '')}"
|
||||
title="${this.getLoraStatusTitle(lorasCount, missingLorasCount)}">
|
||||
<i class="fas fa-layer-group"></i> ${lorasCount}
|
||||
<div class="lora-count ${loraCountStateClass}" title="${this.getLoraStatusTitle(lorasCount, missingLorasCount)}">
|
||||
<i class="fas ${loraCountIcon}" aria-hidden="true"></i> ${loraCountLabel}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
@@ -149,9 +155,17 @@ class RecipeCard {
|
||||
}
|
||||
|
||||
getLoraStatusTitle(totalCount, missingCount) {
|
||||
if (totalCount === 0) return "No LoRAs in this recipe";
|
||||
if (missingCount === 0) return "All LoRAs available - Ready to use";
|
||||
return `${missingCount} of ${totalCount} LoRAs missing`;
|
||||
if (totalCount === 0) {
|
||||
return translate('recipes.loraStatus.none', {}, 'No LoRAs in this recipe');
|
||||
}
|
||||
if (missingCount === 0) {
|
||||
return translate('recipes.loraStatus.allAvailable', {}, 'All LoRAs available - Ready to use');
|
||||
}
|
||||
return translate(
|
||||
'recipes.loraStatus.missing',
|
||||
{ missing: missingCount, total: totalCount },
|
||||
`${missingCount} of ${totalCount} LoRAs missing`
|
||||
);
|
||||
}
|
||||
|
||||
async toggleFavorite(card) {
|
||||
|
||||
@@ -299,21 +299,6 @@ class RecipeModal {
|
||||
tooltip.style.left = (badgeRect.right - tooltip.offsetWidth) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Add tooltip positioning for missing badge
|
||||
if (event.target.closest('.recipe-status.missing')) {
|
||||
const badge = event.target.closest('.recipe-status.missing');
|
||||
const tooltip = badge.querySelector('.missing-tooltip');
|
||||
|
||||
if (tooltip) {
|
||||
// Get badge position
|
||||
const badgeRect = badge.getBoundingClientRect();
|
||||
|
||||
// Position the tooltip
|
||||
tooltip.style.top = (badgeRect.bottom + 4) + 'px';
|
||||
tooltip.style.left = (badgeRect.left) + 'px';
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -872,30 +857,34 @@ class RecipeModal {
|
||||
let statusHTML = '';
|
||||
if (totalCount > 0) {
|
||||
if (allLorasAvailable && deletedLorasCount === 0) {
|
||||
statusHTML = `<div class="recipe-status ready"><i class="fas fa-check-circle"></i> Ready to use</div>`;
|
||||
statusHTML = `<div class="recipe-status ready"><i class="fas fa-check-circle" aria-hidden="true"></i> ${translate('recipes.status.ready', {}, 'Ready to use')}</div>`;
|
||||
} else if (missingLorasCount > 0) {
|
||||
statusHTML = `<div class="recipe-status missing">
|
||||
<i class="fas fa-exclamation-triangle"></i> ${missingLorasCount} missing
|
||||
<div class="missing-tooltip">Click to download missing LoRAs</div>
|
||||
</div>`;
|
||||
// Rendered as a real button so the affordance is visible without
|
||||
// hover and the control is keyboard/screen-reader accessible.
|
||||
// Leading download icon: the red tint + "missing" text already
|
||||
// encode the state, so the icon's job is to hint the action.
|
||||
statusHTML = `<button type="button" class="recipe-status missing clickable"
|
||||
title="${translate('recipes.status.downloadMissingTooltip', {}, 'Click to download missing LoRAs')}"
|
||||
aria-label="${translate('recipes.status.downloadMissing', { count: missingLorasCount }, `Download ${missingLorasCount} missing LoRAs`)}">
|
||||
<i class="fas fa-download" aria-hidden="true"></i> ${translate('recipes.status.missingCount', { count: missingLorasCount }, `${missingLorasCount} missing`)}
|
||||
</button>`;
|
||||
} else if (deletedLorasCount > 0 && missingLorasCount === 0) {
|
||||
statusHTML = `<div class="recipe-status partial"><i class="fas fa-info-circle"></i> ${deletedLorasCount} deleted</div>`;
|
||||
statusHTML = `<div class="recipe-status partial"><i class="fas fa-info-circle" aria-hidden="true"></i> ${translate('recipes.status.deletedCount', { count: deletedLorasCount }, `${deletedLorasCount} deleted`)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
lorasCountElement.innerHTML = `<i class="fas fa-layer-group"></i> ${totalCount} ${totalCount === 1 ? 'LoRA' : 'LoRAs'} ${statusHTML}`;
|
||||
|
||||
const missingStatus = lorasCountElement.querySelector('.recipe-status.missing');
|
||||
if (missingStatus && missingLorasCount > 0) {
|
||||
missingStatus.addEventListener('click', () => this.showDownloadMissingLorasModal());
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const viewRecipeLorasBtn = document.getElementById('viewRecipeLorasBtn');
|
||||
if (viewRecipeLorasBtn) {
|
||||
viewRecipeLorasBtn.addEventListener('click', () => this.navigateToLorasPage());
|
||||
}
|
||||
|
||||
const missingStatus = document.querySelector('.recipe-status.missing');
|
||||
if (missingStatus && missingLorasCount > 0) {
|
||||
missingStatus.classList.add('clickable');
|
||||
missingStatus.addEventListener('click', () => this.showDownloadMissingLorasModal());
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user