Init
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
from .ui_node import UINode
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"UINode": UINode
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"UINode": "UINode [GroxicTinch]",
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./web/comfyui"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
@@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "EasyUI"
|
||||
description = "Allows making a mirror of options that are on a node, for use creating your own UI"
|
||||
version = "1.0.2"
|
||||
license = {text = "MIT License"}
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "GroxicTinch"
|
||||
DisplayName = "EasyUI"
|
||||
Icon = ""
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
class UINode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {}
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "return_empty"
|
||||
CATEGORY = "EasyUI"
|
||||
|
||||
def __init__(self):
|
||||
self.original_node_id = None
|
||||
self.original_node_type = None
|
||||
|
||||
def return_empty(self):
|
||||
return ()
|
||||
@@ -0,0 +1,216 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
// [FIXME] Delete the UINode if the original node is deleted
|
||||
// [TODO] Add a way to get taken to the original node from the UINode
|
||||
|
||||
var ignoredWidgets = ['button'];
|
||||
|
||||
/** ─────────────────────────────────────────────────────────────────────
|
||||
* 1) Extract your two bits of logic into named functions.
|
||||
* These will live in module scope so both the commands and the
|
||||
* context-menu callbacks can refer to them.
|
||||
* ───────────────────────────────────────────────────────────────────── */
|
||||
function createMirror(node) {
|
||||
if (!node) return;
|
||||
const graph = node.graph ?? app.canvas.graph;
|
||||
const uiNode = LiteGraph.createNode("UINode");
|
||||
graph.add(uiNode);
|
||||
|
||||
uiNode.properties = uiNode.properties || {};
|
||||
uiNode.properties.original_node_id = node.id;
|
||||
|
||||
// position & clear I/O
|
||||
uiNode.pos = [node.pos[0] + 50, node.pos[1] + 50];
|
||||
uiNode.inputs = [];
|
||||
uiNode.outputs = [];
|
||||
|
||||
// 1) redraw immediately so the DOM is created
|
||||
app.canvas.setDirty(true);
|
||||
app.canvas.draw(true);
|
||||
|
||||
// [FIXME] elements arent working (preview image, customtext)
|
||||
|
||||
// // 2) find the newly-rendered node element
|
||||
// uiNode.element = node.element;
|
||||
|
||||
// 3) pass that nodeEl into cloneWidgets
|
||||
cloneWidgets(node, uiNode);
|
||||
}
|
||||
|
||||
/** ─────────────────────────────────────────────────────────────────────
|
||||
* 2) Register a single extension that
|
||||
* a) declares both commands (so invokeExtensionCommand() can find them)
|
||||
* b) monkey-patches getExtraMenuOptions to call them in the right-click menu
|
||||
* ───────────────────────────────────────────────────────────────────── */
|
||||
app.registerExtension({
|
||||
name: 'easyui', // just one extension now
|
||||
|
||||
beforeRegisterNodeDef(nodeTypeInfo) {
|
||||
const proto = nodeTypeInfo.prototype;
|
||||
const original = proto.getExtraMenuOptions;
|
||||
|
||||
proto.getExtraMenuOptions = function(_, options = []) {
|
||||
// 1) keep all built-ins
|
||||
if (original) {
|
||||
original.apply(this, arguments);
|
||||
}
|
||||
|
||||
options.push(null); // inserts a divider
|
||||
// 2) if it's a UINode, Don't add “Create Mirror Copy”
|
||||
if (this.type !== 'UINode') {
|
||||
options.push({
|
||||
content: '🖼️ EasyUI – Create Mirror Copy',
|
||||
callback: () => createMirror(this)
|
||||
});
|
||||
}
|
||||
|
||||
// 3) if it’s a UINode, also add “Toggle Widget Visibility”
|
||||
if (this.type === 'UINode') {
|
||||
options.push({
|
||||
content: '🖼️ EasyUI – Toggle Widget Visibility',
|
||||
has_submenu: true,
|
||||
callback: make_submenu
|
||||
});
|
||||
}
|
||||
options.push(null); // inserts a divider
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
// Add configure override for UINode to restore widgets on load
|
||||
if (nodeTypeInfo.comfyClass === 'UINode') {
|
||||
const originalConfigure = proto.configure;
|
||||
proto.configure = function(serializedNode) {
|
||||
// Call the original configure method first
|
||||
if (originalConfigure) {
|
||||
originalConfigure.apply(this, arguments);
|
||||
}
|
||||
// Clone widgets from the original node
|
||||
const originalNodeId = this.properties.original_node_id;
|
||||
if (originalNodeId) {
|
||||
const originalNode = app.canvas.graph.getNodeById(originalNodeId);
|
||||
if (originalNode) {
|
||||
cloneWidgets(originalNode, this);
|
||||
} else {
|
||||
console.warn(`Original node with ID ${originalNodeId} not found.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function make_submenu(value, options, e, menu, node) {
|
||||
var subOptionsDisabled = [];
|
||||
var subOptionsEnabled = [];
|
||||
|
||||
const originalNodeId = node.properties.original_node_id;
|
||||
if (originalNodeId) {
|
||||
const originalNode = app.canvas.graph.getNodeById(originalNodeId);
|
||||
if (originalNode) {
|
||||
// Loop through the widgets and create sub-options
|
||||
originalNode.widgets.forEach(widget => {
|
||||
const isHidden = node.properties.hiddenWidgets.includes(widget.name);
|
||||
if (isHidden) {
|
||||
if(!widget.computedDisabled && !ignoredWidgets.includes(type)) {
|
||||
subOptionsDisabled.push(`Enable ${widget.name}`);
|
||||
}
|
||||
} else {
|
||||
subOptionsEnabled.push(`Disable ${widget.name}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn(`Original node with ID ${originalNodeId} not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
if(subOptionsDisabled.length > 0) {
|
||||
subOptionsDisabled.push(null); // inserts a divider
|
||||
}
|
||||
|
||||
var subOptions = subOptionsDisabled.concat(subOptionsEnabled);
|
||||
|
||||
if (subOptions.length === 0) {
|
||||
subOptions.push("No widgets to toggle");
|
||||
}
|
||||
|
||||
const submenu = new LiteGraph.ContextMenu(
|
||||
subOptions,
|
||||
{
|
||||
event: e,
|
||||
callback: function (v) {
|
||||
if (v === "No widgets to toggle") {
|
||||
return;
|
||||
}
|
||||
if (v.startsWith("Disable")) {
|
||||
const widgetName = v.replace("Disable ", "");
|
||||
node.properties.hiddenWidgets.push(widgetName);
|
||||
} else if (v.startsWith("Enable")) {
|
||||
const widgetName = v.replace("Enable ", "");
|
||||
node.properties.hiddenWidgets = node.properties.hiddenWidgets.filter(name => name !== widgetName);
|
||||
}
|
||||
|
||||
const originalNode = app.canvas.graph.getNodeById(node.properties.original_node_id);
|
||||
if (originalNode) {
|
||||
cloneWidgets(originalNode, node);
|
||||
app.canvas.setDirty(true);
|
||||
app.canvas.draw(true);
|
||||
}
|
||||
},
|
||||
parentMenu: menu,
|
||||
node:node
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** ─────────────────────────────────────────────────────────────────────
|
||||
* 3) Keep your widget-cloning helper unchanged
|
||||
* ───────────────────────────────────────────────────────────────────── */
|
||||
function cloneWidgets(originalNode, uiNode) {
|
||||
uiNode.widgets = [];
|
||||
uiNode.widgets_values = [];
|
||||
uiNode.properties.hiddenWidgets = uiNode.properties.hiddenWidgets || [];
|
||||
|
||||
originalNode.widgets.forEach(origW => {
|
||||
const type = origW.type || 'string';
|
||||
const options = origW.options || {};
|
||||
try {
|
||||
// Example: Mark some widgets to be hidden initially
|
||||
const isHidden = uiNode.properties.hiddenWidgets.includes(origW.name);
|
||||
if (origW.computedDisabled || isHidden || ignoredWidgets.includes(type)) {
|
||||
if (!isHidden) {
|
||||
uiNode.properties.hiddenWidgets.push(origW.name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const oldCallback = origW.callback;
|
||||
|
||||
const newW = uiNode.addWidget(
|
||||
type,
|
||||
origW.name,
|
||||
origW.value,
|
||||
function(...args) {
|
||||
const v = args[0];
|
||||
origW.value = v;
|
||||
if (typeof oldCallback === 'function') {
|
||||
oldCallback.call(origW, ...args);
|
||||
}
|
||||
app.canvas.setDirty(true);
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
origW.callback = function(...args) {
|
||||
const v = args[0];
|
||||
if (typeof oldCallback === 'function') {
|
||||
oldCallback.call(this, ...args);
|
||||
}
|
||||
newW.value = v;
|
||||
app.canvas.setDirty(true);
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn('Skipping unsupported widget:', origW, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user