diff --git a/web/linker.js b/web/linker.js index 3689ce7..c795547 100644 --- a/web/linker.js +++ b/web/linker.js @@ -617,29 +617,29 @@ class LinkerManagerDialog extends ComfyDialog {
| File | +File ? | |
| Hash (sha256) | +Hash (sha256) ? | |
| CivitAI | +CivitAI ? | |
| Name | +Name ? | |
| Base Model | +Base Model ? |
${fallbackText}
`; + const textOnly = finalHtml.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(); + + descEl.innerHTML = finalHtml; + descEl.classList.remove('is-expanded'); + + const shouldCollapse = textOnly.length > 520 || finalHtml.length > 900; + if (actionsEl) { + actionsEl.style.display = shouldCollapse ? '' : 'none'; + } + if (toggleBtn) { + toggleBtn.textContent = 'Show more'; + } + if (!shouldCollapse) { + descEl.classList.add('is-expanded'); + } + const row = descEl.closest('tr'); if (row) row.style.display = ''; } else { @@ -1642,38 +1776,78 @@ class LinkerManagerDialog extends ComfyDialog { justify-content: flex-end; align-items: center; gap: 10px; + flex-wrap: wrap; padding: 16px 20px; background: linear-gradient(to top, var(--ml-bg) 0%, var(--ml-bg) 70%, transparent 100%); border-top: 1px solid var(--ml-border); } + .ml-footer-btn { + min-height: 34px; + min-width: 152px; + padding: 7px 14px; + font-size: 12px; + border-radius: 9px; + line-height: 1; + } /* Tabs */ .ml-tabs { display: flex; - gap: 4px; - padding: 0 20px; + align-items: flex-end; + gap: 8px; + padding: 10px 20px 0 20px; margin-bottom: 0; - background-color: var(--ml-bg, #222); - border-bottom: 1px solid var(--ml-border); + background: + linear-gradient(180deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0.01) 100%); + position: relative; + z-index: 2; } .ml-tab { - padding: 12px 20px; - background: transparent; - border: none; + position: relative; + padding: 11px 18px 12px 18px; + background: linear-gradient(180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.025) 100%); + border: 1px solid rgba(255,255,255,0.08); + border-bottom: none; + border-top-left-radius: 14px; + border-top-right-radius: 14px; color: var(--ml-text-muted, #888); font-size: 13px; - font-weight: 500; + font-weight: 600; cursor: pointer; - border-bottom: 2px solid transparent; - transition: all 0.2s ease; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); + transform: translateY(6px); + opacity: 0.82; + transition: transform 0.18s ease, opacity 0.18s ease, color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; } .ml-tab:hover { color: var(--ml-text, #e0e0e0); - background: rgba(255,255,255,0.05); + background: linear-gradient(180deg, rgba(255,255,255,0.075) 0%, rgba(255,255,255,0.04) 100%); + border-color: rgba(255,255,255,0.12); + transform: translateY(3px); + opacity: 0.96; } .ml-tab.ml-tab-active { - color: var(--ml-accent, #4CAF50); - border-bottom-color: var(--ml-accent, #4CAF50); + color: var(--ml-text, #eef2f7); + background: var(--ml-bg, #222); + border-color: rgba(255,255,255,0.1); + box-shadow: + 0 -10px 24px rgba(0,0,0,0.14), + 0 10px 20px rgba(0,0,0,0.16), + inset 0 1px 0 rgba(255,255,255,0.05); + transform: translateY(1px); + opacity: 1; + z-index: 3; + } + .ml-tab.ml-tab-active::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -8px; + height: 10px; + background: var(--ml-bg, #222); + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; } /* Loaded Models Tab */ @@ -2034,18 +2208,115 @@ class LinkerManagerDialog extends ComfyDialog { color: #d8ebff; box-shadow: 0 0 0 1px rgba(78,161,255,0.15) inset; } + .ml-info-description-wrap { + display: flex; + flex-direction: column; + gap: 8px; + } + .ml-info-description { + line-height: 1.55; + color: var(--input-text, #e0e0e0); + max-height: 220px; + overflow: hidden; + position: relative; + } + .ml-info-description:not(.is-expanded)::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 56px; + background: linear-gradient(180deg, rgba(32,32,32,0) 0%, rgba(32,32,32,0.92) 100%); + pointer-events: none; + } + .ml-info-description.is-expanded { + max-height: none; + overflow: visible; + } + .ml-info-description p, + .ml-info-description ul, + .ml-info-description ol, + .ml-info-description blockquote, + .ml-info-description pre { + margin: 0 0 10px 0; + } + .ml-info-description h1, + .ml-info-description h2, + .ml-info-description h3, + .ml-info-description h4, + .ml-info-description h5, + .ml-info-description h6 { + margin: 14px 0 8px 0; + color: var(--ml-text); + line-height: 1.3; + } + .ml-info-description ul, + .ml-info-description ol { + padding-left: 18px; + } + .ml-info-description code, + .ml-info-description pre { + background: rgba(255,255,255,0.05); + border-radius: 6px; + } + .ml-info-description code { + padding: 1px 5px; + font-family: 'SF Mono', 'Consolas', 'Monaco', monospace; + font-size: 12px; + } + .ml-info-description pre { + padding: 10px 12px; + overflow-x: auto; + white-space: pre-wrap; + } + .ml-info-description a { + color: var(--ml-link-color, #8fc2ff); + text-decoration: none; + } + .ml-info-description a:hover { + text-decoration: underline; + } + .ml-info-description-actions { + display: flex; + justify-content: flex-end; + } + .ml-info-description-toggle { + border: 1px solid rgba(255,255,255,0.12); + background: rgba(255,255,255,0.05); + color: var(--ml-link-color, #8fc2ff); + border-radius: 999px; + padding: 4px 12px; + font-size: 11px; + cursor: pointer; + transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease; + } + .ml-info-description-toggle:hover { + background: rgba(78,161,255,0.14); + border-color: rgba(78,161,255,0.28); + color: #d6eaff; + } .ml-info-help { - display: inline-block; + display: inline-flex; + align-items: center; + justify-content: center; width: 14px; height: 14px; - background: rgba(255,255,255,0.1); + background: rgba(255,255,255,0.14); + color: var(--ml-text-muted, #b4becb); border-radius: 50%; font-size: 10px; - text-align: center; - line-height: 14px; + font-weight: 700; cursor: help; margin-left: 4px; vertical-align: middle; + border: 1px solid rgba(255,255,255,0.12); + user-select: none; + } + .ml-info-help:hover { + background: rgba(78,161,255,0.18); + color: #d9ecff; + border-color: rgba(78,161,255,0.3); } /* Images Section */ @@ -3010,6 +3281,7 @@ class LinkerManagerDialog extends ComfyDialog { switchTab(tab) { this.activeTab = tab; + this.animateTabContentTransition(); if (tab === 'missing') { this.missingTab.classList.add('ml-tab-active'); @@ -3743,38 +4015,26 @@ class LinkerManagerDialog extends ComfyDialog { createFooter() { // Store reference to download all button so we can update its text - this.downloadAllButton = $el("button.ml-btn.ml-btn-download", { - onclick: () => this.handleDownloadAllClick(), - style: { - padding: "10px 20px", - fontSize: "13px" - } + this.downloadAllButton = $el("button.ml-btn.ml-btn-download.ml-footer-btn", { + onclick: () => this.handleDownloadAllClick() }, [ $el("span.ml-btn-icon", { textContent: "☁" }), $el("span", { textContent: " Download All Missing" }) ]); // Auto-resolve button (secondary style) - this.autoResolveButton = $el("button.ml-btn.ml-btn-secondary", { - onclick: () => this.autoResolve100Percent(), - style: { - padding: "10px 20px", - fontSize: "13px" - } + this.autoResolveButton = $el("button.ml-btn.ml-btn-secondary.ml-footer-btn", { + onclick: () => this.autoResolve100Percent() }, [ $el("span.ml-btn-icon", { textContent: "🔗" }), $el("span", { textContent: " Auto-Link 100%" }) ]); // Apply pending resolutions button - this.applyPendingBtn = $el("button.ml-btn.ml-btn-primary", { + this.applyPendingBtn = $el("button.ml-btn.ml-btn-primary.ml-footer-btn", { id: "apply-pending-resolutions", textContent: "Apply Selected (0)", - onclick: () => this.applyPendingResolutions(), - style: { - padding: "10px 20px", - fontSize: "13px" - } + onclick: () => this.applyPendingResolutions() }); return $el("div.ml-footer", { @@ -3791,6 +4051,25 @@ class LinkerManagerDialog extends ComfyDialog { this.downloadAllButton ]); } + + animateTabContentTransition() { + if (!this.contentElement?.animate) return; + + try { + this.contentElement.animate( + [ + { opacity: 0.72, transform: 'translateY(7px)' }, + { opacity: 1, transform: 'translateY(0)' } + ], + { + duration: 180, + easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' + } + ); + } catch (error) { + console.warn('Model Linker: tab content animation failed', error); + } + } /** * Handle click on Download All / Cancel All button