Files

126 lines
7.6 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { InpaintParameterPanel } from "./inpaint_panel.js?v=adachi-name";
import { connectedEditor, connectedSamplers, relocateWidgets, syncCurrentDimensions } from "./parameter_bridge.js?v=0.8.2";
import { routeWheelOutsideImage } from "./wheel_routing.js";
import { T } from "./i18n.js";
import { bindWidgetToNodeWidth } from "./widget_layout.js?v=0.5.5";
export function installInpaintWorkbench(nodeType) {
const created = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const result = created?.apply(this, arguments), node = this;
const root = document.createElement("div"); root.className = "dd-inpaint dd-workbench";
if (node.comfyClass === "AdachiWebUIInpaint" || node.type === "AdachiWebUIInpaint") {
const launch = document.createElement("button");
launch.type = "button"; launch.textContent = T("Open Adachi Workbench");
launch.className = "adachi-workspace-launch";
launch.onclick = event => { event.preventDefault(); event.stopPropagation(); document.dispatchEvent(new CustomEvent("adachi-open-workspace", {detail:{sampler:node}})); };
root.append(launch);
}
const fold = document.createElement("details"); fold.className = "dd-workbench-fold";
const summary = document.createElement("summary"); summary.textContent = T("Painting tools and mask settings");
summary.addEventListener("pointerdown", event => event.stopPropagation());
summary.addEventListener("click", event => event.stopPropagation());
fold.append(summary);
const tools = document.createElement("div"); tools.className = "dd-workbench-tools";
const placeholder = document.createElement("div"); placeholder.className = "dd-param-placeholder dd-tools-placeholder";
placeholder.textContent = T("Connect an inline mask to show the painting tools");
tools.append(placeholder); fold.append(tools);
const actionHost = document.createElement("div"); actionHost.className = "dd-workbench-actions";
root.append(fold, actionHost);
let source = null, mountedEditor = null, frame = 0, removed = false;
const markChanged = () => {
app.canvas?.emitBeforeChange?.(); app.canvas?.emitAfterChange?.();
node.graph?.setDirtyCanvas(true, true);
};
const refreshEditor = () => {
source = connectedEditor(node);
const editor = source?.adachiEditor;
// A live control tree has one host. Additional samplers still get
// their own parameters and use the same source image dimensions.
const canMount = editor?.controls && connectedSamplers(source)[0] === node;
const next = canMount ? editor : null;
if (next !== mountedEditor) {
if (mountedEditor?.controls.parentElement === tools) mountedEditor.controls.remove();
if (mountedEditor?.actions.parentElement === actionHost) mountedEditor.actions.remove();
mountedEditor = next;
if (next) { tools.append(next.controls); actionHost.append(next.actions); }
scheduleSize();
}
placeholder.hidden = !!next;
placeholder.textContent = editor ? T("The painting tools live in the other inpaint node connected to this image") : T("Connect an inline mask to show the painting tools");
};
const contentHeight = () => (root.querySelector('.adachi-workspace-launch')?.offsetHeight || 0) + fold.offsetHeight + actionHost.offsetHeight + (panel?.root.scrollHeight || 620);
const scheduleSize = () => {
cancelAnimationFrame(frame);
frame = requestAnimationFrame(() => {
if (removed || !node.graph || !root.isConnected || root.closest(".adachi-workspace")) return;
const required = node.computeSize()[1];
if (Math.abs(node.size[1] - required) > 1) node.setSize([node.size[0], required]);
node.graph.setDirtyCanvas(true, true);
});
};
let panel;
fold.open = !!node.properties.adachi_tools_open;
fold.ontoggle = () => {
if (node.properties.adachi_tools_open !== fold.open) {
node.properties.adachi_tools_open = fold.open; markChanged();
}
scheduleSize();
};
panel = new InpaintParameterPanel({
getTargets: () => { refreshEditor(); return [node]; },
getImage: () => source?.adachiEditor?.ready ? (source.adachiCrop?.mode === "crop" && source.adachiCrop.region.locked ? source.adachiCrop.region : source.adachiEditor.image) : null,
onTargetChange: (previous, next) => relocateWidgets(node, previous, next),
onDirty: markChanged, onHeight: scheduleSize,
onMaskSettings: element => {
fold.querySelector(".dd-param-mask-settings")?.remove();
fold.append(element); scheduleSize();
},
});
root.append(panel.root);
node.adachiParameters = panel; node.adachiWorkbench = { root, tools, fold, actionHost, get source() { return source; } };
const stopWheelRouting = routeWheelOutsideImage(root, null, () => false);
const widget = node.addDOMWidget("adachi_workbench", "adachi_inpaint_controls", root, {
serialize: false, hideOnZoom: false, getMinHeight: contentHeight, getMaxHeight: () => Math.max(4096, contentHeight()),
});
widget.serialize = false;
bindWidgetToNodeWidth(node, widget, root);
const observer = new ResizeObserver(scheduleSize); observer.observe(tools); observer.observe(fold); observer.observe(actionHost);
const configured = node.onConfigure;
node.onConfigure = function () {
const result = configured?.apply(this, arguments);
const legacyTitles = ["WebUI Inpaint", "WebUI 局部重绘", "WebUI 局部重绘 / WebUI Inpaint", "安达局部重绘工作台"];
if (node.type === "AdachiWebUIInpaint" && legacyTitles.includes(node.title)) {
node.title = T("Adachi Inpaint Workbench");
}
fold.open = !!node.properties.adachi_tools_open;
queueMicrotask(() => { if (!removed) { panel.refresh(); scheduleSize(); } });
return result;
};
const connections = node.onConnectionsChange;
node.onConnectionsChange = function (type, slot, connected) {
const result = connections?.apply(this, arguments);
// Graph restoration also connects wires: never overwrite the sizes
// saved in a workflow while it is being configured.
const userImageConnection = !app.configuringGraph && type === 1 && connected && node.inputs?.[slot]?.name === "image";
queueMicrotask(() => {
if (removed) return;
panel.refresh();
if (userImageConnection) syncCurrentDimensions(connectedEditor(node), node);
});
return result;
};
const previousRemoved = node.onRemoved;
node.onRemoved = function () {
removed = true; cancelAnimationFrame(frame); observer.disconnect();
if (mountedEditor?.controls.parentElement === tools) mountedEditor.controls.remove();
if (mountedEditor?.actions.parentElement === actionHost) mountedEditor.actions.remove();
panel.destroy(); stopWheelRouting(); root.remove();
return previousRemoved?.apply(this, arguments);
};
node.setSize([Math.max(510, node.size[0]), node.size[1]]); scheduleSize();
return result;
};
}