Add FL Show Text node and bump to 2.8.0
Displays a connected STRING input on the node via a flex-fill DOM widget with header, length badge, monospace body, scrollbar styling, and wheel-to-scroll capture so wheel events scroll the body instead of zooming the canvas. The same string is passed through to the output socket. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d164833f16
commit
42297ecf02
@@ -196,6 +196,7 @@ from .nodes.utility.FL_RandomRange import FL_RandomNumber
|
||||
from .nodes.utility.FL_RandomShapeGenerator import FL_RandomShapeGenerator
|
||||
from .nodes.utility.FL_SD_Slices import FL_SDUltimate_Slices
|
||||
from .nodes.utility.FL_SeparateMasks import FL_SeparateMaskComponents
|
||||
from .nodes.utility.FL_ShowText import FL_ShowText
|
||||
from .nodes.utility.FL_StringToLoraName import FL_StringToLoraName
|
||||
from .nodes.utility.FL_Switch import FL_Switch
|
||||
from .nodes.utility.FL_Switch_Big import FL_Switch_Big
|
||||
@@ -449,6 +450,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"FL_RandomShapeGenerator": FL_RandomShapeGenerator,
|
||||
"FL_KartelJobInput": FL_KartelJobInput,
|
||||
"FL_KartelJobOutput": FL_KartelJobOutput,
|
||||
"FL_ShowText": FL_ShowText,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -652,6 +654,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FL_RandomShapeGenerator": "FL Random Shape Generator",
|
||||
"FL_KartelJobInput": "FL Kartel Job Input",
|
||||
"FL_KartelJobOutput": "FL Kartel Job Output",
|
||||
"FL_ShowText": "FL Show Text",
|
||||
}
|
||||
|
||||
# Conditionally register google-genai nodes if the SDK loaded successfully
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
class FL_ShowText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "show"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "🏵️Fill Nodes/Utility"
|
||||
|
||||
def show(self, text, unique_id=None):
|
||||
if text is None:
|
||||
text = ""
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
return {"ui": {"text": [text]}, "result": (text,)}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_fill-nodes"
|
||||
description = "Fill-Nodes is a versatile collection of custom nodes for ComfyUI that extends functionality across multiple domains. Features include advanced image processing (pixelation, slicing, masking), visual effects generation (glitch, halftone, pixel art), comprehensive file handling (PDF creation/extraction, Google Drive integration), AI model interfaces (GPT, DALL-E, Hugging Face), utility nodes for workflow enhancement, and specialized tools for video processing, captioning, and batch operations. The pack provides both practical workflow solutions and creative tools within a unified node collection."
|
||||
version = "2.7.9"
|
||||
version = "2.8.0"
|
||||
license = "LICENSE"
|
||||
dependencies = ["librosa", "sounddevice", "glitch_this", "PyOpenGL", "glfw", "scipy>=1.13.1", "requests", "aiohttp", "moviepy", "matplotlib", "reportlab", "openai", "PyPDF2", "pdf2image", "PyMuPDF", "reportlab", "PyPDF2", "ollama", "kornia", "opencv-python", "gdown", "open_clip_torch", "google-genai"]
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* File: FL_ShowText.js
|
||||
* Project: ComfyUI_Fill-Nodes
|
||||
*
|
||||
* Displays the incoming text on the node via a styled DOM widget and
|
||||
* passes the same string through to the output socket.
|
||||
*/
|
||||
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
|
||||
const STYLES = `
|
||||
.flshowtext-host {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.flshowtext-widget {
|
||||
background: #17181c;
|
||||
border: 1px solid #2a2d34;
|
||||
border-radius: 8px;
|
||||
color: #f4f4f5;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
.flshowtext-widget * { box-sizing: border-box; }
|
||||
.flshowtext-header {
|
||||
align-items: center;
|
||||
background: #101114;
|
||||
border-bottom: 1px solid #2a2d34;
|
||||
color: #cbd5e1;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 6px 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.flshowtext-badge {
|
||||
background: #27272a;
|
||||
border-radius: 999px;
|
||||
color: #a1a1aa;
|
||||
font-size: 9px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
padding: 2px 7px;
|
||||
text-transform: none;
|
||||
}
|
||||
.flshowtext-body {
|
||||
color: #e5e7eb;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #3f3f46 #17181c;
|
||||
}
|
||||
.flshowtext-body::-webkit-scrollbar { width: 8px; }
|
||||
.flshowtext-body::-webkit-scrollbar-track { background: #17181c; }
|
||||
.flshowtext-body::-webkit-scrollbar-thumb {
|
||||
background: #3f3f46;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.flshowtext-body::-webkit-scrollbar-thumb:hover { background: #52525b; }
|
||||
.flshowtext-body.empty {
|
||||
color: #71717a;
|
||||
font-style: italic;
|
||||
}
|
||||
`;
|
||||
|
||||
const PLACEHOLDER = "Waiting for input. Run the workflow to display text.";
|
||||
|
||||
function injectStyles() {
|
||||
const id = "flshowtext-styles";
|
||||
if (document.getElementById(id)) return;
|
||||
const style = document.createElement("style");
|
||||
style.id = id;
|
||||
style.textContent = STYLES;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function formatLength(text) {
|
||||
const chars = text.length;
|
||||
const lines = text ? text.split("\n").length : 0;
|
||||
return `${chars} chars · ${lines} line${lines === 1 ? "" : "s"}`;
|
||||
}
|
||||
|
||||
function buildPanel() {
|
||||
const root = document.createElement("div");
|
||||
root.className = "flshowtext-widget";
|
||||
root.innerHTML = `
|
||||
<div class="flshowtext-header">
|
||||
<span>Show Text</span>
|
||||
<span class="flshowtext-badge" data-role="length">empty</span>
|
||||
</div>
|
||||
<div class="flshowtext-body empty" data-role="body"></div>
|
||||
`;
|
||||
const body = root.querySelector('[data-role="body"]');
|
||||
const badge = root.querySelector('[data-role="length"]');
|
||||
body.textContent = PLACEHOLDER;
|
||||
return { root, body, badge };
|
||||
}
|
||||
|
||||
function attachWheelCapture(body) {
|
||||
body.addEventListener(
|
||||
"wheel",
|
||||
(event) => {
|
||||
const canScrollDown =
|
||||
event.deltaY > 0 && body.scrollTop + body.clientHeight < body.scrollHeight - 1;
|
||||
const canScrollUp = event.deltaY < 0 && body.scrollTop > 0;
|
||||
if (canScrollDown || canScrollUp) {
|
||||
body.scrollTop += event.deltaY;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "fl.node.FL_ShowText",
|
||||
nodeCreated(node) {
|
||||
const comfyClass = (node.constructor && node.constructor.comfyClass) || "";
|
||||
if (comfyClass !== "FL_ShowText") return;
|
||||
|
||||
injectStyles();
|
||||
|
||||
const host = document.createElement("div");
|
||||
host.className = "flshowtext-host";
|
||||
|
||||
const panel = buildPanel();
|
||||
host.appendChild(panel.root);
|
||||
|
||||
attachWheelCapture(panel.body);
|
||||
|
||||
node.addDOMWidget("fl_showtext_display", "fl-showtext", host, {
|
||||
getMinHeight: () => 80,
|
||||
hideOnZoom: false,
|
||||
serialize: false,
|
||||
});
|
||||
|
||||
const setText = (text) => {
|
||||
const value = text == null ? "" : String(text);
|
||||
if (value === "") {
|
||||
panel.body.classList.add("empty");
|
||||
panel.body.textContent = PLACEHOLDER;
|
||||
panel.badge.textContent = "empty";
|
||||
} else {
|
||||
panel.body.classList.remove("empty");
|
||||
panel.body.textContent = value;
|
||||
panel.badge.textContent = formatLength(value);
|
||||
}
|
||||
};
|
||||
|
||||
const [oldW, oldH] = node.size;
|
||||
node.setSize([Math.max(oldW, 320), Math.max(oldH, 220)]);
|
||||
|
||||
const originalOnExecuted = node.onExecuted;
|
||||
node.onExecuted = function (message) {
|
||||
originalOnExecuted?.apply(this, arguments);
|
||||
const lines = message?.text;
|
||||
if (Array.isArray(lines) && lines.length > 0) {
|
||||
setText(lines.join("\n"));
|
||||
}
|
||||
};
|
||||
|
||||
const originalOnConfigure = node.onConfigure;
|
||||
node.onConfigure = function () {
|
||||
originalOnConfigure?.apply(this, arguments);
|
||||
setText("");
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user