From a01be11e27035d4881033d656e7268c3aceab4a6 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Tue, 23 Jun 2026 21:15:22 +0200 Subject: [PATCH] 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 --- web/css/resolver-main.css | 52 ++++++++++++------------ web/resolver/views/model_info_methods.js | 41 ++++++++++++++----- web/utils/icon_utils.js | 6 +++ 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 87e04ba..14379af 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -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; diff --git a/web/resolver/views/model_info_methods.js b/web/resolver/views/model_info_methods.js index cabe89f..da1a3ff 100644 --- a/web/resolver/views/model_info_methods.js +++ b/web/resolver/views/model_info_methods.js @@ -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 ` + + ${getSvgIcon(iconName, 'currentColor', 'mr-info-label-icon')} + ${this.escapeHtml(label)} + ? + + `; + }, + createInfoDialog(loraName, modelData) { const loraDisplayName = loraName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, ''); @@ -310,29 +327,29 @@ export const modelInfoMethods = { - + - + - + - + - + - + - +
File ?${this.renderInfoFieldLabel('file', 'File', 'The model file name found locally or returned by CivitAI.')}
Base Model ?${this.renderInfoFieldLabel('box', 'Base Model', 'Base model this resource was made for, for example SD1.5, SDXL or Flux.')}
Hash (sha256) ?${this.renderInfoFieldLabel('hash', 'Hash (sha256)', 'Unique fingerprint of the local file. Model Resolver uses it to confirm the exact CivitAI version.')}
Size ?${this.renderInfoFieldLabel('hardDrive', 'Size', 'The local model file size, read from metadata or from the file on disk.')}
Location ?${this.renderInfoFieldLabel('locate', 'Location', 'Folder where this local model file is stored.')}
- Trained Words ? + ${this.renderInfoFieldLabel('bookType', 'Trained Words', 'Trigger words recommended by the model author. Click the words you want, then copy them into your prompt.')} 0 selected @@ -345,11 +362,11 @@ export const modelInfoMethods = {
Clip Skip ?${this.renderInfoFieldLabel('wrench', 'Clip Skip', 'Recommended Clip Skip value from the model author, if one is provided.')}
Description ?${this.renderInfoFieldLabel('fileText', 'Description', 'Model description from CivitAI or local metadata. Long descriptions are shortened until you click Show more.')}
@@ -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(); diff --git a/web/utils/icon_utils.js b/web/utils/icon_utils.js index b4c3347..8990bc5 100644 --- a/web/utils/icon_utils.js +++ b/web/utils/icon_utils.js @@ -2,6 +2,12 @@ import { app } from "../../../scripts/app.js"; const SVG_ICONS = { + file: ``, + fileText: ``, + hash: ``, + box: ``, + hardDrive: ``, + bookType: ``, search: ``, refreshCw: ``, locate: ``,