Files
robertvoy-ComfyUI-Flux-Cont…/web/textversions.js
T
2025-06-19 13:39:06 +10:00

223 lines
8.0 KiB
JavaScript

import { app } from "../../../scripts/app.js";
/**
* Default and limit configurations
*/
const CONFIG = {
DEFAULT_VERSION_AMOUNT: 5,
MAX_VERSION_AMOUNT: 100,
MIN_VERSION_AMOUNT: 1
};
/**
* Visual style configuration for version tabs
*/
const TAB_STYLE = {
width: 40,
height: 18,
fontSize: 10,
normalColor: "#333333",
selectedColor: "#666666",
textColor: "white",
borderRadius: 4,
spacing: 10,
offset: 10,
yPosition: 10
};
function createTabConfig(numVersions) {
return {
...TAB_STYLE,
labels: Array.from({length: numVersions}, (_, i) => (i + 1).toString())
};
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
app.registerExtension({
name: "FluxContinuum.TextVersions",
// Modern API uses nodeCreated instead of beforeRegisterNodeDef
nodeCreated(node) {
// Only apply to TextVersions nodes
if (node.type !== "TextVersions" && node.comfyClass !== "TextVersions") return;
// Store original methods
const onNodeCreated = node.onNodeCreated;
const onDrawBackground = node.onDrawBackground;
const getBounding = node.getBounding;
const onSerialize = node.onSerialize;
const onConfigure = node.onConfigure;
const onMouseDown = node.onMouseDown; // IMPORTANT: Store the original onMouseDown
// Add onNodeCreated to the node
node.onNodeCreated = function() {
if (onNodeCreated) {
onNodeCreated.apply(this, arguments);
}
// Add properties
this.addProperty("versionAmount", CONFIG.DEFAULT_VERSION_AMOUNT, "number");
// Initialize node
if (!this.properties) {
this.properties = {};
}
this.properties.versionAmount = this.properties.versionAmount || CONFIG.DEFAULT_VERSION_AMOUNT;
// Create tab config based on number of versions
this.tabConfig = createTabConfig(this.properties.versionAmount);
// Initialize tab state and content
this.activeTab = 0;
this.tabContents = Array(this.properties.versionAmount).fill("");
// Store reference to the text widget
this.textWidget = this.widgets.find(w => w.name === "text");
// Initial content setup
if (this.textWidget) {
this.textWidget.value = this.tabContents[this.activeTab];
// Add debounced change event listener
if (this.textWidget.inputEl) {
const saveContent = debounce(() => {
this.tabContents[this.activeTab] = this.textWidget.value;
}, 300);
this.textWidget.inputEl.addEventListener("input", saveContent);
}
}
};
node.onPropertyChanged = function(name, value) {
if (name === "versionAmount") {
const newValue = Math.max(CONFIG.MIN_VERSION_AMOUNT,
Math.min(CONFIG.MAX_VERSION_AMOUNT, Math.floor(value)));
const oldContents = [...this.tabContents];
this.properties.versionAmount = newValue;
this.tabConfig = createTabConfig(newValue);
this.tabContents = Array(newValue).fill("").map((_, i) => oldContents[i] || "");
this.activeTab = Math.min(this.activeTab, newValue - 1);
if (this.textWidget) {
this.textWidget.value = this.tabContents[this.activeTab];
}
this.setDirtyCanvas(true);
}
};
node.onDrawBackground = function(ctx) {
if (onDrawBackground) {
onDrawBackground.apply(this, arguments);
}
if (this.flags.collapsed) return;
ctx.save();
// Create clipping region for overflow
const nodeWidth = this.size[0];
const clipPadding = 10;
ctx.beginPath();
ctx.rect(clipPadding, this.tabConfig.yPosition - 5,
nodeWidth - (2 * clipPadding),
this.tabConfig.height + 10);
ctx.clip();
// Draw tabs
this.tabConfig.labels.forEach((label, i) => {
const x = this.tabConfig.offset + (this.tabConfig.width + this.tabConfig.spacing) * i;
const y = this.tabConfig.yPosition;
ctx.fillStyle = i === this.activeTab ? this.tabConfig.selectedColor : this.tabConfig.normalColor;
ctx.beginPath();
ctx.roundRect(x, y, this.tabConfig.width, this.tabConfig.height, this.tabConfig.borderRadius);
ctx.fill();
ctx.fillStyle = this.tabConfig.textColor;
ctx.font = `${this.tabConfig.fontSize}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(label, x + this.tabConfig.width / 2, y + this.tabConfig.height / 2);
});
ctx.restore();
};
node.onMouseDown = function(event, local_pos, graphCanvas) {
const [x, y] = local_pos;
const { yPosition, height, width, spacing, offset, labels } = this.tabConfig;
if (y >= yPosition && y <= yPosition + height) {
for (let i = 0; i < labels.length; i++) {
const tabX = offset + (width + spacing) * i;
if (x >= tabX && x <= tabX + width) {
if (i === this.activeTab) return false;
if (this.textWidget) {
this.tabContents[this.activeTab] = this.textWidget.value;
}
this.activeTab = i;
if (this.textWidget) {
this.textWidget.value = this.tabContents[i];
}
this.setDirtyCanvas(true);
return true;
}
}
}
// IMPORTANT: Call the original onMouseDown if we didn't handle the click
if (onMouseDown) {
return onMouseDown.apply(this, arguments);
}
return false;
};
node.getBounding = function() {
const bounds = getBounding?.apply(this, arguments) || new Float32Array(4);
const tabsHeight = Math.abs(this.tabConfig.yPosition) + this.tabConfig.height;
bounds[1] -= tabsHeight;
bounds[3] += tabsHeight;
return bounds;
};
node.onSerialize = function(o) {
if (onSerialize) {
onSerialize.apply(this, arguments);
}
o.tabContents = this.tabContents;
o.activeTab = this.activeTab;
};
node.onConfigure = function(o) {
if (onConfigure) {
onConfigure.apply(this, arguments);
}
this.tabConfig = createTabConfig(this.properties.versionAmount);
if (o.tabContents && Array.isArray(o.tabContents)) {
this.tabContents = Array(this.properties.versionAmount).fill("").map((_, i) => o.tabContents[i] || "");
this.activeTab = o.activeTab >= 0 && o.activeTab < this.properties.versionAmount ? o.activeTab : 0;
if (this.textWidget) {
this.textWidget.value = this.tabContents[this.activeTab];
}
}
};
}
});