668 lines
28 KiB
JavaScript
668 lines
28 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { tinyterraReloadNode, wait, rebootAPI, getConfig, convertToInput, hideWidget } from "./utils.js";
|
|
import { openFullscreenApp, _setDefaultFullscreenNode } from "./ttNfullscreen.js";
|
|
|
|
class TinyTerra extends EventTarget {
|
|
constructor() {
|
|
super();
|
|
this.ctrlKey = false
|
|
this.altKey = false
|
|
this.shiftKey = false
|
|
this.downKeys = {}
|
|
this.processingMouseDown = false
|
|
this.processingMouseUp = false
|
|
this.processingMouseMove = false
|
|
window.addEventListener("keydown", (e) => {
|
|
this.handleKeydown(e)
|
|
})
|
|
window.addEventListener("keyup", (e) => {
|
|
this.handleKeyup(e)
|
|
})
|
|
this.initialiseContextMenu()
|
|
this.initialiseNodeMenu()
|
|
this.injectTtnCss()
|
|
}
|
|
async initialiseContextMenu() {
|
|
const that = this;
|
|
setTimeout(async () => {
|
|
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
|
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
|
const options = getCanvasMenuOptions.apply(this, [...args]);
|
|
options.push(null);
|
|
options.push({
|
|
content: `🌏 tinyterraNodes`,
|
|
className: "ttN-contextmenu-item ttN-contextmenu-main-item",
|
|
submenu: {
|
|
options: that.getTinyTerraContextMenuItems(),
|
|
},
|
|
});
|
|
|
|
// Remove consecutive null entries
|
|
let i = 0;
|
|
while (i < options.length) {
|
|
if (options[i] === null && (i === 0 || options[i - 1] === null)) {
|
|
options.splice(i, 1);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
return options;
|
|
};
|
|
}, 1000);
|
|
}
|
|
getTinyTerraContextMenuItems() {
|
|
const that = this
|
|
return [
|
|
{
|
|
content: "🌏 Nodes",
|
|
disabled: true,
|
|
className: "tinyterra-contextmenu-item tinyterra-contextmenu-label",
|
|
},
|
|
{
|
|
content: "base",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback: (...args) => {
|
|
that.addTTNodeMenu('base/', args[3], args[2])
|
|
}
|
|
},
|
|
{
|
|
content: "pipe",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback: (...args) => {
|
|
that.addTTNodeMenu('pipe/', args[3], args[2])
|
|
}
|
|
},
|
|
{
|
|
content: "xyPlot",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback: (...args) => {
|
|
that.addTTNodeMenu('xyPlot/', args[3], args[2])
|
|
}
|
|
},
|
|
{
|
|
content: "text",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback: (...args) => {
|
|
that.addTTNodeMenu('text/', args[3], args[2])
|
|
}
|
|
},
|
|
{
|
|
content: "image",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback: (...args) => {
|
|
that.addTTNodeMenu('image/', args[3], args[2])
|
|
}
|
|
},
|
|
{
|
|
content: "util",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback: (...args) => {
|
|
that.addTTNodeMenu('util/', args[3], args[2])
|
|
}
|
|
},
|
|
{
|
|
content: "🌏 Add Group",
|
|
disabled: true,
|
|
className: "tinyterra-contextmenu-item tinyterra-contextmenu-label",
|
|
},
|
|
{
|
|
content: "Basic Sampling",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback : function(value, event, mouseEvent, contextMenu){
|
|
that.addGroupMenu('basic', contextMenu, mouseEvent)
|
|
}
|
|
},
|
|
{
|
|
content: "Upscaling",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback : function(value, event, mouseEvent, contextMenu){
|
|
that.addGroupMenu('upscale', contextMenu, mouseEvent)
|
|
}
|
|
},
|
|
{
|
|
content: "xyPlotting",
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: true,
|
|
callback : function(value, event, mouseEvent, contextMenu){
|
|
that.addGroupMenu('xyPlot', contextMenu, mouseEvent)
|
|
}
|
|
},
|
|
{
|
|
content: "🌏 Extras",
|
|
disabled: true,
|
|
className: "tinyterra-contextmenu-item tinyterra-contextmenu-label",
|
|
},
|
|
{
|
|
content: "⚙️ Settings (tinyterra)",
|
|
disabled: true, //!!this.settingsDialog,
|
|
className: "tinyterra-contextmenu-item",
|
|
callback: (...args) => {
|
|
this.settingsDialog = new tinyterraConfigDialog().show();
|
|
this.settingsDialog.addEventListener("close", (e) => {
|
|
this.settingsDialog = null;
|
|
});
|
|
},
|
|
},
|
|
{
|
|
content: "🛑 Reboot Comfy",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback: (...args) => {
|
|
rebootAPI();
|
|
wait(1000).then(() => {
|
|
window.location.reload();
|
|
});
|
|
}
|
|
},
|
|
{
|
|
content: "⭐ Star on Github",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback: (...args) => {
|
|
window.open("https://github.com/TinyTerra/ComfyUI_tinyterraNodes", "_blank");
|
|
},
|
|
},
|
|
{
|
|
content: "☕ Support TinyTerra",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback: (...args) => {
|
|
window.open("https://buymeacoffee.com/tinyterra", "_blank");
|
|
},
|
|
},
|
|
|
|
];
|
|
}
|
|
addNode = async (node, pos) => {
|
|
var canvas = LGraphCanvas.active_canvas;
|
|
canvas.graph.beforeChange();
|
|
var node = LiteGraph.createNode(node);
|
|
if (node) {
|
|
node.pos = pos;
|
|
canvas.graph.add(node);
|
|
}
|
|
canvas.graph.afterChange();
|
|
return node
|
|
}
|
|
addGroup = async (contextMenu, nodes) => {
|
|
var first_event = contextMenu.getFirstEvent();
|
|
var canvas = LGraphCanvas.active_canvas;
|
|
var canvasOffset = canvas.convertEventToCanvasOffset(first_event);
|
|
|
|
// Create Nodes
|
|
for (const nodeData of Object.values(nodes)) {
|
|
var node = await this.addNode(nodeData.nodeType, canvasOffset);
|
|
nodeData.graphNode = node;
|
|
canvasOffset = [canvasOffset[0] + nodeData.width + 10, canvasOffset[1]];
|
|
}
|
|
|
|
// Handle Widget Changes
|
|
for (const nodeData of Object.values(nodes)) {
|
|
var node = nodeData.graphNode;
|
|
if (nodeData.widgets) {
|
|
for (const [widget, value] of Object.entries(nodeData.widgets)) {
|
|
if (value == 'toInput') {
|
|
const config = getConfig(widget, node)
|
|
convertToInput(node, node.widgets.find((w) => w.name === widget), config);
|
|
} else {
|
|
if (node) {
|
|
node.widgets.find((w) => w.name === widget).value = value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle Connections
|
|
for (const nodeData of Object.values(nodes)) {
|
|
var node = nodeData.graphNode;
|
|
if (nodeData.connections) {
|
|
for (const c of nodeData.connections) {
|
|
node.connect(parseInt(c[0]), nodes[c[1]].graphNode.id, c[2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
addTTNodeMenu(category, prev_menu, e, callback=null) {
|
|
var canvas = LGraphCanvas.active_canvas;
|
|
var ref_window = canvas.getCanvasWindow();
|
|
var graph = canvas.graph;
|
|
const base_category = '🌏 tinyterra/' + category
|
|
|
|
var entries = [];
|
|
|
|
var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter );
|
|
nodes.map(function(node){
|
|
if (node.skip_list)
|
|
return;
|
|
|
|
var entry = {
|
|
value: node.type,
|
|
content: node.title,
|
|
className: "tinyterra-contextmenu-item",
|
|
has_submenu: false,
|
|
callback : function(value, event, mouseEvent, contextMenu){
|
|
var first_event = contextMenu.getFirstEvent();
|
|
canvas.graph.beforeChange();
|
|
var node = LiteGraph.createNode(value.value);
|
|
if (node) {
|
|
node.pos = canvas.convertEventToCanvasOffset(first_event);
|
|
canvas.graph.add(node);
|
|
}
|
|
if(callback)
|
|
callback(node);
|
|
canvas.graph.afterChange();
|
|
}
|
|
}
|
|
|
|
entries.push(entry);
|
|
});
|
|
|
|
new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window );
|
|
}
|
|
addGroupMenu(group, prev_menu, e) {
|
|
const that = this;
|
|
var canvas = LGraphCanvas.active_canvas;
|
|
var ref_window = canvas.getCanvasWindow();
|
|
let entries;
|
|
switch (group) {
|
|
case "basic":
|
|
entries = [
|
|
{ content: "Base ttN",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'Loader': {
|
|
nodeType: 'ttN tinyLoader',
|
|
graphNode: null,
|
|
width: 315,
|
|
connections: [
|
|
[0, 'Conditioning', 'model'],
|
|
[1, 'KSampler', 'latent'],
|
|
[2, 'KSampler', 'vae'],
|
|
[3, 'Conditioning', 'clip'],
|
|
],
|
|
},
|
|
'Conditioning': {
|
|
nodeType: 'ttN conditioning',
|
|
graphNode: null,
|
|
width: 400,
|
|
connections: [
|
|
[0, 'KSampler', 'model'],
|
|
[1, 'KSampler', 'positive'],
|
|
[2, 'KSampler', 'negative'],
|
|
[3, 'KSampler', 'clip'],
|
|
],
|
|
},
|
|
'KSampler': {
|
|
nodeType: 'ttN KSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
widgets: {
|
|
image_output: 'Preview'
|
|
}
|
|
}
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
{ content: "Pipe Basic",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'Loader': {
|
|
nodeType: 'ttN pipeLoader_v2',
|
|
graphNode: null,
|
|
width: 315,
|
|
connections: [
|
|
[0, 'KSampler', 'pipe']
|
|
],
|
|
},
|
|
'KSampler': {
|
|
nodeType: 'ttN pipeKSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
widgets: {
|
|
image_output: 'Preview'
|
|
}
|
|
}
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
{ content: "Pipe SDXL",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'Loader': {
|
|
nodeType: 'ttN pipeLoaderSDXL_v2',
|
|
graphNode: null,
|
|
width: 365,
|
|
connections: [
|
|
[0, 'KSampler', 'sdxl_pipe']
|
|
],
|
|
},
|
|
'KSampler': {
|
|
nodeType: 'ttN pipeKSamplerSDXL_v2',
|
|
graphNode: null,
|
|
width: 365,
|
|
widgets: {
|
|
image_output: 'Preview'
|
|
}
|
|
}
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
];
|
|
break;
|
|
|
|
case "upscale":
|
|
entries = [
|
|
{ content: "Base upscale",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'Loader': {
|
|
nodeType: 'ttN tinyLoader',
|
|
graphNode: null,
|
|
width: 315,
|
|
connections: [
|
|
[0, 'Conditioning', 'model'],
|
|
[1, 'KSampler', 'latent'],
|
|
[2, 'KSampler', 'vae'],
|
|
[3, 'Conditioning', 'clip'],
|
|
],
|
|
},
|
|
'Conditioning': {
|
|
nodeType: 'ttN conditioning',
|
|
graphNode: null,
|
|
width: 400,
|
|
connections: [
|
|
[0, 'KSampler', 'model'],
|
|
[1, 'KSampler', 'positive'],
|
|
[2, 'KSampler', 'negative'],
|
|
[3, 'KSampler', 'clip'],
|
|
],
|
|
},
|
|
'KSampler': {
|
|
nodeType: 'ttN KSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
connections: [
|
|
[0, 'KSampler2', 'model'],
|
|
[1, 'KSampler2', 'positive'],
|
|
[2, 'KSampler2', 'negative'],
|
|
[3, 'KSampler2', 'latent'],
|
|
[4, 'KSampler2', 'vae'],
|
|
[5, 'KSampler2', 'clip'],
|
|
[6, 'KSampler2', 'input_image_override']
|
|
],
|
|
widgets: {
|
|
image_output: 'Preview',
|
|
}
|
|
},
|
|
'KSampler2': {
|
|
nodeType: 'ttN KSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
widgets: {
|
|
upscale_method: '[hiresFix] nearest-exact',
|
|
image_output: 'Preview',
|
|
denoise: 0.5,
|
|
steps: 15
|
|
}
|
|
},
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
{ content: "Pipe Upscale",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'loader1': {
|
|
nodeType: 'ttN pipeLoader_v2',
|
|
graphNode: null,
|
|
width: 315,
|
|
connections: [
|
|
[0, 'ksampler', 'pipe']
|
|
],
|
|
},
|
|
'ksampler': {
|
|
nodeType: 'ttN pipeKSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
connections: [
|
|
[0, 'ksampler2', 'pipe']
|
|
],
|
|
widgets: {
|
|
image_output: 'Preview'
|
|
},
|
|
},
|
|
'ksampler2': {
|
|
nodeType: 'ttN pipeKSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
widgets: {
|
|
upscale_method: '[hiresFix] nearest-exact',
|
|
denoise: 0.5,
|
|
seed: 'toInput',
|
|
image_output: 'Preview'
|
|
}
|
|
}
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
];
|
|
break;
|
|
|
|
case "xyPlot":
|
|
entries = [
|
|
{ content: "Base xyPlot",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'Loader': {
|
|
nodeType: 'ttN tinyLoader',
|
|
graphNode: null,
|
|
width: 315,
|
|
connections: [
|
|
[0, 'Conditioning', 'model'],
|
|
[1, 'KSampler', 'latent'],
|
|
[2, 'KSampler', 'vae'],
|
|
[3, 'Conditioning', 'clip'],
|
|
],
|
|
},
|
|
'Conditioning': {
|
|
nodeType: 'ttN conditioning',
|
|
graphNode: null,
|
|
width: 400,
|
|
connections: [
|
|
[0, 'KSampler', 'model'],
|
|
[1, 'KSampler', 'positive'],
|
|
[2, 'KSampler', 'negative'],
|
|
[3, 'KSampler', 'clip'],
|
|
],
|
|
},
|
|
'xyPlot': {
|
|
nodeType: 'ttN advanced xyPlot',
|
|
graphNode: null,
|
|
width: 400,
|
|
connections: [
|
|
[0, 'KSampler', 'adv_xyPlot'],
|
|
],
|
|
},
|
|
'KSampler': {
|
|
nodeType: 'ttN KSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
widgets: {
|
|
image_output: 'Preview'
|
|
}
|
|
}
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
{ content: "Pipe xyPlot",
|
|
className: "tinyterra-contextmenu-item",
|
|
callback : async function(value, event, mouseEvent, contextMenu){
|
|
const nodes = {
|
|
'Loader': {
|
|
nodeType: 'ttN pipeLoader_v2',
|
|
graphNode: null,
|
|
width: 315,
|
|
connections: [
|
|
[0, 'KSampler', 'pipe'],
|
|
],
|
|
},
|
|
'xyPlot': {
|
|
nodeType: 'ttN advanced xyPlot',
|
|
graphNode: null,
|
|
width: 400,
|
|
connections: [
|
|
[0, 'KSampler', 'adv_xyPlot'],
|
|
],
|
|
},
|
|
'KSampler': {
|
|
nodeType: 'ttN pipeKSampler_v2',
|
|
graphNode: null,
|
|
width: 262,
|
|
widgets: {
|
|
image_output: 'Preview'
|
|
}
|
|
}
|
|
}
|
|
that.addGroup(contextMenu, nodes)
|
|
}
|
|
},
|
|
]
|
|
}
|
|
new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window );
|
|
}
|
|
async initialiseNodeMenu() {
|
|
const that = this;
|
|
setTimeout(async () => {
|
|
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions;
|
|
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
|
const options = getNodeMenuOptions.apply(this, arguments);
|
|
node.setDirtyCanvas(true, true);
|
|
const ttNoptions = that.getTinyTerraNodeMenuItems(node)
|
|
options.splice(options.length - 1, 0, ...ttNoptions, null);
|
|
|
|
return options;
|
|
};
|
|
},500)
|
|
}
|
|
getTinyTerraNodeMenuItems(node) {
|
|
return [
|
|
{
|
|
content: "🌏 Fullscreen",
|
|
callback: () => { openFullscreenApp(node) }
|
|
},
|
|
{
|
|
content: "🌏 Set Default Fullscreen Node",
|
|
callback: _setDefaultFullscreenNode
|
|
},
|
|
{
|
|
content: "🌏 Clear Default Fullscreen Node",
|
|
callback: function () {
|
|
sessionStorage.removeItem('Comfy.Settings.ttN.default_fullscreen_node');
|
|
}
|
|
},
|
|
null,
|
|
{
|
|
content: "🌏 Default Node BG Color",
|
|
has_submenu: true,
|
|
callback: LGraphCanvas.ttNsetDefaultBGColor
|
|
},
|
|
{
|
|
content: "🌏 Node Dimensions",
|
|
callback: () => { LGraphCanvas.prototype.ttNsetNodeDimension(node); }
|
|
},
|
|
{
|
|
content: "🌏 Reload Node",
|
|
callback: () => {
|
|
const active_canvas = LGraphCanvas.active_canvas;
|
|
if (!active_canvas.selected_nodes || Object.keys(active_canvas.selected_nodes).length <= 1) {
|
|
tinyterraReloadNode(node);
|
|
} else {
|
|
for (var i in active_canvas.selected_nodes) {
|
|
tinyterraReloadNode(active_canvas.selected_nodes[i]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
]
|
|
}
|
|
handleKeydown(e) {
|
|
this.ctrlKey = !!e.ctrlKey
|
|
this.altKey = !!e.altKey
|
|
this.shiftKey = !!e.shiftKey
|
|
this.downKeys[e.key.toLocaleUpperCase()] = true
|
|
this.downKeys["^" + e.key.toLocaleUpperCase()] = true
|
|
}
|
|
handleKeyup(e) {
|
|
this.ctrlKey = !!e.ctrlKey
|
|
this.altKey = !!e.altKey
|
|
this.shiftKey = !!e.shiftKey
|
|
this.downKeys[e.key.toLocaleUpperCase()] = false
|
|
this.downKeys["^" + e.key.toLocaleUpperCase()] = false
|
|
}
|
|
injectTtnCss() {
|
|
let link = document.createElement("link");
|
|
link.rel = "stylesheet";
|
|
link.type = "text/css";
|
|
link.href = "extensions/ComfyUI_tinyterraNodes/ttN.css";
|
|
document.head.appendChild(link);
|
|
}
|
|
}
|
|
|
|
export const tinyterra = new TinyTerra();
|
|
window.tinyterra = tinyterra;
|
|
|
|
app.registerExtension({
|
|
name: "comfy.ttN",
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name.startsWith("ttN")) {
|
|
const origOnConfigure = nodeType.prototype.onConfigure;
|
|
nodeType.prototype.onConfigure = function () {
|
|
const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined;
|
|
let nodeVersion = nodeData.input.hidden?.ttNnodeVersion ? nodeData.input.hidden.ttNnodeVersion : null;
|
|
nodeType.ttNnodeVersion = nodeVersion;
|
|
this.properties['ttNnodeVersion'] = this.properties['ttNnodeVersion'] ? this.properties['ttNnodeVersion'] : nodeVersion;
|
|
if ((this.properties['ttNnodeVersion'].split(".")[0] !== nodeVersion.split(".")[0]) || (this.properties['ttNnodeVersion'].split(".")[1] !== nodeVersion.split(".")[1])) {
|
|
if (!this.properties['origVals']) {
|
|
this.properties['origVals'] = { bgcolor: this.bgcolor, color: this.color, title: this.title }
|
|
}
|
|
this.bgcolor = "#e76066";
|
|
this.color = "#ff0b1e";
|
|
this.title = this.title.includes("Node Version Mismatch") ? this.title : this.title + " - Node Version Mismatch"
|
|
} else if (this.properties['origVals']) {
|
|
this.bgcolor = this.properties.origVals.bgcolor;
|
|
this.color = this.properties.origVals.color;
|
|
this.title = this.properties.origVals.title;
|
|
delete this.properties['origVals']
|
|
}
|
|
return r;
|
|
};
|
|
}
|
|
},
|
|
nodeCreated(node) {
|
|
if (["pipeLoader", "pipeLoaderSDXL"].includes(node.constructor.title)) {
|
|
for (let widget of node.widgets) {
|
|
if (widget.name === "control_after_generate") {
|
|
widget.value = "fixed"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|