init: first available
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -0,0 +1,15 @@
|
||||
class StringChain:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict:
|
||||
return {
|
||||
'optional': { 'previous': ('STRING', {'forceInput': True, 'default': '', 'multiline': True}) },
|
||||
'required': {
|
||||
'separator': ('STRING', {'default': ', '}),
|
||||
'string': ('STRING', {'default': '', 'multiline': True}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ('STRING',)
|
||||
FUNCTION = 'combine'
|
||||
CATEGORY = 'utils'
|
||||
def combine(self, string: str, separator: str, previous: str = '') -> tuple[str]:
|
||||
return (f'{previous}{separator}{string}',) if previous and string else (previous or string,)
|
||||
@@ -0,0 +1,19 @@
|
||||
class StringConcat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict:
|
||||
return {
|
||||
'optional': {
|
||||
'previous': ('STRING', {'forceInput': True, 'default': '', 'multiline': True}),
|
||||
'string': ('STRING', {'default': '', 'multiline': True})
|
||||
},
|
||||
'required': {
|
||||
'separator': ('STRING', {'default': ', '}),
|
||||
'reverse': ('BOOLEAN', {'default': False})
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ('STRING',)
|
||||
FUNCTION = 'concat'
|
||||
CATEGORY = 'utils'
|
||||
def concat(self, separator: str, reverse: bool, previous: str = '', string: str = '') -> tuple[str]:
|
||||
if reverse: previous, string = string, previous
|
||||
return (f'{previous}{separator}{string}',) if previous and string else (previous or string,)
|
||||
@@ -0,0 +1,27 @@
|
||||
from .common import FlexibleOptionalInputType, AnyType
|
||||
|
||||
optional = FlexibleOptionalInputType(AnyType('STRING'))
|
||||
optional.update({
|
||||
'previous': ('STRING', {'forceInput': True, 'default': '', 'multiline': True})
|
||||
})
|
||||
|
||||
class StringToggle:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict:
|
||||
return {
|
||||
'required': {
|
||||
'separator': ('STRING', {'default': ', '}),
|
||||
'reverse': ('BOOLEAN', {'default': False})
|
||||
},
|
||||
'optional': optional
|
||||
}
|
||||
RETURN_TYPES = ('STRING',)
|
||||
FUNCTION = 'join'
|
||||
CATEGORY = 'utils'
|
||||
def join(self, separator: str, reverse: bool, previous: str = '', **kwargs) -> tuple[str]:
|
||||
arguments = {}
|
||||
for (argtype, unique), value in [(k.split('_'),v) for k,v in kwargs.items() if '_' in k]:
|
||||
arguments.setdefault(argtype, {})[unique] = value
|
||||
strings = ([previous] if previous else []) + [text for unique, text in arguments['text'].items() if arguments['append'][unique]]
|
||||
if reverse: strings = strings[::-1]
|
||||
return (separator.join(strings),)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
from .StringChain import StringChain
|
||||
from .StringConcat import StringConcat
|
||||
from .StringToggle import StringToggle
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
'String Chain': StringChain,
|
||||
'String Concat': StringConcat,
|
||||
'String Toggle': StringToggle,
|
||||
'String Toggle (Multiline)': StringToggle
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'WEB_DIRECTORY']
|
||||
@@ -0,0 +1,31 @@
|
||||
''' Credit to rgthree (https://github.com/rgthree/rgthree-comfy) below '''
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
class FlexibleOptionalInputType(dict):
|
||||
"""A special class to make flexible nodes that pass data to our python handlers.
|
||||
|
||||
Enables both flexible/dynamic input types (like for Any Switch) or a dynamic number of inputs
|
||||
(like for Any Switch, Context Switch, Context Merge, Power Lora Loader, etc).
|
||||
|
||||
Note, for ComfyUI, all that's needed is the `__contains__` override below, which tells ComfyUI
|
||||
that our node will handle the input, regardless of what it is.
|
||||
|
||||
However, with https://github.com/comfyanonymous/ComfyUI/pull/2666 a large change would occur
|
||||
requiring more details on the input itself. There, we need to return a list/tuple where the first
|
||||
item is the type. This can be a real type, or use the AnyType for additional flexibility.
|
||||
|
||||
This should be forwards compatible unless more changes occur in the PR.
|
||||
"""
|
||||
def __init__(self, type):
|
||||
self.type = type
|
||||
|
||||
def __getitem__(self, key):
|
||||
return (self.type, )
|
||||
|
||||
def __contains__(self, key):
|
||||
return True
|
||||
@@ -0,0 +1,46 @@
|
||||
import { app } from "../../scripts/app.js"
|
||||
import { DividerWidget } from "./common.js"
|
||||
import { OVERRIDDEN_SERVER_NODES } from './common.js'
|
||||
|
||||
const upgradeNodeType = (nodeType) => class extends nodeType {
|
||||
constructor() {
|
||||
super(...arguments)
|
||||
this.units = []
|
||||
this.nameUniqueCounter = 0
|
||||
this.addWidget('button', 'Add', null, () => this.addTextUnit())
|
||||
this.addWidget('button', 'Remove', null, () => this.removeTextUnit(this.units.pop()?.id))
|
||||
}
|
||||
configure(info) {
|
||||
super.configure(info)
|
||||
const { widgets_values } = info
|
||||
const units_values = widgets_values.slice(3)
|
||||
units_values.filter((each, index) => {
|
||||
if (typeof each !== 'string') return
|
||||
this.addTextUnit(each, units_values[index-1] ?? true)
|
||||
})
|
||||
}
|
||||
addTextUnit(text='', append=true) {
|
||||
const id = Symbol()
|
||||
const unit = { id, enabled: true, text }
|
||||
const [width,] = this.size
|
||||
Object.assign(this.addCustomWidget(new DividerWidget({ marginTop: 2, marginBottom: 2, thickness: 2 })), { id })
|
||||
Object.assign(this.addWidget('toggle', `append_${this.nameUniqueCounter}`, append, () => unit.enabled = !unit.enabled), { id })
|
||||
Object.assign(this.addWidget('text', `text_${this.nameUniqueCounter++}`, text), { id })
|
||||
this.units.push(unit)
|
||||
this.setSize([width, this.computeSize()[1]])
|
||||
return id
|
||||
}
|
||||
removeTextUnit(id) {
|
||||
this.units = this.units.filter(each => each.id !== id)
|
||||
this.widgets = this.widgets.filter(each => each.id !== id)
|
||||
this.setSize(this.size)
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "string-master.StringToggle",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === "String Toggle")
|
||||
OVERRIDDEN_SERVER_NODES.set(nodeType, upgradeNodeType(nodeType))
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
import { app } from "../../scripts/app.js"
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js"
|
||||
import { DividerWidget } from "./common.js"
|
||||
import { OVERRIDDEN_SERVER_NODES } from './common.js'
|
||||
|
||||
const upgradeNodeType = (nodeType) => class extends nodeType {
|
||||
constructor() {
|
||||
super(...arguments)
|
||||
this.units = []
|
||||
this.nameUniqueCounter = 0
|
||||
this.addWidget('button', 'Add', null, () => this.addTextUnit())
|
||||
this.addWidget('button', 'Remove', null, () => this.removeTextUnit(this.units.pop()?.id))
|
||||
}
|
||||
configure(info) {
|
||||
super.configure(info)
|
||||
const { widgets_values } = info
|
||||
const units_values = widgets_values.slice(3)
|
||||
units_values.filter((each, index) => {
|
||||
if (typeof each !== 'string') return
|
||||
this.addTextUnit(each, units_values[index-1] ?? true)
|
||||
})
|
||||
}
|
||||
addTextUnit(text='', append=true) {
|
||||
const id = Symbol()
|
||||
const unit = { id, enabled: true, text }
|
||||
const [width, height] = this.size
|
||||
Object.assign(this.addCustomWidget(new DividerWidget({ marginTop: 2, marginBottom: 2, thickness: 2 })), { id })
|
||||
Object.assign(this.addWidget('toggle', `append_${this.nameUniqueCounter}`, append, () => unit.enabled = !unit.enabled), { id })
|
||||
Object.assign(ComfyWidgets.STRING(this, `text_${this.nameUniqueCounter++}`, [ 'STRING', { default: unit.text, multiline: true } ], app).widget, { id, isDOMWidget: true })
|
||||
this.units.push(unit)
|
||||
this.setSize([width, height])
|
||||
return id
|
||||
}
|
||||
removeTextUnit(id) {
|
||||
this.units = this.units.filter(each => each.id !== id)
|
||||
this.widgets = this.widgets.filter(each => {
|
||||
if (each.id === id) {
|
||||
if (each.isDOMWidget) each.element.remove()
|
||||
} else return true
|
||||
})
|
||||
this.setSize(this.size)
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "string-master.StringToggleMultiline",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name === "String Toggle (Multiline)")
|
||||
OVERRIDDEN_SERVER_NODES.set(nodeType, upgradeNodeType(nodeType))
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
export const OVERRIDDEN_SERVER_NODES = new Map()
|
||||
|
||||
const registerNodeType = LiteGraph.registerNodeType
|
||||
LiteGraph.registerNodeType = async (nodeId, baseClass) => registerNodeType.call(LiteGraph, nodeId, OVERRIDDEN_SERVER_NODES.get(baseClass) || baseClass)
|
||||
|
||||
export class DividerWidget { // Credit to rgthree
|
||||
options = { serialize: false }
|
||||
value = null
|
||||
name = "divider"
|
||||
|
||||
widgetOptions = {
|
||||
marginTop: 7,
|
||||
marginBottom: 7,
|
||||
marginLeft: 15,
|
||||
marginRight: 15,
|
||||
color: LiteGraph.WIDGET_OUTLINE_COLOR,
|
||||
thickness: 1
|
||||
}
|
||||
|
||||
constructor(widgetOptions) {
|
||||
Object.assign(this.widgetOptions, widgetOptions || {})
|
||||
}
|
||||
|
||||
draw(ctx, node, width, posY, h) {
|
||||
if (this.widgetOptions.thickness) {
|
||||
ctx.strokeStyle = this.widgetOptions.color
|
||||
const x = this.widgetOptions.marginLeft
|
||||
const y = posY + this.widgetOptions.marginTop
|
||||
const w = width - this.widgetOptions.marginLeft - this.widgetOptions.marginRight
|
||||
ctx.stroke(new Path2D(`M ${x} ${y} h ${w}`))
|
||||
}
|
||||
}
|
||||
|
||||
computeSize(width) {
|
||||
return [
|
||||
width,
|
||||
this.widgetOptions.marginTop + this.widgetOptions.marginBottom + this.widgetOptions.thickness
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user