diff --git a/web/text_gate.js b/web/text_gate.js index b2880cb..e2c27c9 100644 --- a/web/text_gate.js +++ b/web/text_gate.js @@ -6,13 +6,18 @@ import { api } from "../../scripts/api.js"; // blocks in run() on GateBus.wait_payload(); this extension renders the editor // the server pushes via the "datasete-textgate-show" socket event and POSTs the // edited text back. Outputs are static (text, signal) — no dynamic slots. +// +// Sizing follows the Image Pool node: the editor is always present and FILLS the +// node, with only a min-height floor (no max) so the node stays freely resizable +// and the textarea grows with it. const NODE = "TextGate"; const R = "/datasete_text_gate"; -const EDITOR_H = 160; // textarea area height -const BTN_ROW_H = 36; // Pass button row -const MARGIN = 10; // ComfyUI DOM-widget inset, matches the other gate nodes +const MIN_W = 320; // default node width (freely resizable) +const MIN_EDITOR_H = 140; // textarea floor +const BTN_ROW_H = 34; // Pass button row +const MARGIN = 10; // ComfyUI DOM-widget inset, matches the other nodes // ---- server call ------------------------------------------------------------ @@ -23,32 +28,19 @@ async function postPass(node, text) { await api.fetchApi(`${R}/pass`, { method: "POST", body: fd }); } -// ---- preview DOM widget ----------------------------------------------------- +// ---- sizing (Image Pool pattern) -------------------------------------------- -function previewHeight(node) { - return node._tgActive ? 2 * MARGIN + EDITOR_H + BTN_ROW_H : 0; +// Only a min-height FLOOR — no max — so the DOM widget fills the node and grows +// when the user resizes it. (A fixed height, or forcing node height on every +// interaction, would lock the node and leave dead grey space below the editor.) +function widgetFloor() { + return 2 * MARGIN + MIN_EDITOR_H + BTN_ROW_H; } -function resizePreview(node) { - // Fully remove the editor from layout when idle so it never paints below the - // node frame (collapsing height to 0 alone wouldn't clip the textarea). - if (node._tg) node._tg.wrap.style.display = node._tgActive ? "flex" : "none"; - const w = node.size?.[0] || 240; - node.setSize([w, node.computeSize()[1]]); - node.setDirtyCanvas(true, true); -} - -function showEditor(node, text) { - node._tgActive = true; - node._tg.area.value = text || ""; - resizePreview(node); - try { node._tg.area.focus(); } catch (e) { /* ignore */ } -} - -function hideEditor(node) { - node._tgActive = false; - if (node._tg) node._tg.area.value = ""; - resizePreview(node); +// DomWidgets sizes the editor container from the widget width, which can lag +// node.size[0] on this frontend — pin it so the textarea reflows to fill. +function syncWidgetWidth(node) { + if (node._tgWidget) node._tgWidget.width = node.size?.[0] || MIN_W; } // ---- styles + node setup ---------------------------------------------------- @@ -67,6 +59,7 @@ function injectStyles() { border:1px solid #555; color:#fff; } .tgate-pass { background:rgba(40,130,70,0.95); } .tgate-pass:hover { background:rgba(55,160,90,0.98); } + .tgate-status { font-size:11px; opacity:0.6; margin-left:auto; } `; const style = document.createElement("style"); style.id = "tgate-styles"; @@ -91,23 +84,36 @@ function setupTextGateNode(node) { const pass = document.createElement("button"); pass.className = "tgate-pass"; pass.textContent = "▶ Pass"; + const status = document.createElement("span"); + status.className = "tgate-status"; pass.onclick = async () => { await postPass(node, area.value); - hideEditor(node); + status.textContent = "passed"; }; btns.appendChild(pass); + btns.appendChild(status); wrap.appendChild(area); wrap.appendChild(btns); - node._tg = { wrap, area, btns }; + node._tg = { wrap, area, status }; - node._previewWidget = node.addDOMWidget("textgate_editor", "div", wrap, { + // FILLS the node: floor-only min height, no max (Image Pool pattern). + node._tgWidget = node.addDOMWidget("textgate_editor", "div", wrap, { serialize: false, - getMinHeight: () => previewHeight(node), + getMinHeight: () => widgetFloor(), }); - node._tgActive = false; - resizePreview(node); + // keep the editor width synced on manual resize so the textarea reflows + const onResize = node.onResize; + node.onResize = function () { + const r = onResize?.apply(this, arguments); + syncWidgetWidth(node); + return r; + }; + + // sensible default size; the node stays freely resizable (no width floor lock) + node.setSize([Math.max(node.size?.[0] || 0, MIN_W), node.computeSize()[1]]); + syncWidgetWidth(node); } app.registerExtension({ @@ -118,8 +124,11 @@ app.registerExtension({ api.addEventListener("datasete-textgate-show", (e) => { const d = e.detail || {}; const node = app.graph?.getNodeById?.(parseInt(d.id, 10)); - if (!node || node.type !== NODE) return; - showEditor(node, d.text); + if (!node || node.type !== NODE || !node._tg) return; + node._tg.area.value = d.text || ""; + node._tg.status.textContent = "edit, then Pass"; + try { node._tg.area.focus(); } catch (err) { /* ignore */ } + node.setDirtyCanvas?.(true, true); }); },