| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const REPOS = [ |
| { |
| label: "Koolook", |
| categoryRoot: "Koolook", |
| select: "all", |
| excludePatterns: [/\bdeprecated\b/i], |
| }, |
| ]; |
|
|
| |
| |
| |
| export const TAB_ID = "koolook.curatedNodes"; |
| export const TAB_TITLE = "Kforge Labs"; |
| export const TAB_TOOLTIP = "Kforge Labs — curated nodes & workflows"; |
| export const TAB_ICON = "pi pi-star"; |
| export const ROOT_GROUP_LABEL = "Nodes (favorites)"; |
| export const WORKFLOWS_GROUP_LABEL = "Workflows"; |
|
|
| |
| |
| export const STORAGE_KEY = "koolook.curated.userPicks.v1"; |
| export const PICKS_CHANGED_EVENT = "koolook-picks-changed"; |
|
|
| |
| |
| |
| |
| |
| export const AUTOPULL_HIDDEN_KEY = "koolook.autoPullHidden.v1"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| export const SNAPSHOT_STATUS_CHANGED_EVENT = "koolook-snapshot-status-changed"; |
|
|
| |
| |
| |
| |
| export const GROUP_MODE_KEY = "koolook.groupMode.v1"; |
| |
| |
| |
| export const GROUP_MODE_DEFAULT = "category"; |
|
|
| |
| |
| |
| |
| |
| |
| export const STARTER_SEEDED_KEY = "koolook.starter.seeded.v1"; |
| export const STARTER_URL = new URL("../starter_preset.json", import.meta.url).href; |
| |
| |
| export const STARTER_PRESET_FILENAME = "starter"; |
|
|
| export const WORKFLOWS_USERDATA_PATH = "koolook_workflows.json"; |
| export const WORKFLOWS_FALLBACK_KEY = "koolook.workflows.fallback.v1"; |
| export const WORKFLOWS_SEEDED_KEY = "koolook.workflows.seeded.v1"; |
| export const WORKFLOWS_CHANGED_EVENT = "koolook-workflows-changed"; |
| export const WORKFLOWS_DEFAULTS_URL = new URL("../workflow_defaults.json", import.meta.url).href; |
| export const GUIDE_URL = new URL("../guide/index.html", import.meta.url).href; |
| |
| |
| export const GITHUB_REPO_URL = "https://github.com/malkuthro/ComfyUI-Koolook"; |
|
|
| export function noStoreUrl(url) { |
| const sep = url.includes("?") ? "&" : "?"; |
| return `${url}${sep}_=${Date.now()}`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const MODULE_TAG = "module"; |
|
|
| |
| |
| |
| const STYLE_ID = "koolook-sidebar-style"; |
| export function ensureStyle() { |
| if (document.getElementById(STYLE_ID)) return; |
| const s = document.createElement("style"); |
| s.id = STYLE_ID; |
| s.textContent = ` |
| .koolook-sidebar { display: flex; flex-direction: column; height: 100%; font-size: 13px; user-select: none; padding-top: 10px; } |
| .koolook-search-row { margin: 6px; flex-shrink: 0; } |
| .koolook-actions-row { display: flex; align-items: center; gap: 3px; padding: 2px 6px; flex-shrink: 0; } |
| .koolook-actions-label { font-size: 10px; opacity: 0.55; text-transform: uppercase; letter-spacing: 0.08em; flex: 1; font-weight: 600; } |
| /* Snapshot status indicator — replaces the static "Snapshot" label. Shows |
| the currently-tracked preset name + a colored dot indicating whether the |
| live state matches the last named save (green), the latest periodic |
| auto-save (blue), neither (orange = unsaved), or there's no tracked |
| preset at all (grey). Tooltip carries the full breakdown. */ |
| .koolook-snap-status { flex: 1; display: flex; align-items: center; font-size: 11px; min-width: 0; cursor: default; gap: 0; } |
| .koolook-snap-status-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 7px; flex-shrink: 0; box-shadow: 0 0 0 1px rgba(0,0,0,0.35); } |
| .koolook-snap-status-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: 0.9; } |
| .koolook-snap-status-name-empty { font-style: italic; opacity: 0.55; } |
| .koolook-snap-status-state { opacity: 0.55; flex-shrink: 0; margin-left: 4px; font-size: 10px; } |
| .koolook-snap-status-saved { background: #6db86d; } |
| .koolook-snap-status-autosaved { background: #6db4ff; } |
| .koolook-snap-status-unsaved { background: #e0a64a; } |
| /* Drifted (#161) — named-snapshot file diverged from live state at boot. |
| More intense red-orange than "unsaved" because this is a recovery- |
| actionable warning, not just an "edited since last save" indicator. */ |
| .koolook-snap-status-drifted { background: #d97546; box-shadow: 0 0 0 1px rgba(0,0,0,0.45), 0 0 4px rgba(217,117,70,0.55); } |
| .koolook-snap-status-none { background: rgba(255,255,255,0.28); } |
| .koolook-snap-status-comparing { background: #6db4ff; } |
| .koolook-tree-divider { margin: 8px 8px; border-top: 1px solid rgba(255,255,255,0.08); flex-shrink: 0; } |
| .koolook-search-wrap { position: relative; width: 100%; } |
| .koolook-search-icon { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); opacity: 0.55; font-size: 11px; pointer-events: none; } |
| .koolook-search { width: 100%; padding: 5px 8px 5px 26px; box-sizing: border-box; background: var(--comfy-input-bg, rgba(0,0,0,0.25)); border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 4px; color: var(--input-text, inherit); font-size: 12px; outline: none; } |
| .koolook-search:focus { border-color: var(--p-primary-color, rgba(100,150,255,0.5)); } |
| .koolook-add-btn { padding: 0; width: 28px; height: 17px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; background: var(--comfy-input-bg, rgba(0,0,0,0.25)); border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 3px; cursor: pointer; color: rgba(218,221,226,0.78); font-size: 12px; line-height: 1; flex-shrink: 0; } |
| .koolook-add-btn:hover { background: rgba(255,255,255,0.1); color: rgba(248,249,250,0.96); } |
| .koolook-add-btn:active { background: rgba(255,255,255,0.15); } |
| /* Dusty-green accent on the "+" Add-to-favorites button — distinguishes the |
| primary action in the Nodes row from the neutral toolbar buttons it sits |
| alongside. Same dimensions as koolook-icon-btn; only the colors differ. */ |
| .koolook-add-btn-green { background: rgba(120, 165, 100, 0.25); border-color: rgba(120, 165, 100, 0.5); } |
| .koolook-add-btn-green:hover { background: rgba(120, 165, 100, 0.45); } |
| .koolook-add-btn-green:active { background: rgba(120, 165, 100, 0.6); } |
| .koolook-icon-btn { padding: 0; width: 28px; height: 17px; font-size: 12px; } |
| .koolook-icon-btn:disabled { opacity: 0.35; cursor: not-allowed; } |
| .koolook-icon-btn > .pi { font-size: 11px; } |
| /* Toggled-on state for filter buttons (e.g. the "P" published filter). */ |
| .koolook-icon-btn-active { background: rgba(80,140,235,0.35); border-color: rgba(80,140,235,0.6); opacity: 1; } |
| .koolook-letter-icon { font-size: 11px; font-weight: 800; line-height: 1; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } |
| .koolook-inline-svg-icon { width: 11px; height: 11px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } |
| .koolook-filled-square-icon { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: currentColor; opacity: 0.9; } |
| .koolook-stair-icon, .koolook-list-icon { display: inline-grid; grid-template-rows: repeat(3, 1.75px); gap: 2px; width: 15px; height: 10px; align-content: center; } |
| .koolook-stair-icon span, .koolook-list-icon span { display: block; width: 11px; height: 1.75px; background: currentColor; border-radius: 0; } |
| .koolook-stair-icon span:nth-child(2) { margin-left: 2px; } |
| .koolook-stair-icon span:nth-child(3) { margin-left: 4px; } |
| .koolook-add-btn-green > .pi { font-size: 13px; -webkit-text-stroke: 0.5px currentColor; } |
| .koolook-tree { flex: 1; overflow-y: auto; padding: 0 4px 8px; } |
| .koolook-row { display: flex; align-items: center; padding: 3px 6px; cursor: pointer; gap: 6px; border-radius: 3px; line-height: 1.3; } |
| .koolook-row:hover { background: var(--comfy-input-bg, rgba(255,255,255,0.06)); } |
| .koolook-chevron { width: 10px; display: inline-block; opacity: 0.7; text-align: center; font-size: 10px; flex-shrink: 0; } |
| .koolook-folder-icon { opacity: 0.85; flex-shrink: 0; } |
| .koolook-pin-icon { color: #ffb84d; opacity: 0.95; flex-shrink: 0; } |
| .koolook-workflows-icon { color: #6db4ff; opacity: 0.95; flex-shrink: 0; } |
| .koolook-archive-icon { opacity: 0.55; flex-shrink: 0; } |
| .koolook-leaf-icon { opacity: 0.7; flex-shrink: 0; font-size: 11px; } |
| .koolook-module-icon { color: #7be08a; opacity: 0.95; flex-shrink: 0; font-size: 11px; } |
| .koolook-leaf-dot { width: 6px; height: 6px; margin: 0 2px; border-radius: 50%; background: rgba(255,255,255,0.45); flex-shrink: 0; } |
| .koolook-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-name-stack { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.15; } |
| .koolook-name-stack .koolook-name { flex: none; width: 100%; } |
| .koolook-row-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; opacity: 0.55; margin-top: 1px; } |
| .koolook-count { background: rgba(255,255,255,0.08); padding: 1px 7px; border-radius: 8px; font-size: 11px; opacity: 0.75; } |
| .koolook-children { padding-left: 14px; } |
| .koolook-empty { padding: 14px 8px; opacity: 0.65; font-size: 12px; line-height: 1.4; } |
| .koolook-leaf .koolook-chevron { visibility: hidden; } |
| .koolook-remove { padding: 0 4px; margin-left: 2px; opacity: 0; font-size: 14px; line-height: 1; cursor: pointer; flex-shrink: 0; } |
| .koolook-row:hover .koolook-remove { opacity: 0.5; } |
| .koolook-remove:hover { opacity: 1 !important; color: #ff7777; } |
| .koolook-section-divider { border-top: 1px solid var(--border-color, rgba(255,255,255,0.08)); margin: 8px 4px 0; } |
| .koolook-toast { position: fixed; bottom: 30px; right: 30px; background: rgba(40,40,40,0.95); color: #fff; padding: 8px 14px; border-radius: 4px; font-size: 12px; z-index: 9999; transition: opacity 0.3s; box-shadow: 0 2px 8px rgba(0,0,0,0.4); pointer-events: none; max-width: 360px; } |
| .koolook-toast-critical { pointer-events: auto; background: rgba(180, 60, 60, 0.95); max-width: 440px; padding: 10px 14px; } |
| .koolook-toast-critical-stack { bottom: auto; top: 30px; } |
| .koolook-toast-msg { margin-bottom: 8px; line-height: 1.4; font-size: 12px; word-break: break-word; } |
| .koolook-toast-actions { display: flex; gap: 8px; justify-content: flex-end; } |
| .koolook-toast-btn { padding: 4px 10px; background: rgba(0,0,0,0.25); border: 1px solid rgba(255,255,255,0.25); border-radius: 3px; color: #fff; cursor: pointer; font-size: 11px; font-family: inherit; } |
| .koolook-toast-btn:hover { background: rgba(0,0,0,0.4); } |
| .koolook-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 9998; display: flex; align-items: center; justify-content: center; } |
| .koolook-modal { background: var(--comfy-menu-bg, #151515); border: 1px solid var(--border-color, #4f4f54); border-radius: 8px; padding: 16px 18px; min-width: 320px; max-width: 520px; box-shadow: 0 10px 40px rgba(0,0,0,0.6); color: var(--input-text, #f9fafb); overflow: hidden; } |
| .koolook-modal-title { font-size: 15px; font-weight: 700; margin: -16px -18px 14px; padding: 14px 18px; border-bottom: 1px solid var(--border-color, #302f2f); background: var(--comfy-menu-bg, #181818); } |
| .koolook-modal-pathline { font-size: 11px; opacity: 0.55; margin: -6px 0 12px; white-space: normal; overflow-wrap: anywhere; cursor: default; } |
| .koolook-modal-message { font-size: 12px; opacity: 0.85; margin-bottom: 14px; line-height: 1.45; } |
| .koolook-confirm-list { margin: 0; padding-left: 18px; display: grid; gap: 6px; } |
| .koolook-confirm-list li { padding-left: 2px; } |
| .koolook-modal-label { font-size: 11px; opacity: 0.7; margin: 6px 0 4px; display: block; text-transform: uppercase; letter-spacing: 0.04em; } |
| .koolook-modal-input, .koolook-modal-select, .koolook-modal-textarea { width: 100%; padding: 6px 8px; background: var(--comfy-input-bg, #111111); border: 1px solid var(--border-color, #302f2f); border-radius: 4px; color: var(--input-text, #e6e8ec); font-size: 13px; box-sizing: border-box; outline: none; } |
| .koolook-modal-input:focus, .koolook-modal-select:focus, .koolook-modal-textarea:focus { border-color: var(--p-primary-color, rgba(100,150,255,0.5)); } |
| .koolook-modal-textarea { min-height: 84px; resize: vertical; font: 12px/1.35 ui-monospace, "Cascadia Mono", Menlo, monospace; } |
| .koolook-publish-modal { width: min(620px, calc(100vw - 40px)); max-height: calc(100vh - 48px); overflow: auto; } |
| .koolook-publish-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 10px; } |
| .koolook-publish-wide { grid-column: 1 / -1; } |
| .koolook-publish-surface { margin-top: 8px; padding: 8px 10px; border: 1px solid var(--border-color, #302f2f); border-radius: 6px; background: rgba(255,255,255,0.035); font-size: 12px; line-height: 1.45; } |
| .koolook-publish-surface-title { font-size: 11px; opacity: 0.75; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; } |
| .koolook-publish-advanced { margin-top: 8px; font-size: 12px; } |
| .koolook-publish-advanced summary { cursor: pointer; opacity: 0.82; } |
| .koolook-publish-message { min-height: 16px; margin-top: 8px; color: #ffb4a8; font-size: 12px; line-height: 1.35; } |
| /* Library-folder name + path pair used by the redesigned Save and Load |
| dialogs to render the inline 'Saved to' / 'Loaded from' info row. |
| Naming kept from the (deleted) Settings dialog for git-blame continuity; |
| the two classes have always rendered the same shape (leaf folder name |
| on the first line, full absolute path on the second). */ |
| .koolook-settings-folder-name { opacity: 1; font-size: 13px; font-weight: 700; color: var(--input-text, #e6e8ec); margin-top: 2px; } |
| .koolook-settings-folder-path { color: var(--input-text, #8f959c); opacity: 0.68; font: 11px/1.35 ui-monospace, "Cascadia Mono", Menlo, monospace; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin: 16px -18px -16px; padding: 12px 18px; border-top: 1px solid var(--border-color, #302f2f); background: var(--comfy-menu-bg, #131313); } |
| .koolook-modal-checkbox-row { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 12px; opacity: 0.85; cursor: pointer; user-select: none; } |
| .koolook-modal-checkbox-row input[type="checkbox"] { cursor: pointer; } |
| .koolook-modal-btn { padding: 6px 14px; background: var(--comfy-input-bg, #1a1a1f); border: 1px solid var(--border-color, #4f4f54); border-radius: 4px; color: var(--input-text, #e6e8ec); cursor: pointer; font: inherit; font-size: 12px; font-weight: 600; } |
| .koolook-modal-btn:hover { background: rgba(255,255,255,0.1); } |
| .koolook-modal-btn-primary { background: rgba(80,140,235,0.35); border-color: rgba(80,140,235,0.55); } |
| .koolook-modal-btn-primary:hover { background: rgba(80,140,235,0.5); } |
| .koolook-modal-btn-danger { background: rgba(220,80,80,0.25); border-color: rgba(220,80,80,0.5); } |
| .koolook-modal-btn-danger:hover { background: rgba(220,80,80,0.4); } |
| .koolook-modal-btn:disabled { opacity: 0.45; cursor: default; background: var(--comfy-input-bg, rgba(0,0,0,0.3)); border-color: rgba(255,255,255,0.1); } |
| .koolook-modal-btn:disabled:hover { background: var(--comfy-input-bg, rgba(0,0,0,0.3)); } |
| .koolook-context-menu { position: fixed; background: var(--comfy-menu-bg, #2a2a2a); border: 1px solid var(--border-color, rgba(255,255,255,0.15)); border-radius: 4px; padding: 4px 0; min-width: 160px; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.4); font-size: 12px; } |
| .koolook-context-item { padding: 5px 12px; cursor: pointer; } |
| .koolook-context-item:hover { background: rgba(255,255,255,0.08); } |
| .koolook-context-danger { color: #ff8888; } |
| .koolook-context-sep { height: 1px; background: rgba(255,255,255,0.1); margin: 4px 0; } |
| .koolook-context-submenu-arrow { float: right; opacity: 0.5; } |
| .koolook-row[draggable="true"] { cursor: grab; } |
| .koolook-row[draggable="true"]:active { cursor: grabbing; } |
| .koolook-drop-target { background: rgba(80,140,235,0.18) !important; outline: 2px solid rgba(80,140,235,0.6); outline-offset: -2px; } |
| .koolook-tags-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 4px; min-height: 22px; align-items: center; } |
| .koolook-tags-empty { opacity: 0.55; font-size: 12px; } |
| .koolook-tag-chip { display: inline-flex; align-items: center; gap: 4px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; padding: 2px 4px 2px 9px; font-size: 11px; line-height: 1.4; } |
| .koolook-tag-chip-x { cursor: pointer; opacity: 0.6; padding: 0 4px; font-size: 13px; line-height: 1; border-radius: 50%; } |
| .koolook-tag-chip-x:hover { opacity: 1; color: #ff7777; background: rgba(255,255,255,0.06); } |
| .koolook-tag-add-row { display: flex; gap: 6px; align-items: center; } |
| .koolook-tag-add-row .koolook-modal-input { flex: 1; } |
| .koolook-install-stat-line { font-size: 12px; line-height: 1.5; padding: 1px 0; } |
| .koolook-install-stat-line.koolook-install-stat-fail { color: #ff8888; } |
| .koolook-install-unresolved { font-size: 11px; opacity: 0.75; margin-top: 4px; padding: 6px 8px; background: rgba(255,255,255,0.04); border-radius: 4px; word-break: break-all; max-height: 110px; overflow-y: auto; } |
| .koolook-install-unresolved-summary { cursor: pointer; font-size: 11px; opacity: 0.75; margin-top: 4px; } |
| .koolook-install-progress { width: 100%; height: 6px; background: rgba(255,255,255,0.08); border-radius: 3px; margin: 10px 0 4px; overflow: hidden; } |
| .koolook-install-progress-bar { height: 100%; background: rgba(80,140,235,0.6); width: 0%; transition: width 200ms ease; } |
| .koolook-snapshot-list { max-height: 280px; overflow-y: auto; border: 1px solid var(--border-color, #302f2f); border-radius: 4px; background: var(--comfy-input-bg, #111111); overflow: hidden auto; } |
| /* Recovery auto-saves section in the Load dialog. The collapsed summary is |
| always visible at the bottom of the body; clicking a preset with a newer |
| autosave opens one scoped group inside this container. */ |
| .koolook-recovery-section { margin-top: 14px; border: 1px solid var(--border-color, #302f2f); border-radius: 4px; background: rgba(255,255,255,0.02); background: color-mix(in srgb, var(--comfy-menu-bg, #151515) 92%, var(--comfy-input-bg, #111111)); overflow: hidden; } |
| .koolook-recovery-summary { cursor: pointer; font-size: 12px; color: var(--input-text, #d8dadd); padding: 10px 12px; user-select: none; outline: none; } |
| .koolook-recovery-summary-passive { cursor: default; } |
| .koolook-recovery-summary:hover { opacity: 1; } |
| /* No outer border on the recovery list — each group carries its own |
| bordered rows-list, mirroring the library section's structure. We |
| keep the scroll container so a library with many autosave subdirs |
| does not blow out the modal. |
| IMPORTANT: this whole CSS string lives inside a JS template literal |
| (backtick-delimited), so do NOT use literal backticks in comments |
| here — they would prematurely close the template literal in ES |
| module mode and break the entire constants.js module load (and with |
| it the whole sidebar). Use plain quotes or no quotes at all. */ |
| .koolook-recovery-list { margin-top: 0; max-height: 240px; overflow-y: auto; border-top: 1px solid rgba(255,255,255,0.04); } |
| .koolook-recovery-group { padding: 10px 12px; } |
| .koolook-recovery-group-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 6px; } |
| .koolook-recovery-group-title { color: var(--input-text, #e6e8ec); font-size: 12px; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-recovery-group-path { color: var(--input-text, #8f959c); opacity: 0.68; font: 11px/1.35 ui-monospace, "Cascadia Mono", Menlo, monospace; margin-bottom: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-recovery-group + .koolook-recovery-group { margin-top: 10px; } |
| .koolook-recovery-kind { display: inline-flex; align-items: center; width: fit-content; font-size: 10px; line-height: 1.2; text-transform: uppercase; letter-spacing: 0.06em; padding: 2px 6px; border-radius: 3px; font-weight: 700; margin-bottom: 6px; } |
| .koolook-recovery-kind-pre_load { background: rgba(255,184,77,0.16); color: #f5d3a0; border: 1px solid rgba(255,184,77,0.35); } |
| .koolook-recovery-kind-periodic { background: rgba(109,180,255,0.16); color: #b3d4f5; border: 1px solid rgba(109,180,255,0.35); } |
| .koolook-recovery-row { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid var(--border-color, #302f2f); border-radius: 3px; background: var(--comfy-input-bg, #111111); } |
| .koolook-recovery-row-selected { background: rgba(80,140,235,0.16); background: color-mix(in srgb, var(--comfy-input-bg, #111111) 88%, var(--p-primary-color, #6db4ff)); } |
| .koolook-recovery-row-info { flex: 1; min-width: 0; cursor: pointer; font-size: 12px; } |
| .koolook-recovery-row-info:hover .koolook-recovery-row-meta { color: var(--p-primary-color, rgba(120,170,255,1)); } |
| .koolook-recovery-row-meta { color: var(--input-text, #8f959c); opacity: 0.68; font-size: 11px; line-height: 1.35; } |
| .koolook-snapshot-row { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border-color, #302f2f); background: var(--comfy-input-bg, #111111); } |
| .koolook-snapshot-row-selected { background: rgba(80,140,235,0.16); background: color-mix(in srgb, var(--comfy-input-bg, #111111) 88%, var(--p-primary-color, #6db4ff)); } |
| .koolook-snapshot-row:last-child { border-bottom: none; } |
| .koolook-snapshot-row-info { flex: 1; min-width: 0; cursor: pointer; } |
| .koolook-snapshot-row-info:hover .koolook-snapshot-row-name { color: var(--p-primary-color, rgba(120,170,255,1)); } |
| .koolook-snapshot-row-name { color: var(--input-text, #e6e8ec); font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-snapshot-row-meta { color: var(--input-text, #8f959c); opacity: 0.68; font-size: 11px; margin-top: 2px; } |
| .koolook-snapshot-row-actions { display: flex; gap: 4px; flex-shrink: 0; } |
| .koolook-snapshot-row-btn { min-width: 26px; height: 24px; padding: 0 8px; background: var(--comfy-input-bg, #1a1a1f); border: 1px solid var(--border-color, #4f4f54); border-radius: 4px; cursor: pointer; color: var(--input-text, #e6e8ec); font-size: 16px; font-weight: 800; line-height: 1; } |
| .koolook-snapshot-row-btn:hover { background: rgba(255,255,255,0.08); } |
| .koolook-snapshot-row-btn-danger:hover { background: rgba(74,5,5,0.45); border-color: rgba(255,138,138,0.45); color: #ffd9d9; } |
| .koolook-snapshot-empty { padding: 20px; opacity: 0.5; font-size: 12px; text-align: center; } |
| .koolook-mode-toggle { display: inline-flex; gap: 0; margin-right: 3px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 3px; overflow: hidden; } |
| .koolook-mode-toggle-btn { padding: 0; width: 28px; height: 17px; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; background: transparent; border: none; cursor: pointer; color: rgba(218,221,226,0.78); font-size: 11px; opacity: 1; line-height: 1; } |
| .koolook-mode-toggle-btn:hover { background: rgba(255,255,255,0.06); color: rgba(248,249,250,0.96); } |
| .koolook-mode-toggle-btn.koolook-mode-active { background: rgba(80,140,235,0.25); color: rgba(248,249,250,0.96); } |
| .koolook-leaf-unresolved { opacity: 0.55; font-style: italic; } |
| .koolook-leaf-crumb { opacity: 0.5; font-size: 11px; margin-right: 1px; } |
| .koolook-pack-badge { opacity: 0.5; font-size: 11px; margin-left: 6px; flex-shrink: 0; white-space: nowrap; } |
| /* "published" marker on a workflow row that has been published as a setup. */ |
| .koolook-published-badge { margin-left: 6px; padding: 0 6px; border-radius: 8px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #b9d4ff; background: rgba(80,140,235,0.22); flex-shrink: 0; white-space: nowrap; } |
| .koolook-preview-card { position: fixed; z-index: 10000; background: #353535; border: 1px solid rgba(190,190,190,0.38); border-radius: 12px; box-shadow: 0 6px 24px rgba(0,0,0,0.55); color: var(--input-text, #ddd); font-size: 12px; width: 300px; max-width: calc(100vw - 16px); box-sizing: border-box; max-height: calc(100vh - 16px); pointer-events: none; overflow: hidden auto; padding-bottom: 10px; } |
| .koolook-preview-header { display: flex; align-items: center; gap: 9px; padding: 8px 13px 7px; font-size: 14px; line-height: 1; white-space: nowrap; overflow: hidden; background: rgba(255,255,255,0.045); border-bottom: 1px solid rgba(255,255,255,0.07); } |
| .koolook-preview-headtitle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; } |
| .koolook-preview-header-unresolved .koolook-preview-headtitle { font-style: italic; opacity: 0.75; } |
| .koolook-preview-headdot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 0 1px rgba(0,0,0,0.4); } |
| .koolook-preview-badge { padding: 0 13px 7px; color: #ff5f6d; font-size: 11px; letter-spacing: 0.08em; font-weight: 600; text-transform: uppercase; } |
| .koolook-preview-stub { margin: 4px 10px 6px; padding: 8px 10px; background: rgba(0,0,0,0.18); border-radius: 6px; font-style: italic; opacity: 0.78; line-height: 1.4; } |
| .koolook-preview-row { display: grid; grid-template-columns: 14px 1fr 14px 1fr 14px; column-gap: 8px; align-items: center; padding: 0 9px; line-height: 1.55; min-height: 18px; } |
| .koolook-preview-row-slot { padding: 2px 9px; } |
| .koolook-preview-col { overflow: hidden; min-width: 0; } |
| .koolook-preview-col-input { text-align: left; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-preview-col-output { text-align: right; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-preview-col-middle { /* spacer, intentionally empty */ } |
| .koolook-preview-slot-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; box-shadow: 0 0 0 1px rgba(0,0,0,0.4); vertical-align: middle; } |
| .koolook-preview-slot-optional { opacity: 0.7; font-style: italic; } |
| .koolook-preview-row-widget { background: rgba(0,0,0,0.24); border: 1px solid rgba(255,255,255,0.14); margin: 5px 6px 0; border-radius: 9px; padding: 0 6px; line-height: 1.65; } |
| .koolook-preview-arrow { color: rgba(255,255,255,0.6); font-size: 9px; text-align: center; user-select: none; } |
| .koolook-preview-widget-name { text-align: left; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-preview-widget-value { text-align: right; font-size: 11px; opacity: 0.85; font-family: monospace; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-preview-desc { margin: 10px 9px 0; padding: 7px 9px; background: rgba(0,0,0,0.18); border: 1px solid rgba(255,255,255,0.05); border-radius: 6px; font-style: italic; font-weight: 500; font-size: 11px; line-height: 1.45; word-break: break-word; opacity: 0.86; max-height: 120px; overflow: hidden; } |
| .koolook-update-footer { flex-shrink: 0; display: none; justify-content: center; gap: 6px; padding: 4px 10px 12px; color: var(--input-text, inherit); opacity: 0.62; font-size: 11px; line-height: 1.45; text-align: center; } |
| .koolook-update-footer-visible { display: flex; } |
| .koolook-update-footer a { color: #b9d8ff; font-weight: 700; text-decoration: none; opacity: 1; } |
| .koolook-update-footer a:hover { color: #ffffff; } |
| .koolook-star-footer { flex-shrink: 0; display: flex; justify-content: center; align-items: baseline; gap: 5px; padding: 0 10px 12px; color: var(--input-text, inherit); opacity: 0.55; font-size: 11px; line-height: 1.45; text-align: center; } |
| .koolook-star-footer a { color: #b9d8ff; font-weight: 700; text-decoration: none; opacity: 1; } |
| .koolook-star-footer a:hover { color: #ffffff; } |
| .koolook-star-footer .koolook-star-glyph { color: #ffd479; } |
| .koolook-build-tag { flex-shrink: 0; padding: 0 10px 12px; font-size: 10px; opacity: 0.5; text-align: center; letter-spacing: 0.04em; font-family: monospace; color: var(--input-text, inherit); line-height: 1.45; } |
| .koolook-build-sha { font-size: 13px; letter-spacing: 0.06em; } |
| .koolook-build-scope { display: block; font-size: 12px; margin-top: 3px; letter-spacing: 0.02em; font-family: var(--font-family, sans-serif); font-style: italic; } |
| /* Folder-browse picker (issue #137, mockup section 6). Navigate-into |
| model: path input shows current location, clicking a folder row |
| drills in, the Up button climbs one level. Files appear greyed so |
| the user can confirm 'yes, this is the folder I expected' before |
| committing. |
| The picker reuses the modal shell (overlay, title, action row); |
| only the body chrome (toolbar / list / row styling) is folder- |
| picker-specific. Reminder per the recovery-list comment above: |
| this whole CSS string is a JS template literal, so do NOT use |
| backticks in comments here — they prematurely close the template |
| literal and break the entire sidebar at load. */ |
| .koolook-folder-picker { min-width: 480px; max-width: 600px; } |
| .koolook-folder-picker-toolbar { display: flex; gap: 8px; align-items: center; margin: 0 0 10px; } |
| .koolook-folder-picker-up { flex-shrink: 0; } |
| /* End-visible path overflow: 'direction: rtl' makes the input's |
| overflow-axis right-to-left, 'unicode-bidi: plaintext' keeps the |
| Latin path characters in natural left-to-right order. Net effect: |
| long paths clip the LEFT (leading /Users/...) and always show the |
| trailing folder name on the right. Per mockup section 6. */ |
| .koolook-folder-picker-path { flex: 1; min-width: 0; direction: rtl; text-align: left; unicode-bidi: plaintext; font-family: ui-monospace, "Cascadia Mono", Menlo, monospace; font-size: 12px; } |
| .koolook-folder-picker-list { max-height: 320px; min-height: 120px; overflow-y: auto; border: 1px solid var(--border-color, #302f2f); border-radius: 4px; background: var(--comfy-input-bg, #111111); overflow: hidden auto; } |
| .koolook-folder-picker-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 10px 12px; border: 0; border-bottom: 1px solid var(--border-color, #302f2f); background: var(--comfy-input-bg, #111111); color: var(--input-text, #e6e8ec); cursor: pointer; font: inherit; font-size: 12px; } |
| .koolook-folder-picker-row:last-child { border-bottom: none; } |
| .koolook-folder-picker-row:hover { background: rgba(80,140,235,0.16); background: color-mix(in srgb, var(--comfy-input-bg, #111111) 88%, var(--p-primary-color, #6db4ff)); } |
| .koolook-folder-picker-row-file { cursor: default; color: var(--input-text, #8f959c); opacity: 0.68; } |
| .koolook-folder-picker-row-file:hover { background: var(--comfy-input-bg, #111111); } |
| .koolook-folder-picker-icon { font-size: 13px; line-height: 1; flex-shrink: 0; } |
| .koolook-folder-picker-row:not(.koolook-folder-picker-row-file) .koolook-folder-picker-icon { filter: hue-rotate(-30deg) saturate(1.4); } |
| .koolook-folder-picker-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| .koolook-folder-picker-empty { padding: 20px; color: var(--input-text, #8f959c); opacity: 0.68; font-size: 12px; font-style: italic; text-align: center; } |
| .koolook-folder-picker-error { color: #ffae9a; font-style: normal; opacity: 0.9; } |
| .koolook-folder-picker-spacer { flex: 1; } |
| .koolook-folder-picker-newfolder-label { color: var(--input-text, #8f959c); opacity: 0.68; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; flex-shrink: 0; } |
| .koolook-folder-picker-newfolder-input { flex: 1; min-width: 0; } |
| /* Save dialog redesign (issue #137, mockup section 2). The library row |
| at the top is purely informational — label + Open folder link sit on |
| the top edge so a long absolute path can't overlap the link. Every |
| action lives in the bottom command bar (Save to... | Cancel | Save |
| as new... | Save). */ |
| .koolook-snap-lib-row { padding: 10px 12px; border: 1px solid var(--border-color, #302f2f); border-radius: 4px; background: var(--comfy-input-bg, #181818); margin-bottom: 14px; } |
| .koolook-snap-lib-row-info { min-width: 0; } |
| .koolook-snap-lib-row-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 2px; } |
| .koolook-snap-lib-label { color: var(--input-text, #8f959c); opacity: 0.68; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; } |
| .koolook-snap-open-folder-link { font-size: 11px; opacity: 1; color: #6db4ff; text-decoration: none; white-space: nowrap; } |
| .koolook-snap-open-folder-link:hover { opacity: 1; text-decoration: underline; } |
| .koolook-snap-save-message { margin: 6px 0 0; } |
| /* Four-state primary button (mockup section 5). The in-progress and |
| done states sit on different button classes so the colour shift |
| reads at a glance: default+dirty are primary blue (action), in- |
| progress is dimmed primary (busy), done is subtle grey (confirmed, |
| no further action expected). Disabled state is enforced via the |
| 'disabled' attribute, not just the class — keyboard activation |
| short-circuits naturally. */ |
| .koolook-snap-save-in-progress { opacity: 0.7; cursor: progress; } |
| .koolook-snap-save-done { background: rgba(120, 200, 120, 0.18); border-color: rgba(120, 200, 120, 0.5); color: #b3e3b3; opacity: 0.95; cursor: default; } |
| .koolook-delete-confirm-text { align-self: center; color: #ffb1b1; font-size: 12px; font-weight: 600; } |
| /* Inline delete state outlines the target row in red. Reminder: no |
| backticks in comments inside this CSS template literal. */ |
| .koolook-snapshot-row-pending-delete { outline: 1.5px solid rgba(220, 80, 80, 0.75); outline-offset: -1px; background: rgba(220, 80, 80, 0.06); } |
| /* Compare mode (issue 181): two live sidebars side by side, plus a bottom |
| status bar. The columns reuse the normal koolook-sidebar render verbatim. |
| Reminder: no backticks in comments inside this CSS template literal. */ |
| .koolook-compare-host { display: flex; flex-direction: column; height: 100%; min-height: 0; } |
| .koolook-compare-split { display: flex; flex: 1 1 auto; gap: 8px; min-height: 0; overflow: auto; padding: 0 4px; } |
| /* Each column stacks the rendered panel over a SOURCE/TARGET footer stripe. */ |
| .koolook-compare-col { display: flex; flex-direction: column; flex: 1 1 0; min-width: 0; min-height: 0; } |
| .koolook-compare-panelhost { flex: 1 1 auto; min-height: 0; overflow: auto; } |
| /* Stripe 1 — SOURCE/TARGET orientation footer (one line per column). */ |
| .koolook-compare-colfoot { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; padding: 3px 6px; border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.1)); overflow: hidden; } |
| .koolook-foot-role { font-size: 9px; font-weight: 700; letter-spacing: 0.08em; padding: 1px 5px; border-radius: 3px; flex: 0 0 auto; } |
| .koolook-foot-target .koolook-foot-role { background: rgba(123, 224, 138, 0.28); color: #bff0c7; } |
| .koolook-foot-source .koolook-foot-role { background: rgba(120, 150, 200, 0.22); color: rgba(205, 218, 240, 0.95); } |
| .koolook-foot-name { font-size: 10px; font-weight: 600; opacity: 0.8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| /* Stripe 2 — the dedicated SAVE stripe (full width, separate from the footers): |
| save state + instructions + a Save button when the snapshot has edits. */ |
| .koolook-compare-savebar { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: 10px; padding: 4px 10px; font-size: 10px; border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.1)); background: rgba(255, 255, 255, 0.03); } |
| .koolook-savebar-msg { opacity: 0.72; text-align: center; overflow: hidden; text-overflow: ellipsis; } |
| .koolook-savebar-unsaved { color: #e0a64a; opacity: 1; font-weight: 600; } |
| .koolook-savebar-btn { flex: 0 0 auto; font: inherit; font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; cursor: pointer; padding: 2px 10px; border-radius: 4px; color: inherit; background: rgba(224, 166, 74, 0.3); border: 1px solid rgba(224, 166, 74, 0.6); } |
| .koolook-savebar-btn:hover { background: rgba(224, 166, 74, 0.45); } |
| /* Active (editable TARGET) column — a quiet green outline mirrors the green |
| new-item tint so the eye reads "this side is live". */ |
| .koolook-compare-active { outline: 1px solid rgba(123, 224, 138, 0.5); outline-offset: -1px; border-radius: 4px; } |
| .koolook-compare-status { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 5px 10px; border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.1)); background: rgba(80, 140, 235, 0.14); } |
| /* Swap (A<->B) toggle — flips which column is the editable destination. */ |
| .koolook-compare-swap { font: inherit; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer; padding: 2px 8px; border-radius: 4px; color: inherit; background: rgba(80, 140, 235, 0.28); border: 1px solid var(--border-color, rgba(255, 255, 255, 0.18)); } |
| .koolook-compare-swap:hover { background: rgba(80, 140, 235, 0.45); } |
| /* Legend chips that double as diff filters (green = new, red = modified). */ |
| .koolook-cmp-chip { font: inherit; font-size: 10px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 10px; color: inherit; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border-color, rgba(255, 255, 255, 0.15)); } |
| .koolook-cmp-chip:hover { background: rgba(255, 255, 255, 0.1); } |
| .koolook-cmp-chip-on { background: rgba(80, 140, 235, 0.32); border-color: rgba(120, 170, 250, 0.6); } |
| .koolook-cmp-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: 0 0 auto; } |
| .koolook-cmp-dot-new { background: #7be08a; } |
| .koolook-cmp-dot-diff { background: #ff7d7d; } |
| /* Text-only diff tint, applied only to the comparison column's rows. */ |
| .koolook-cmp-new .koolook-name { color: #7be08a; } |
| .koolook-cmp-diff .koolook-name { color: #ff7d7d; } |
| `; |
| document.head.appendChild(s); |
| } |
|
|
| |
| |
| |
| export function toast(msg, duration = 2200) { |
| const t = document.createElement("div"); |
| t.className = "koolook-toast"; |
| t.textContent = msg; |
| document.body.appendChild(t); |
| setTimeout(() => { |
| t.style.opacity = "0"; |
| setTimeout(() => t.remove(), 300); |
| }, duration); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function criticalToast(msg, { copyText, onDismiss, actions: extraActions } = {}) { |
| const t = document.createElement("div"); |
| t.className = "koolook-toast koolook-toast-critical"; |
| |
| |
| |
| const existing = document.querySelectorAll(".koolook-toast-critical").length; |
| if (existing > 0) { |
| t.classList.add("koolook-toast-critical-stack"); |
| t.style.top = `${30 + existing * 90}px`; |
| } |
|
|
| const msgEl = document.createElement("div"); |
| msgEl.className = "koolook-toast-msg"; |
| msgEl.textContent = msg; |
| t.appendChild(msgEl); |
|
|
| const actionRow = document.createElement("div"); |
| actionRow.className = "koolook-toast-actions"; |
|
|
| const close = () => { |
| if (t.parentNode) t.remove(); |
| if (typeof onDismiss === "function") onDismiss(); |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| if (Array.isArray(extraActions)) { |
| for (const action of extraActions) { |
| const btn = document.createElement("button"); |
| btn.className = "koolook-toast-btn"; |
| if (action.primary) btn.classList.add("koolook-toast-btn-primary"); |
| const originalLabel = action.label; |
| btn.textContent = originalLabel; |
| btn.addEventListener("click", async () => { |
| if (btn.disabled) return; |
| btn.disabled = true; |
| if (action.busyLabel) btn.textContent = action.busyLabel; |
| try { |
| const result = await Promise.resolve(action.onClick()); |
| if (result && result.dismiss === false) { |
| btn.disabled = false; |
| btn.textContent = originalLabel; |
| } else { |
| close(); |
| } |
| } catch (e) { |
| console.error("[Koolook] critical-toast action failed:", e); |
| btn.disabled = false; |
| btn.textContent = originalLabel; |
| } |
| }); |
| actionRow.appendChild(btn); |
| } |
| } |
|
|
| if (copyText) { |
| const copyBtn = document.createElement("button"); |
| copyBtn.className = "koolook-toast-btn"; |
| copyBtn.textContent = "Copy details"; |
| copyBtn.addEventListener("click", () => { |
| const finishCopy = () => { |
| copyBtn.textContent = "Copied ✓"; |
| setTimeout(() => { copyBtn.textContent = "Copy details"; }, 1500); |
| }; |
| |
| |
| |
| |
| if (navigator.clipboard && navigator.clipboard.writeText) { |
| navigator.clipboard.writeText(copyText).then(finishCopy).catch(() => { |
| legacyCopy(copyText); |
| finishCopy(); |
| }); |
| } else { |
| legacyCopy(copyText); |
| finishCopy(); |
| } |
| }); |
| actionRow.appendChild(copyBtn); |
| } |
|
|
| const dismissBtn = document.createElement("button"); |
| dismissBtn.className = "koolook-toast-btn"; |
| dismissBtn.textContent = "Dismiss"; |
| dismissBtn.addEventListener("click", close); |
| actionRow.appendChild(dismissBtn); |
|
|
| t.appendChild(actionRow); |
| document.body.appendChild(t); |
| return { dismiss: close }; |
| } |
|
|
| function legacyCopy(text) { |
| const ta = document.createElement("textarea"); |
| ta.value = text; |
| ta.style.cssText = "position:fixed;left:-9999px;top:-9999px;opacity:0;"; |
| document.body.appendChild(ta); |
| ta.focus(); |
| ta.select(); |
| try { document.execCommand("copy"); } catch (e) { } |
| ta.remove(); |
| } |
|
|
| |
| |
| |
| |
| |
| export function compareNames(a, b) { |
| return String(a).localeCompare(String(b), undefined, { sensitivity: "base" }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function formatLibraryPathBreadcrumb(fullPath, maxChars = 50) { |
| if (typeof fullPath !== "string" || !fullPath) return ""; |
| if (fullPath.length <= maxChars) return fullPath; |
| |
| const tail = fullPath.slice(-(maxChars - 1)); |
| |
| |
| |
| const snapAt = tail.search(/[\\/]/); |
| const cleanTail = snapAt >= 0 ? tail.slice(snapAt) : tail; |
| return "…" + cleanTail; |
| } |
|
|