fix: text gate editor fills node + freely resizable (Image Pool sizing)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+43
-34
@@ -6,13 +6,18 @@ import { api } from "../../scripts/api.js";
|
|||||||
// blocks in run() on GateBus.wait_payload(); this extension renders the editor
|
// 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
|
// the server pushes via the "datasete-textgate-show" socket event and POSTs the
|
||||||
// edited text back. Outputs are static (text, signal) — no dynamic slots.
|
// 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 NODE = "TextGate";
|
||||||
const R = "/datasete_text_gate";
|
const R = "/datasete_text_gate";
|
||||||
|
|
||||||
const EDITOR_H = 160; // textarea area height
|
const MIN_W = 320; // default node width (freely resizable)
|
||||||
const BTN_ROW_H = 36; // Pass button row
|
const MIN_EDITOR_H = 140; // textarea floor
|
||||||
const MARGIN = 10; // ComfyUI DOM-widget inset, matches the other gate nodes
|
const BTN_ROW_H = 34; // Pass button row
|
||||||
|
const MARGIN = 10; // ComfyUI DOM-widget inset, matches the other nodes
|
||||||
|
|
||||||
// ---- server call ------------------------------------------------------------
|
// ---- server call ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -23,32 +28,19 @@ async function postPass(node, text) {
|
|||||||
await api.fetchApi(`${R}/pass`, { method: "POST", body: fd });
|
await api.fetchApi(`${R}/pass`, { method: "POST", body: fd });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- preview DOM widget -----------------------------------------------------
|
// ---- sizing (Image Pool pattern) --------------------------------------------
|
||||||
|
|
||||||
function previewHeight(node) {
|
// Only a min-height FLOOR — no max — so the DOM widget fills the node and grows
|
||||||
return node._tgActive ? 2 * MARGIN + EDITOR_H + BTN_ROW_H : 0;
|
// 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) {
|
// DomWidgets sizes the editor container from the widget width, which can lag
|
||||||
// Fully remove the editor from layout when idle so it never paints below the
|
// node.size[0] on this frontend — pin it so the textarea reflows to fill.
|
||||||
// node frame (collapsing height to 0 alone wouldn't clip the textarea).
|
function syncWidgetWidth(node) {
|
||||||
if (node._tg) node._tg.wrap.style.display = node._tgActive ? "flex" : "none";
|
if (node._tgWidget) node._tgWidget.width = node.size?.[0] || MIN_W;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- styles + node setup ----------------------------------------------------
|
// ---- styles + node setup ----------------------------------------------------
|
||||||
@@ -67,6 +59,7 @@ function injectStyles() {
|
|||||||
border:1px solid #555; color:#fff; }
|
border:1px solid #555; color:#fff; }
|
||||||
.tgate-pass { background:rgba(40,130,70,0.95); }
|
.tgate-pass { background:rgba(40,130,70,0.95); }
|
||||||
.tgate-pass:hover { background:rgba(55,160,90,0.98); }
|
.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");
|
const style = document.createElement("style");
|
||||||
style.id = "tgate-styles";
|
style.id = "tgate-styles";
|
||||||
@@ -91,23 +84,36 @@ function setupTextGateNode(node) {
|
|||||||
const pass = document.createElement("button");
|
const pass = document.createElement("button");
|
||||||
pass.className = "tgate-pass";
|
pass.className = "tgate-pass";
|
||||||
pass.textContent = "▶ Pass";
|
pass.textContent = "▶ Pass";
|
||||||
|
const status = document.createElement("span");
|
||||||
|
status.className = "tgate-status";
|
||||||
pass.onclick = async () => {
|
pass.onclick = async () => {
|
||||||
await postPass(node, area.value);
|
await postPass(node, area.value);
|
||||||
hideEditor(node);
|
status.textContent = "passed";
|
||||||
};
|
};
|
||||||
btns.appendChild(pass);
|
btns.appendChild(pass);
|
||||||
|
btns.appendChild(status);
|
||||||
|
|
||||||
wrap.appendChild(area);
|
wrap.appendChild(area);
|
||||||
wrap.appendChild(btns);
|
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,
|
serialize: false,
|
||||||
getMinHeight: () => previewHeight(node),
|
getMinHeight: () => widgetFloor(),
|
||||||
});
|
});
|
||||||
|
|
||||||
node._tgActive = false;
|
// keep the editor width synced on manual resize so the textarea reflows
|
||||||
resizePreview(node);
|
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({
|
app.registerExtension({
|
||||||
@@ -118,8 +124,11 @@ app.registerExtension({
|
|||||||
api.addEventListener("datasete-textgate-show", (e) => {
|
api.addEventListener("datasete-textgate-show", (e) => {
|
||||||
const d = e.detail || {};
|
const d = e.detail || {};
|
||||||
const node = app.graph?.getNodeById?.(parseInt(d.id, 10));
|
const node = app.graph?.getNodeById?.(parseInt(d.id, 10));
|
||||||
if (!node || node.type !== NODE) return;
|
if (!node || node.type !== NODE || !node._tg) return;
|
||||||
showEditor(node, d.text);
|
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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user