feat(info-dialog): improve model info dialog layout and UX

- Add Escape key handler to close the info dialog, with proper cleanup
- Extract reusable renderInfoFieldLabel() helper to reduce code duplication
- Add dedicated SVG icons (file, fileText, hash, box, hardDrive, bookType) for field labels
- Adjust dialog sizing to calc(100vh - 16px) with 92vw max width
- Refactor CSS into .mr-info-field-label / .mr-info-label-icon / .mr-info-label-text
- Improve trained-words row layout with flex styling
This commit is contained in:
Dariusz L
2026-06-23 21:15:22 +02:00
parent b292411b93
commit a01be11e27
3 changed files with 63 additions and 36 deletions
+26 -26
View File
@@ -3238,6 +3238,8 @@
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
box-sizing: border-box;
}
.mr-info-dialog {
background: var(--comfy-menu-bg, #202020);
@@ -3245,7 +3247,7 @@
border-radius: 8px;
width: 640px;
max-width: 92vw;
max-height: 80vh;
max-height: calc(100vh - 16px);
min-width: min(520px, 92vw);
min-height: 320px;
overflow: hidden;
@@ -3524,8 +3526,7 @@
font-weight: 650;
word-break: break-word;
}
.mr-info-table td:first-child > span,
.mr-info-trained-words-label {
.mr-info-field-label {
display: grid;
grid-template-columns: 14px minmax(0, 1fr) auto;
align-items: center;
@@ -3536,22 +3537,19 @@
line-height: 1.25;
white-space: nowrap;
}
.mr-info-table td:first-child > span {
overflow: visible;
}
.mr-info-table td:first-child .mr-tooltip-badge,
.mr-info-trained-words-label .mr-tooltip-badge {
justify-self: end;
}
.mr-info-table td:first-child > span::before,
.mr-info-trained-words-label::before {
content: "";
.mr-info-label-icon {
width: 14px;
height: 14px;
border: 1px solid rgba(255,255,255,0.22);
border-radius: 4px;
background: rgba(255,255,255,0.035);
box-shadow: 0 1px 0 rgba(255,255,255,0.05) inset;
color: var(--mr-text-muted, #888);
opacity: 0.82;
}
.mr-info-label-text {
min-width: 0;
overflow: hidden;
text-overflow: clip;
}
.mr-info-field-label .mr-tooltip-badge {
justify-self: end;
}
.mr-info-location {
font-family: "Cascadia Mono", "Consolas", monospace;
@@ -3594,11 +3592,20 @@
opacity: 0.78;
}
.mr-info-trained-words-label {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
width: 100%;
min-width: 0;
}
.mr-info-trained-words-meta {
grid-column: 1 / -1;
justify-self: start;
align-self: flex-start;
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--mr-text-muted, #888);
}
.mr-info-trainedwords-row td {
align-items: flex-start;
@@ -3612,13 +3619,6 @@
align-items: center;
justify-content: flex-start;
}
.mr-info-trained-words-meta {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--mr-text-muted, #888);
}
.mr-info-trained-words-hint {
margin-bottom: 8px;
font-size: 11px;
+31 -10
View File
@@ -253,6 +253,13 @@ export const modelInfoMethods = {
document.body.appendChild(dialog);
this.bindTooltips(dialog);
this.bindInfoDialogResizePersistence(dialog);
dialog._onInfoDialogKeyDown = (event) => {
if (event.key !== 'Escape') return;
if (document.querySelector('.mr-image-preview-backdrop')) return;
event.preventDefault();
this.closeInfoDialog(dialog);
};
document.addEventListener('keydown', dialog._onInfoDialogKeyDown);
// Add close handlers
const closeBtn = dialog.querySelector('.mr-info-dialog-close');
@@ -281,6 +288,16 @@ export const modelInfoMethods = {
/**
* Create the info dialog element
*/
renderInfoFieldLabel(iconName, label, tooltip) {
return `
<span class="mr-info-field-label">
${getSvgIcon(iconName, 'currentColor', 'mr-info-label-icon')}
<span class="mr-info-label-text">${this.escapeHtml(label)}</span>
<span class="mr-tooltip-badge" data-tooltip="${this.escapeHtml(tooltip)}">?</span>
</span>
`;
},
createInfoDialog(loraName, modelData) {
const loraDisplayName = loraName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
@@ -310,29 +327,29 @@ export const modelInfoMethods = {
<table class="mr-info-table">
<tbody>
<tr class="mr-info-file-row">
<td><span>File <span class="mr-tooltip-badge" data-tooltip="The model file name found locally or returned by CivitAI.">?</span></span></td>
<td>${this.renderInfoFieldLabel('file', 'File', 'The model file name found locally or returned by CivitAI.')}</td>
<td><span class="mr-info-file"></span></td>
</tr>
<tr class="mr-info-basemodel-row">
<td><span>Base Model <span class="mr-tooltip-badge" data-tooltip="Base model this resource was made for, for example SD1.5, SDXL or Flux.">?</span></span></td>
<td>${this.renderInfoFieldLabel('box', 'Base Model', 'Base model this resource was made for, for example SD1.5, SDXL or Flux.')}</td>
<td><span class="mr-info-base-model"></span></td>
</tr>
<tr class="mr-info-hash-row">
<td><span>Hash (sha256) <span class="mr-tooltip-badge" data-tooltip="Unique fingerprint of the local file. Model Resolver uses it to confirm the exact CivitAI version.">?</span></span></td>
<td>${this.renderInfoFieldLabel('hash', 'Hash (sha256)', 'Unique fingerprint of the local file. Model Resolver uses it to confirm the exact CivitAI version.')}</td>
<td><span class="mr-info-hash"></span></td>
</tr>
<tr class="mr-info-size-row mr-hidden-initial">
<td><span>Size <span class="mr-tooltip-badge" data-tooltip="The local model file size, read from metadata or from the file on disk.">?</span></span></td>
<td>${this.renderInfoFieldLabel('hardDrive', 'Size', 'The local model file size, read from metadata or from the file on disk.')}</td>
<td><span class="mr-info-size"></span></td>
</tr>
<tr class="mr-info-location-row mr-info-row-wide mr-hidden-initial">
<td><span>Location <span class="mr-tooltip-badge" data-tooltip="Folder where this local model file is stored.">?</span></span></td>
<td>${this.renderInfoFieldLabel('locate', 'Location', 'Folder where this local model file is stored.')}</td>
<td><span class="mr-info-location"></span></td>
</tr>
<tr class="mr-info-trainedwords-row mr-hidden-initial">
<td>
<div class="mr-info-trained-words-label">
Trained Words <span class="mr-tooltip-badge" data-tooltip="Trigger words recommended by the model author. Click the words you want, then copy them into your prompt.">?</span>
${this.renderInfoFieldLabel('bookType', 'Trained Words', 'Trigger words recommended by the model author. Click the words you want, then copy them into your prompt.')}
<small class="mr-info-trained-words-meta">
<span class="mr-info-trained-words-count">0 selected</span>
<button type="button" class="mr-info-copy-trained-words" disabled>Copy</button>
@@ -345,11 +362,11 @@ export const modelInfoMethods = {
</td>
</tr>
<tr class="mr-info-clipskip-row mr-hidden-initial">
<td><span>Clip Skip <span class="mr-tooltip-badge" data-tooltip="Recommended Clip Skip value from the model author, if one is provided.">?</span></span></td>
<td>${this.renderInfoFieldLabel('wrench', 'Clip Skip', 'Recommended Clip Skip value from the model author, if one is provided.')}</td>
<td><span class="mr-info-clip-skip"></span></td>
</tr>
<tr class="mr-info-description-row mr-hidden-initial">
<td><span>Description <span class="mr-tooltip-badge" data-tooltip="Model description from CivitAI or local metadata. Long descriptions are shortened until you click Show more.">?</span></span></td>
<td>${this.renderInfoFieldLabel('fileText', 'Description', 'Model description from CivitAI or local metadata. Long descriptions are shortened until you click Show more.')}</td>
<td>
<div class="mr-info-description-wrap">
<div class="mr-info-description"></div>
@@ -775,8 +792,8 @@ export const modelInfoMethods = {
const height = Number(saved.h);
if (!Number.isFinite(width) || !Number.isFinite(height)) return;
const maxWidth = Math.floor(window.innerWidth * 0.9);
const maxHeight = Math.floor(window.innerHeight * 0.8);
const maxWidth = Math.floor(window.innerWidth * 0.92);
const maxHeight = Math.max(320, window.innerHeight - 16);
const clampedWidth = Math.max(420, Math.min(width, maxWidth));
const clampedHeight = Math.max(320, Math.min(height, maxHeight));
@@ -2057,6 +2074,10 @@ export const modelInfoMethods = {
clearTimeout(dialog._infoDialogResizeSaveTimer);
dialog._infoDialogResizeSaveTimer = null;
}
if (dialog?._onInfoDialogKeyDown) {
document.removeEventListener('keydown', dialog._onInfoDialogKeyDown);
dialog._onInfoDialogKeyDown = null;
}
this.saveInfoDialogSize(dialog);
this.closeInfoImagePreview();
+6
View File
@@ -2,6 +2,12 @@
import { app } from "../../../scripts/app.js";
const SVG_ICONS = {
file: `<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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path></svg>`,
fileText: `<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="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"></path><path d="M14 2v5a1 1 0 0 0 1 1h5"></path><path d="M10 9H8"></path><path d="M16 13H8"></path><path d="M16 17H8"></path></svg>`,
hash: `<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"><line x1="4" x2="20" y1="9" y2="9"></line><line x1="4" x2="20" y1="15" y2="15"></line><line x1="10" x2="8" y1="3" y2="21"></line><line x1="16" x2="14" y1="3" y2="21"></line></svg>`,
box: `<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="M21 8a2 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.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"></path><path d="m3.3 7 8.7 5 8.7-5"></path><path d="M12 22V12"></path></svg>`,
hardDrive: `<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="M10 16h.01"></path><path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path><path d="M21.946 12.013H2.054"></path><path d="M6 16h.01"></path></svg>`,
bookType: `<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="M10 13h4"></path><path d="M12 6v7"></path><path d="M16 8V6H8v2"></path><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"></path></svg>`,
search: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="6.5"></circle><path d="M16 16l5 5"></path></svg>`,
refreshCw: `<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="M21 12a9 9 0 1 1-2.64-6.36"></path><path d="M21 3v6h-6"></path></svg>`,
locate: `<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="M2 12h3"></path><path d="M19 12h3"></path><path d="M12 2v3"></path><path d="M12 19v3"></path><circle cx="12" cy="12" r="7"></circle><circle cx="12" cy="12" r="2.5"></circle></svg>`,