2024-08-15 20:50:25 +00:00
var _ _defProp = Object . defineProperty ;
2024-08-16 19:25:02 +00:00
var _ _name = ( target , value ) => _ _defProp ( target , "name" , { value , configurable : true } ) ;
2024-09-05 22:56:01 +00:00
import { C as ComfyDialog , $ as $el , a as ComfyApp , b as app , L as LGraphCanvas , c as LiteGraph , d as LGraphNode , e as applyTextReplacements , f as ComfyWidgets , g as addValueControlWidgets , D as DraggableList , h as api , i as LGraphGroup , u as useToastStore } from "./index-Dfv2aLsq.js" ;
2024-08-30 23:32:10 +00:00
class ClipspaceDialog extends ComfyDialog {
static {
_ _name ( this , "ClipspaceDialog" ) ;
}
static items = [ ] ;
static instance = null ;
2024-08-15 20:50:25 +00:00
static registerButton ( name , contextPredicate , callback ) {
const item = $el ( "button" , {
type : "button" ,
textContent : name ,
contextPredicate ,
onclick : callback
} ) ;
2024-08-30 23:32:10 +00:00
ClipspaceDialog . items . push ( item ) ;
2024-08-15 20:50:25 +00:00
}
static invalidatePreview ( ) {
if ( ComfyApp . clipspace && ComfyApp . clipspace . imgs && ComfyApp . clipspace . imgs . length > 0 ) {
const img _preview = document . getElementById (
"clipspace_preview"
) ;
if ( img _preview ) {
img _preview . src = ComfyApp . clipspace . imgs [ ComfyApp . clipspace [ "selectedIndex" ] ] . src ;
img _preview . style . maxHeight = "100%" ;
img _preview . style . maxWidth = "100%" ;
}
}
}
static invalidate ( ) {
2024-08-30 23:32:10 +00:00
if ( ClipspaceDialog . instance ) {
const self = ClipspaceDialog . instance ;
2024-08-15 20:50:25 +00:00
const children = $el ( "div.comfy-modal-content" , [
self . createImgSettings ( ) ,
... self . createButtons ( )
] ) ;
if ( self . element ) {
self . element . removeChild ( self . element . firstChild ) ;
self . element . appendChild ( children ) ;
} else {
self . element = $el ( "div.comfy-modal" , { parent : document . body } , [
children
] ) ;
}
if ( self . element . children [ 0 ] . children . length <= 1 ) {
self . element . children [ 0 ] . appendChild (
$el ( "p" , { } , [
"Unable to find the features to edit content of a format stored in the current Clipspace."
] )
) ;
}
2024-08-30 23:32:10 +00:00
ClipspaceDialog . invalidatePreview ( ) ;
2024-08-15 20:50:25 +00:00
}
}
constructor ( ) {
super ( ) ;
}
createButtons ( ) {
const buttons = [ ] ;
2024-08-30 23:32:10 +00:00
for ( let idx in ClipspaceDialog . items ) {
const item = ClipspaceDialog . items [ idx ] ;
2024-08-15 20:50:25 +00:00
if ( ! item . contextPredicate || item . contextPredicate ( ) )
2024-08-30 23:32:10 +00:00
buttons . push ( ClipspaceDialog . items [ idx ] ) ;
2024-08-15 20:50:25 +00:00
}
buttons . push (
$el ( "button" , {
type : "button" ,
textContent : "Close" ,
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
this . close ( ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} )
) ;
return buttons ;
}
createImgSettings ( ) {
if ( ComfyApp . clipspace . imgs ) {
const combo _items = [ ] ;
const imgs = ComfyApp . clipspace . imgs ;
for ( let i = 0 ; i < imgs . length ; i ++ ) {
combo _items . push ( $el ( "option" , { value : i } , [ ` ${ i } ` ] ) ) ;
}
const combo1 = $el (
"select" ,
{
id : "clipspace_img_selector" ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( event ) => {
2024-08-15 20:50:25 +00:00
ComfyApp . clipspace [ "selectedIndex" ] = event . target . selectedIndex ;
2024-08-30 23:32:10 +00:00
ClipspaceDialog . invalidatePreview ( ) ;
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} ,
combo _items
) ;
const row1 = $el ( "tr" , { } , [
$el ( "td" , { } , [ $el ( "font" , { color : "white" } , [ "Select Image" ] ) ] ) ,
$el ( "td" , { } , [ combo1 ] )
] ) ;
const combo2 = $el (
"select" ,
{
id : "clipspace_img_paste_mode" ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( event ) => {
2024-08-15 20:50:25 +00:00
ComfyApp . clipspace [ "img_paste_mode" ] = event . target . value ;
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} ,
[
$el ( "option" , { value : "selected" } , "selected" ) ,
$el ( "option" , { value : "all" } , "all" )
]
) ;
combo2 . value = ComfyApp . clipspace [ "img_paste_mode" ] ;
const row2 = $el ( "tr" , { } , [
$el ( "td" , { } , [ $el ( "font" , { color : "white" } , [ "Paste Mode" ] ) ] ) ,
$el ( "td" , { } , [ combo2 ] )
] ) ;
const td = $el (
"td" ,
{ align : "center" , width : "100px" , height : "100px" , colSpan : "2" } ,
2024-08-16 19:25:02 +00:00
[ $el ( "img" , { id : "clipspace_preview" , ondragstart : /* @__PURE__ */ _ _name ( ( ) => false , "ondragstart" ) } , [ ] ) ]
2024-08-15 20:50:25 +00:00
) ;
const row3 = $el ( "tr" , { } , [ td ] ) ;
return $el ( "table" , { } , [ row1 , row2 , row3 ] ) ;
} else {
return [ ] ;
}
}
createImgPreview ( ) {
if ( ComfyApp . clipspace . imgs ) {
2024-08-16 19:25:02 +00:00
return $el ( "img" , { id : "clipspace_preview" , ondragstart : /* @__PURE__ */ _ _name ( ( ) => false , "ondragstart" ) } ) ;
2024-08-15 20:50:25 +00:00
} else return [ ] ;
}
show ( ) {
const img _preview = document . getElementById ( "clipspace_preview" ) ;
2024-08-30 23:32:10 +00:00
ClipspaceDialog . invalidate ( ) ;
2024-08-15 20:50:25 +00:00
this . element . style . display = "block" ;
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : "Comfy.Clipspace" ,
init ( app2 ) {
app2 . openClipspace = function ( ) {
if ( ! ClipspaceDialog . instance ) {
ClipspaceDialog . instance = new ClipspaceDialog ( ) ;
ComfyApp . clipspace _invalidate _handler = ClipspaceDialog . invalidate ;
}
if ( ComfyApp . clipspace ) {
ClipspaceDialog . instance . show ( ) ;
} else app2 . ui . dialog . show ( "Clipspace is Empty!" ) ;
} ;
}
} ) ;
window . comfyAPI = window . comfyAPI || { } ;
window . comfyAPI . clipspace = window . comfyAPI . clipspace || { } ;
window . comfyAPI . clipspace . ClipspaceDialog = ClipspaceDialog ;
const colorPalettes = {
dark : {
id : "dark" ,
name : "Dark (Default)" ,
colors : {
node _slot : {
CLIP : "#FFD500" ,
// bright yellow
CLIP _VISION : "#A8DADC" ,
// light blue-gray
CLIP _VISION _OUTPUT : "#ad7452" ,
// rusty brown-orange
CONDITIONING : "#FFA931" ,
// vibrant orange-yellow
CONTROL _NET : "#6EE7B7" ,
// soft mint green
IMAGE : "#64B5F6" ,
// bright sky blue
LATENT : "#FF9CF9" ,
// light pink-purple
MASK : "#81C784" ,
// muted green
MODEL : "#B39DDB" ,
// light lavender-purple
STYLE _MODEL : "#C2FFAE" ,
// light green-yellow
VAE : "#FF6E6E" ,
// bright red
NOISE : "#B0B0B0" ,
// gray
GUIDER : "#66FFFF" ,
// cyan
SAMPLER : "#ECB4B4" ,
// very soft red
SIGMAS : "#CDFFCD" ,
// soft lime green
TAESD : "#DCC274"
// cheesecake
} ,
litegraph _base : {
BACKGROUND _IMAGE : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=" ,
CLEAR _BACKGROUND _COLOR : "#222" ,
NODE _TITLE _COLOR : "#999" ,
NODE _SELECTED _TITLE _COLOR : "#FFF" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#AAA" ,
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#333" ,
NODE _DEFAULT _BGCOLOR : "#353535" ,
NODE _DEFAULT _BOXCOLOR : "#666" ,
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#FFF" ,
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.5)" ,
DEFAULT _GROUP _FONT : 24 ,
WIDGET _BGCOLOR : "#222" ,
WIDGET _OUTLINE _COLOR : "#666" ,
WIDGET _TEXT _COLOR : "#DDD" ,
WIDGET _SECONDARY _TEXT _COLOR : "#999" ,
LINK _COLOR : "#9A9" ,
EVENT _LINK _COLOR : "#A86" ,
CONNECTING _LINK _COLOR : "#AFA"
} ,
comfy _base : {
"fg-color" : "#fff" ,
"bg-color" : "#202020" ,
"comfy-menu-bg" : "#353535" ,
"comfy-input-bg" : "#222" ,
"input-text" : "#ddd" ,
"descrip-text" : "#999" ,
"drag-text" : "#ccc" ,
"error-text" : "#ff4444" ,
"border-color" : "#4e4e4e" ,
"tr-even-bg-color" : "#222" ,
"tr-odd-bg-color" : "#353535" ,
"content-bg" : "#4e4e4e" ,
"content-fg" : "#fff" ,
"content-hover-bg" : "#222" ,
"content-hover-fg" : "#fff"
}
}
} ,
light : {
id : "light" ,
name : "Light" ,
colors : {
node _slot : {
CLIP : "#FFA726" ,
// orange
CLIP _VISION : "#5C6BC0" ,
// indigo
CLIP _VISION _OUTPUT : "#8D6E63" ,
// brown
CONDITIONING : "#EF5350" ,
// red
CONTROL _NET : "#66BB6A" ,
// green
IMAGE : "#42A5F5" ,
// blue
LATENT : "#AB47BC" ,
// purple
MASK : "#9CCC65" ,
// light green
MODEL : "#7E57C2" ,
// deep purple
STYLE _MODEL : "#D4E157" ,
// lime
VAE : "#FF7043"
// deep orange
} ,
litegraph _base : {
BACKGROUND _IMAGE : "data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==" ,
CLEAR _BACKGROUND _COLOR : "lightgray" ,
NODE _TITLE _COLOR : "#222" ,
NODE _SELECTED _TITLE _COLOR : "#000" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#444" ,
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#F7F7F7" ,
NODE _DEFAULT _BGCOLOR : "#F5F5F5" ,
NODE _DEFAULT _BOXCOLOR : "#CCC" ,
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#000" ,
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.1)" ,
DEFAULT _GROUP _FONT : 24 ,
WIDGET _BGCOLOR : "#D4D4D4" ,
WIDGET _OUTLINE _COLOR : "#999" ,
WIDGET _TEXT _COLOR : "#222" ,
WIDGET _SECONDARY _TEXT _COLOR : "#555" ,
LINK _COLOR : "#4CAF50" ,
EVENT _LINK _COLOR : "#FF9800" ,
CONNECTING _LINK _COLOR : "#2196F3"
} ,
comfy _base : {
"fg-color" : "#222" ,
"bg-color" : "#DDD" ,
"comfy-menu-bg" : "#F5F5F5" ,
"comfy-input-bg" : "#C9C9C9" ,
"input-text" : "#222" ,
"descrip-text" : "#444" ,
"drag-text" : "#555" ,
"error-text" : "#F44336" ,
"border-color" : "#888" ,
"tr-even-bg-color" : "#f9f9f9" ,
"tr-odd-bg-color" : "#fff" ,
"content-bg" : "#e0e0e0" ,
"content-fg" : "#222" ,
"content-hover-bg" : "#adadad" ,
"content-hover-fg" : "#222"
}
}
} ,
solarized : {
id : "solarized" ,
name : "Solarized" ,
colors : {
node _slot : {
CLIP : "#2AB7CA" ,
// light blue
CLIP _VISION : "#6c71c4" ,
// blue violet
CLIP _VISION _OUTPUT : "#859900" ,
// olive green
CONDITIONING : "#d33682" ,
// magenta
CONTROL _NET : "#d1ffd7" ,
// light mint green
IMAGE : "#5940bb" ,
// deep blue violet
LATENT : "#268bd2" ,
// blue
MASK : "#CCC9E7" ,
// light purple-gray
MODEL : "#dc322f" ,
// red
STYLE _MODEL : "#1a998a" ,
// teal
UPSCALE _MODEL : "#054A29" ,
// dark green
VAE : "#facfad"
// light pink-orange
} ,
litegraph _base : {
NODE _TITLE _COLOR : "#fdf6e3" ,
// Base3
NODE _SELECTED _TITLE _COLOR : "#A9D400" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#657b83" ,
// Base00
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#094656" ,
NODE _DEFAULT _BGCOLOR : "#073642" ,
// Base02
NODE _DEFAULT _BOXCOLOR : "#839496" ,
// Base0
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#fdf6e3" ,
// Base3
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.5)" ,
DEFAULT _GROUP _FONT : 24 ,
WIDGET _BGCOLOR : "#002b36" ,
// Base03
WIDGET _OUTLINE _COLOR : "#839496" ,
// Base0
WIDGET _TEXT _COLOR : "#fdf6e3" ,
// Base3
WIDGET _SECONDARY _TEXT _COLOR : "#93a1a1" ,
// Base1
LINK _COLOR : "#2aa198" ,
// Solarized Cyan
EVENT _LINK _COLOR : "#268bd2" ,
// Solarized Blue
CONNECTING _LINK _COLOR : "#859900"
// Solarized Green
} ,
comfy _base : {
"fg-color" : "#fdf6e3" ,
// Base3
"bg-color" : "#002b36" ,
// Base03
"comfy-menu-bg" : "#073642" ,
// Base02
"comfy-input-bg" : "#002b36" ,
// Base03
"input-text" : "#93a1a1" ,
// Base1
"descrip-text" : "#586e75" ,
// Base01
"drag-text" : "#839496" ,
// Base0
"error-text" : "#dc322f" ,
// Solarized Red
"border-color" : "#657b83" ,
// Base00
"tr-even-bg-color" : "#002b36" ,
"tr-odd-bg-color" : "#073642" ,
"content-bg" : "#657b83" ,
"content-fg" : "#fdf6e3" ,
"content-hover-bg" : "#002b36" ,
"content-hover-fg" : "#fdf6e3"
}
}
} ,
arc : {
id : "arc" ,
name : "Arc" ,
colors : {
node _slot : {
BOOLEAN : "" ,
CLIP : "#eacb8b" ,
CLIP _VISION : "#A8DADC" ,
CLIP _VISION _OUTPUT : "#ad7452" ,
CONDITIONING : "#cf876f" ,
CONTROL _NET : "#00d78d" ,
CONTROL _NET _WEIGHTS : "" ,
FLOAT : "" ,
GLIGEN : "" ,
IMAGE : "#80a1c0" ,
IMAGEUPLOAD : "" ,
INT : "" ,
LATENT : "#b38ead" ,
LATENT _KEYFRAME : "" ,
MASK : "#a3bd8d" ,
MODEL : "#8978a7" ,
SAMPLER : "" ,
SIGMAS : "" ,
STRING : "" ,
STYLE _MODEL : "#C2FFAE" ,
T2I _ADAPTER _WEIGHTS : "" ,
TAESD : "#DCC274" ,
TIMESTEP _KEYFRAME : "" ,
UPSCALE _MODEL : "" ,
VAE : "#be616b"
} ,
litegraph _base : {
BACKGROUND _IMAGE : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcklEQVR4nO3YMUoDARgF4RfxBqZI6/0vZqFn0MYtrLIQMFN8U6V4LAtD+Jm9XG/v30OGl2e/AP7yevz4+vx45nvgF/+QGITEICQGITEIiUFIjNNC3q43u3/YnRJyPOzeQ+0e220nhRzReC8e7R7bbdvl+Jal1Bs46jEIiUFIDEJiEBKDkBhKPbZT6qHdptRTu02p53DUYxASg5AYhMQgJAYhMZR6bKfUQ7tNqad2m1LP4ajHICQGITEIiUFIDEJiKPXYTqmHdptST+02pZ7DUY9BSAxCYhASg5AYhMRQ6rGdUg/tNqWe2m1KPYejHoOQGITEICQGITEIiaHUYzulHtptSj2125R6Dkc9BiExCIlBSAxCYhASQ6nHdko9tNuUemq3KfUcjnoMQmIQEoOQGITEICSGUo/tlHpotyn11G5T6jkc9RiExCAkBiExCIlBSAylHtsp9dBuU+qp3abUczjqMQiJQUgMQmIQEoOQGITE+AHFISNQrFTGuwAAAABJRU5ErkJggg==" ,
CLEAR _BACKGROUND _COLOR : "#2b2f38" ,
NODE _TITLE _COLOR : "#b2b7bd" ,
NODE _SELECTED _TITLE _COLOR : "#FFF" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#AAA" ,
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#2b2f38" ,
NODE _DEFAULT _BGCOLOR : "#242730" ,
NODE _DEFAULT _BOXCOLOR : "#6e7581" ,
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#FFF" ,
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.5)" ,
DEFAULT _GROUP _FONT : 22 ,
WIDGET _BGCOLOR : "#2b2f38" ,
WIDGET _OUTLINE _COLOR : "#6e7581" ,
WIDGET _TEXT _COLOR : "#DDD" ,
WIDGET _SECONDARY _TEXT _COLOR : "#b2b7bd" ,
LINK _COLOR : "#9A9" ,
EVENT _LINK _COLOR : "#A86" ,
CONNECTING _LINK _COLOR : "#AFA"
} ,
comfy _base : {
"fg-color" : "#fff" ,
"bg-color" : "#2b2f38" ,
"comfy-menu-bg" : "#242730" ,
"comfy-input-bg" : "#2b2f38" ,
"input-text" : "#ddd" ,
"descrip-text" : "#b2b7bd" ,
"drag-text" : "#ccc" ,
"error-text" : "#ff4444" ,
"border-color" : "#6e7581" ,
"tr-even-bg-color" : "#2b2f38" ,
"tr-odd-bg-color" : "#242730" ,
"content-bg" : "#6e7581" ,
"content-fg" : "#fff" ,
"content-hover-bg" : "#2b2f38" ,
"content-hover-fg" : "#fff"
}
}
} ,
nord : {
id : "nord" ,
name : "Nord" ,
colors : {
node _slot : {
BOOLEAN : "" ,
CLIP : "#eacb8b" ,
CLIP _VISION : "#A8DADC" ,
CLIP _VISION _OUTPUT : "#ad7452" ,
CONDITIONING : "#cf876f" ,
CONTROL _NET : "#00d78d" ,
CONTROL _NET _WEIGHTS : "" ,
FLOAT : "" ,
GLIGEN : "" ,
IMAGE : "#80a1c0" ,
IMAGEUPLOAD : "" ,
INT : "" ,
LATENT : "#b38ead" ,
LATENT _KEYFRAME : "" ,
MASK : "#a3bd8d" ,
MODEL : "#8978a7" ,
SAMPLER : "" ,
SIGMAS : "" ,
STRING : "" ,
STYLE _MODEL : "#C2FFAE" ,
T2I _ADAPTER _WEIGHTS : "" ,
TAESD : "#DCC274" ,
TIMESTEP _KEYFRAME : "" ,
UPSCALE _MODEL : "" ,
VAE : "#be616b"
} ,
litegraph _base : {
BACKGROUND _IMAGE : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFu2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjUwNDFhMmZjLTEzNzQtMTk0ZC1hZWY4LTYxMzM1MTVmNjUwMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDQxYTJmYy0xMzc0LTE5NGQtYWVmOC02MTMzNTE1ZjY1MDAiIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz73jWg/AAAAyUlEQVR42u3WKwoAIBRFQRdiMb1idv9Lsxn9gEFw4Dbb8JCTojbbXEJwjJVL2HKwYMGCBQuWLbDmjr+9zrBGjHl1WVcvy2DBggULFizTWQpewSt4HzwsgwULFiwFr7MUvMtS8D54WLBgGSxYCl7BK3iXZbBgwYIFC5bpLAWv4BW8Dx6WwYIFC5aC11kK3mUpeB88LFiwDBYsBa/gFbzLMliwYMGCBct0loJX8AreBw/LYMGCBUvB6ywF77IUvA8eFixYBgsWrNfWAZPltufdad+1AAAAAElFTkSuQmCC" ,
CLEAR _BACKGROUND _COLOR : "#212732" ,
NODE _TITLE _COLOR : "#999" ,
NODE _SELECTED _TITLE _COLOR : "#e5eaf0" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#bcc2c8" ,
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#2e3440" ,
NODE _DEFAULT _BGCOLOR : "#161b22" ,
NODE _DEFAULT _BOXCOLOR : "#545d70" ,
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#e5eaf0" ,
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.5)" ,
DEFAULT _GROUP _FONT : 24 ,
WIDGET _BGCOLOR : "#2e3440" ,
WIDGET _OUTLINE _COLOR : "#545d70" ,
WIDGET _TEXT _COLOR : "#bcc2c8" ,
WIDGET _SECONDARY _TEXT _COLOR : "#999" ,
LINK _COLOR : "#9A9" ,
EVENT _LINK _COLOR : "#A86" ,
CONNECTING _LINK _COLOR : "#AFA"
} ,
comfy _base : {
"fg-color" : "#e5eaf0" ,
"bg-color" : "#2e3440" ,
"comfy-menu-bg" : "#161b22" ,
"comfy-input-bg" : "#2e3440" ,
"input-text" : "#bcc2c8" ,
"descrip-text" : "#999" ,
"drag-text" : "#ccc" ,
"error-text" : "#ff4444" ,
"border-color" : "#545d70" ,
"tr-even-bg-color" : "#2e3440" ,
"tr-odd-bg-color" : "#161b22" ,
"content-bg" : "#545d70" ,
"content-fg" : "#e5eaf0" ,
"content-hover-bg" : "#2e3440" ,
"content-hover-fg" : "#e5eaf0"
}
}
} ,
github : {
id : "github" ,
name : "Github" ,
colors : {
node _slot : {
BOOLEAN : "" ,
CLIP : "#eacb8b" ,
CLIP _VISION : "#A8DADC" ,
CLIP _VISION _OUTPUT : "#ad7452" ,
CONDITIONING : "#cf876f" ,
CONTROL _NET : "#00d78d" ,
CONTROL _NET _WEIGHTS : "" ,
FLOAT : "" ,
GLIGEN : "" ,
IMAGE : "#80a1c0" ,
IMAGEUPLOAD : "" ,
INT : "" ,
LATENT : "#b38ead" ,
LATENT _KEYFRAME : "" ,
MASK : "#a3bd8d" ,
MODEL : "#8978a7" ,
SAMPLER : "" ,
SIGMAS : "" ,
STRING : "" ,
STYLE _MODEL : "#C2FFAE" ,
T2I _ADAPTER _WEIGHTS : "" ,
TAESD : "#DCC274" ,
TIMESTEP _KEYFRAME : "" ,
UPSCALE _MODEL : "" ,
VAE : "#be616b"
} ,
litegraph _base : {
BACKGROUND _IMAGE : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==" ,
CLEAR _BACKGROUND _COLOR : "#040506" ,
NODE _TITLE _COLOR : "#999" ,
NODE _SELECTED _TITLE _COLOR : "#e5eaf0" ,
NODE _TEXT _SIZE : 14 ,
NODE _TEXT _COLOR : "#bcc2c8" ,
NODE _SUBTEXT _SIZE : 12 ,
NODE _DEFAULT _COLOR : "#161b22" ,
NODE _DEFAULT _BGCOLOR : "#13171d" ,
NODE _DEFAULT _BOXCOLOR : "#30363d" ,
NODE _DEFAULT _SHAPE : "box" ,
NODE _BOX _OUTLINE _COLOR : "#e5eaf0" ,
DEFAULT _SHADOW _COLOR : "rgba(0,0,0,0.5)" ,
DEFAULT _GROUP _FONT : 24 ,
WIDGET _BGCOLOR : "#161b22" ,
WIDGET _OUTLINE _COLOR : "#30363d" ,
WIDGET _TEXT _COLOR : "#bcc2c8" ,
WIDGET _SECONDARY _TEXT _COLOR : "#999" ,
LINK _COLOR : "#9A9" ,
EVENT _LINK _COLOR : "#A86" ,
CONNECTING _LINK _COLOR : "#AFA"
} ,
comfy _base : {
"fg-color" : "#e5eaf0" ,
"bg-color" : "#161b22" ,
"comfy-menu-bg" : "#13171d" ,
"comfy-input-bg" : "#161b22" ,
"input-text" : "#bcc2c8" ,
"descrip-text" : "#999" ,
"drag-text" : "#ccc" ,
"error-text" : "#ff4444" ,
"border-color" : "#30363d" ,
"tr-even-bg-color" : "#161b22" ,
"tr-odd-bg-color" : "#13171d" ,
"content-bg" : "#30363d" ,
"content-fg" : "#e5eaf0" ,
"content-hover-bg" : "#161b22" ,
"content-hover-fg" : "#e5eaf0"
}
}
}
} ;
const id$4 = "Comfy.ColorPalette" ;
const idCustomColorPalettes = "Comfy.CustomColorPalettes" ;
const defaultColorPaletteId = "dark" ;
const els = {
select : null
} ;
app . registerExtension ( {
name : id$4 ,
init ( ) {
LGraphCanvas . prototype . updateBackground = function ( image , clearBackgroundColor ) {
this . _bg _img = new Image ( ) ;
this . _bg _img . name = image ;
this . _bg _img . src = image ;
this . _bg _img . onload = ( ) => {
this . draw ( true , true ) ;
} ;
this . background _image = image ;
this . clear _background = true ;
this . clear _background _color = clearBackgroundColor ;
this . _pattern = null ;
} ;
} ,
addCustomNodeDefs ( node _defs ) {
2024-08-16 19:25:02 +00:00
const sortObjectKeys = /* @__PURE__ */ _ _name ( ( unordered ) => {
2024-08-15 20:50:25 +00:00
return Object . keys ( unordered ) . sort ( ) . reduce ( ( obj , key ) => {
obj [ key ] = unordered [ key ] ;
return obj ;
} , { } ) ;
2024-08-16 19:25:02 +00:00
} , "sortObjectKeys" ) ;
2024-08-15 20:50:25 +00:00
function getSlotTypes ( ) {
var types = [ ] ;
const defs = node _defs ;
for ( const nodeId in defs ) {
const nodeData = defs [ nodeId ] ;
var inputs = nodeData [ "input" ] [ "required" ] ;
if ( nodeData [ "input" ] [ "optional" ] !== void 0 ) {
inputs = Object . assign (
{ } ,
nodeData [ "input" ] [ "required" ] ,
nodeData [ "input" ] [ "optional" ]
) ;
}
for ( const inputName in inputs ) {
const inputData = inputs [ inputName ] ;
const type = inputData [ 0 ] ;
if ( ! Array . isArray ( type ) ) {
types . push ( type ) ;
}
}
for ( const o in nodeData [ "output" ] ) {
const output = nodeData [ "output" ] [ o ] ;
types . push ( output ) ;
}
}
return types ;
}
2024-08-16 19:25:02 +00:00
_ _name ( getSlotTypes , "getSlotTypes" ) ;
2024-08-15 20:50:25 +00:00
function completeColorPalette ( colorPalette ) {
var types = getSlotTypes ( ) ;
for ( const type of types ) {
if ( ! colorPalette . colors . node _slot [ type ] ) {
colorPalette . colors . node _slot [ type ] = "" ;
}
}
colorPalette . colors . node _slot = sortObjectKeys (
colorPalette . colors . node _slot
) ;
return colorPalette ;
}
2024-08-16 19:25:02 +00:00
_ _name ( completeColorPalette , "completeColorPalette" ) ;
2024-08-30 23:32:10 +00:00
const getColorPaletteTemplate = /* @__PURE__ */ _ _name ( async ( ) => {
2024-08-15 20:50:25 +00:00
let colorPalette = {
id : "my_color_palette_unique_id" ,
name : "My Color Palette" ,
colors : {
node _slot : { } ,
litegraph _base : { } ,
comfy _base : { }
}
} ;
const defaultColorPalette = colorPalettes [ defaultColorPaletteId ] ;
for ( const key in defaultColorPalette . colors . litegraph _base ) {
if ( ! colorPalette . colors . litegraph _base [ key ] ) {
colorPalette . colors . litegraph _base [ key ] = "" ;
}
}
for ( const key in defaultColorPalette . colors . comfy _base ) {
if ( ! colorPalette . colors . comfy _base [ key ] ) {
colorPalette . colors . comfy _base [ key ] = "" ;
}
}
return completeColorPalette ( colorPalette ) ;
2024-08-30 23:32:10 +00:00
} , "getColorPaletteTemplate" ) ;
2024-08-16 19:25:02 +00:00
const getCustomColorPalettes = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
return app . ui . settings . getSettingValue ( idCustomColorPalettes , { } ) ;
2024-08-16 19:25:02 +00:00
} , "getCustomColorPalettes" ) ;
const setCustomColorPalettes = /* @__PURE__ */ _ _name ( ( customColorPalettes ) => {
2024-08-15 20:50:25 +00:00
return app . ui . settings . setSettingValue (
idCustomColorPalettes ,
customColorPalettes
) ;
2024-08-16 19:25:02 +00:00
} , "setCustomColorPalettes" ) ;
2024-08-30 23:32:10 +00:00
const addCustomColorPalette = /* @__PURE__ */ _ _name ( async ( colorPalette ) => {
2024-08-15 20:50:25 +00:00
if ( typeof colorPalette !== "object" ) {
alert ( "Invalid color palette." ) ;
return ;
}
if ( ! colorPalette . id ) {
alert ( "Color palette missing id." ) ;
return ;
}
if ( ! colorPalette . name ) {
alert ( "Color palette missing name." ) ;
return ;
}
if ( ! colorPalette . colors ) {
alert ( "Color palette missing colors." ) ;
return ;
}
if ( colorPalette . colors . node _slot && typeof colorPalette . colors . node _slot !== "object" ) {
alert ( "Invalid color palette colors.node_slot." ) ;
return ;
}
const customColorPalettes = getCustomColorPalettes ( ) ;
customColorPalettes [ colorPalette . id ] = colorPalette ;
setCustomColorPalettes ( customColorPalettes ) ;
for ( const option of els . select . childNodes ) {
if ( option . value === "custom_" + colorPalette . id ) {
els . select . removeChild ( option ) ;
}
}
els . select . append (
$el ( "option" , {
textContent : colorPalette . name + " (custom)" ,
value : "custom_" + colorPalette . id ,
selected : true
} )
) ;
setColorPalette ( "custom_" + colorPalette . id ) ;
2024-08-30 23:32:10 +00:00
await loadColorPalette ( colorPalette ) ;
} , "addCustomColorPalette" ) ;
const deleteCustomColorPalette = /* @__PURE__ */ _ _name ( async ( colorPaletteId ) => {
2024-08-15 20:50:25 +00:00
const customColorPalettes = getCustomColorPalettes ( ) ;
delete customColorPalettes [ colorPaletteId ] ;
setCustomColorPalettes ( customColorPalettes ) ;
for ( const opt of els . select . childNodes ) {
const option = opt ;
if ( option . value === defaultColorPaletteId ) {
option . selected = true ;
}
if ( option . value === "custom_" + colorPaletteId ) {
els . select . removeChild ( option ) ;
}
}
setColorPalette ( defaultColorPaletteId ) ;
2024-08-30 23:32:10 +00:00
await loadColorPalette ( getColorPalette ( ) ) ;
} , "deleteCustomColorPalette" ) ;
const loadColorPalette = /* @__PURE__ */ _ _name ( async ( colorPalette ) => {
colorPalette = await completeColorPalette ( colorPalette ) ;
2024-08-15 20:50:25 +00:00
if ( colorPalette . colors ) {
if ( colorPalette . colors . node _slot ) {
Object . assign (
// @ts-expect-error
app . canvas . default _connection _color _byType ,
colorPalette . colors . node _slot
) ;
Object . assign (
LGraphCanvas . link _type _colors ,
colorPalette . colors . node _slot
) ;
}
if ( colorPalette . colors . litegraph _base ) {
app . canvas . node _title _color = colorPalette . colors . litegraph _base . NODE _TITLE _COLOR ;
app . canvas . default _link _color = colorPalette . colors . litegraph _base . LINK _COLOR ;
for ( const key in colorPalette . colors . litegraph _base ) {
if ( colorPalette . colors . litegraph _base . hasOwnProperty ( key ) && LiteGraph . hasOwnProperty ( key ) ) {
LiteGraph [ key ] = colorPalette . colors . litegraph _base [ key ] ;
}
}
}
if ( colorPalette . colors . comfy _base ) {
const rootStyle = document . documentElement . style ;
for ( const key in colorPalette . colors . comfy _base ) {
rootStyle . setProperty (
"--" + key ,
colorPalette . colors . comfy _base [ key ]
) ;
}
}
app . canvas . draw ( true , true ) ;
}
2024-08-30 23:32:10 +00:00
} , "loadColorPalette" ) ;
2024-08-16 19:25:02 +00:00
const getColorPalette = /* @__PURE__ */ _ _name ( ( colorPaletteId ) => {
2024-08-15 20:50:25 +00:00
if ( ! colorPaletteId ) {
colorPaletteId = app . ui . settings . getSettingValue (
id$4 ,
defaultColorPaletteId
) ;
}
if ( colorPaletteId . startsWith ( "custom_" ) ) {
colorPaletteId = colorPaletteId . substr ( 7 ) ;
let customColorPalettes = getCustomColorPalettes ( ) ;
if ( customColorPalettes [ colorPaletteId ] ) {
return customColorPalettes [ colorPaletteId ] ;
}
}
return colorPalettes [ colorPaletteId ] ;
2024-08-16 19:25:02 +00:00
} , "getColorPalette" ) ;
const setColorPalette = /* @__PURE__ */ _ _name ( ( colorPaletteId ) => {
2024-08-15 20:50:25 +00:00
app . ui . settings . setSettingValue ( id$4 , colorPaletteId ) ;
2024-08-16 19:25:02 +00:00
} , "setColorPalette" ) ;
2024-08-15 20:50:25 +00:00
const fileInput = $el ( "input" , {
type : "file" ,
accept : ".json" ,
style : { display : "none" } ,
parent : document . body ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
const file2 = fileInput . files [ 0 ] ;
if ( file2 . type === "application/json" || file2 . name . endsWith ( ".json" ) ) {
const reader = new FileReader ( ) ;
2024-08-30 23:32:10 +00:00
reader . onload = async ( ) => {
await addCustomColorPalette ( JSON . parse ( reader . result ) ) ;
} ;
2024-08-15 20:50:25 +00:00
reader . readAsText ( file2 ) ;
}
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} ) ;
app . ui . settings . addSetting ( {
id : id$4 ,
2024-08-21 04:00:49 +00:00
category : [ "Comfy" , "ColorPalette" ] ,
2024-08-15 20:50:25 +00:00
name : "Color Palette" ,
2024-08-16 19:25:02 +00:00
type : /* @__PURE__ */ _ _name ( ( name , setter , value ) => {
2024-08-15 20:50:25 +00:00
const options = [
... Object . values ( colorPalettes ) . map (
( c ) => $el ( "option" , {
textContent : c . name ,
value : c . id ,
selected : c . id === value
} )
) ,
... Object . values ( getCustomColorPalettes ( ) ) . map (
( c ) => $el ( "option" , {
textContent : ` ${ c . name } (custom) ` ,
value : ` custom_ ${ c . id } ` ,
selected : ` custom_ ${ c . id } ` === value
} )
)
] ;
els . select = $el (
"select" ,
{
style : {
marginBottom : "0.15rem" ,
width : "100%"
} ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
setter ( e . target . value ) ;
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} ,
options
) ;
return $el ( "tr" , [
$el ( "td" , [
els . select ,
$el (
"div" ,
{
style : {
display : "grid" ,
gap : "4px" ,
gridAutoFlow : "column"
}
} ,
[
$el ( "input" , {
type : "button" ,
value : "Export" ,
2024-08-30 23:32:10 +00:00
onclick : /* @__PURE__ */ _ _name ( async ( ) => {
2024-08-15 20:50:25 +00:00
const colorPaletteId = app . ui . settings . getSettingValue (
id$4 ,
defaultColorPaletteId
) ;
2024-08-30 23:32:10 +00:00
const colorPalette = await completeColorPalette (
2024-08-15 20:50:25 +00:00
getColorPalette ( colorPaletteId )
) ;
const json = JSON . stringify ( colorPalette , null , 2 ) ;
const blob = new Blob ( [ json ] , { type : "application/json" } ) ;
const url = URL . createObjectURL ( blob ) ;
const a = $el ( "a" , {
href : url ,
download : colorPaletteId + ".json" ,
style : { display : "none" } ,
parent : document . body
} ) ;
a . click ( ) ;
setTimeout ( function ( ) {
a . remove ( ) ;
window . URL . revokeObjectURL ( url ) ;
} , 0 ) ;
2024-08-30 23:32:10 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} ) ,
$el ( "input" , {
type : "button" ,
value : "Import" ,
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
fileInput . click ( ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} ) ,
$el ( "input" , {
type : "button" ,
value : "Template" ,
2024-08-30 23:32:10 +00:00
onclick : /* @__PURE__ */ _ _name ( async ( ) => {
const colorPalette = await getColorPaletteTemplate ( ) ;
2024-08-15 20:50:25 +00:00
const json = JSON . stringify ( colorPalette , null , 2 ) ;
const blob = new Blob ( [ json ] , { type : "application/json" } ) ;
const url = URL . createObjectURL ( blob ) ;
const a = $el ( "a" , {
href : url ,
download : "color_palette.json" ,
style : { display : "none" } ,
parent : document . body
} ) ;
a . click ( ) ;
setTimeout ( function ( ) {
a . remove ( ) ;
window . URL . revokeObjectURL ( url ) ;
} , 0 ) ;
2024-08-30 23:32:10 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} ) ,
$el ( "input" , {
type : "button" ,
value : "Delete" ,
2024-08-30 23:32:10 +00:00
onclick : /* @__PURE__ */ _ _name ( async ( ) => {
2024-08-15 20:50:25 +00:00
let colorPaletteId = app . ui . settings . getSettingValue (
id$4 ,
defaultColorPaletteId
) ;
if ( colorPalettes [ colorPaletteId ] ) {
alert ( "You cannot delete a built-in color palette." ) ;
return ;
}
if ( colorPaletteId . startsWith ( "custom_" ) ) {
colorPaletteId = colorPaletteId . substr ( 7 ) ;
}
2024-08-30 23:32:10 +00:00
await deleteCustomColorPalette ( colorPaletteId ) ;
} , "onclick" )
2024-08-15 20:50:25 +00:00
} )
]
)
] )
] ) ;
2024-08-16 19:25:02 +00:00
} , "type" ) ,
2024-08-15 20:50:25 +00:00
defaultValue : defaultColorPaletteId ,
2024-08-30 23:32:10 +00:00
async onChange ( value ) {
if ( ! value ) {
return ;
}
let palette = colorPalettes [ value ] ;
if ( palette ) {
await loadColorPalette ( palette ) ;
} else if ( value . startsWith ( "custom_" ) ) {
value = value . substr ( 7 ) ;
let customColorPalettes = getCustomColorPalettes ( ) ;
if ( customColorPalettes [ value ] ) {
palette = customColorPalettes [ value ] ;
await loadColorPalette ( customColorPalettes [ value ] ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
let { BACKGROUND _IMAGE , CLEAR _BACKGROUND _COLOR } = palette . colors . litegraph _base ;
if ( BACKGROUND _IMAGE === void 0 || CLEAR _BACKGROUND _COLOR === void 0 ) {
const base = colorPalettes [ "dark" ] . colors . litegraph _base ;
BACKGROUND _IMAGE = base . BACKGROUND _IMAGE ;
CLEAR _BACKGROUND _COLOR = base . CLEAR _BACKGROUND _COLOR ;
}
app . canvas . updateBackground ( BACKGROUND _IMAGE , CLEAR _BACKGROUND _COLOR ) ;
2024-08-15 20:50:25 +00:00
}
} ) ;
}
} ) ;
const ext$2 = {
name : "Comfy.ContextMenuFilter" ,
init ( ) {
const ctxMenu = LiteGraph . ContextMenu ;
LiteGraph . ContextMenu = function ( values , options ) {
2024-08-30 23:32:10 +00:00
const ctx = new ctxMenu ( values , options ) ;
if ( options ? . className === "dark" && values ? . length > 4 ) {
2024-08-15 20:50:25 +00:00
const filter = document . createElement ( "input" ) ;
filter . classList . add ( "comfy-context-menu-filter" ) ;
filter . placeholder = "Filter list" ;
2024-08-30 23:32:10 +00:00
ctx . root . prepend ( filter ) ;
2024-08-15 20:50:25 +00:00
const items = Array . from (
2024-08-30 23:32:10 +00:00
ctx . root . querySelectorAll ( ".litemenu-entry" )
2024-08-15 20:50:25 +00:00
) ;
let displayedItems = [ ... items ] ;
let itemCount = displayedItems . length ;
requestAnimationFrame ( ( ) => {
const currentNode = LGraphCanvas . active _canvas . current _node ;
2024-08-30 23:32:10 +00:00
const clickedComboValue = currentNode . widgets ? . filter (
2024-08-15 20:50:25 +00:00
( w ) => w . type === "combo" && w . options . values . length === values . length
) . find (
( w ) => w . options . values . every ( ( v , i ) => v === values [ i ] )
2024-08-30 23:32:10 +00:00
) ? . value ;
2024-08-15 20:50:25 +00:00
let selectedIndex = clickedComboValue ? values . findIndex ( ( v ) => v === clickedComboValue ) : 0 ;
if ( selectedIndex < 0 ) {
selectedIndex = 0 ;
}
let selectedItem = displayedItems [ selectedIndex ] ;
updateSelected ( ) ;
function updateSelected ( ) {
2024-08-30 23:32:10 +00:00
selectedItem ? . style . setProperty ( "background-color" , "" ) ;
selectedItem ? . style . setProperty ( "color" , "" ) ;
2024-08-15 20:50:25 +00:00
selectedItem = displayedItems [ selectedIndex ] ;
2024-08-30 23:32:10 +00:00
selectedItem ? . style . setProperty (
2024-08-15 20:50:25 +00:00
"background-color" ,
"#ccc" ,
"important"
) ;
2024-08-30 23:32:10 +00:00
selectedItem ? . style . setProperty ( "color" , "#000" , "important" ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( updateSelected , "updateSelected" ) ;
const positionList = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-30 23:32:10 +00:00
const rect = ctx . root . getBoundingClientRect ( ) ;
2024-08-15 20:50:25 +00:00
if ( rect . top < 0 ) {
2024-08-30 23:32:10 +00:00
const scale = 1 - ctx . root . getBoundingClientRect ( ) . height / ctx . root . clientHeight ;
const shift = ctx . root . clientHeight * scale / 2 ;
ctx . root . style . top = - shift + "px" ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
} , "positionList" ) ;
2024-08-15 20:50:25 +00:00
filter . addEventListener ( "keydown" , ( event ) => {
switch ( event . key ) {
case "ArrowUp" :
event . preventDefault ( ) ;
if ( selectedIndex === 0 ) {
selectedIndex = itemCount - 1 ;
} else {
selectedIndex -- ;
}
updateSelected ( ) ;
break ;
case "ArrowRight" :
event . preventDefault ( ) ;
selectedIndex = itemCount - 1 ;
updateSelected ( ) ;
break ;
case "ArrowDown" :
event . preventDefault ( ) ;
if ( selectedIndex === itemCount - 1 ) {
selectedIndex = 0 ;
} else {
selectedIndex ++ ;
}
updateSelected ( ) ;
break ;
case "ArrowLeft" :
event . preventDefault ( ) ;
selectedIndex = 0 ;
updateSelected ( ) ;
break ;
case "Enter" :
2024-08-30 23:32:10 +00:00
selectedItem ? . click ( ) ;
2024-08-15 20:50:25 +00:00
break ;
case "Escape" :
2024-08-30 23:32:10 +00:00
ctx . close ( ) ;
2024-08-15 20:50:25 +00:00
break ;
}
} ) ;
filter . addEventListener ( "input" , ( ) => {
const term = filter . value . toLocaleLowerCase ( ) ;
displayedItems = items . filter ( ( item ) => {
const isVisible = ! term || item . textContent . toLocaleLowerCase ( ) . includes ( term ) ;
item . style . display = isVisible ? "block" : "none" ;
return isVisible ;
} ) ;
selectedIndex = 0 ;
if ( displayedItems . includes ( selectedItem ) ) {
selectedIndex = displayedItems . findIndex (
( d ) => d === selectedItem
) ;
}
itemCount = displayedItems . length ;
updateSelected ( ) ;
if ( options . event ) {
let top = options . event . clientY - 10 ;
const bodyRect = document . body . getBoundingClientRect ( ) ;
2024-08-30 23:32:10 +00:00
const rootRect = ctx . root . getBoundingClientRect ( ) ;
2024-08-15 20:50:25 +00:00
if ( bodyRect . height && top > bodyRect . height - rootRect . height - 10 ) {
top = Math . max ( 0 , bodyRect . height - rootRect . height - 10 ) ;
}
2024-08-30 23:32:10 +00:00
ctx . root . style . top = top + "px" ;
2024-08-15 20:50:25 +00:00
positionList ( ) ;
}
} ) ;
requestAnimationFrame ( ( ) => {
filter . focus ( ) ;
positionList ( ) ;
} ) ;
} ) ;
}
return ctx ;
} ;
LiteGraph . ContextMenu . prototype = ctxMenu . prototype ;
}
} ;
app . registerExtension ( ext$2 ) ;
function stripComments ( str ) {
return str . replace ( /\/\*[\s\S]*?\*\/|\/\/.*/g , "" ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( stripComments , "stripComments" ) ;
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : "Comfy.DynamicPrompts" ,
nodeCreated ( node ) {
if ( node . widgets ) {
const widgets = node . widgets . filter ( ( n ) => n . dynamicPrompts ) ;
for ( const widget of widgets ) {
widget . serializeValue = ( workflowNode , widgetIndex ) => {
let prompt2 = stripComments ( widget . value ) ;
while ( prompt2 . replace ( "\\{" , "" ) . includes ( "{" ) && prompt2 . replace ( "\\}" , "" ) . includes ( "}" ) ) {
const startIndex = prompt2 . replace ( "\\{" , "00" ) . indexOf ( "{" ) ;
const endIndex = prompt2 . replace ( "\\}" , "00" ) . indexOf ( "}" ) ;
const optionsString = prompt2 . substring ( startIndex + 1 , endIndex ) ;
const options = optionsString . split ( "|" ) ;
const randomIndex = Math . floor ( Math . random ( ) * options . length ) ;
const randomOption = options [ randomIndex ] ;
prompt2 = prompt2 . substring ( 0 , startIndex ) + randomOption + prompt2 . substring ( endIndex + 1 ) ;
}
2024-08-30 23:32:10 +00:00
if ( workflowNode ? . widgets _values )
2024-08-15 20:50:25 +00:00
workflowNode . widgets _values [ widgetIndex ] = prompt2 ;
return prompt2 ;
} ;
}
}
}
} ) ;
app . registerExtension ( {
name : "Comfy.EditAttention" ,
init ( ) {
const editAttentionDelta = app . ui . settings . addSetting ( {
id : "Comfy.EditAttention.Delta" ,
name : "Ctrl+up/down precision" ,
type : "slider" ,
attrs : {
min : 0.01 ,
max : 0.5 ,
step : 0.01
} ,
defaultValue : 0.05
} ) ;
function incrementWeight ( weight , delta ) {
const floatWeight = parseFloat ( weight ) ;
if ( isNaN ( floatWeight ) ) return weight ;
const newWeight = floatWeight + delta ;
return String ( Number ( newWeight . toFixed ( 10 ) ) ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( incrementWeight , "incrementWeight" ) ;
2024-08-15 20:50:25 +00:00
function findNearestEnclosure ( text , cursorPos ) {
let start = cursorPos , end = cursorPos ;
let openCount = 0 , closeCount = 0 ;
while ( start >= 0 ) {
start -- ;
if ( text [ start ] === "(" && openCount === closeCount ) break ;
if ( text [ start ] === "(" ) openCount ++ ;
if ( text [ start ] === ")" ) closeCount ++ ;
}
if ( start < 0 ) return false ;
openCount = 0 ;
closeCount = 0 ;
while ( end < text . length ) {
if ( text [ end ] === ")" && openCount === closeCount ) break ;
if ( text [ end ] === "(" ) openCount ++ ;
if ( text [ end ] === ")" ) closeCount ++ ;
end ++ ;
}
if ( end === text . length ) return false ;
return { start : start + 1 , end } ;
}
2024-08-16 19:25:02 +00:00
_ _name ( findNearestEnclosure , "findNearestEnclosure" ) ;
2024-08-15 20:50:25 +00:00
function addWeightToParentheses ( text ) {
const parenRegex = /^\((.*)\)$/ ;
const parenMatch = text . match ( parenRegex ) ;
const floatRegex = /:([+-]?(\d*\.)?\d+([eE][+-]?\d+)?)/ ;
const floatMatch = text . match ( floatRegex ) ;
if ( parenMatch && ! floatMatch ) {
return ` ( ${ parenMatch [ 1 ] } :1.0) ` ;
} else {
return text ;
}
}
2024-08-16 19:25:02 +00:00
_ _name ( addWeightToParentheses , "addWeightToParentheses" ) ;
2024-08-15 20:50:25 +00:00
function editAttention ( event ) {
const inputField = event . composedPath ( ) [ 0 ] ;
const delta = parseFloat ( editAttentionDelta . value ) ;
if ( inputField . tagName !== "TEXTAREA" ) return ;
if ( ! ( event . key === "ArrowUp" || event . key === "ArrowDown" ) ) return ;
if ( ! event . ctrlKey && ! event . metaKey ) return ;
event . preventDefault ( ) ;
let start = inputField . selectionStart ;
let end = inputField . selectionEnd ;
let selectedText = inputField . value . substring ( start , end ) ;
if ( ! selectedText ) {
const nearestEnclosure = findNearestEnclosure ( inputField . value , start ) ;
if ( nearestEnclosure ) {
start = nearestEnclosure . start ;
end = nearestEnclosure . end ;
selectedText = inputField . value . substring ( start , end ) ;
} else {
const delimiters = " .,\\/!?%^*;:{}=-_`~()\r\n " ;
while ( ! delimiters . includes ( inputField . value [ start - 1 ] ) && start > 0 ) {
start -- ;
}
while ( ! delimiters . includes ( inputField . value [ end ] ) && end < inputField . value . length ) {
end ++ ;
}
selectedText = inputField . value . substring ( start , end ) ;
if ( ! selectedText ) return ;
}
}
if ( selectedText [ selectedText . length - 1 ] === " " ) {
selectedText = selectedText . substring ( 0 , selectedText . length - 1 ) ;
end -= 1 ;
}
if ( inputField . value [ start - 1 ] === "(" && inputField . value [ end ] === ")" ) {
start -= 1 ;
end += 1 ;
selectedText = inputField . value . substring ( start , end ) ;
}
if ( selectedText [ 0 ] !== "(" || selectedText [ selectedText . length - 1 ] !== ")" ) {
selectedText = ` ( ${ selectedText } ) ` ;
}
selectedText = addWeightToParentheses ( selectedText ) ;
const weightDelta = event . key === "ArrowUp" ? delta : - delta ;
const updatedText = selectedText . replace (
2024-08-21 04:00:49 +00:00
/\((.*):([+-]?\d+(?:\.\d+)?)\)/ ,
2024-08-15 20:50:25 +00:00
( match , text , weight ) => {
weight = incrementWeight ( weight , weightDelta ) ;
if ( weight == 1 ) {
return text ;
} else {
return ` ( ${ text } : ${ weight } ) ` ;
}
}
) ;
2024-08-30 23:32:10 +00:00
inputField . setSelectionRange ( start , end ) ;
document . execCommand ( "insertText" , false , updatedText ) ;
inputField . setSelectionRange ( start , start + updatedText . length ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( editAttention , "editAttention" ) ;
2024-08-15 20:50:25 +00:00
window . addEventListener ( "keydown" , editAttention ) ;
}
} ) ;
const CONVERTED _TYPE = "converted-widget" ;
const VALID _TYPES = [ "STRING" , "combo" , "number" , "toggle" , "BOOLEAN" ] ;
const CONFIG = Symbol ( ) ;
const GET _CONFIG = Symbol ( ) ;
const TARGET = Symbol ( ) ;
const replacePropertyName = "Run widget replace on values" ;
2024-08-30 23:32:10 +00:00
class PrimitiveNode extends LGraphNode {
static {
_ _name ( this , "PrimitiveNode" ) ;
}
controlValues ;
lastType ;
static category ;
constructor ( title ) {
super ( title ) ;
2024-08-15 20:50:25 +00:00
this . addOutput ( "connect to widget input" , "*" ) ;
this . serialize _widgets = true ;
this . isVirtualNode = true ;
if ( ! this . properties || ! ( replacePropertyName in this . properties ) ) {
this . addProperty ( replacePropertyName , false , "boolean" ) ;
}
}
applyToGraph ( extraLinks = [ ] ) {
2024-08-30 23:32:10 +00:00
if ( ! this . outputs [ 0 ] . links ? . length ) return ;
2024-08-15 20:50:25 +00:00
function get _links ( node ) {
let links2 = [ ] ;
for ( const l of node . outputs [ 0 ] . links ) {
const linkInfo = app . graph . links [ l ] ;
const n = node . graph . getNodeById ( linkInfo . target _id ) ;
if ( n . type == "Reroute" ) {
links2 = links2 . concat ( get _links ( n ) ) ;
} else {
links2 . push ( l ) ;
}
}
return links2 ;
}
2024-08-16 19:25:02 +00:00
_ _name ( get _links , "get_links" ) ;
2024-08-15 20:50:25 +00:00
let links = [
... get _links ( this ) . map ( ( l ) => app . graph . links [ l ] ) ,
... extraLinks
] ;
2024-08-30 23:32:10 +00:00
let v = this . widgets ? . [ 0 ] . value ;
2024-08-15 20:50:25 +00:00
if ( v && this . properties [ replacePropertyName ] ) {
v = applyTextReplacements ( app , v ) ;
}
for ( const linkInfo of links ) {
const node = this . graph . getNodeById ( linkInfo . target _id ) ;
const input = node . inputs [ linkInfo . target _slot ] ;
let widget ;
if ( input . widget [ TARGET ] ) {
widget = input . widget [ TARGET ] ;
} else {
const widgetName = input . widget . name ;
if ( widgetName ) {
widget = node . widgets . find ( ( w ) => w . name === widgetName ) ;
}
}
if ( widget ) {
widget . value = v ;
if ( widget . callback ) {
widget . callback (
widget . value ,
app . canvas ,
node ,
app . canvas . graph _mouse ,
{ }
) ;
}
}
}
}
refreshComboInNode ( ) {
2024-08-30 23:32:10 +00:00
const widget = this . widgets ? . [ 0 ] ;
if ( widget ? . type === "combo" ) {
2024-08-15 20:50:25 +00:00
widget . options . values = this . outputs [ 0 ] . widget [ GET _CONFIG ] ( ) [ 0 ] ;
if ( ! widget . options . values . includes ( widget . value ) ) {
widget . value = widget . options . values [ 0 ] ;
widget . callback ( widget . value ) ;
}
}
}
onAfterGraphConfigured ( ) {
2024-08-30 23:32:10 +00:00
if ( this . outputs [ 0 ] . links ? . length && ! this . widgets ? . length ) {
if ( ! this . # onFirstConnection ( ) ) return ;
2024-08-15 20:50:25 +00:00
if ( this . widgets ) {
for ( let i = 0 ; i < this . widgets _values . length ; i ++ ) {
const w = this . widgets [ i ] ;
if ( w ) {
w . value = this . widgets _values [ i ] ;
}
}
}
2024-08-30 23:32:10 +00:00
this . # mergeWidgetConfig ( ) ;
2024-08-15 20:50:25 +00:00
}
}
onConnectionsChange ( _ , index , connected ) {
if ( app . configuringGraph ) {
return ;
}
const links = this . outputs [ 0 ] . links ;
if ( connected ) {
2024-08-30 23:32:10 +00:00
if ( links ? . length && ! this . widgets ? . length ) {
this . # onFirstConnection ( ) ;
2024-08-15 20:50:25 +00:00
}
} else {
2024-08-30 23:32:10 +00:00
this . # mergeWidgetConfig ( ) ;
if ( ! links ? . length ) {
2024-08-15 20:50:25 +00:00
this . onLastDisconnect ( ) ;
}
}
}
onConnectOutput ( slot , type , input , target _node , target _slot ) {
if ( ! input . widget ) {
if ( ! ( input . type in ComfyWidgets ) ) return false ;
}
2024-08-30 23:32:10 +00:00
if ( this . outputs [ slot ] . links ? . length ) {
const valid = this . # isValidConnection ( input ) ;
2024-08-15 20:50:25 +00:00
if ( valid ) {
this . applyToGraph ( [ { target _id : target _node . id , target _slot } ] ) ;
}
return valid ;
}
}
2024-08-30 23:32:10 +00:00
# onFirstConnection ( recreating ) {
if ( ! this . outputs [ 0 ] . links ) {
this . onLastDisconnect ( ) ;
return ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
const linkId = this . outputs [ 0 ] . links [ 0 ] ;
const link = this . graph . links [ linkId ] ;
if ( ! link ) return ;
const theirNode = this . graph . getNodeById ( link . target _id ) ;
if ( ! theirNode || ! theirNode . inputs ) return ;
const input = theirNode . inputs [ link . target _slot ] ;
if ( ! input ) return ;
let widget ;
if ( ! input . widget ) {
if ( ! ( input . type in ComfyWidgets ) ) return ;
widget = { name : input . name , [ GET _CONFIG ] : ( ) => [ input . type , { } ] } ;
} else {
widget = input . widget ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
const config = widget [ GET _CONFIG ] ? . ( ) ;
if ( ! config ) return ;
const { type } = getWidgetType ( config ) ;
this . outputs [ 0 ] . type = type ;
this . outputs [ 0 ] . name = type ;
this . outputs [ 0 ] . widget = widget ;
this . # createWidget (
widget [ CONFIG ] ? ? config ,
theirNode ,
widget . name ,
recreating ,
widget [ TARGET ]
) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
# createWidget ( inputData , node , widgetName , recreating , targetWidget ) {
let type = inputData [ 0 ] ;
if ( type instanceof Array ) {
type = "COMBO" ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
const size = this . size ;
let widget ;
if ( type in ComfyWidgets ) {
widget = ( ComfyWidgets [ type ] ( this , "value" , inputData , app ) || { } ) . widget ;
} else {
widget = this . addWidget ( type , "value" , null , ( ) => {
} , { } ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
if ( targetWidget ) {
widget . value = targetWidget . value ;
} else if ( node ? . widgets && widget ) {
const theirWidget = node . widgets . find ( ( w ) => w . name === widgetName ) ;
if ( theirWidget ) {
widget . value = theirWidget . value ;
}
}
if ( ! inputData ? . [ 1 ] ? . control _after _generate && ( widget . type === "number" || widget . type === "combo" ) ) {
let control _value = this . widgets _values ? . [ 1 ] ;
if ( ! control _value ) {
control _value = "fixed" ;
}
addValueControlWidgets (
this ,
widget ,
control _value ,
void 0 ,
inputData
) ;
let filter = this . widgets _values ? . [ 2 ] ;
if ( filter && this . widgets . length === 3 ) {
this . widgets [ 2 ] . value = filter ;
}
}
const controlValues = this . controlValues ;
if ( this . lastType === this . widgets [ 0 ] . type && controlValues ? . length === this . widgets . length - 1 ) {
for ( let i = 0 ; i < controlValues . length ; i ++ ) {
this . widgets [ i + 1 ] . value = controlValues [ i ] ;
}
}
const callback = widget . callback ;
const self = this ;
widget . callback = function ( ) {
const r = callback ? callback . apply ( this , arguments ) : void 0 ;
self . applyToGraph ( ) ;
return r ;
} ;
this . size = [
Math . max ( this . size [ 0 ] , size [ 0 ] ) ,
Math . max ( this . size [ 1 ] , size [ 1 ] )
] ;
if ( ! recreating ) {
const sz = this . computeSize ( ) ;
if ( this . size [ 0 ] < sz [ 0 ] ) {
this . size [ 0 ] = sz [ 0 ] ;
}
if ( this . size [ 1 ] < sz [ 1 ] ) {
this . size [ 1 ] = sz [ 1 ] ;
}
requestAnimationFrame ( ( ) => {
if ( this . onResize ) {
this . onResize ( this . size ) ;
}
} ) ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
recreateWidget ( ) {
const values = this . widgets ? . map ( ( w ) => w . value ) ;
this . # removeWidgets ( ) ;
this . # onFirstConnection ( true ) ;
if ( values ? . length ) {
for ( let i = 0 ; i < this . widgets ? . length ; i ++ )
this . widgets [ i ] . value = values [ i ] ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
return this . widgets ? . [ 0 ] ;
}
# mergeWidgetConfig ( ) {
const output = this . outputs [ 0 ] ;
const links = output . links ;
const hasConfig = ! ! output . widget [ CONFIG ] ;
if ( hasConfig ) {
delete output . widget [ CONFIG ] ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
if ( links ? . length < 2 && hasConfig ) {
if ( links . length ) {
this . recreateWidget ( ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
return ;
}
const config1 = output . widget [ GET _CONFIG ] ( ) ;
const isNumber = config1 [ 0 ] === "INT" || config1 [ 0 ] === "FLOAT" ;
if ( ! isNumber ) return ;
for ( const linkId of links ) {
const link = app . graph . links [ linkId ] ;
if ( ! link ) continue ;
const theirNode = app . graph . getNodeById ( link . target _id ) ;
const theirInput = theirNode . inputs [ link . target _slot ] ;
this . # isValidConnection ( theirInput , hasConfig ) ;
}
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
# isValidConnection ( input , forceUpdate ) {
const output = this . outputs [ 0 ] ;
const config2 = input . widget [ GET _CONFIG ] ( ) ;
return ! ! mergeIfValid . call (
this ,
output ,
config2 ,
forceUpdate ,
this . recreateWidget
) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
# removeWidgets ( ) {
if ( this . widgets ) {
for ( const w of this . widgets ) {
if ( w . onRemove ) {
w . onRemove ( ) ;
}
}
this . controlValues = [ ] ;
this . lastType = this . widgets [ 0 ] ? . type ;
for ( let i = 1 ; i < this . widgets . length ; i ++ ) {
this . controlValues . push ( this . widgets [ i ] . value ) ;
}
setTimeout ( ( ) => {
delete this . lastType ;
delete this . controlValues ;
} , 15 ) ;
this . widgets . length = 0 ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
onLastDisconnect ( ) {
this . outputs [ 0 ] . type = "*" ;
this . outputs [ 0 ] . name = "connect to widget input" ;
delete this . outputs [ 0 ] . widget ;
this . # removeWidgets ( ) ;
}
}
2024-08-15 20:50:25 +00:00
function getWidgetConfig ( slot ) {
2024-08-30 23:32:10 +00:00
return slot . widget [ CONFIG ] ? ? slot . widget [ GET _CONFIG ] ( ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( getWidgetConfig , "getWidgetConfig" ) ;
2024-08-15 20:50:25 +00:00
function getConfig ( widgetName ) {
const { nodeData } = this . constructor ;
2024-08-30 23:32:10 +00:00
return nodeData ? . input ? . required ? . [ widgetName ] ? ? nodeData ? . input ? . optional ? . [ widgetName ] ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( getConfig , "getConfig" ) ;
2024-08-15 20:50:25 +00:00
function isConvertibleWidget ( widget , config ) {
2024-08-30 23:32:10 +00:00
return ( VALID _TYPES . includes ( widget . type ) || VALID _TYPES . includes ( config [ 0 ] ) ) && ! widget . options ? . forceInput ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( isConvertibleWidget , "isConvertibleWidget" ) ;
2024-08-15 20:50:25 +00:00
function hideWidget ( node , widget , suffix = "" ) {
2024-08-30 23:32:10 +00:00
if ( widget . type ? . startsWith ( CONVERTED _TYPE ) ) return ;
2024-08-15 20:50:25 +00:00
widget . origType = widget . type ;
widget . origComputeSize = widget . computeSize ;
widget . origSerializeValue = widget . serializeValue ;
widget . computeSize = ( ) => [ 0 , - 4 ] ;
widget . type = CONVERTED _TYPE + suffix ;
widget . serializeValue = ( ) => {
if ( ! node . inputs ) {
return void 0 ;
}
2024-08-30 23:32:10 +00:00
let node _input = node . inputs . find ( ( i ) => i . widget ? . name === widget . name ) ;
2024-08-15 20:50:25 +00:00
if ( ! node _input || ! node _input . link ) {
return void 0 ;
}
return widget . origSerializeValue ? widget . origSerializeValue ( ) : widget . value ;
} ;
if ( widget . linkedWidgets ) {
for ( const w of widget . linkedWidgets ) {
hideWidget ( node , w , ":" + widget . name ) ;
}
}
}
2024-08-16 19:25:02 +00:00
_ _name ( hideWidget , "hideWidget" ) ;
2024-08-15 20:50:25 +00:00
function showWidget ( widget ) {
widget . type = widget . origType ;
widget . computeSize = widget . origComputeSize ;
widget . serializeValue = widget . origSerializeValue ;
delete widget . origType ;
delete widget . origComputeSize ;
delete widget . origSerializeValue ;
if ( widget . linkedWidgets ) {
for ( const w of widget . linkedWidgets ) {
showWidget ( w ) ;
}
}
}
2024-08-16 19:25:02 +00:00
_ _name ( showWidget , "showWidget" ) ;
2024-08-15 20:50:25 +00:00
function convertToInput ( node , widget , config ) {
hideWidget ( node , widget ) ;
const { type } = getWidgetType ( config ) ;
const sz = node . size ;
node . addInput ( widget . name , type , {
widget : { name : widget . name , [ GET _CONFIG ] : ( ) => config }
} ) ;
for ( const widget2 of node . widgets ) {
widget2 . last _y += LiteGraph . NODE _SLOT _HEIGHT ;
}
node . setSize ( [ Math . max ( sz [ 0 ] , node . size [ 0 ] ) , Math . max ( sz [ 1 ] , node . size [ 1 ] ) ] ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( convertToInput , "convertToInput" ) ;
2024-08-15 20:50:25 +00:00
function convertToWidget ( node , widget ) {
showWidget ( widget ) ;
const sz = node . size ;
2024-08-30 23:32:10 +00:00
node . removeInput ( node . inputs . findIndex ( ( i ) => i . widget ? . name === widget . name ) ) ;
2024-08-15 20:50:25 +00:00
for ( const widget2 of node . widgets ) {
widget2 . last _y -= LiteGraph . NODE _SLOT _HEIGHT ;
}
node . setSize ( [ Math . max ( sz [ 0 ] , node . size [ 0 ] ) , Math . max ( sz [ 1 ] , node . size [ 1 ] ) ] ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( convertToWidget , "convertToWidget" ) ;
2024-08-15 20:50:25 +00:00
function getWidgetType ( config ) {
let type = config [ 0 ] ;
if ( type instanceof Array ) {
type = "COMBO" ;
}
return { type } ;
}
2024-08-16 19:25:02 +00:00
_ _name ( getWidgetType , "getWidgetType" ) ;
2024-08-15 20:50:25 +00:00
function isValidCombo ( combo , obj ) {
if ( ! ( obj instanceof Array ) ) {
console . log ( ` connection rejected: tried to connect combo to ${ obj } ` ) ;
return false ;
}
if ( combo . length !== obj . length ) {
console . log ( ` connection rejected: combo lists dont match ` ) ;
return false ;
}
if ( combo . find ( ( v , i ) => obj [ i ] !== v ) ) {
console . log ( ` connection rejected: combo lists dont match ` ) ;
return false ;
}
return true ;
}
2024-08-16 19:25:02 +00:00
_ _name ( isValidCombo , "isValidCombo" ) ;
2024-08-15 20:50:25 +00:00
function isPrimitiveNode ( node ) {
return node . type === "PrimitiveNode" ;
}
2024-08-16 19:25:02 +00:00
_ _name ( isPrimitiveNode , "isPrimitiveNode" ) ;
2024-08-15 20:50:25 +00:00
function setWidgetConfig ( slot , config , target ) {
if ( ! slot . widget ) return ;
if ( config ) {
slot . widget [ GET _CONFIG ] = ( ) => config ;
slot . widget [ TARGET ] = target ;
} else {
delete slot . widget ;
}
if ( slot . link ) {
const link = app . graph . links [ slot . link ] ;
if ( link ) {
const originNode = app . graph . getNodeById ( link . origin _id ) ;
if ( isPrimitiveNode ( originNode ) ) {
if ( config ) {
originNode . recreateWidget ( ) ;
} else if ( ! app . configuringGraph ) {
originNode . disconnectOutput ( 0 ) ;
originNode . onLastDisconnect ( ) ;
}
}
}
}
}
2024-08-16 19:25:02 +00:00
_ _name ( setWidgetConfig , "setWidgetConfig" ) ;
2024-08-15 20:50:25 +00:00
function mergeIfValid ( output , config2 , forceUpdate , recreateWidget , config1 ) {
if ( ! config1 ) {
2024-08-30 23:32:10 +00:00
config1 = output . widget [ CONFIG ] ? ? output . widget [ GET _CONFIG ] ( ) ;
2024-08-15 20:50:25 +00:00
}
if ( config1 [ 0 ] instanceof Array ) {
if ( ! isValidCombo ( config1 [ 0 ] , config2 [ 0 ] ) ) return ;
} else if ( config1 [ 0 ] !== config2 [ 0 ] ) {
console . log ( ` connection rejected: types dont match ` , config1 [ 0 ] , config2 [ 0 ] ) ;
return ;
}
const keys = /* @__PURE__ */ new Set ( [
2024-08-30 23:32:10 +00:00
... Object . keys ( config1 [ 1 ] ? ? { } ) ,
... Object . keys ( config2 [ 1 ] ? ? { } )
2024-08-15 20:50:25 +00:00
] ) ;
let customConfig ;
2024-08-16 19:25:02 +00:00
const getCustomConfig = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
if ( ! customConfig ) {
if ( typeof structuredClone === "undefined" ) {
2024-08-30 23:32:10 +00:00
customConfig = JSON . parse ( JSON . stringify ( config1 [ 1 ] ? ? { } ) ) ;
2024-08-15 20:50:25 +00:00
} else {
2024-08-30 23:32:10 +00:00
customConfig = structuredClone ( config1 [ 1 ] ? ? { } ) ;
2024-08-15 20:50:25 +00:00
}
}
return customConfig ;
2024-08-16 19:25:02 +00:00
} , "getCustomConfig" ) ;
2024-08-15 20:50:25 +00:00
const isNumber = config1 [ 0 ] === "INT" || config1 [ 0 ] === "FLOAT" ;
for ( const k of keys . values ( ) ) {
if ( k !== "default" && k !== "forceInput" && k !== "defaultInput" && k !== "control_after_generate" && k !== "multiline" && k !== "tooltip" ) {
let v1 = config1 [ 1 ] [ k ] ;
2024-08-30 23:32:10 +00:00
let v2 = config2 [ 1 ] ? . [ k ] ;
2024-08-15 20:50:25 +00:00
if ( v1 === v2 || ! v1 && ! v2 ) continue ;
if ( isNumber ) {
if ( k === "min" ) {
2024-08-30 23:32:10 +00:00
const theirMax = config2 [ 1 ] ? . [ "max" ] ;
2024-08-15 20:50:25 +00:00
if ( theirMax != null && v1 > theirMax ) {
console . log ( "connection rejected: min > max" , v1 , theirMax ) ;
return ;
}
getCustomConfig ( ) [ k ] = v1 == null ? v2 : v2 == null ? v1 : Math . max ( v1 , v2 ) ;
continue ;
} else if ( k === "max" ) {
2024-08-30 23:32:10 +00:00
const theirMin = config2 [ 1 ] ? . [ "min" ] ;
2024-08-15 20:50:25 +00:00
if ( theirMin != null && v1 < theirMin ) {
console . log ( "connection rejected: max < min" , v1 , theirMin ) ;
return ;
}
getCustomConfig ( ) [ k ] = v1 == null ? v2 : v2 == null ? v1 : Math . min ( v1 , v2 ) ;
continue ;
} else if ( k === "step" ) {
let step ;
if ( v1 == null ) {
step = v2 ;
} else if ( v2 == null ) {
step = v1 ;
} else {
if ( v1 < v2 ) {
const a = v2 ;
v2 = v1 ;
v1 = a ;
}
if ( v1 % v2 ) {
console . log (
"connection rejected: steps not divisible" ,
"current:" ,
v1 ,
"new:" ,
v2
) ;
return ;
}
step = v1 ;
}
getCustomConfig ( ) [ k ] = step ;
continue ;
}
}
console . log ( ` connection rejected: config ${ k } values dont match ` , v1 , v2 ) ;
return ;
}
}
if ( customConfig || forceUpdate ) {
if ( customConfig ) {
output . widget [ CONFIG ] = [ config1 [ 0 ] , customConfig ] ;
}
2024-08-30 23:32:10 +00:00
const widget = recreateWidget ? . call ( this ) ;
2024-08-15 20:50:25 +00:00
if ( widget ) {
const min = widget . options . min ;
const max = widget . options . max ;
if ( min != null && widget . value < min ) widget . value = min ;
if ( max != null && widget . value > max ) widget . value = max ;
widget . callback ( widget . value ) ;
}
}
return { customConfig } ;
}
2024-08-16 19:25:02 +00:00
_ _name ( mergeIfValid , "mergeIfValid" ) ;
2024-08-15 20:50:25 +00:00
let useConversionSubmenusSetting ;
app . registerExtension ( {
name : "Comfy.WidgetInputs" ,
init ( ) {
useConversionSubmenusSetting = app . ui . settings . addSetting ( {
id : "Comfy.NodeInputConversionSubmenus" ,
2024-08-21 04:00:49 +00:00
name : "In the node context menu, place the entries that convert between input/widget in sub-menus." ,
2024-08-15 20:50:25 +00:00
type : "boolean" ,
defaultValue : true
} ) ;
} ,
2024-08-30 23:32:10 +00:00
async beforeRegisterNodeDef ( nodeType , nodeData , app2 ) {
const origGetExtraMenuOptions = nodeType . prototype . getExtraMenuOptions ;
nodeType . prototype . convertWidgetToInput = function ( widget ) {
const config = getConfig . call ( this , widget . name ) ? ? [
widget . type ,
widget . options || { }
] ;
if ( ! isConvertibleWidget ( widget , config ) ) return false ;
convertToInput ( this , widget , config ) ;
return true ;
} ;
nodeType . prototype . getExtraMenuOptions = function ( _ , options ) {
const r = origGetExtraMenuOptions ? origGetExtraMenuOptions . apply ( this , arguments ) : void 0 ;
if ( this . widgets ) {
let toInput = [ ] ;
let toWidget = [ ] ;
for ( const w of this . widgets ) {
if ( w . options ? . forceInput ) {
continue ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
if ( w . type === CONVERTED _TYPE ) {
toWidget . push ( {
content : ` Convert ${ w . name } to widget ` ,
callback : /* @__PURE__ */ _ _name ( ( ) => convertToWidget ( this , w ) , "callback" )
} ) ;
} else {
const config = getConfig . call ( this , w . name ) ? ? [
w . type ,
w . options || { }
] ;
if ( isConvertibleWidget ( w , config ) ) {
toInput . push ( {
content : ` Convert ${ w . name } to input ` ,
callback : /* @__PURE__ */ _ _name ( ( ) => convertToInput ( this , w , config ) , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
}
}
}
2024-08-30 23:32:10 +00:00
if ( toInput . length ) {
if ( useConversionSubmenusSetting . value ) {
options . push ( {
content : "Convert Widget to Input" ,
submenu : {
options : toInput
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
} ) ;
} else {
options . push ( ... toInput , null ) ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
if ( toWidget . length ) {
if ( useConversionSubmenusSetting . value ) {
options . push ( {
content : "Convert Input to Widget" ,
submenu : {
options : toWidget
}
} ) ;
} else {
options . push ( ... toWidget , null ) ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
}
return r ;
} ;
nodeType . prototype . onGraphConfigured = function ( ) {
if ( ! this . inputs ) return ;
for ( const input of this . inputs ) {
if ( input . widget ) {
if ( ! input . widget [ GET _CONFIG ] ) {
input . widget [ GET _CONFIG ] = ( ) => getConfig . call ( this , input . widget . name ) ;
}
if ( input . widget . config ) {
if ( input . widget . config [ 0 ] instanceof Array ) {
input . type = "COMBO" ;
const link = app2 . graph . links [ input . link ] ;
if ( link ) {
link . type = input . type ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
delete input . widget . config ;
}
const w = this . widgets . find ( ( w2 ) => w2 . name === input . widget . name ) ;
if ( w ) {
hideWidget ( this , w ) ;
} else {
convertToWidget ( this , input ) ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
}
} ;
const origOnNodeCreated = nodeType . prototype . onNodeCreated ;
nodeType . prototype . onNodeCreated = function ( ) {
const r = origOnNodeCreated ? origOnNodeCreated . apply ( this ) : void 0 ;
if ( ! app2 . configuringGraph && this . widgets ) {
for ( const w of this . widgets ) {
if ( w ? . options ? . forceInput || w ? . options ? . defaultInput ) {
const config = getConfig . call ( this , w . name ) ? ? [
w . type ,
w . options || { }
] ;
convertToInput ( this , w , config ) ;
2024-08-15 20:50:25 +00:00
}
}
}
2024-08-30 23:32:10 +00:00
return r ;
} ;
const origOnConfigure = nodeType . prototype . onConfigure ;
nodeType . prototype . onConfigure = function ( ) {
const r = origOnConfigure ? origOnConfigure . apply ( this , arguments ) : void 0 ;
if ( ! app2 . configuringGraph && this . inputs ) {
for ( const input of this . inputs ) {
if ( input . widget && ! input . widget [ GET _CONFIG ] ) {
input . widget [ GET _CONFIG ] = ( ) => getConfig . call ( this , input . widget . name ) ;
const w = this . widgets . find ( ( w2 ) => w2 . name === input . widget . name ) ;
if ( w ) {
hideWidget ( this , w ) ;
}
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
}
return r ;
} ;
function isNodeAtPos ( pos ) {
for ( const n of app2 . graph . _nodes ) {
if ( n . pos [ 0 ] === pos [ 0 ] && n . pos [ 1 ] === pos [ 1 ] ) {
return true ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
return false ;
}
_ _name ( isNodeAtPos , "isNodeAtPos" ) ;
const origOnInputDblClick = nodeType . prototype . onInputDblClick ;
const ignoreDblClick = Symbol ( ) ;
nodeType . prototype . onInputDblClick = function ( slot ) {
const r = origOnInputDblClick ? origOnInputDblClick . apply ( this , arguments ) : void 0 ;
const input = this . inputs [ slot ] ;
if ( ! input . widget || ! input [ ignoreDblClick ] ) {
if ( ! ( input . type in ComfyWidgets ) && ! ( input . widget [ GET _CONFIG ] ? . ( ) ? . [ 0 ] instanceof Array ) ) {
return r ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
const node = LiteGraph . createNode ( "PrimitiveNode" ) ;
app2 . graph . add ( node ) ;
const pos = [
this . pos [ 0 ] - node . size [ 0 ] - 30 ,
this . pos [ 1 ]
] ;
while ( isNodeAtPos ( pos ) ) {
pos [ 1 ] += LiteGraph . NODE _TITLE _HEIGHT ;
}
node . pos = pos ;
node . connect ( 0 , this , slot ) ;
node . title = input . name ;
input [ ignoreDblClick ] = true ;
setTimeout ( ( ) => {
delete input [ ignoreDblClick ] ;
} , 300 ) ;
return r ;
} ;
const onConnectInput = nodeType . prototype . onConnectInput ;
nodeType . prototype . onConnectInput = function ( targetSlot , type , output , originNode , originSlot ) {
const v = onConnectInput ? . ( this , arguments ) ;
if ( type !== "COMBO" ) return v ;
if ( originNode . outputs [ originSlot ] . widget ) return v ;
const targetCombo = this . inputs [ targetSlot ] . widget ? . [ GET _CONFIG ] ? . ( ) ? . [ 0 ] ;
if ( ! targetCombo || ! ( targetCombo instanceof Array ) ) return v ;
const originConfig = originNode . constructor ? . nodeData ? . output ? . [ originSlot ] ;
if ( ! originConfig || ! isValidCombo ( targetCombo , originConfig ) ) {
return false ;
}
return v ;
} ;
2024-08-15 20:50:25 +00:00
} ,
registerCustomNodes ( ) {
LiteGraph . registerNodeType (
"PrimitiveNode" ,
Object . assign ( PrimitiveNode , {
title : "Primitive"
} )
) ;
PrimitiveNode . category = "utils" ;
}
} ) ;
window . comfyAPI = window . comfyAPI || { } ;
window . comfyAPI . widgetInputs = window . comfyAPI . widgetInputs || { } ;
window . comfyAPI . widgetInputs . getWidgetConfig = getWidgetConfig ;
window . comfyAPI . widgetInputs . setWidgetConfig = setWidgetConfig ;
window . comfyAPI . widgetInputs . mergeIfValid = mergeIfValid ;
const ORDER = Symbol ( ) ;
function merge ( target , source ) {
if ( typeof target === "object" && typeof source === "object" ) {
for ( const key in source ) {
const sv = source [ key ] ;
if ( typeof sv === "object" ) {
let tv = target [ key ] ;
if ( ! tv ) tv = target [ key ] = { } ;
merge ( tv , source [ key ] ) ;
} else {
target [ key ] = sv ;
}
}
}
return target ;
}
2024-08-16 19:25:02 +00:00
_ _name ( merge , "merge" ) ;
2024-08-30 23:32:10 +00:00
class ManageGroupDialog extends ComfyDialog {
static {
_ _name ( this , "ManageGroupDialog" ) ;
}
tabs ;
selectedNodeIndex ;
selectedTab = "Inputs" ;
selectedGroup ;
modifications = { } ;
nodeItems ;
app ;
groupNodeType ;
groupNodeDef ;
groupData ;
innerNodesList ;
widgetsPage ;
inputsPage ;
outputsPage ;
draggable ;
get selectedNodeInnerIndex ( ) {
return + this . nodeItems [ this . selectedNodeIndex ] . dataset . nodeindex ;
}
2024-08-15 20:50:25 +00:00
constructor ( app2 ) {
super ( ) ;
this . app = app2 ;
this . element = $el ( "dialog.comfy-group-manage" , {
parent : document . body
} ) ;
}
changeTab ( tab ) {
this . tabs [ this . selectedTab ] . tab . classList . remove ( "active" ) ;
this . tabs [ this . selectedTab ] . page . classList . remove ( "active" ) ;
this . tabs [ tab ] . tab . classList . add ( "active" ) ;
this . tabs [ tab ] . page . classList . add ( "active" ) ;
this . selectedTab = tab ;
}
changeNode ( index , force ) {
if ( ! force && this . selectedNodeIndex === index ) return ;
if ( this . selectedNodeIndex != null ) {
this . nodeItems [ this . selectedNodeIndex ] . classList . remove ( "selected" ) ;
}
this . nodeItems [ index ] . classList . add ( "selected" ) ;
this . selectedNodeIndex = index ;
if ( ! this . buildInputsPage ( ) && this . selectedTab === "Inputs" ) {
this . changeTab ( "Widgets" ) ;
}
if ( ! this . buildWidgetsPage ( ) && this . selectedTab === "Widgets" ) {
this . changeTab ( "Outputs" ) ;
}
if ( ! this . buildOutputsPage ( ) && this . selectedTab === "Outputs" ) {
this . changeTab ( "Inputs" ) ;
}
this . changeTab ( this . selectedTab ) ;
}
getGroupData ( ) {
this . groupNodeType = LiteGraph . registered _node _types [ "workflow/" + this . selectedGroup ] ;
this . groupNodeDef = this . groupNodeType . nodeData ;
this . groupData = GroupNodeHandler . getGroupData ( this . groupNodeType ) ;
}
changeGroup ( group , reset = true ) {
this . selectedGroup = group ;
this . getGroupData ( ) ;
const nodes = this . groupData . nodeData . nodes ;
this . nodeItems = nodes . map (
2024-08-30 23:32:10 +00:00
( n , i ) => $el (
"li.draggable-item" ,
{
dataset : {
nodeindex : n . index + ""
2024-08-15 20:50:25 +00:00
} ,
2024-08-30 23:32:10 +00:00
onclick : /* @__PURE__ */ _ _name ( ( ) => {
this . changeNode ( i ) ;
} , "onclick" )
} ,
[
$el ( "span.drag-handle" ) ,
$el (
"div" ,
{
textContent : n . title ? ? n . type
} ,
n . title ? $el ( "span" , {
textContent : n . type
} ) : [ ]
)
]
)
2024-08-15 20:50:25 +00:00
) ;
this . innerNodesList . replaceChildren ( ... this . nodeItems ) ;
if ( reset ) {
this . selectedNodeIndex = null ;
this . changeNode ( 0 ) ;
} else {
const items = this . draggable . getAllItems ( ) ;
let index = items . findIndex ( ( item ) => item . classList . contains ( "selected" ) ) ;
if ( index === - 1 ) index = this . selectedNodeIndex ;
this . changeNode ( index , true ) ;
}
const ordered = [ ... nodes ] ;
2024-08-30 23:32:10 +00:00
this . draggable ? . dispose ( ) ;
2024-08-15 20:50:25 +00:00
this . draggable = new DraggableList ( this . innerNodesList , "li" ) ;
this . draggable . addEventListener (
"dragend" ,
( { detail : { oldPosition , newPosition } } ) => {
if ( oldPosition === newPosition ) return ;
ordered . splice ( newPosition , 0 , ordered . splice ( oldPosition , 1 ) [ 0 ] ) ;
for ( let i = 0 ; i < ordered . length ; i ++ ) {
this . storeModification ( {
nodeIndex : ordered [ i ] . index ,
section : ORDER ,
prop : "order" ,
value : i
} ) ;
}
}
) ;
}
storeModification ( props ) {
const { nodeIndex , section , prop , value } = props ;
2024-08-30 23:32:10 +00:00
const groupMod = this . modifications [ this . selectedGroup ] ? ? = { } ;
const nodesMod = groupMod . nodes ? ? = { } ;
const nodeMod = nodesMod [ nodeIndex ? ? this . selectedNodeInnerIndex ] ? ? = { } ;
const typeMod = nodeMod [ section ] ? ? = { } ;
2024-08-15 20:50:25 +00:00
if ( typeof value === "object" ) {
2024-08-30 23:32:10 +00:00
const objMod = typeMod [ prop ] ? ? = { } ;
2024-08-15 20:50:25 +00:00
Object . assign ( objMod , value ) ;
} else {
typeMod [ prop ] = value ;
}
}
getEditElement ( section , prop , value , placeholder , checked , checkable = true ) {
if ( value === placeholder ) value = "" ;
2024-08-30 23:32:10 +00:00
const mods = this . modifications [ this . selectedGroup ] ? . nodes ? . [ this . selectedNodeInnerIndex ] ? . [ section ] ? . [ prop ] ;
2024-08-15 20:50:25 +00:00
if ( mods ) {
if ( mods . name != null ) {
value = mods . name ;
}
if ( mods . visible != null ) {
checked = mods . visible ;
}
}
return $el ( "div" , [
$el ( "input" , {
value ,
placeholder ,
type : "text" ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
this . storeModification ( {
section ,
prop ,
value : { name : e . target . value }
} ) ;
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} ) ,
$el ( "label" , { textContent : "Visible" } , [
$el ( "input" , {
type : "checkbox" ,
checked ,
disabled : ! checkable ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
this . storeModification ( {
section ,
prop ,
value : { visible : ! ! e . target . checked }
} ) ;
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} )
] )
] ) ;
}
buildWidgetsPage ( ) {
const widgets = this . groupData . oldToNewWidgetMap [ this . selectedNodeInnerIndex ] ;
2024-08-30 23:32:10 +00:00
const items = Object . keys ( widgets ? ? { } ) ;
2024-08-15 20:50:25 +00:00
const type = app . graph . extra . groupNodes [ this . selectedGroup ] ;
2024-08-30 23:32:10 +00:00
const config = type . config ? . [ this . selectedNodeInnerIndex ] ? . input ;
2024-08-15 20:50:25 +00:00
this . widgetsPage . replaceChildren (
... items . map ( ( oldName ) => {
return this . getEditElement (
"input" ,
oldName ,
widgets [ oldName ] ,
oldName ,
2024-08-30 23:32:10 +00:00
config ? . [ oldName ] ? . visible !== false
2024-08-15 20:50:25 +00:00
) ;
} )
) ;
return ! ! items . length ;
}
buildInputsPage ( ) {
const inputs = this . groupData . nodeInputs [ this . selectedNodeInnerIndex ] ;
2024-08-30 23:32:10 +00:00
const items = Object . keys ( inputs ? ? { } ) ;
2024-08-15 20:50:25 +00:00
const type = app . graph . extra . groupNodes [ this . selectedGroup ] ;
2024-08-30 23:32:10 +00:00
const config = type . config ? . [ this . selectedNodeInnerIndex ] ? . input ;
2024-08-15 20:50:25 +00:00
this . inputsPage . replaceChildren (
... items . map ( ( oldName ) => {
let value = inputs [ oldName ] ;
if ( ! value ) {
return ;
}
return this . getEditElement (
"input" ,
oldName ,
value ,
oldName ,
2024-08-30 23:32:10 +00:00
config ? . [ oldName ] ? . visible !== false
2024-08-15 20:50:25 +00:00
) ;
} ) . filter ( Boolean )
) ;
return ! ! items . length ;
}
buildOutputsPage ( ) {
const nodes = this . groupData . nodeData . nodes ;
const innerNodeDef = this . groupData . getNodeDef (
nodes [ this . selectedNodeInnerIndex ]
) ;
2024-08-30 23:32:10 +00:00
const outputs = innerNodeDef ? . output ? ? [ ] ;
2024-08-15 20:50:25 +00:00
const groupOutputs = this . groupData . oldToNewOutputMap [ this . selectedNodeInnerIndex ] ;
const type = app . graph . extra . groupNodes [ this . selectedGroup ] ;
2024-08-30 23:32:10 +00:00
const config = type . config ? . [ this . selectedNodeInnerIndex ] ? . output ;
2024-08-15 20:50:25 +00:00
const node = this . groupData . nodeData . nodes [ this . selectedNodeInnerIndex ] ;
const checkable = node . type !== "PrimitiveNode" ;
this . outputsPage . replaceChildren (
... outputs . map ( ( type2 , slot ) => {
2024-08-30 23:32:10 +00:00
const groupOutputIndex = groupOutputs ? . [ slot ] ;
const oldName = innerNodeDef . output _name ? . [ slot ] ? ? type2 ;
let value = config ? . [ slot ] ? . name ;
const visible = config ? . [ slot ] ? . visible || groupOutputIndex != null ;
2024-08-15 20:50:25 +00:00
if ( ! value || value === oldName ) {
value = "" ;
}
return this . getEditElement (
"output" ,
slot ,
value ,
oldName ,
visible ,
checkable
) ;
} ) . filter ( Boolean )
) ;
return ! ! outputs . length ;
}
show ( type ) {
2024-08-30 23:32:10 +00:00
const groupNodes = Object . keys ( app . graph . extra ? . groupNodes ? ? { } ) . sort (
2024-08-15 20:50:25 +00:00
( a , b ) => a . localeCompare ( b )
) ;
this . innerNodesList = $el (
"ul.comfy-group-manage-list-items"
) ;
this . widgetsPage = $el ( "section.comfy-group-manage-node-page" ) ;
this . inputsPage = $el ( "section.comfy-group-manage-node-page" ) ;
this . outputsPage = $el ( "section.comfy-group-manage-node-page" ) ;
const pages = $el ( "div" , [
this . widgetsPage ,
this . inputsPage ,
this . outputsPage
] ) ;
this . tabs = [
[ "Inputs" , this . inputsPage ] ,
[ "Widgets" , this . widgetsPage ] ,
[ "Outputs" , this . outputsPage ]
] . reduce ( ( p , [ name , page ] ) => {
p [ name ] = {
tab : $el ( "a" , {
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
this . changeTab ( name ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" ) ,
2024-08-15 20:50:25 +00:00
textContent : name
} ) ,
page
} ;
return p ;
} , { } ) ;
const outer = $el ( "div.comfy-group-manage-outer" , [
$el ( "header" , [
$el ( "h2" , "Group Nodes" ) ,
$el (
"select" ,
{
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
this . changeGroup ( e . target . value ) ;
2024-08-16 19:25:02 +00:00
} , "onchange" )
2024-08-15 20:50:25 +00:00
} ,
groupNodes . map (
( g ) => $el ( "option" , {
textContent : g ,
selected : "workflow/" + g === type ,
value : g
} )
)
)
] ) ,
$el ( "main" , [
$el ( "section.comfy-group-manage-list" , this . innerNodesList ) ,
$el ( "section.comfy-group-manage-node" , [
$el (
"header" ,
Object . values ( this . tabs ) . map ( ( t ) => t . tab )
) ,
pages
] )
] ) ,
$el ( "footer" , [
$el (
"button.comfy-btn" ,
{
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
const node = app . graph . _nodes . find (
( n ) => n . type === "workflow/" + this . selectedGroup
) ;
if ( node ) {
alert (
"This group node is in use in the current workflow, please first remove these."
) ;
return ;
}
if ( confirm (
` Are you sure you want to remove the node: " ${ this . selectedGroup } " `
) ) {
delete app . graph . extra . groupNodes [ this . selectedGroup ] ;
LiteGraph . unregisterNodeType ( "workflow/" + this . selectedGroup ) ;
}
this . show ( ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} ,
"Delete Group Node"
) ,
$el (
"button.comfy-btn" ,
{
2024-08-30 23:32:10 +00:00
onclick : /* @__PURE__ */ _ _name ( async ( ) => {
2024-08-15 20:50:25 +00:00
let nodesByType ;
let recreateNodes = [ ] ;
const types = { } ;
for ( const g in this . modifications ) {
const type2 = app . graph . extra . groupNodes [ g ] ;
2024-08-30 23:32:10 +00:00
let config = type2 . config ? ? = { } ;
let nodeMods = this . modifications [ g ] ? . nodes ;
2024-08-15 20:50:25 +00:00
if ( nodeMods ) {
const keys = Object . keys ( nodeMods ) ;
if ( nodeMods [ keys [ 0 ] ] [ ORDER ] ) {
const orderedNodes = [ ] ;
const orderedMods = { } ;
const orderedConfig = { } ;
for ( const n of keys ) {
const order = nodeMods [ n ] [ ORDER ] . order ;
orderedNodes [ order ] = type2 . nodes [ + n ] ;
orderedMods [ order ] = nodeMods [ n ] ;
orderedNodes [ order ] . index = order ;
}
for ( const l of type2 . links ) {
if ( l [ 0 ] != null ) l [ 0 ] = type2 . nodes [ l [ 0 ] ] . index ;
if ( l [ 2 ] != null ) l [ 2 ] = type2 . nodes [ l [ 2 ] ] . index ;
}
if ( type2 . external ) {
for ( const ext2 of type2 . external ) {
ext2 [ 0 ] = type2 . nodes [ ext2 [ 0 ] ] ;
}
}
for ( const id2 of keys ) {
if ( config [ id2 ] ) {
orderedConfig [ type2 . nodes [ id2 ] . index ] = config [ id2 ] ;
}
delete config [ id2 ] ;
}
type2 . nodes = orderedNodes ;
nodeMods = orderedMods ;
type2 . config = config = orderedConfig ;
}
merge ( config , nodeMods ) ;
}
types [ g ] = type2 ;
if ( ! nodesByType ) {
nodesByType = app . graph . _nodes . reduce ( ( p , n ) => {
2024-08-30 23:32:10 +00:00
p [ n . type ] ? ? = [ ] ;
2024-08-15 20:50:25 +00:00
p [ n . type ] . push ( n ) ;
return p ;
} , { } ) ;
}
const nodes = nodesByType [ "workflow/" + g ] ;
if ( nodes ) recreateNodes . push ( ... nodes ) ;
}
2024-08-30 23:32:10 +00:00
await GroupNodeConfig . registerFromWorkflow ( types , { } ) ;
2024-08-15 20:50:25 +00:00
for ( const node of recreateNodes ) {
node . recreate ( ) ;
}
this . modifications = { } ;
this . app . graph . setDirtyCanvas ( true , true ) ;
this . changeGroup ( this . selectedGroup , false ) ;
2024-08-30 23:32:10 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} ,
"Save"
) ,
$el (
"button.comfy-btn" ,
2024-08-16 19:25:02 +00:00
{ onclick : /* @__PURE__ */ _ _name ( ( ) => this . element . close ( ) , "onclick" ) } ,
2024-08-15 20:50:25 +00:00
"Close"
)
] )
] ) ;
this . element . replaceChildren ( outer ) ;
this . changeGroup (
type ? groupNodes . find ( ( g ) => "workflow/" + g === type ) : groupNodes [ 0 ]
) ;
this . element . showModal ( ) ;
this . element . addEventListener ( "close" , ( ) => {
2024-08-30 23:32:10 +00:00
this . draggable ? . dispose ( ) ;
2024-08-15 20:50:25 +00:00
} ) ;
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
window . comfyAPI = window . comfyAPI || { } ;
window . comfyAPI . groupNodeManage = window . comfyAPI . groupNodeManage || { } ;
window . comfyAPI . groupNodeManage . ManageGroupDialog = ManageGroupDialog ;
const GROUP = Symbol ( ) ;
const Workflow = {
InUse : {
Free : 0 ,
Registered : 1 ,
InWorkflow : 2
} ,
isInUseGroupNode ( name ) {
const id2 = ` workflow/ ${ name } ` ;
2024-08-30 23:32:10 +00:00
if ( app . graph . extra ? . groupNodes ? . [ name ] ) {
2024-08-15 20:50:25 +00:00
if ( app . graph . _nodes . find ( ( n ) => n . type === id2 ) ) {
return Workflow . InUse . InWorkflow ;
} else {
return Workflow . InUse . Registered ;
}
}
return Workflow . InUse . Free ;
} ,
storeGroupNode ( name , data ) {
let extra = app . graph . extra ;
if ( ! extra ) app . graph . extra = extra = { } ;
let groupNodes = extra . groupNodes ;
if ( ! groupNodes ) extra . groupNodes = groupNodes = { } ;
groupNodes [ name ] = data ;
}
} ;
2024-08-30 23:32:10 +00:00
class GroupNodeBuilder {
static {
_ _name ( this , "GroupNodeBuilder" ) ;
}
nodes ;
nodeData ;
2024-08-15 20:50:25 +00:00
constructor ( nodes ) {
this . nodes = nodes ;
}
build ( ) {
const name = this . getName ( ) ;
if ( ! name ) return ;
this . sortNodes ( ) ;
this . nodeData = this . getNodeData ( ) ;
Workflow . storeGroupNode ( name , this . nodeData ) ;
return { name , nodeData : this . nodeData } ;
}
getName ( ) {
const name = prompt ( "Enter group name" ) ;
if ( ! name ) return ;
const used = Workflow . isInUseGroupNode ( name ) ;
switch ( used ) {
case Workflow . InUse . InWorkflow :
alert (
"An in use group node with this name already exists embedded in this workflow, please remove any instances or use a new name."
) ;
return ;
case Workflow . InUse . Registered :
if ( ! confirm (
"A group node with this name already exists embedded in this workflow, are you sure you want to overwrite it?"
) ) {
return ;
}
break ;
}
return name ;
}
sortNodes ( ) {
const nodesInOrder = app . graph . computeExecutionOrder ( false ) ;
this . nodes = this . nodes . map ( ( node ) => ( { index : nodesInOrder . indexOf ( node ) , node } ) ) . sort ( ( a , b ) => a . index - b . index || a . node . id - b . node . id ) . map ( ( { node } ) => node ) ;
}
getNodeData ( ) {
2024-08-16 19:25:02 +00:00
const storeLinkTypes = /* @__PURE__ */ _ _name ( ( config ) => {
2024-08-15 20:50:25 +00:00
for ( const link of config . links ) {
const origin = app . graph . getNodeById ( link [ 4 ] ) ;
const type = origin . outputs [ link [ 1 ] ] . type ;
link . push ( type ) ;
}
2024-08-16 19:25:02 +00:00
} , "storeLinkTypes" ) ;
const storeExternalLinks = /* @__PURE__ */ _ _name ( ( config ) => {
2024-08-15 20:50:25 +00:00
config . external = [ ] ;
for ( let i = 0 ; i < this . nodes . length ; i ++ ) {
const node = this . nodes [ i ] ;
2024-08-30 23:32:10 +00:00
if ( ! node . outputs ? . length ) continue ;
2024-08-15 20:50:25 +00:00
for ( let slot = 0 ; slot < node . outputs . length ; slot ++ ) {
let hasExternal = false ;
const output = node . outputs [ slot ] ;
let type = output . type ;
2024-08-30 23:32:10 +00:00
if ( ! output . links ? . length ) continue ;
2024-08-15 20:50:25 +00:00
for ( const l of output . links ) {
const link = app . graph . links [ l ] ;
if ( ! link ) continue ;
if ( type === "*" ) type = link . type ;
if ( ! app . canvas . selected _nodes [ link . target _id ] ) {
hasExternal = true ;
break ;
}
}
if ( hasExternal ) {
config . external . push ( [ i , slot , type ] ) ;
}
}
}
2024-08-16 19:25:02 +00:00
} , "storeExternalLinks" ) ;
2024-08-15 20:50:25 +00:00
const backup = localStorage . getItem ( "litegrapheditor_clipboard" ) ;
try {
app . canvas . copyToClipboard ( this . nodes ) ;
const config = JSON . parse (
localStorage . getItem ( "litegrapheditor_clipboard" )
) ;
storeLinkTypes ( config ) ;
storeExternalLinks ( config ) ;
return config ;
} finally {
localStorage . setItem ( "litegrapheditor_clipboard" , backup ) ;
}
}
2024-08-30 23:32:10 +00:00
}
class GroupNodeConfig {
static {
_ _name ( this , "GroupNodeConfig" ) ;
}
name ;
nodeData ;
inputCount ;
oldToNewOutputMap ;
newToOldOutputMap ;
oldToNewInputMap ;
oldToNewWidgetMap ;
newToOldWidgetMap ;
primitiveDefs ;
widgetToPrimitive ;
primitiveToWidget ;
nodeInputs ;
outputVisibility ;
nodeDef ;
inputs ;
linksFrom ;
linksTo ;
externalFrom ;
2024-08-15 20:50:25 +00:00
constructor ( name , nodeData ) {
this . name = name ;
this . nodeData = nodeData ;
this . getLinks ( ) ;
this . inputCount = 0 ;
this . oldToNewOutputMap = { } ;
this . newToOldOutputMap = { } ;
this . oldToNewInputMap = { } ;
this . oldToNewWidgetMap = { } ;
this . newToOldWidgetMap = { } ;
this . primitiveDefs = { } ;
this . widgetToPrimitive = { } ;
this . primitiveToWidget = { } ;
this . nodeInputs = { } ;
this . outputVisibility = [ ] ;
}
2024-08-30 23:32:10 +00:00
async registerType ( source = "workflow" ) {
this . nodeDef = {
output : [ ] ,
output _name : [ ] ,
output _is _list : [ ] ,
output _is _hidden : [ ] ,
name : source + "/" + this . name ,
display _name : this . name ,
category : "group nodes" + ( "/" + source ) ,
input : { required : { } } ,
[ GROUP ] : this
} ;
this . inputs = [ ] ;
const seenInputs = { } ;
const seenOutputs = { } ;
for ( let i = 0 ; i < this . nodeData . nodes . length ; i ++ ) {
const node = this . nodeData . nodes [ i ] ;
node . index = i ;
this . processNode ( node , seenInputs , seenOutputs ) ;
}
for ( const p of this . # convertedToProcess ) {
p ( ) ;
}
this . # convertedToProcess = null ;
await app . registerNodeDef ( "workflow/" + this . name , this . nodeDef ) ;
2024-08-15 20:50:25 +00:00
}
getLinks ( ) {
this . linksFrom = { } ;
this . linksTo = { } ;
this . externalFrom = { } ;
for ( const l of this . nodeData . links ) {
const [ sourceNodeId , sourceNodeSlot , targetNodeId , targetNodeSlot ] = l ;
if ( sourceNodeId == null ) continue ;
if ( ! this . linksFrom [ sourceNodeId ] ) {
this . linksFrom [ sourceNodeId ] = { } ;
}
if ( ! this . linksFrom [ sourceNodeId ] [ sourceNodeSlot ] ) {
this . linksFrom [ sourceNodeId ] [ sourceNodeSlot ] = [ ] ;
}
this . linksFrom [ sourceNodeId ] [ sourceNodeSlot ] . push ( l ) ;
if ( ! this . linksTo [ targetNodeId ] ) {
this . linksTo [ targetNodeId ] = { } ;
}
this . linksTo [ targetNodeId ] [ targetNodeSlot ] = l ;
}
if ( this . nodeData . external ) {
for ( const ext2 of this . nodeData . external ) {
if ( ! this . externalFrom [ ext2 [ 0 ] ] ) {
this . externalFrom [ ext2 [ 0 ] ] = { [ ext2 [ 1 ] ] : ext2 [ 2 ] } ;
} else {
this . externalFrom [ ext2 [ 0 ] ] [ ext2 [ 1 ] ] = ext2 [ 2 ] ;
}
}
}
}
processNode ( node , seenInputs , seenOutputs ) {
const def = this . getNodeDef ( node ) ;
if ( ! def ) return ;
2024-08-30 23:32:10 +00:00
const inputs = { ... def . input ? . required , ... def . input ? . optional } ;
2024-08-15 20:50:25 +00:00
this . inputs . push ( this . processNodeInputs ( node , seenInputs , inputs ) ) ;
2024-08-30 23:32:10 +00:00
if ( def . output ? . length ) this . processNodeOutputs ( node , seenOutputs , def ) ;
2024-08-15 20:50:25 +00:00
}
getNodeDef ( node ) {
const def = globalDefs [ node . type ] ;
if ( def ) return def ;
const linksFrom = this . linksFrom [ node . index ] ;
if ( node . type === "PrimitiveNode" ) {
if ( ! linksFrom ) return ;
let type = linksFrom [ "0" ] [ 0 ] [ 5 ] ;
if ( type === "COMBO" ) {
const source = node . outputs [ 0 ] . widget . name ;
const fromTypeName = this . nodeData . nodes [ linksFrom [ "0" ] [ 0 ] [ 2 ] ] . type ;
const fromType = globalDefs [ fromTypeName ] ;
2024-08-30 23:32:10 +00:00
const input = fromType . input . required [ source ] ? ? fromType . input . optional [ source ] ;
2024-08-15 20:50:25 +00:00
type = input [ 0 ] ;
}
const def2 = this . primitiveDefs [ node . index ] = {
input : {
required : {
value : [ type , { } ]
}
} ,
output : [ type ] ,
output _name : [ ] ,
output _is _list : [ ]
} ;
return def2 ;
} else if ( node . type === "Reroute" ) {
const linksTo = this . linksTo [ node . index ] ;
2024-08-30 23:32:10 +00:00
if ( linksTo && linksFrom && ! this . externalFrom [ node . index ] ? . [ 0 ] ) {
2024-08-15 20:50:25 +00:00
return null ;
}
let config = { } ;
let rerouteType = "*" ;
if ( linksFrom ) {
for ( const [ , , id2 , slot ] of linksFrom [ "0" ] ) {
const node2 = this . nodeData . nodes [ id2 ] ;
const input = node2 . inputs [ slot ] ;
if ( rerouteType === "*" ) {
rerouteType = input . type ;
}
if ( input . widget ) {
const targetDef = globalDefs [ node2 . type ] ;
2024-08-30 23:32:10 +00:00
const targetWidget = targetDef . input . required [ input . widget . name ] ? ? targetDef . input . optional [ input . widget . name ] ;
2024-08-15 20:50:25 +00:00
const widget = [ targetWidget [ 0 ] , config ] ;
const res = mergeIfValid (
{
widget
} ,
targetWidget ,
false ,
null ,
widget
) ;
2024-08-30 23:32:10 +00:00
config = res ? . customConfig ? ? config ;
2024-08-15 20:50:25 +00:00
}
}
} else if ( linksTo ) {
const [ id2 , slot ] = linksTo [ "0" ] ;
rerouteType = this . nodeData . nodes [ id2 ] . outputs [ slot ] . type ;
} else {
for ( const l of this . nodeData . links ) {
if ( l [ 2 ] === node . index ) {
rerouteType = l [ 5 ] ;
break ;
}
}
if ( rerouteType === "*" ) {
2024-08-30 23:32:10 +00:00
const t = this . externalFrom [ node . index ] ? . [ 0 ] ;
2024-08-15 20:50:25 +00:00
if ( t ) {
rerouteType = t ;
}
}
}
config . forceInput = true ;
return {
input : {
required : {
[ rerouteType ] : [ rerouteType , config ]
}
} ,
output : [ rerouteType ] ,
output _name : [ ] ,
output _is _list : [ ]
} ;
}
console . warn (
"Skipping virtual node " + node . type + " when building group node " + this . name
) ;
}
getInputConfig ( node , inputName , seenInputs , config , extra ) {
2024-08-30 23:32:10 +00:00
const customConfig = this . nodeData . config ? . [ node . index ] ? . input ? . [ inputName ] ;
let name = customConfig ? . name ? ? node . inputs ? . find ( ( inp ) => inp . name === inputName ) ? . label ? ? inputName ;
2024-08-15 20:50:25 +00:00
let key = name ;
let prefix = "" ;
if ( node . type === "PrimitiveNode" && node . title || name in seenInputs ) {
2024-08-30 23:32:10 +00:00
prefix = ` ${ node . title ? ? node . type } ` ;
2024-08-15 20:50:25 +00:00
key = name = ` ${ prefix } ${ inputName } ` ;
if ( name in seenInputs ) {
name = ` ${ prefix } ${ seenInputs [ name ] } ${ inputName } ` ;
}
}
2024-08-30 23:32:10 +00:00
seenInputs [ key ] = ( seenInputs [ key ] ? ? 1 ) + 1 ;
2024-08-15 20:50:25 +00:00
if ( inputName === "seed" || inputName === "noise_seed" ) {
if ( ! extra ) extra = { } ;
extra . control _after _generate = ` ${ prefix } control_after_generate ` ;
}
if ( config [ 0 ] === "IMAGEUPLOAD" ) {
if ( ! extra ) extra = { } ;
2024-08-30 23:32:10 +00:00
extra . widget = this . oldToNewWidgetMap [ node . index ] ? . [ config [ 1 ] ? . widget ? ? "image" ] ? ? "image" ;
2024-08-15 20:50:25 +00:00
}
if ( extra ) {
2024-08-30 23:32:10 +00:00
config = [ config [ 0 ] , { ... config [ 1 ] , ... extra } ] ;
2024-08-15 20:50:25 +00:00
}
return { name , config , customConfig } ;
}
processWidgetInputs ( inputs , node , inputNames , seenInputs ) {
const slots = [ ] ;
const converted = /* @__PURE__ */ new Map ( ) ;
const widgetMap = this . oldToNewWidgetMap [ node . index ] = { } ;
for ( const inputName of inputNames ) {
let widgetType = app . getWidgetType ( inputs [ inputName ] , inputName ) ;
if ( widgetType ) {
2024-08-30 23:32:10 +00:00
const convertedIndex = node . inputs ? . findIndex (
( inp ) => inp . name === inputName && inp . widget ? . name === inputName
2024-08-15 20:50:25 +00:00
) ;
if ( convertedIndex > - 1 ) {
converted . set ( convertedIndex , inputName ) ;
widgetMap [ inputName ] = null ;
} else {
const { name , config } = this . getInputConfig (
node ,
inputName ,
seenInputs ,
inputs [ inputName ]
) ;
this . nodeDef . input . required [ name ] = config ;
widgetMap [ inputName ] = name ;
this . newToOldWidgetMap [ name ] = { node , inputName } ;
}
} else {
slots . push ( inputName ) ;
}
}
return { converted , slots } ;
}
checkPrimitiveConnection ( link , inputName , inputs ) {
const sourceNode = this . nodeData . nodes [ link [ 0 ] ] ;
if ( sourceNode . type === "PrimitiveNode" ) {
const [ sourceNodeId , _ , targetNodeId , _ _ ] = link ;
const primitiveDef = this . primitiveDefs [ sourceNodeId ] ;
const targetWidget = inputs [ inputName ] ;
const primitiveConfig = primitiveDef . input . required . value ;
const output = { widget : primitiveConfig } ;
const config = mergeIfValid (
output ,
targetWidget ,
false ,
null ,
primitiveConfig
) ;
2024-08-30 23:32:10 +00:00
primitiveConfig [ 1 ] = config ? . customConfig ? ? inputs [ inputName ] [ 1 ] ? { ... inputs [ inputName ] [ 1 ] } : { } ;
2024-08-15 20:50:25 +00:00
let name = this . oldToNewWidgetMap [ sourceNodeId ] [ "value" ] ;
name = name . substr ( 0 , name . length - 6 ) ;
primitiveConfig [ 1 ] . control _after _generate = true ;
primitiveConfig [ 1 ] . control _prefix = name ;
let toPrimitive = this . widgetToPrimitive [ targetNodeId ] ;
if ( ! toPrimitive ) {
toPrimitive = this . widgetToPrimitive [ targetNodeId ] = { } ;
}
if ( toPrimitive [ inputName ] ) {
toPrimitive [ inputName ] . push ( sourceNodeId ) ;
}
toPrimitive [ inputName ] = sourceNodeId ;
let toWidget = this . primitiveToWidget [ sourceNodeId ] ;
if ( ! toWidget ) {
toWidget = this . primitiveToWidget [ sourceNodeId ] = [ ] ;
}
toWidget . push ( { nodeId : targetNodeId , inputName } ) ;
}
}
processInputSlots ( inputs , node , slots , linksTo , inputMap , seenInputs ) {
this . nodeInputs [ node . index ] = { } ;
for ( let i = 0 ; i < slots . length ; i ++ ) {
const inputName = slots [ i ] ;
if ( linksTo [ i ] ) {
this . checkPrimitiveConnection ( linksTo [ i ] , inputName , inputs ) ;
continue ;
}
const { name , config , customConfig } = this . getInputConfig (
node ,
inputName ,
seenInputs ,
inputs [ inputName ]
) ;
this . nodeInputs [ node . index ] [ inputName ] = name ;
2024-08-30 23:32:10 +00:00
if ( customConfig ? . visible === false ) continue ;
2024-08-15 20:50:25 +00:00
this . nodeDef . input . required [ name ] = config ;
inputMap [ i ] = this . inputCount ++ ;
}
}
processConvertedWidgets ( inputs , node , slots , converted , linksTo , inputMap , seenInputs ) {
const convertedSlots = [ ... converted . keys ( ) ] . sort ( ) . map ( ( k ) => converted . get ( k ) ) ;
for ( let i = 0 ; i < convertedSlots . length ; i ++ ) {
const inputName = convertedSlots [ i ] ;
if ( linksTo [ slots . length + i ] ) {
this . checkPrimitiveConnection (
linksTo [ slots . length + i ] ,
inputName ,
inputs
) ;
continue ;
}
const { name , config } = this . getInputConfig (
node ,
inputName ,
seenInputs ,
inputs [ inputName ] ,
{
defaultInput : true
}
) ;
this . nodeDef . input . required [ name ] = config ;
this . newToOldWidgetMap [ name ] = { node , inputName } ;
if ( ! this . oldToNewWidgetMap [ node . index ] ) {
this . oldToNewWidgetMap [ node . index ] = { } ;
}
this . oldToNewWidgetMap [ node . index ] [ inputName ] = name ;
inputMap [ slots . length + i ] = this . inputCount ++ ;
}
}
2024-08-30 23:32:10 +00:00
# convertedToProcess = [ ] ;
2024-08-15 20:50:25 +00:00
processNodeInputs ( node , seenInputs , inputs ) {
const inputMapping = [ ] ;
const inputNames = Object . keys ( inputs ) ;
if ( ! inputNames . length ) return ;
const { converted , slots } = this . processWidgetInputs (
inputs ,
node ,
inputNames ,
seenInputs
) ;
2024-08-30 23:32:10 +00:00
const linksTo = this . linksTo [ node . index ] ? ? { } ;
2024-08-15 20:50:25 +00:00
const inputMap = this . oldToNewInputMap [ node . index ] = { } ;
this . processInputSlots ( inputs , node , slots , linksTo , inputMap , seenInputs ) ;
2024-08-30 23:32:10 +00:00
this . # convertedToProcess . push (
2024-08-15 20:50:25 +00:00
( ) => this . processConvertedWidgets (
inputs ,
node ,
slots ,
converted ,
linksTo ,
inputMap ,
seenInputs
)
) ;
return inputMapping ;
}
processNodeOutputs ( node , seenOutputs , def ) {
const oldToNew = this . oldToNewOutputMap [ node . index ] = { } ;
for ( let outputId = 0 ; outputId < def . output . length ; outputId ++ ) {
const linksFrom = this . linksFrom [ node . index ] ;
2024-08-30 23:32:10 +00:00
const hasLink = linksFrom ? . [ outputId ] && ! this . externalFrom [ node . index ] ? . [ outputId ] ;
const customConfig = this . nodeData . config ? . [ node . index ] ? . output ? . [ outputId ] ;
const visible = customConfig ? . visible ? ? ! hasLink ;
2024-08-15 20:50:25 +00:00
this . outputVisibility . push ( visible ) ;
if ( ! visible ) {
continue ;
}
oldToNew [ outputId ] = this . nodeDef . output . length ;
this . newToOldOutputMap [ this . nodeDef . output . length ] = {
node ,
slot : outputId
} ;
this . nodeDef . output . push ( def . output [ outputId ] ) ;
this . nodeDef . output _is _list . push ( def . output _is _list [ outputId ] ) ;
2024-08-30 23:32:10 +00:00
let label = customConfig ? . name ;
2024-08-15 20:50:25 +00:00
if ( ! label ) {
2024-08-30 23:32:10 +00:00
label = def . output _name ? . [ outputId ] ? ? def . output [ outputId ] ;
2024-08-15 20:50:25 +00:00
const output = node . outputs . find ( ( o ) => o . name === label ) ;
2024-08-30 23:32:10 +00:00
if ( output ? . label ) {
2024-08-15 20:50:25 +00:00
label = output . label ;
}
}
let name = label ;
if ( name in seenOutputs ) {
2024-08-30 23:32:10 +00:00
const prefix = ` ${ node . title ? ? node . type } ` ;
2024-08-15 20:50:25 +00:00
name = ` ${ prefix } ${ label } ` ;
if ( name in seenOutputs ) {
name = ` ${ prefix } ${ node . index } ${ label } ` ;
}
}
seenOutputs [ name ] = 1 ;
this . nodeDef . output _name . push ( name ) ;
}
}
2024-08-30 23:32:10 +00:00
static async registerFromWorkflow ( groupNodes , missingNodeTypes ) {
const clean = app . clean ;
app . clean = function ( ) {
2024-08-15 20:50:25 +00:00
for ( const g in groupNodes ) {
2024-08-30 23:32:10 +00:00
try {
LiteGraph . unregisterNodeType ( "workflow/" + g ) ;
} catch ( error ) {
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
app . clean = clean ;
} ;
for ( const g in groupNodes ) {
const groupData = groupNodes [ g ] ;
let hasMissing = false ;
for ( const n of groupData . nodes ) {
if ( ! ( n . type in LiteGraph . registered _node _types ) ) {
missingNodeTypes . push ( {
type : n . type ,
hint : ` (In group node 'workflow/ ${ g } ') `
} ) ;
missingNodeTypes . push ( {
type : "workflow/" + g ,
action : {
text : "Remove from workflow" ,
callback : /* @__PURE__ */ _ _name ( ( e ) => {
delete groupNodes [ g ] ;
e . target . textContent = "Removed" ;
e . target . style . pointerEvents = "none" ;
e . target . style . opacity = 0.7 ;
} , "callback" )
}
} ) ;
hasMissing = true ;
}
}
if ( hasMissing ) continue ;
const config = new GroupNodeConfig ( g , groupData ) ;
await config . registerType ( ) ;
}
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
class GroupNodeHandler {
static {
_ _name ( this , "GroupNodeHandler" ) ;
}
node ;
groupData ;
innerNodes ;
2024-08-15 20:50:25 +00:00
constructor ( node ) {
this . node = node ;
2024-08-30 23:32:10 +00:00
this . groupData = node . constructor ? . nodeData ? . [ GROUP ] ;
2024-08-15 20:50:25 +00:00
this . node . setInnerNodes = ( innerNodes ) => {
this . innerNodes = innerNodes ;
for ( let innerNodeIndex = 0 ; innerNodeIndex < this . innerNodes . length ; innerNodeIndex ++ ) {
const innerNode = this . innerNodes [ innerNodeIndex ] ;
2024-08-30 23:32:10 +00:00
for ( const w of innerNode . widgets ? ? [ ] ) {
2024-08-15 20:50:25 +00:00
if ( w . type === "converted-widget" ) {
w . serializeValue = w . origSerializeValue ;
}
}
innerNode . index = innerNodeIndex ;
innerNode . getInputNode = ( slot ) => {
2024-08-30 23:32:10 +00:00
const externalSlot = this . groupData . oldToNewInputMap [ innerNode . index ] ? . [ slot ] ;
2024-08-15 20:50:25 +00:00
if ( externalSlot != null ) {
return this . node . getInputNode ( externalSlot ) ;
}
2024-08-30 23:32:10 +00:00
const innerLink = this . groupData . linksTo [ innerNode . index ] ? . [ slot ] ;
2024-08-15 20:50:25 +00:00
if ( ! innerLink ) return null ;
const inputNode = innerNodes [ innerLink [ 0 ] ] ;
if ( inputNode . type === "PrimitiveNode" ) return null ;
return inputNode ;
} ;
innerNode . getInputLink = ( slot ) => {
2024-08-30 23:32:10 +00:00
const externalSlot = this . groupData . oldToNewInputMap [ innerNode . index ] ? . [ slot ] ;
2024-08-15 20:50:25 +00:00
if ( externalSlot != null ) {
const linkId = this . node . inputs [ externalSlot ] . link ;
let link2 = app . graph . links [ linkId ] ;
2024-08-30 23:32:10 +00:00
link2 = {
... link2 ,
2024-08-15 20:50:25 +00:00
target _id : innerNode . id ,
target _slot : + slot
2024-08-30 23:32:10 +00:00
} ;
2024-08-15 20:50:25 +00:00
return link2 ;
}
2024-08-30 23:32:10 +00:00
let link = this . groupData . linksTo [ innerNode . index ] ? . [ slot ] ;
2024-08-15 20:50:25 +00:00
if ( ! link ) return null ;
link = {
origin _id : innerNodes [ link [ 0 ] ] . id ,
origin _slot : link [ 1 ] ,
target _id : innerNode . id ,
target _slot : + slot
} ;
return link ;
} ;
}
} ;
this . node . updateLink = ( link ) => {
2024-08-30 23:32:10 +00:00
link = { ... link } ;
2024-08-15 20:50:25 +00:00
const output = this . groupData . newToOldOutputMap [ link . origin _slot ] ;
let innerNode = this . innerNodes [ output . node . index ] ;
let l ;
2024-08-30 23:32:10 +00:00
while ( innerNode ? . type === "Reroute" ) {
2024-08-15 20:50:25 +00:00
l = innerNode . getInputLink ( 0 ) ;
innerNode = innerNode . getInputNode ( 0 ) ;
}
if ( ! innerNode ) {
return null ;
}
2024-08-30 23:32:10 +00:00
if ( l && GroupNodeHandler . isGroupNode ( innerNode ) ) {
2024-08-15 20:50:25 +00:00
return innerNode . updateLink ( l ) ;
}
link . origin _id = innerNode . id ;
2024-08-30 23:32:10 +00:00
link . origin _slot = l ? . origin _slot ? ? output . slot ;
2024-08-15 20:50:25 +00:00
return link ;
} ;
this . node . getInnerNodes = ( ) => {
if ( ! this . innerNodes ) {
this . node . setInnerNodes (
this . groupData . nodeData . nodes . map ( ( n , i ) => {
const innerNode = LiteGraph . createNode ( n . type ) ;
innerNode . configure ( n ) ;
innerNode . id = ` ${ this . node . id } : ${ i } ` ;
return innerNode ;
} )
) ;
}
this . updateInnerWidgets ( ) ;
return this . innerNodes ;
} ;
2024-08-30 23:32:10 +00:00
this . node . recreate = async ( ) => {
2024-08-15 20:50:25 +00:00
const id2 = this . node . id ;
const sz = this . node . size ;
const nodes = this . node . convertToNodes ( ) ;
const groupNode = LiteGraph . createNode ( this . node . type ) ;
groupNode . id = id2 ;
groupNode . setInnerNodes ( nodes ) ;
groupNode [ GROUP ] . populateWidgets ( ) ;
app . graph . add ( groupNode ) ;
groupNode . size = [
Math . max ( groupNode . size [ 0 ] , sz [ 0 ] ) ,
Math . max ( groupNode . size [ 1 ] , sz [ 1 ] )
] ;
groupNode [ GROUP ] . replaceNodes ( nodes ) ;
return groupNode ;
2024-08-30 23:32:10 +00:00
} ;
2024-08-15 20:50:25 +00:00
this . node . convertToNodes = ( ) => {
2024-08-16 19:25:02 +00:00
const addInnerNodes = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
const backup = localStorage . getItem ( "litegrapheditor_clipboard" ) ;
2024-08-30 23:32:10 +00:00
const c = { ... this . groupData . nodeData } ;
2024-08-15 20:50:25 +00:00
c . nodes = [ ... c . nodes ] ;
const innerNodes = this . node . getInnerNodes ( ) ;
let ids = [ ] ;
for ( let i = 0 ; i < c . nodes . length ; i ++ ) {
2024-08-30 23:32:10 +00:00
let id2 = innerNodes ? . [ i ] ? . id ;
2024-08-15 20:50:25 +00:00
if ( id2 == null || isNaN ( id2 ) ) {
id2 = void 0 ;
} else {
ids . push ( id2 ) ;
}
2024-08-30 23:32:10 +00:00
c . nodes [ i ] = { ... c . nodes [ i ] , id : id2 } ;
2024-08-15 20:50:25 +00:00
}
localStorage . setItem ( "litegrapheditor_clipboard" , JSON . stringify ( c ) ) ;
app . canvas . pasteFromClipboard ( ) ;
localStorage . setItem ( "litegrapheditor_clipboard" , backup ) ;
const [ x , y ] = this . node . pos ;
let top ;
let left ;
const selectedIds2 = ids . length ? ids : Object . keys ( app . canvas . selected _nodes ) ;
const newNodes2 = [ ] ;
for ( let i = 0 ; i < selectedIds2 . length ; i ++ ) {
const id2 = selectedIds2 [ i ] ;
const newNode = app . graph . getNodeById ( id2 ) ;
const innerNode = innerNodes [ i ] ;
newNodes2 . push ( newNode ) ;
if ( left == null || newNode . pos [ 0 ] < left ) {
left = newNode . pos [ 0 ] ;
}
if ( top == null || newNode . pos [ 1 ] < top ) {
top = newNode . pos [ 1 ] ;
}
if ( ! newNode . widgets ) continue ;
const map = this . groupData . oldToNewWidgetMap [ innerNode . index ] ;
if ( map ) {
const widgets = Object . keys ( map ) ;
for ( const oldName of widgets ) {
const newName = map [ oldName ] ;
if ( ! newName ) continue ;
const widgetIndex = this . node . widgets . findIndex (
( w ) => w . name === newName
) ;
if ( widgetIndex === - 1 ) continue ;
if ( innerNode . type === "PrimitiveNode" ) {
for ( let i2 = 0 ; i2 < newNode . widgets . length ; i2 ++ ) {
newNode . widgets [ i2 ] . value = this . node . widgets [ widgetIndex + i2 ] . value ;
}
} else {
const outerWidget = this . node . widgets [ widgetIndex ] ;
const newWidget = newNode . widgets . find (
( w ) => w . name === oldName
) ;
if ( ! newWidget ) continue ;
newWidget . value = outerWidget . value ;
2024-08-30 23:32:10 +00:00
for ( let w = 0 ; w < outerWidget . linkedWidgets ? . length ; w ++ ) {
2024-08-15 20:50:25 +00:00
newWidget . linkedWidgets [ w ] . value = outerWidget . linkedWidgets [ w ] . value ;
}
}
}
}
}
for ( const newNode of newNodes2 ) {
newNode . pos = [
newNode . pos [ 0 ] - ( left - x ) ,
newNode . pos [ 1 ] - ( top - y )
] ;
}
return { newNodes : newNodes2 , selectedIds : selectedIds2 } ;
2024-08-16 19:25:02 +00:00
} , "addInnerNodes" ) ;
const reconnectInputs = /* @__PURE__ */ _ _name ( ( selectedIds2 ) => {
2024-08-15 20:50:25 +00:00
for ( const innerNodeIndex in this . groupData . oldToNewInputMap ) {
const id2 = selectedIds2 [ innerNodeIndex ] ;
const newNode = app . graph . getNodeById ( id2 ) ;
const map = this . groupData . oldToNewInputMap [ innerNodeIndex ] ;
for ( const innerInputId in map ) {
const groupSlotId = map [ innerInputId ] ;
if ( groupSlotId == null ) continue ;
const slot = node . inputs [ groupSlotId ] ;
if ( slot . link == null ) continue ;
const link = app . graph . links [ slot . link ] ;
if ( ! link ) continue ;
const originNode = app . graph . getNodeById ( link . origin _id ) ;
originNode . connect ( link . origin _slot , newNode , + innerInputId ) ;
}
}
2024-08-16 19:25:02 +00:00
} , "reconnectInputs" ) ;
const reconnectOutputs = /* @__PURE__ */ _ _name ( ( selectedIds2 ) => {
2024-08-30 23:32:10 +00:00
for ( let groupOutputId = 0 ; groupOutputId < node . outputs ? . length ; groupOutputId ++ ) {
2024-08-15 20:50:25 +00:00
const output = node . outputs [ groupOutputId ] ;
if ( ! output . links ) continue ;
const links = [ ... output . links ] ;
for ( const l of links ) {
const slot = this . groupData . newToOldOutputMap [ groupOutputId ] ;
const link = app . graph . links [ l ] ;
const targetNode = app . graph . getNodeById ( link . target _id ) ;
const newNode = app . graph . getNodeById ( selectedIds2 [ slot . node . index ] ) ;
newNode . connect ( slot . slot , targetNode , link . target _slot ) ;
}
}
2024-08-16 19:25:02 +00:00
} , "reconnectOutputs" ) ;
2024-08-15 20:50:25 +00:00
const { newNodes , selectedIds } = addInnerNodes ( ) ;
reconnectInputs ( selectedIds ) ;
reconnectOutputs ( selectedIds ) ;
app . graph . remove ( this . node ) ;
return newNodes ;
} ;
const getExtraMenuOptions = this . node . getExtraMenuOptions ;
this . node . getExtraMenuOptions = function ( _ , options ) {
2024-08-30 23:32:10 +00:00
getExtraMenuOptions ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
let optionIndex = options . findIndex ( ( o ) => o . content === "Outputs" ) ;
if ( optionIndex === - 1 ) optionIndex = options . length ;
else optionIndex ++ ;
options . splice (
optionIndex ,
0 ,
null ,
{
content : "Convert to nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
return this . convertToNodes ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ,
{
content : "Manage Group Node" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
new ManageGroupDialog ( app ) . show ( this . type ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
}
) ;
} ;
const onDrawTitleBox = this . node . onDrawTitleBox ;
this . node . onDrawTitleBox = function ( ctx , height , size , scale ) {
2024-08-30 23:32:10 +00:00
onDrawTitleBox ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
const fill = ctx . fillStyle ;
ctx . beginPath ( ) ;
ctx . rect ( 11 , - height + 11 , 2 , 2 ) ;
ctx . rect ( 14 , - height + 11 , 2 , 2 ) ;
ctx . rect ( 17 , - height + 11 , 2 , 2 ) ;
ctx . rect ( 11 , - height + 14 , 2 , 2 ) ;
ctx . rect ( 14 , - height + 14 , 2 , 2 ) ;
ctx . rect ( 17 , - height + 14 , 2 , 2 ) ;
ctx . rect ( 11 , - height + 17 , 2 , 2 ) ;
ctx . rect ( 14 , - height + 17 , 2 , 2 ) ;
ctx . rect ( 17 , - height + 17 , 2 , 2 ) ;
ctx . fillStyle = this . boxcolor || LiteGraph . NODE _DEFAULT _BOXCOLOR ;
ctx . fill ( ) ;
ctx . fillStyle = fill ;
} ;
const onDrawForeground = node . onDrawForeground ;
const groupData = this . groupData . nodeData ;
node . onDrawForeground = function ( ctx ) {
2024-08-30 23:32:10 +00:00
const r = onDrawForeground ? . apply ? . ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
if ( + app . runningNodeId === this . id && this . runningInternalNodeId !== null ) {
const n = groupData . nodes [ this . runningInternalNodeId ] ;
if ( ! n ) return ;
const message = ` Running ${ n . title || n . type } ( ${ this . runningInternalNodeId } / ${ groupData . nodes . length } ) ` ;
ctx . save ( ) ;
ctx . font = "12px sans-serif" ;
const sz = ctx . measureText ( message ) ;
ctx . fillStyle = node . boxcolor || LiteGraph . NODE _DEFAULT _BOXCOLOR ;
ctx . beginPath ( ) ;
ctx . roundRect (
0 ,
- LiteGraph . NODE _TITLE _HEIGHT - 20 ,
sz . width + 12 ,
20 ,
5
) ;
ctx . fill ( ) ;
ctx . fillStyle = "#fff" ;
ctx . fillText ( message , 6 , - LiteGraph . NODE _TITLE _HEIGHT - 6 ) ;
ctx . restore ( ) ;
}
} ;
const onExecutionStart = this . node . onExecutionStart ;
this . node . onExecutionStart = function ( ) {
this . resetExecution = true ;
2024-08-30 23:32:10 +00:00
return onExecutionStart ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
} ;
const self = this ;
const onNodeCreated = this . node . onNodeCreated ;
this . node . onNodeCreated = function ( ) {
if ( ! this . widgets ) {
return ;
}
const config = self . groupData . nodeData . config ;
if ( config ) {
for ( const n in config ) {
2024-08-30 23:32:10 +00:00
const inputs = config [ n ] ? . input ;
2024-08-15 20:50:25 +00:00
for ( const w in inputs ) {
if ( inputs [ w ] . visible !== false ) continue ;
const widgetName = self . groupData . oldToNewWidgetMap [ n ] [ w ] ;
const widget = this . widgets . find ( ( w2 ) => w2 . name === widgetName ) ;
if ( widget ) {
widget . type = "hidden" ;
widget . computeSize = ( ) => [ 0 , - 4 ] ;
}
}
}
}
2024-08-30 23:32:10 +00:00
return onNodeCreated ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
} ;
function handleEvent ( type , getId , getEvent ) {
2024-08-16 19:25:02 +00:00
const handler = /* @__PURE__ */ _ _name ( ( { detail } ) => {
2024-08-15 20:50:25 +00:00
const id2 = getId ( detail ) ;
if ( ! id2 ) return ;
const node2 = app . graph . getNodeById ( id2 ) ;
if ( node2 ) return ;
2024-08-30 23:32:10 +00:00
const innerNodeIndex = this . innerNodes ? . findIndex ( ( n ) => n . id == id2 ) ;
2024-08-15 20:50:25 +00:00
if ( innerNodeIndex > - 1 ) {
this . node . runningInternalNodeId = innerNodeIndex ;
api . dispatchEvent (
new CustomEvent ( type , {
detail : getEvent ( detail , this . node . id + "" , this . node )
} )
) ;
}
2024-08-16 19:25:02 +00:00
} , "handler" ) ;
2024-08-15 20:50:25 +00:00
api . addEventListener ( type , handler ) ;
return handler ;
}
2024-08-16 19:25:02 +00:00
_ _name ( handleEvent , "handleEvent" ) ;
2024-08-15 20:50:25 +00:00
const executing = handleEvent . call (
this ,
"executing" ,
( d ) => d ,
( d , id2 , node2 ) => id2
) ;
const executed = handleEvent . call (
this ,
"executed" ,
2024-08-30 23:32:10 +00:00
( d ) => d ? . display _node || d ? . node ,
( d , id2 , node2 ) => ( {
... d ,
2024-08-15 20:50:25 +00:00
node : id2 ,
display _node : id2 ,
merge : ! node2 . resetExecution
} )
) ;
const onRemoved = node . onRemoved ;
this . node . onRemoved = function ( ) {
2024-08-30 23:32:10 +00:00
onRemoved ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
api . removeEventListener ( "executing" , executing ) ;
api . removeEventListener ( "executed" , executed ) ;
} ;
this . node . refreshComboInNode = ( defs ) => {
for ( const widgetName in this . groupData . newToOldWidgetMap ) {
const widget = this . node . widgets . find ( ( w ) => w . name === widgetName ) ;
2024-08-30 23:32:10 +00:00
if ( widget ? . type === "combo" ) {
2024-08-15 20:50:25 +00:00
const old = this . groupData . newToOldWidgetMap [ widgetName ] ;
const def = defs [ old . node . type ] ;
2024-08-30 23:32:10 +00:00
const input = def ? . input ? . required ? . [ old . inputName ] ? ? def ? . input ? . optional ? . [ old . inputName ] ;
2024-08-15 20:50:25 +00:00
if ( ! input ) continue ;
widget . options . values = input [ 0 ] ;
if ( old . inputName !== "image" && ! widget . options . values . includes ( widget . value ) ) {
widget . value = widget . options . values [ 0 ] ;
widget . callback ( widget . value ) ;
}
}
}
} ;
}
updateInnerWidgets ( ) {
for ( const newWidgetName in this . groupData . newToOldWidgetMap ) {
const newWidget = this . node . widgets . find ( ( w ) => w . name === newWidgetName ) ;
if ( ! newWidget ) continue ;
const newValue = newWidget . value ;
const old = this . groupData . newToOldWidgetMap [ newWidgetName ] ;
let innerNode = this . innerNodes [ old . node . index ] ;
if ( innerNode . type === "PrimitiveNode" ) {
innerNode . primitiveValue = newValue ;
const primitiveLinked = this . groupData . primitiveToWidget [ old . node . index ] ;
2024-08-30 23:32:10 +00:00
for ( const linked of primitiveLinked ? ? [ ] ) {
2024-08-15 20:50:25 +00:00
const node = this . innerNodes [ linked . nodeId ] ;
const widget2 = node . widgets . find ( ( w ) => w . name === linked . inputName ) ;
if ( widget2 ) {
widget2 . value = newValue ;
}
}
continue ;
} else if ( innerNode . type === "Reroute" ) {
const rerouteLinks = this . groupData . linksFrom [ old . node . index ] ;
if ( rerouteLinks ) {
for ( const [ _ , , targetNodeId , targetSlot ] of rerouteLinks [ "0" ] ) {
const node = this . innerNodes [ targetNodeId ] ;
const input = node . inputs [ targetSlot ] ;
if ( input . widget ) {
2024-08-30 23:32:10 +00:00
const widget2 = node . widgets ? . find (
2024-08-15 20:50:25 +00:00
( w ) => w . name === input . widget . name
) ;
if ( widget2 ) {
widget2 . value = newValue ;
}
}
}
}
}
2024-08-30 23:32:10 +00:00
const widget = innerNode . widgets ? . find ( ( w ) => w . name === old . inputName ) ;
2024-08-15 20:50:25 +00:00
if ( widget ) {
widget . value = newValue ;
}
}
}
populatePrimitive ( node , nodeId , oldName , i , linkedShift ) {
2024-08-30 23:32:10 +00:00
const primitiveId = this . groupData . widgetToPrimitive [ nodeId ] ? . [ oldName ] ;
2024-08-15 20:50:25 +00:00
if ( primitiveId == null ) return ;
const targetWidgetName = this . groupData . oldToNewWidgetMap [ primitiveId ] [ "value" ] ;
const targetWidgetIndex = this . node . widgets . findIndex (
( w ) => w . name === targetWidgetName
) ;
if ( targetWidgetIndex > - 1 ) {
const primitiveNode = this . innerNodes [ primitiveId ] ;
let len = primitiveNode . widgets . length ;
2024-08-30 23:32:10 +00:00
if ( len - 1 !== this . node . widgets [ targetWidgetIndex ] . linkedWidgets ? . length ) {
2024-08-15 20:50:25 +00:00
len = 1 ;
}
for ( let i2 = 0 ; i2 < len ; i2 ++ ) {
this . node . widgets [ targetWidgetIndex + i2 ] . value = primitiveNode . widgets [ i2 ] . value ;
}
}
return true ;
}
populateReroute ( node , nodeId , map ) {
if ( node . type !== "Reroute" ) return ;
2024-08-30 23:32:10 +00:00
const link = this . groupData . linksFrom [ nodeId ] ? . [ 0 ] ? . [ 0 ] ;
2024-08-15 20:50:25 +00:00
if ( ! link ) return ;
const [ , , targetNodeId , targetNodeSlot ] = link ;
const targetNode = this . groupData . nodeData . nodes [ targetNodeId ] ;
const inputs = targetNode . inputs ;
2024-08-30 23:32:10 +00:00
const targetWidget = inputs ? . [ targetNodeSlot ] ? . widget ;
2024-08-15 20:50:25 +00:00
if ( ! targetWidget ) return ;
2024-08-30 23:32:10 +00:00
const offset = inputs . length - ( targetNode . widgets _values ? . length ? ? 0 ) ;
const v = targetNode . widgets _values ? . [ targetNodeSlot - offset ] ;
2024-08-15 20:50:25 +00:00
if ( v == null ) return ;
const widgetName = Object . values ( map ) [ 0 ] ;
const widget = this . node . widgets . find ( ( w ) => w . name === widgetName ) ;
if ( widget ) {
widget . value = v ;
}
}
populateWidgets ( ) {
if ( ! this . node . widgets ) return ;
for ( let nodeId = 0 ; nodeId < this . groupData . nodeData . nodes . length ; nodeId ++ ) {
const node = this . groupData . nodeData . nodes [ nodeId ] ;
2024-08-30 23:32:10 +00:00
const map = this . groupData . oldToNewWidgetMap [ nodeId ] ? ? { } ;
2024-08-15 20:50:25 +00:00
const widgets = Object . keys ( map ) ;
2024-08-30 23:32:10 +00:00
if ( ! node . widgets _values ? . length ) {
2024-08-15 20:50:25 +00:00
this . populateReroute ( node , nodeId , map ) ;
continue ;
}
let linkedShift = 0 ;
for ( let i = 0 ; i < widgets . length ; i ++ ) {
const oldName = widgets [ i ] ;
const newName = map [ oldName ] ;
const widgetIndex = this . node . widgets . findIndex (
( w ) => w . name === newName
) ;
const mainWidget = this . node . widgets [ widgetIndex ] ;
if ( this . populatePrimitive ( node , nodeId , oldName , i , linkedShift ) || widgetIndex === - 1 ) {
2024-08-30 23:32:10 +00:00
const innerWidget = this . innerNodes [ nodeId ] . widgets ? . find (
2024-08-15 20:50:25 +00:00
( w ) => w . name === oldName
) ;
2024-08-30 23:32:10 +00:00
linkedShift += innerWidget ? . linkedWidgets ? . length ? ? 0 ;
2024-08-15 20:50:25 +00:00
}
if ( widgetIndex === - 1 ) {
continue ;
}
mainWidget . value = node . widgets _values [ i + linkedShift ] ;
2024-08-30 23:32:10 +00:00
for ( let w = 0 ; w < mainWidget . linkedWidgets ? . length ; w ++ ) {
2024-08-15 20:50:25 +00:00
this . node . widgets [ widgetIndex + w + 1 ] . value = node . widgets _values [ i + ++ linkedShift ] ;
}
}
}
}
replaceNodes ( nodes ) {
let top ;
let left ;
for ( let i = 0 ; i < nodes . length ; i ++ ) {
const node = nodes [ i ] ;
if ( left == null || node . pos [ 0 ] < left ) {
left = node . pos [ 0 ] ;
}
if ( top == null || node . pos [ 1 ] < top ) {
top = node . pos [ 1 ] ;
}
this . linkOutputs ( node , i ) ;
app . graph . remove ( node ) ;
}
this . linkInputs ( ) ;
this . node . pos = [ left , top ] ;
}
linkOutputs ( originalNode , nodeId ) {
if ( ! originalNode . outputs ) return ;
for ( const output of originalNode . outputs ) {
if ( ! output . links ) continue ;
const links = [ ... output . links ] ;
for ( const l of links ) {
const link = app . graph . links [ l ] ;
if ( ! link ) continue ;
const targetNode = app . graph . getNodeById ( link . target _id ) ;
2024-08-30 23:32:10 +00:00
const newSlot = this . groupData . oldToNewOutputMap [ nodeId ] ? . [ link . origin _slot ] ;
2024-08-15 20:50:25 +00:00
if ( newSlot != null ) {
this . node . connect ( newSlot , targetNode , link . target _slot ) ;
}
}
}
}
linkInputs ( ) {
2024-08-30 23:32:10 +00:00
for ( const link of this . groupData . nodeData . links ? ? [ ] ) {
2024-08-15 20:50:25 +00:00
const [ , originSlot , targetId , targetSlot , actualOriginId ] = link ;
const originNode = app . graph . getNodeById ( actualOriginId ) ;
if ( ! originNode ) continue ;
originNode . connect (
originSlot ,
this . node . id ,
this . groupData . oldToNewInputMap [ targetId ] [ targetSlot ]
) ;
}
}
static getGroupData ( node ) {
2024-08-30 23:32:10 +00:00
return ( node . nodeData ? ? node . constructor ? . nodeData ) ? . [ GROUP ] ;
2024-08-15 20:50:25 +00:00
}
static isGroupNode ( node ) {
2024-08-30 23:32:10 +00:00
return ! ! node . constructor ? . nodeData ? . [ GROUP ] ;
}
static async fromNodes ( nodes ) {
const builder = new GroupNodeBuilder ( nodes ) ;
const res = builder . build ( ) ;
if ( ! res ) return ;
const { name , nodeData } = res ;
const config = new GroupNodeConfig ( name , nodeData ) ;
await config . registerType ( ) ;
const groupNode = LiteGraph . createNode ( ` workflow/ ${ name } ` ) ;
groupNode . setInnerNodes ( builder . nodes ) ;
groupNode [ GROUP ] . populateWidgets ( ) ;
app . graph . add ( groupNode ) ;
groupNode [ GROUP ] . replaceNodes ( builder . nodes ) ;
return groupNode ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
function addConvertToGroupOptions ( ) {
function addConvertOption ( options , index ) {
2024-08-30 23:32:10 +00:00
const selected = Object . values ( app . canvas . selected _nodes ? ? { } ) ;
2024-08-15 20:50:25 +00:00
const disabled = selected . length < 2 || selected . find ( ( n ) => GroupNodeHandler . isGroupNode ( n ) ) ;
options . splice ( index + 1 , null , {
content : ` Convert to Group Node ` ,
disabled ,
2024-08-30 23:32:10 +00:00
callback : /* @__PURE__ */ _ _name ( async ( ) => {
return await GroupNodeHandler . fromNodes ( selected ) ;
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( addConvertOption , "addConvertOption" ) ;
2024-08-15 20:50:25 +00:00
function addManageOption ( options , index ) {
2024-08-30 23:32:10 +00:00
const groups = app . graph . extra ? . groupNodes ;
2024-08-15 20:50:25 +00:00
const disabled = ! groups || ! Object . keys ( groups ) . length ;
options . splice ( index + 1 , null , {
content : ` Manage Group Nodes ` ,
disabled ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
new ManageGroupDialog ( app ) . show ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( addManageOption , "addManageOption" ) ;
2024-08-15 20:50:25 +00:00
const getCanvasMenuOptions = LGraphCanvas . prototype . getCanvasMenuOptions ;
LGraphCanvas . prototype . getCanvasMenuOptions = function ( ) {
const options = getCanvasMenuOptions . apply ( this , arguments ) ;
2024-08-30 23:32:10 +00:00
const index = options . findIndex ( ( o ) => o ? . content === "Add Group" ) + 1 || options . length ;
2024-08-15 20:50:25 +00:00
addConvertOption ( options , index ) ;
addManageOption ( options , index + 1 ) ;
return options ;
} ;
const getNodeMenuOptions = LGraphCanvas . prototype . getNodeMenuOptions ;
LGraphCanvas . prototype . getNodeMenuOptions = function ( node ) {
const options = getNodeMenuOptions . apply ( this , arguments ) ;
if ( ! GroupNodeHandler . isGroupNode ( node ) ) {
2024-08-30 23:32:10 +00:00
const index = options . findIndex ( ( o ) => o ? . content === "Outputs" ) + 1 || options . length - 1 ;
2024-08-15 20:50:25 +00:00
addConvertOption ( options , index ) ;
}
return options ;
} ;
}
2024-08-16 19:25:02 +00:00
_ _name ( addConvertToGroupOptions , "addConvertToGroupOptions" ) ;
2024-08-15 20:50:25 +00:00
const id$3 = "Comfy.GroupNode" ;
let globalDefs ;
const ext$1 = {
name : id$3 ,
setup ( ) {
addConvertToGroupOptions ( ) ;
} ,
2024-08-30 23:32:10 +00:00
async beforeConfigureGraph ( graphData , missingNodeTypes ) {
const nodes = graphData ? . extra ? . groupNodes ;
if ( nodes ) {
await GroupNodeConfig . registerFromWorkflow ( nodes , missingNodeTypes ) ;
}
2024-08-15 20:50:25 +00:00
} ,
addCustomNodeDefs ( defs ) {
globalDefs = defs ;
} ,
nodeCreated ( node ) {
if ( GroupNodeHandler . isGroupNode ( node ) ) {
node [ GROUP ] = new GroupNodeHandler ( node ) ;
}
} ,
2024-08-30 23:32:10 +00:00
async refreshComboInNodes ( defs ) {
Object . assign ( globalDefs , defs ) ;
const nodes = app . graph . extra ? . groupNodes ;
if ( nodes ) {
await GroupNodeConfig . registerFromWorkflow ( nodes , { } ) ;
}
2024-08-15 20:50:25 +00:00
}
} ;
app . registerExtension ( ext$1 ) ;
window . comfyAPI = window . comfyAPI || { } ;
window . comfyAPI . groupNode = window . comfyAPI . groupNode || { } ;
window . comfyAPI . groupNode . GroupNodeConfig = GroupNodeConfig ;
window . comfyAPI . groupNode . GroupNodeHandler = GroupNodeHandler ;
function setNodeMode ( node , mode ) {
node . mode = mode ;
node . graph . change ( ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( setNodeMode , "setNodeMode" ) ;
2024-08-15 20:50:25 +00:00
function addNodesToGroup ( group , nodes = [ ] ) {
var x1 , y1 , x2 , y2 ;
var nx1 , ny1 , nx2 , ny2 ;
var node ;
x1 = y1 = x2 = y2 = - 1 ;
nx1 = ny1 = nx2 = ny2 = - 1 ;
for ( var n of [ group . _nodes , nodes ] ) {
for ( var i in n ) {
node = n [ i ] ;
nx1 = node . pos [ 0 ] ;
ny1 = node . pos [ 1 ] ;
nx2 = node . pos [ 0 ] + node . size [ 0 ] ;
ny2 = node . pos [ 1 ] + node . size [ 1 ] ;
if ( node . type != "Reroute" ) {
ny1 -= LiteGraph . NODE _TITLE _HEIGHT ;
}
2024-08-30 23:32:10 +00:00
if ( node . flags ? . collapsed ) {
2024-08-15 20:50:25 +00:00
ny2 = ny1 + LiteGraph . NODE _TITLE _HEIGHT ;
2024-08-30 23:32:10 +00:00
if ( node ? . _collapsed _width ) {
2024-08-15 20:50:25 +00:00
nx2 = nx1 + Math . round ( node . _collapsed _width ) ;
}
}
if ( x1 == - 1 || nx1 < x1 ) {
x1 = nx1 ;
}
if ( y1 == - 1 || ny1 < y1 ) {
y1 = ny1 ;
}
if ( x2 == - 1 || nx2 > x2 ) {
x2 = nx2 ;
}
if ( y2 == - 1 || ny2 > y2 ) {
y2 = ny2 ;
}
}
}
var padding = 10 ;
y1 = y1 - Math . round ( group . font _size * 1.4 ) ;
group . pos = [ x1 - padding , y1 - padding ] ;
group . size = [ x2 - x1 + padding * 2 , y2 - y1 + padding * 2 ] ;
}
2024-08-16 19:25:02 +00:00
_ _name ( addNodesToGroup , "addNodesToGroup" ) ;
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : "Comfy.GroupOptions" ,
setup ( ) {
const orig = LGraphCanvas . prototype . getCanvasMenuOptions ;
LGraphCanvas . prototype . getCanvasMenuOptions = function ( ) {
const options = orig . apply ( this , arguments ) ;
const group = this . graph . getGroupOnPos (
this . graph _mouse [ 0 ] ,
this . graph _mouse [ 1 ]
) ;
if ( ! group ) {
options . push ( {
content : "Add Group For Selected Nodes" ,
disabled : ! Object . keys ( app . canvas . selected _nodes || { } ) . length ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-09-05 22:56:01 +00:00
const group2 = new LGraphGroup ( ) ;
2024-08-15 20:50:25 +00:00
addNodesToGroup ( group2 , this . selected _nodes ) ;
app . canvas . graph . add ( group2 ) ;
this . graph . change ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
return options ;
}
group . recomputeInsideNodes ( ) ;
const nodesInGroup = group . _nodes ;
options . push ( {
content : "Add Selected Nodes To Group" ,
disabled : ! Object . keys ( app . canvas . selected _nodes || { } ) . length ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
addNodesToGroup ( group , this . selected _nodes ) ;
this . graph . change ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
if ( nodesInGroup . length === 0 ) {
return options ;
} else {
options . push ( null ) ;
}
let allNodesAreSameMode = true ;
for ( let i = 1 ; i < nodesInGroup . length ; i ++ ) {
if ( nodesInGroup [ i ] . mode !== nodesInGroup [ 0 ] . mode ) {
allNodesAreSameMode = false ;
break ;
}
}
options . push ( {
content : "Fit Group To Nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
addNodesToGroup ( group ) ;
this . graph . change ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Select Nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
this . selectNodes ( nodesInGroup ) ;
this . graph . change ( ) ;
this . canvas . focus ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
if ( allNodesAreSameMode ) {
const mode = nodesInGroup [ 0 ] . mode ;
switch ( mode ) {
case 0 :
options . push ( {
content : "Set Group Nodes to Never" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 2 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Bypass Group Nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 4 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
break ;
case 2 :
options . push ( {
content : "Set Group Nodes to Always" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 0 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Bypass Group Nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 4 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
break ;
case 4 :
options . push ( {
content : "Set Group Nodes to Always" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 0 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Set Group Nodes to Never" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 2 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
break ;
default :
options . push ( {
content : "Set Group Nodes to Always" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 0 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Set Group Nodes to Never" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 2 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Bypass Group Nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 4 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
break ;
}
} else {
options . push ( {
content : "Set Group Nodes to Always" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 0 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Set Group Nodes to Never" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 2 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Bypass Group Nodes" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
for ( const node of nodesInGroup ) {
setNodeMode ( node , 4 ) ;
}
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
}
return options ;
} ;
}
} ) ;
const id$2 = "Comfy.InvertMenuScrolling" ;
app . registerExtension ( {
name : id$2 ,
init ( ) {
const ctxMenu = LiteGraph . ContextMenu ;
2024-08-16 19:25:02 +00:00
const replace = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
LiteGraph . ContextMenu = function ( values , options ) {
options = options || { } ;
if ( options . scroll _speed ) {
options . scroll _speed *= - 1 ;
} else {
options . scroll _speed = - 0.1 ;
}
return ctxMenu . call ( this , values , options ) ;
} ;
LiteGraph . ContextMenu . prototype = ctxMenu . prototype ;
2024-08-16 19:25:02 +00:00
} , "replace" ) ;
2024-08-15 20:50:25 +00:00
app . ui . settings . addSetting ( {
id : id$2 ,
2024-08-21 04:00:49 +00:00
category : [ "Comfy" , "Graph" , "InvertMenuScrolling" ] ,
name : "Invert Context Menu Scrolling" ,
2024-08-15 20:50:25 +00:00
type : "boolean" ,
defaultValue : false ,
onChange ( value ) {
if ( value ) {
replace ( ) ;
} else {
LiteGraph . ContextMenu = ctxMenu ;
}
}
} ) ;
}
} ) ;
app . registerExtension ( {
name : "Comfy.Keybinds" ,
init ( ) {
2024-08-30 23:32:10 +00:00
const keybindListener = /* @__PURE__ */ _ _name ( async function ( event ) {
const modifierPressed = event . ctrlKey || event . metaKey ;
if ( modifierPressed && event . key === "Enter" ) {
if ( event . altKey ) {
await api . interrupt ( ) ;
useToastStore ( ) . add ( {
severity : "info" ,
summary : "Interrupted" ,
detail : "Execution has been interrupted" ,
life : 1e3
2024-08-21 04:00:49 +00:00
} ) ;
2024-08-30 23:32:10 +00:00
return ;
2024-08-21 04:00:49 +00:00
}
2024-08-30 23:32:10 +00:00
app . queuePrompt ( event . shiftKey ? - 1 : 0 ) . then ( ) ;
return ;
}
const target = event . composedPath ( ) [ 0 ] ;
if ( target . tagName === "TEXTAREA" || target . tagName === "INPUT" || target . tagName === "SPAN" && target . classList . contains ( "property_value" ) ) {
return ;
}
const modifierKeyIdMap = {
s : "#comfy-save-button" ,
o : "#comfy-file-input" ,
Backspace : "#comfy-clear-button" ,
d : "#comfy-load-default-button" ,
g : "#comfy-group-selected-nodes-button"
} ;
const modifierKeybindId = modifierKeyIdMap [ event . key ] ;
if ( modifierPressed && modifierKeybindId ) {
event . preventDefault ( ) ;
const elem = document . querySelector ( modifierKeybindId ) ;
elem . click ( ) ;
return ;
}
if ( event . ctrlKey || event . altKey || event . metaKey ) {
return ;
}
if ( event . key === "Escape" ) {
const modals = document . querySelectorAll ( ".comfy-modal" ) ;
const modal = Array . from ( modals ) . find (
( modal2 ) => window . getComputedStyle ( modal2 ) . getPropertyValue ( "display" ) !== "none"
) ;
if ( modal ) {
modal . style . display = "none" ;
2024-08-21 04:00:49 +00:00
}
2024-08-30 23:32:10 +00:00
;
[ ... document . querySelectorAll ( "dialog" ) ] . forEach ( ( d ) => {
d . close ( ) ;
} ) ;
}
const keyIdMap = {
q : ".queue-tab-button.side-bar-button" ,
h : ".queue-tab-button.side-bar-button" ,
r : "#comfy-refresh-button"
} ;
const buttonId = keyIdMap [ event . key ] ;
if ( buttonId ) {
const button = document . querySelector ( buttonId ) ;
button . click ( ) ;
}
2024-08-16 19:25:02 +00:00
} , "keybindListener" ) ;
2024-08-15 20:50:25 +00:00
window . addEventListener ( "keydown" , keybindListener , true ) ;
}
} ) ;
const id$1 = "Comfy.LinkRenderMode" ;
const ext = {
name : id$1 ,
2024-08-30 23:32:10 +00:00
async setup ( app2 ) {
app2 . ui . settings . addSetting ( {
id : id$1 ,
category : [ "Comfy" , "Graph" , "LinkRenderMode" ] ,
name : "Link Render Mode" ,
defaultValue : 2 ,
type : "combo" ,
// @ts-expect-error
options : [ ... LiteGraph . LINK _RENDER _MODES , "Hidden" ] . map ( ( m , i ) => ( {
value : i ,
text : m ,
selected : i == app2 . canvas . links _render _mode
} ) ) ,
onChange ( value ) {
app2 . canvas . links _render _mode = + value ;
app2 . graph . setDirtyCanvas ( true ) ;
}
2024-08-15 20:50:25 +00:00
} ) ;
}
} ;
app . registerExtension ( ext ) ;
function dataURLToBlob ( dataURL ) {
const parts = dataURL . split ( ";base64," ) ;
const contentType = parts [ 0 ] . split ( ":" ) [ 1 ] ;
const byteString = atob ( parts [ 1 ] ) ;
const arrayBuffer = new ArrayBuffer ( byteString . length ) ;
const uint8Array = new Uint8Array ( arrayBuffer ) ;
for ( let i = 0 ; i < byteString . length ; i ++ ) {
uint8Array [ i ] = byteString . charCodeAt ( i ) ;
}
return new Blob ( [ arrayBuffer ] , { type : contentType } ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( dataURLToBlob , "dataURLToBlob" ) ;
2024-08-15 20:50:25 +00:00
function loadedImageToBlob ( image ) {
const canvas = document . createElement ( "canvas" ) ;
canvas . width = image . width ;
canvas . height = image . height ;
const ctx = canvas . getContext ( "2d" ) ;
ctx . drawImage ( image , 0 , 0 ) ;
const dataURL = canvas . toDataURL ( "image/png" , 1 ) ;
const blob = dataURLToBlob ( dataURL ) ;
return blob ;
}
2024-08-16 19:25:02 +00:00
_ _name ( loadedImageToBlob , "loadedImageToBlob" ) ;
2024-08-15 20:50:25 +00:00
function loadImage ( imagePath ) {
return new Promise ( ( resolve , reject ) => {
const image = new Image ( ) ;
image . onload = function ( ) {
resolve ( image ) ;
} ;
image . src = imagePath ;
} ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( loadImage , "loadImage" ) ;
2024-08-30 23:32:10 +00:00
async function uploadMask ( filepath , formData ) {
await api . fetchApi ( "/upload/mask" , {
method : "POST" ,
body : formData
} ) . then ( ( response ) => {
} ) . catch ( ( error ) => {
console . error ( "Error:" , error ) ;
2024-08-15 20:50:25 +00:00
} ) ;
2024-08-30 23:32:10 +00:00
ComfyApp . clipspace . imgs [ ComfyApp . clipspace [ "selectedIndex" ] ] = new Image ( ) ;
ComfyApp . clipspace . imgs [ ComfyApp . clipspace [ "selectedIndex" ] ] . src = api . apiURL (
"/view?" + new URLSearchParams ( filepath ) . toString ( ) + app . getPreviewFormatParam ( ) + app . getRandParam ( )
) ;
if ( ComfyApp . clipspace . images )
ComfyApp . clipspace . images [ ComfyApp . clipspace [ "selectedIndex" ] ] = filepath ;
ClipspaceDialog . invalidatePreview ( ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( uploadMask , "uploadMask" ) ;
2024-08-15 20:50:25 +00:00
function prepare _mask ( image , maskCanvas , maskCtx , maskColor ) {
maskCtx . drawImage ( image , 0 , 0 , maskCanvas . width , maskCanvas . height ) ;
const maskData = maskCtx . getImageData (
0 ,
0 ,
maskCanvas . width ,
maskCanvas . height
) ;
for ( let i = 0 ; i < maskData . data . length ; i += 4 ) {
if ( maskData . data [ i + 3 ] == 255 ) maskData . data [ i + 3 ] = 0 ;
else maskData . data [ i + 3 ] = 255 ;
maskData . data [ i ] = maskColor . r ;
maskData . data [ i + 1 ] = maskColor . g ;
maskData . data [ i + 2 ] = maskColor . b ;
}
maskCtx . globalCompositeOperation = "source-over" ;
maskCtx . putImageData ( maskData , 0 , 0 ) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( prepare _mask , "prepare_mask" ) ;
2024-08-30 23:32:10 +00:00
class MaskEditorDialog extends ComfyDialog {
static {
_ _name ( this , "MaskEditorDialog" ) ;
}
static instance = null ;
static mousedown _x = null ;
static mousedown _y = null ;
brush ;
maskCtx ;
maskCanvas ;
brush _size _slider ;
brush _opacity _slider ;
colorButton ;
saveButton ;
zoom _ratio ;
pan _x ;
pan _y ;
imgCanvas ;
last _display _style ;
is _visible ;
image ;
handler _registered ;
brush _slider _input ;
cursorX ;
cursorY ;
mousedown _pan _x ;
mousedown _pan _y ;
last _pressure ;
static getInstance ( ) {
if ( ! MaskEditorDialog . instance ) {
MaskEditorDialog . instance = new MaskEditorDialog ( ) ;
}
return MaskEditorDialog . instance ;
}
is _layout _created = false ;
2024-08-15 20:50:25 +00:00
constructor ( ) {
super ( ) ;
this . element = $el ( "div.comfy-modal" , { parent : document . body } , [
$el ( "div.comfy-modal-content" , [ ... this . createButtons ( ) ] )
] ) ;
}
createButtons ( ) {
return [ ] ;
}
createButton ( name , callback ) {
var button = document . createElement ( "button" ) ;
button . style . pointerEvents = "auto" ;
button . innerText = name ;
button . addEventListener ( "click" , callback ) ;
return button ;
}
createLeftButton ( name , callback ) {
var button = this . createButton ( name , callback ) ;
button . style . cssFloat = "left" ;
button . style . marginRight = "4px" ;
return button ;
}
createRightButton ( name , callback ) {
var button = this . createButton ( name , callback ) ;
button . style . cssFloat = "right" ;
button . style . marginLeft = "4px" ;
return button ;
}
createLeftSlider ( self , name , callback ) {
const divElement = document . createElement ( "div" ) ;
divElement . id = "maskeditor-slider" ;
divElement . style . cssFloat = "left" ;
divElement . style . fontFamily = "sans-serif" ;
divElement . style . marginRight = "4px" ;
divElement . style . color = "var(--input-text)" ;
divElement . style . backgroundColor = "var(--comfy-input-bg)" ;
divElement . style . borderRadius = "8px" ;
divElement . style . borderColor = "var(--border-color)" ;
divElement . style . borderStyle = "solid" ;
divElement . style . fontSize = "15px" ;
divElement . style . height = "21px" ;
divElement . style . padding = "1px 6px" ;
divElement . style . display = "flex" ;
divElement . style . position = "relative" ;
divElement . style . top = "2px" ;
divElement . style . pointerEvents = "auto" ;
self . brush _slider _input = document . createElement ( "input" ) ;
self . brush _slider _input . setAttribute ( "type" , "range" ) ;
self . brush _slider _input . setAttribute ( "min" , "1" ) ;
self . brush _slider _input . setAttribute ( "max" , "100" ) ;
self . brush _slider _input . setAttribute ( "value" , "10" ) ;
const labelElement = document . createElement ( "label" ) ;
labelElement . textContent = name ;
divElement . appendChild ( labelElement ) ;
divElement . appendChild ( self . brush _slider _input ) ;
self . brush _slider _input . addEventListener ( "change" , callback ) ;
return divElement ;
}
createOpacitySlider ( self , name , callback ) {
const divElement = document . createElement ( "div" ) ;
divElement . id = "maskeditor-opacity-slider" ;
divElement . style . cssFloat = "left" ;
divElement . style . fontFamily = "sans-serif" ;
divElement . style . marginRight = "4px" ;
divElement . style . color = "var(--input-text)" ;
divElement . style . backgroundColor = "var(--comfy-input-bg)" ;
divElement . style . borderRadius = "8px" ;
divElement . style . borderColor = "var(--border-color)" ;
divElement . style . borderStyle = "solid" ;
divElement . style . fontSize = "15px" ;
divElement . style . height = "21px" ;
divElement . style . padding = "1px 6px" ;
divElement . style . display = "flex" ;
divElement . style . position = "relative" ;
divElement . style . top = "2px" ;
divElement . style . pointerEvents = "auto" ;
self . opacity _slider _input = document . createElement ( "input" ) ;
self . opacity _slider _input . setAttribute ( "type" , "range" ) ;
self . opacity _slider _input . setAttribute ( "min" , "0.1" ) ;
self . opacity _slider _input . setAttribute ( "max" , "1.0" ) ;
self . opacity _slider _input . setAttribute ( "step" , "0.01" ) ;
self . opacity _slider _input . setAttribute ( "value" , "0.7" ) ;
const labelElement = document . createElement ( "label" ) ;
labelElement . textContent = name ;
divElement . appendChild ( labelElement ) ;
divElement . appendChild ( self . opacity _slider _input ) ;
self . opacity _slider _input . addEventListener ( "input" , callback ) ;
return divElement ;
}
setlayout ( imgCanvas , maskCanvas ) {
const self = this ;
var bottom _panel = document . createElement ( "div" ) ;
bottom _panel . style . position = "absolute" ;
bottom _panel . style . bottom = "0px" ;
bottom _panel . style . left = "20px" ;
bottom _panel . style . right = "20px" ;
bottom _panel . style . height = "50px" ;
bottom _panel . style . pointerEvents = "none" ;
var brush = document . createElement ( "div" ) ;
brush . id = "brush" ;
brush . style . backgroundColor = "transparent" ;
brush . style . outline = "1px dashed black" ;
brush . style . boxShadow = "0 0 0 1px white" ;
brush . style . borderRadius = "50%" ;
brush . style . MozBorderRadius = "50%" ;
brush . style . WebkitBorderRadius = "50%" ;
brush . style . position = "absolute" ;
brush . style . zIndex = "8889" ;
brush . style . pointerEvents = "none" ;
this . brush = brush ;
this . element . appendChild ( imgCanvas ) ;
this . element . appendChild ( maskCanvas ) ;
this . element . appendChild ( bottom _panel ) ;
document . body . appendChild ( brush ) ;
var clearButton = this . createLeftButton ( "Clear" , ( ) => {
self . maskCtx . clearRect (
0 ,
0 ,
self . maskCanvas . width ,
self . maskCanvas . height
) ;
} ) ;
this . brush _size _slider = this . createLeftSlider (
self ,
"Thickness" ,
( event ) => {
self . brush _size = event . target . value ;
self . updateBrushPreview ( self ) ;
}
) ;
this . brush _opacity _slider = this . createOpacitySlider (
self ,
"Opacity" ,
( event ) => {
self . brush _opacity = event . target . value ;
if ( self . brush _color _mode !== "negative" ) {
self . maskCanvas . style . opacity = self . brush _opacity . toString ( ) ;
}
}
) ;
this . colorButton = this . createLeftButton ( this . getColorButtonText ( ) , ( ) => {
if ( self . brush _color _mode === "black" ) {
self . brush _color _mode = "white" ;
} else if ( self . brush _color _mode === "white" ) {
self . brush _color _mode = "negative" ;
} else {
self . brush _color _mode = "black" ;
}
self . updateWhenBrushColorModeChanged ( ) ;
} ) ;
var cancelButton = this . createRightButton ( "Cancel" , ( ) => {
2024-08-30 23:32:10 +00:00
document . removeEventListener ( "keydown" , MaskEditorDialog . handleKeyDown ) ;
2024-08-15 20:50:25 +00:00
self . close ( ) ;
} ) ;
this . saveButton = this . createRightButton ( "Save" , ( ) => {
2024-08-30 23:32:10 +00:00
document . removeEventListener ( "keydown" , MaskEditorDialog . handleKeyDown ) ;
2024-08-15 20:50:25 +00:00
self . save ( ) ;
} ) ;
this . element . appendChild ( imgCanvas ) ;
this . element . appendChild ( maskCanvas ) ;
this . element . appendChild ( bottom _panel ) ;
bottom _panel . appendChild ( clearButton ) ;
bottom _panel . appendChild ( this . saveButton ) ;
bottom _panel . appendChild ( cancelButton ) ;
bottom _panel . appendChild ( this . brush _size _slider ) ;
bottom _panel . appendChild ( this . brush _opacity _slider ) ;
bottom _panel . appendChild ( this . colorButton ) ;
imgCanvas . style . position = "absolute" ;
maskCanvas . style . position = "absolute" ;
imgCanvas . style . top = "200" ;
imgCanvas . style . left = "0" ;
maskCanvas . style . top = imgCanvas . style . top ;
maskCanvas . style . left = imgCanvas . style . left ;
const maskCanvasStyle = this . getMaskCanvasStyle ( ) ;
maskCanvas . style . mixBlendMode = maskCanvasStyle . mixBlendMode ;
maskCanvas . style . opacity = maskCanvasStyle . opacity . toString ( ) ;
}
2024-08-30 23:32:10 +00:00
async show ( ) {
this . zoom _ratio = 1 ;
this . pan _x = 0 ;
this . pan _y = 0 ;
if ( ! this . is _layout _created ) {
const imgCanvas = document . createElement ( "canvas" ) ;
const maskCanvas = document . createElement ( "canvas" ) ;
imgCanvas . id = "imageCanvas" ;
maskCanvas . id = "maskCanvas" ;
this . setlayout ( imgCanvas , maskCanvas ) ;
this . imgCanvas = imgCanvas ;
this . maskCanvas = maskCanvas ;
this . maskCtx = maskCanvas . getContext ( "2d" , { willReadFrequently : true } ) ;
this . setEventHandler ( maskCanvas ) ;
this . is _layout _created = true ;
const self = this ;
const observer = new MutationObserver ( function ( mutations ) {
mutations . forEach ( function ( mutation ) {
if ( mutation . type === "attributes" && mutation . attributeName === "style" ) {
if ( self . last _display _style && self . last _display _style != "none" && self . element . style . display == "none" ) {
self . brush . style . display = "none" ;
ComfyApp . onClipspaceEditorClosed ( ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
self . last _display _style = self . element . style . display ;
}
2024-08-15 20:50:25 +00:00
} ) ;
2024-08-30 23:32:10 +00:00
} ) ;
const config = { attributes : true } ;
observer . observe ( this . element , config ) ;
}
document . addEventListener ( "keydown" , MaskEditorDialog . handleKeyDown ) ;
if ( ComfyApp . clipspace _return _node ) {
this . saveButton . innerText = "Save to node" ;
} else {
this . saveButton . innerText = "Save" ;
}
this . saveButton . disabled = false ;
this . element . style . display = "block" ;
this . element . style . width = "85%" ;
this . element . style . margin = "0 7.5%" ;
this . element . style . height = "100vh" ;
this . element . style . top = "50%" ;
this . element . style . left = "42%" ;
this . element . style . zIndex = "8888" ;
await this . setImages ( this . imgCanvas ) ;
this . is _visible = true ;
2024-08-15 20:50:25 +00:00
}
isOpened ( ) {
return this . element . style . display == "block" ;
}
invalidateCanvas ( orig _image , mask _image ) {
this . imgCanvas . width = orig _image . width ;
this . imgCanvas . height = orig _image . height ;
this . maskCanvas . width = orig _image . width ;
this . maskCanvas . height = orig _image . height ;
let imgCtx = this . imgCanvas . getContext ( "2d" , { willReadFrequently : true } ) ;
let maskCtx = this . maskCanvas . getContext ( "2d" , {
willReadFrequently : true
} ) ;
imgCtx . drawImage ( orig _image , 0 , 0 , orig _image . width , orig _image . height ) ;
prepare _mask ( mask _image , this . maskCanvas , maskCtx , this . getMaskColor ( ) ) ;
}
2024-08-30 23:32:10 +00:00
async setImages ( imgCanvas ) {
let self = this ;
const imgCtx = imgCanvas . getContext ( "2d" , { willReadFrequently : true } ) ;
const maskCtx = this . maskCtx ;
const maskCanvas = this . maskCanvas ;
imgCtx . clearRect ( 0 , 0 , this . imgCanvas . width , this . imgCanvas . height ) ;
maskCtx . clearRect ( 0 , 0 , this . maskCanvas . width , this . maskCanvas . height ) ;
const filepath = ComfyApp . clipspace . images ;
const alpha _url = new URL (
ComfyApp . clipspace . imgs [ ComfyApp . clipspace [ "selectedIndex" ] ] . src
) ;
alpha _url . searchParams . delete ( "channel" ) ;
alpha _url . searchParams . delete ( "preview" ) ;
alpha _url . searchParams . set ( "channel" , "a" ) ;
let mask _image = await loadImage ( alpha _url ) ;
const rgb _url = new URL (
ComfyApp . clipspace . imgs [ ComfyApp . clipspace [ "selectedIndex" ] ] . src
) ;
rgb _url . searchParams . delete ( "channel" ) ;
rgb _url . searchParams . set ( "channel" , "rgb" ) ;
this . image = new Image ( ) ;
this . image . onload = function ( ) {
maskCanvas . width = self . image . width ;
maskCanvas . height = self . image . height ;
self . invalidateCanvas ( self . image , mask _image ) ;
self . initializeCanvasPanZoom ( ) ;
} ;
this . image . src = rgb _url . toString ( ) ;
2024-08-15 20:50:25 +00:00
}
initializeCanvasPanZoom ( ) {
let drawWidth = this . image . width ;
let drawHeight = this . image . height ;
let width = this . element . clientWidth ;
let height = this . element . clientHeight ;
if ( this . image . width > width ) {
drawWidth = width ;
drawHeight = drawWidth / this . image . width * this . image . height ;
}
if ( drawHeight > height ) {
drawHeight = height ;
drawWidth = drawHeight / this . image . height * this . image . width ;
}
this . zoom _ratio = drawWidth / this . image . width ;
const canvasX = ( width - drawWidth ) / 2 ;
const canvasY = ( height - drawHeight ) / 2 ;
this . pan _x = canvasX ;
this . pan _y = canvasY ;
this . invalidatePanZoom ( ) ;
}
invalidatePanZoom ( ) {
let raw _width = this . image . width * this . zoom _ratio ;
let raw _height = this . image . height * this . zoom _ratio ;
if ( this . pan _x + raw _width < 10 ) {
this . pan _x = 10 - raw _width ;
}
if ( this . pan _y + raw _height < 10 ) {
this . pan _y = 10 - raw _height ;
}
let width = ` ${ raw _width } px ` ;
let height = ` ${ raw _height } px ` ;
let left = ` ${ this . pan _x } px ` ;
let top = ` ${ this . pan _y } px ` ;
this . maskCanvas . style . width = width ;
this . maskCanvas . style . height = height ;
this . maskCanvas . style . left = left ;
this . maskCanvas . style . top = top ;
this . imgCanvas . style . width = width ;
this . imgCanvas . style . height = height ;
this . imgCanvas . style . left = left ;
this . imgCanvas . style . top = top ;
}
setEventHandler ( maskCanvas ) {
const self = this ;
if ( ! this . handler _registered ) {
maskCanvas . addEventListener ( "contextmenu" , ( event ) => {
event . preventDefault ( ) ;
} ) ;
this . element . addEventListener (
"wheel" ,
( event ) => this . handleWheelEvent ( self , event )
) ;
this . element . addEventListener (
"pointermove" ,
( event ) => this . pointMoveEvent ( self , event )
) ;
this . element . addEventListener (
"touchmove" ,
( event ) => this . pointMoveEvent ( self , event )
) ;
this . element . addEventListener ( "dragstart" , ( event ) => {
if ( event . ctrlKey ) {
event . preventDefault ( ) ;
}
} ) ;
maskCanvas . addEventListener (
"pointerdown" ,
( event ) => this . handlePointerDown ( self , event )
) ;
maskCanvas . addEventListener (
"pointermove" ,
( event ) => this . draw _move ( self , event )
) ;
maskCanvas . addEventListener (
"touchmove" ,
( event ) => this . draw _move ( self , event )
) ;
maskCanvas . addEventListener ( "pointerover" , ( event ) => {
this . brush . style . display = "block" ;
} ) ;
maskCanvas . addEventListener ( "pointerleave" , ( event ) => {
this . brush . style . display = "none" ;
} ) ;
2024-08-30 23:32:10 +00:00
document . addEventListener ( "pointerup" , MaskEditorDialog . handlePointerUp ) ;
2024-08-15 20:50:25 +00:00
this . handler _registered = true ;
}
}
getMaskCanvasStyle ( ) {
if ( this . brush _color _mode === "negative" ) {
return {
mixBlendMode : "difference" ,
opacity : "1"
} ;
} else {
return {
mixBlendMode : "initial" ,
opacity : this . brush _opacity
} ;
}
}
getMaskColor ( ) {
if ( this . brush _color _mode === "black" ) {
return { r : 0 , g : 0 , b : 0 } ;
}
if ( this . brush _color _mode === "white" ) {
return { r : 255 , g : 255 , b : 255 } ;
}
if ( this . brush _color _mode === "negative" ) {
return { r : 255 , g : 255 , b : 255 } ;
}
return { r : 0 , g : 0 , b : 0 } ;
}
getMaskFillStyle ( ) {
const maskColor = this . getMaskColor ( ) ;
return "rgb(" + maskColor . r + "," + maskColor . g + "," + maskColor . b + ")" ;
}
getColorButtonText ( ) {
let colorCaption = "unknown" ;
if ( this . brush _color _mode === "black" ) {
colorCaption = "black" ;
} else if ( this . brush _color _mode === "white" ) {
colorCaption = "white" ;
} else if ( this . brush _color _mode === "negative" ) {
colorCaption = "negative" ;
}
return "Color: " + colorCaption ;
}
updateWhenBrushColorModeChanged ( ) {
this . colorButton . innerText = this . getColorButtonText ( ) ;
const maskCanvasStyle = this . getMaskCanvasStyle ( ) ;
this . maskCanvas . style . mixBlendMode = maskCanvasStyle . mixBlendMode ;
this . maskCanvas . style . opacity = maskCanvasStyle . opacity . toString ( ) ;
const maskColor = this . getMaskColor ( ) ;
const maskData = this . maskCtx . getImageData (
0 ,
0 ,
this . maskCanvas . width ,
this . maskCanvas . height
) ;
for ( let i = 0 ; i < maskData . data . length ; i += 4 ) {
maskData . data [ i ] = maskColor . r ;
maskData . data [ i + 1 ] = maskColor . g ;
maskData . data [ i + 2 ] = maskColor . b ;
}
this . maskCtx . putImageData ( maskData , 0 , 0 ) ;
}
2024-08-30 23:32:10 +00:00
brush _opacity = 0.7 ;
brush _size = 10 ;
brush _color _mode = "black" ;
drawing _mode = false ;
lastx = - 1 ;
lasty = - 1 ;
lasttime = 0 ;
2024-08-15 20:50:25 +00:00
static handleKeyDown ( event ) {
2024-08-30 23:32:10 +00:00
const self = MaskEditorDialog . instance ;
2024-08-15 20:50:25 +00:00
if ( event . key === "]" ) {
self . brush _size = Math . min ( self . brush _size + 2 , 100 ) ;
self . brush _slider _input . value = self . brush _size ;
} else if ( event . key === "[" ) {
self . brush _size = Math . max ( self . brush _size - 2 , 1 ) ;
self . brush _slider _input . value = self . brush _size ;
} else if ( event . key === "Enter" ) {
self . save ( ) ;
}
self . updateBrushPreview ( self ) ;
}
static handlePointerUp ( event ) {
event . preventDefault ( ) ;
this . mousedown _x = null ;
this . mousedown _y = null ;
2024-08-30 23:32:10 +00:00
MaskEditorDialog . instance . drawing _mode = false ;
2024-08-15 20:50:25 +00:00
}
updateBrushPreview ( self ) {
const brush = self . brush ;
var centerX = self . cursorX ;
var centerY = self . cursorY ;
brush . style . width = self . brush _size * 2 * this . zoom _ratio + "px" ;
brush . style . height = self . brush _size * 2 * this . zoom _ratio + "px" ;
brush . style . left = centerX - self . brush _size * this . zoom _ratio + "px" ;
brush . style . top = centerY - self . brush _size * this . zoom _ratio + "px" ;
}
handleWheelEvent ( self , event ) {
event . preventDefault ( ) ;
if ( event . ctrlKey ) {
if ( event . deltaY < 0 ) {
this . zoom _ratio = Math . min ( 10 , this . zoom _ratio + 0.2 ) ;
} else {
this . zoom _ratio = Math . max ( 0.2 , this . zoom _ratio - 0.2 ) ;
}
this . invalidatePanZoom ( ) ;
} else {
if ( event . deltaY < 0 ) this . brush _size = Math . min ( this . brush _size + 2 , 100 ) ;
else this . brush _size = Math . max ( this . brush _size - 2 , 1 ) ;
this . brush _slider _input . value = this . brush _size . toString ( ) ;
this . updateBrushPreview ( this ) ;
}
}
pointMoveEvent ( self , event ) {
this . cursorX = event . pageX ;
this . cursorY = event . pageY ;
self . updateBrushPreview ( self ) ;
if ( event . ctrlKey ) {
event . preventDefault ( ) ;
self . pan _move ( self , event ) ;
}
let left _button _down = window . TouchEvent && event instanceof TouchEvent || event . buttons == 1 ;
if ( event . shiftKey && left _button _down ) {
self . drawing _mode = false ;
const y = event . clientY ;
let delta = ( self . zoom _lasty - y ) * 5e-3 ;
self . zoom _ratio = Math . max (
Math . min ( 10 , self . last _zoom _ratio - delta ) ,
0.2
) ;
this . invalidatePanZoom ( ) ;
return ;
}
}
pan _move ( self , event ) {
if ( event . buttons == 1 ) {
2024-08-30 23:32:10 +00:00
if ( MaskEditorDialog . mousedown _x ) {
let deltaX = MaskEditorDialog . mousedown _x - event . clientX ;
let deltaY = MaskEditorDialog . mousedown _y - event . clientY ;
2024-08-15 20:50:25 +00:00
self . pan _x = this . mousedown _pan _x - deltaX ;
self . pan _y = this . mousedown _pan _y - deltaY ;
self . invalidatePanZoom ( ) ;
}
}
}
draw _move ( self , event ) {
if ( event . ctrlKey || event . shiftKey ) {
return ;
}
event . preventDefault ( ) ;
this . cursorX = event . pageX ;
this . cursorY = event . pageY ;
self . updateBrushPreview ( self ) ;
let left _button _down = window . TouchEvent && event instanceof TouchEvent || event . buttons == 1 ;
let right _button _down = [ 2 , 5 , 32 ] . includes ( event . buttons ) ;
if ( ! event . altKey && left _button _down ) {
var diff = performance . now ( ) - self . lasttime ;
const maskRect = self . maskCanvas . getBoundingClientRect ( ) ;
var x = event . offsetX ;
var y = event . offsetY ;
if ( event . offsetX == null ) {
x = event . targetTouches [ 0 ] . clientX - maskRect . left ;
}
if ( event . offsetY == null ) {
y = event . targetTouches [ 0 ] . clientY - maskRect . top ;
}
x /= self . zoom _ratio ;
y /= self . zoom _ratio ;
var brush _size = this . brush _size ;
if ( event instanceof PointerEvent && event . pointerType == "pen" ) {
brush _size *= event . pressure ;
this . last _pressure = event . pressure ;
} else if ( window . TouchEvent && event instanceof TouchEvent && diff < 20 ) {
brush _size *= this . last _pressure ;
} else {
brush _size = this . brush _size ;
}
if ( diff > 20 && ! this . drawing _mode )
requestAnimationFrame ( ( ) => {
self . maskCtx . beginPath ( ) ;
self . maskCtx . fillStyle = this . getMaskFillStyle ( ) ;
self . maskCtx . globalCompositeOperation = "source-over" ;
self . maskCtx . arc ( x , y , brush _size , 0 , Math . PI * 2 , false ) ;
self . maskCtx . fill ( ) ;
self . lastx = x ;
self . lasty = y ;
} ) ;
else
requestAnimationFrame ( ( ) => {
self . maskCtx . beginPath ( ) ;
self . maskCtx . fillStyle = this . getMaskFillStyle ( ) ;
self . maskCtx . globalCompositeOperation = "source-over" ;
var dx = x - self . lastx ;
var dy = y - self . lasty ;
var distance = Math . sqrt ( dx * dx + dy * dy ) ;
var directionX = dx / distance ;
var directionY = dy / distance ;
for ( var i = 0 ; i < distance ; i += 5 ) {
var px = self . lastx + directionX * i ;
var py = self . lasty + directionY * i ;
self . maskCtx . arc ( px , py , brush _size , 0 , Math . PI * 2 , false ) ;
self . maskCtx . fill ( ) ;
}
self . lastx = x ;
self . lasty = y ;
} ) ;
self . lasttime = performance . now ( ) ;
} else if ( event . altKey && left _button _down || right _button _down ) {
const maskRect = self . maskCanvas . getBoundingClientRect ( ) ;
const x2 = ( event . offsetX || event . targetTouches [ 0 ] . clientX - maskRect . left ) / self . zoom _ratio ;
const y2 = ( event . offsetY || event . targetTouches [ 0 ] . clientY - maskRect . top ) / self . zoom _ratio ;
var brush _size = this . brush _size ;
if ( event instanceof PointerEvent && event . pointerType == "pen" ) {
brush _size *= event . pressure ;
this . last _pressure = event . pressure ;
} else if ( window . TouchEvent && event instanceof TouchEvent && diff < 20 ) {
brush _size *= this . last _pressure ;
} else {
brush _size = this . brush _size ;
}
if ( diff > 20 && ! this . drawing _mode )
requestAnimationFrame ( ( ) => {
self . maskCtx . beginPath ( ) ;
self . maskCtx . globalCompositeOperation = "destination-out" ;
self . maskCtx . arc ( x2 , y2 , brush _size , 0 , Math . PI * 2 , false ) ;
self . maskCtx . fill ( ) ;
self . lastx = x2 ;
self . lasty = y2 ;
} ) ;
else
requestAnimationFrame ( ( ) => {
self . maskCtx . beginPath ( ) ;
self . maskCtx . globalCompositeOperation = "destination-out" ;
var dx = x2 - self . lastx ;
var dy = y2 - self . lasty ;
var distance = Math . sqrt ( dx * dx + dy * dy ) ;
var directionX = dx / distance ;
var directionY = dy / distance ;
for ( var i = 0 ; i < distance ; i += 5 ) {
var px = self . lastx + directionX * i ;
var py = self . lasty + directionY * i ;
self . maskCtx . arc ( px , py , brush _size , 0 , Math . PI * 2 , false ) ;
self . maskCtx . fill ( ) ;
}
self . lastx = x2 ;
self . lasty = y2 ;
} ) ;
self . lasttime = performance . now ( ) ;
}
}
handlePointerDown ( self , event ) {
if ( event . ctrlKey ) {
if ( event . buttons == 1 ) {
2024-08-30 23:32:10 +00:00
MaskEditorDialog . mousedown _x = event . clientX ;
MaskEditorDialog . mousedown _y = event . clientY ;
2024-08-15 20:50:25 +00:00
this . mousedown _pan _x = this . pan _x ;
this . mousedown _pan _y = this . pan _y ;
}
return ;
}
var brush _size = this . brush _size ;
if ( event instanceof PointerEvent && event . pointerType == "pen" ) {
brush _size *= event . pressure ;
this . last _pressure = event . pressure ;
}
if ( [ 0 , 2 , 5 ] . includes ( event . button ) ) {
self . drawing _mode = true ;
event . preventDefault ( ) ;
if ( event . shiftKey ) {
self . zoom _lasty = event . clientY ;
self . last _zoom _ratio = self . zoom _ratio ;
return ;
}
const maskRect = self . maskCanvas . getBoundingClientRect ( ) ;
const x = ( event . offsetX || event . targetTouches [ 0 ] . clientX - maskRect . left ) / self . zoom _ratio ;
const y = ( event . offsetY || event . targetTouches [ 0 ] . clientY - maskRect . top ) / self . zoom _ratio ;
self . maskCtx . beginPath ( ) ;
if ( ! event . altKey && event . button == 0 ) {
self . maskCtx . fillStyle = this . getMaskFillStyle ( ) ;
self . maskCtx . globalCompositeOperation = "source-over" ;
} else {
self . maskCtx . globalCompositeOperation = "destination-out" ;
}
self . maskCtx . arc ( x , y , brush _size , 0 , Math . PI * 2 , false ) ;
self . maskCtx . fill ( ) ;
self . lastx = x ;
self . lasty = y ;
self . lasttime = performance . now ( ) ;
}
}
2024-08-30 23:32:10 +00:00
async save ( ) {
const backupCanvas = document . createElement ( "canvas" ) ;
const backupCtx = backupCanvas . getContext ( "2d" , {
willReadFrequently : true
2024-08-15 20:50:25 +00:00
} ) ;
2024-08-30 23:32:10 +00:00
backupCanvas . width = this . image . width ;
backupCanvas . height = this . image . height ;
backupCtx . clearRect ( 0 , 0 , backupCanvas . width , backupCanvas . height ) ;
backupCtx . drawImage (
this . maskCanvas ,
0 ,
0 ,
this . maskCanvas . width ,
this . maskCanvas . height ,
0 ,
0 ,
backupCanvas . width ,
backupCanvas . height
) ;
const backupData = backupCtx . getImageData (
0 ,
0 ,
backupCanvas . width ,
backupCanvas . height
) ;
for ( let i = 0 ; i < backupData . data . length ; i += 4 ) {
if ( backupData . data [ i + 3 ] == 255 ) backupData . data [ i + 3 ] = 0 ;
else backupData . data [ i + 3 ] = 255 ;
backupData . data [ i ] = 0 ;
backupData . data [ i + 1 ] = 0 ;
backupData . data [ i + 2 ] = 0 ;
}
backupCtx . globalCompositeOperation = "source-over" ;
backupCtx . putImageData ( backupData , 0 , 0 ) ;
const formData = new FormData ( ) ;
const filename = "clipspace-mask-" + performance . now ( ) + ".png" ;
const item = {
filename ,
subfolder : "clipspace" ,
type : "input"
} ;
if ( ComfyApp . clipspace . images ) ComfyApp . clipspace . images [ 0 ] = item ;
if ( ComfyApp . clipspace . widgets ) {
const index = ComfyApp . clipspace . widgets . findIndex (
( obj ) => obj . name === "image"
) ;
if ( index >= 0 ) ComfyApp . clipspace . widgets [ index ] . value = item ;
}
const dataURL = backupCanvas . toDataURL ( ) ;
const blob = dataURLToBlob ( dataURL ) ;
let original _url = new URL ( this . image . src ) ;
const original _ref = {
filename : original _url . searchParams . get ( "filename" )
} ;
let original _subfolder = original _url . searchParams . get ( "subfolder" ) ;
if ( original _subfolder ) original _ref . subfolder = original _subfolder ;
let original _type = original _url . searchParams . get ( "type" ) ;
if ( original _type ) original _ref . type = original _type ;
formData . append ( "image" , blob , filename ) ;
formData . append ( "original_ref" , JSON . stringify ( original _ref ) ) ;
formData . append ( "type" , "input" ) ;
formData . append ( "subfolder" , "clipspace" ) ;
this . saveButton . innerText = "Saving..." ;
this . saveButton . disabled = true ;
await uploadMask ( item , formData ) ;
ComfyApp . onClipspaceEditorSave ( ) ;
this . close ( ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : "Comfy.MaskEditor" ,
init ( app2 ) {
ComfyApp . open _maskeditor = function ( ) {
const dlg = MaskEditorDialog . getInstance ( ) ;
if ( ! dlg . isOpened ( ) ) {
dlg . show ( ) ;
}
} ;
2024-08-16 19:25:02 +00:00
const context _predicate = /* @__PURE__ */ _ _name ( ( ) => ComfyApp . clipspace && ComfyApp . clipspace . imgs && ComfyApp . clipspace . imgs . length > 0 , "context_predicate" ) ;
2024-08-15 20:50:25 +00:00
ClipspaceDialog . registerButton (
"MaskEditor" ,
context _predicate ,
ComfyApp . open _maskeditor
) ;
}
} ) ;
const id = "Comfy.NodeTemplates" ;
const file = "comfy.templates.json" ;
2024-08-30 23:32:10 +00:00
class ManageTemplates extends ComfyDialog {
static {
_ _name ( this , "ManageTemplates" ) ;
}
templates ;
draggedEl ;
saveVisualCue ;
emptyImg ;
importInput ;
2024-08-15 20:50:25 +00:00
constructor ( ) {
super ( ) ;
this . load ( ) . then ( ( v ) => {
this . templates = v ;
} ) ;
this . element . classList . add ( "comfy-manage-templates" ) ;
this . draggedEl = null ;
this . saveVisualCue = null ;
this . emptyImg = new Image ( ) ;
this . emptyImg . src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=" ;
this . importInput = $el ( "input" , {
type : "file" ,
accept : ".json" ,
multiple : true ,
style : { display : "none" } ,
parent : document . body ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( ) => this . importAll ( ) , "onchange" )
2024-08-15 20:50:25 +00:00
} ) ;
}
createButtons ( ) {
const btns = super . createButtons ( ) ;
btns [ 0 ] . textContent = "Close" ;
btns [ 0 ] . onclick = ( e ) => {
clearTimeout ( this . saveVisualCue ) ;
this . close ( ) ;
} ;
btns . unshift (
$el ( "button" , {
type : "button" ,
textContent : "Export" ,
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( ) => this . exportAll ( ) , "onclick" )
2024-08-15 20:50:25 +00:00
} )
) ;
btns . unshift (
$el ( "button" , {
type : "button" ,
textContent : "Import" ,
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
this . importInput . click ( ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} )
) ;
return btns ;
}
2024-08-30 23:32:10 +00:00
async load ( ) {
let templates = [ ] ;
if ( app . storageLocation === "server" ) {
if ( app . isNewUserSession ) {
2024-08-15 20:50:25 +00:00
const json = localStorage . getItem ( id ) ;
if ( json ) {
templates = JSON . parse ( json ) ;
}
2024-08-30 23:32:10 +00:00
await api . storeUserData ( file , json , { stringify : false } ) ;
} else {
const res = await api . getUserData ( file ) ;
if ( res . status === 200 ) {
try {
templates = await res . json ( ) ;
} catch ( error ) {
}
} else if ( res . status !== 404 ) {
console . error ( res . status + " " + res . statusText ) ;
}
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
} else {
const json = localStorage . getItem ( id ) ;
if ( json ) {
templates = JSON . parse ( json ) ;
}
}
return templates ? ? [ ] ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
async store ( ) {
if ( app . storageLocation === "server" ) {
const templates = JSON . stringify ( this . templates , void 0 , 4 ) ;
localStorage . setItem ( id , templates ) ;
try {
await api . storeUserData ( file , templates , { stringify : false } ) ;
} catch ( error ) {
console . error ( error ) ;
alert ( error . message ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
} else {
localStorage . setItem ( id , JSON . stringify ( this . templates ) ) ;
}
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
async importAll ( ) {
for ( const file2 of this . importInput . files ) {
if ( file2 . type === "application/json" || file2 . name . endsWith ( ".json" ) ) {
const reader = new FileReader ( ) ;
reader . onload = async ( ) => {
const importFile = JSON . parse ( reader . result ) ;
if ( importFile ? . templates ) {
for ( const template of importFile . templates ) {
if ( template ? . name && template ? . data ) {
this . templates . push ( template ) ;
2024-08-15 20:50:25 +00:00
}
}
2024-08-30 23:32:10 +00:00
await this . store ( ) ;
}
} ;
await reader . readAsText ( file2 ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
}
this . importInput . value = null ;
this . close ( ) ;
2024-08-15 20:50:25 +00:00
}
exportAll ( ) {
if ( this . templates . length == 0 ) {
alert ( "No templates to export." ) ;
return ;
}
const json = JSON . stringify ( { templates : this . templates } , null , 2 ) ;
const blob = new Blob ( [ json ] , { type : "application/json" } ) ;
const url = URL . createObjectURL ( blob ) ;
const a = $el ( "a" , {
href : url ,
download : "node_templates.json" ,
style : { display : "none" } ,
parent : document . body
} ) ;
a . click ( ) ;
setTimeout ( function ( ) {
a . remove ( ) ;
window . URL . revokeObjectURL ( url ) ;
} , 0 ) ;
}
show ( ) {
super . show (
$el (
"div" ,
{ } ,
this . templates . flatMap ( ( t , i ) => {
let nameInput ;
return [
$el (
"div" ,
{
dataset : { id : i . toString ( ) } ,
className : "templateManagerRow" ,
style : {
display : "grid" ,
gridTemplateColumns : "1fr auto" ,
border : "1px dashed transparent" ,
gap : "5px" ,
backgroundColor : "var(--comfy-menu-bg)"
} ,
2024-08-16 19:25:02 +00:00
ondragstart : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
this . draggedEl = e . currentTarget ;
e . currentTarget . style . opacity = "0.6" ;
e . currentTarget . style . border = "1px dashed yellow" ;
e . dataTransfer . effectAllowed = "move" ;
e . dataTransfer . setDragImage ( this . emptyImg , 0 , 0 ) ;
2024-08-16 19:25:02 +00:00
} , "ondragstart" ) ,
ondragend : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
e . target . style . opacity = "1" ;
e . currentTarget . style . border = "1px dashed transparent" ;
e . currentTarget . removeAttribute ( "draggable" ) ;
this . element . querySelectorAll ( ".templateManagerRow" ) . forEach ( ( el , i2 ) => {
var prev _i = Number . parseInt ( el . dataset . id ) ;
if ( el == this . draggedEl && prev _i != i2 ) {
this . templates . splice (
i2 ,
0 ,
this . templates . splice ( prev _i , 1 ) [ 0 ]
) ;
}
el . dataset . id = i2 . toString ( ) ;
} ) ;
this . store ( ) ;
2024-08-16 19:25:02 +00:00
} , "ondragend" ) ,
ondragover : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
e . preventDefault ( ) ;
if ( e . currentTarget == this . draggedEl ) return ;
let rect = e . currentTarget . getBoundingClientRect ( ) ;
if ( e . clientY > rect . top + rect . height / 2 ) {
e . currentTarget . parentNode . insertBefore (
this . draggedEl ,
e . currentTarget . nextSibling
) ;
} else {
e . currentTarget . parentNode . insertBefore (
this . draggedEl ,
e . currentTarget
) ;
}
2024-08-16 19:25:02 +00:00
} , "ondragover" )
2024-08-15 20:50:25 +00:00
} ,
[
$el (
"label" ,
{
textContent : "Name: " ,
style : {
cursor : "grab"
} ,
2024-08-16 19:25:02 +00:00
onmousedown : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
if ( e . target . localName == "label" )
e . currentTarget . parentNode . draggable = "true" ;
2024-08-16 19:25:02 +00:00
} , "onmousedown" )
2024-08-15 20:50:25 +00:00
} ,
[
$el ( "input" , {
value : t . name ,
dataset : { name : t . name } ,
style : {
transitionProperty : "background-color" ,
transitionDuration : "0s"
} ,
2024-08-16 19:25:02 +00:00
onchange : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
clearTimeout ( this . saveVisualCue ) ;
var el = e . target ;
var row = el . parentNode . parentNode ;
this . templates [ row . dataset . id ] . name = el . value . trim ( ) || "untitled" ;
this . store ( ) ;
el . style . backgroundColor = "rgb(40, 95, 40)" ;
el . style . transitionDuration = "0s" ;
this . saveVisualCue = setTimeout ( function ( ) {
el . style . transitionDuration = ".7s" ;
el . style . backgroundColor = "var(--comfy-input-bg)" ;
} , 15 ) ;
2024-08-16 19:25:02 +00:00
} , "onchange" ) ,
onkeypress : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
var el = e . target ;
clearTimeout ( this . saveVisualCue ) ;
el . style . transitionDuration = "0s" ;
el . style . backgroundColor = "var(--comfy-input-bg)" ;
2024-08-16 19:25:02 +00:00
} , "onkeypress" ) ,
$ : /* @__PURE__ */ _ _name ( ( el ) => nameInput = el , "$" )
2024-08-15 20:50:25 +00:00
} )
]
) ,
$el ( "div" , { } , [
$el ( "button" , {
textContent : "Export" ,
style : {
fontSize : "12px" ,
fontWeight : "normal"
} ,
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
const json = JSON . stringify ( { templates : [ t ] } , null , 2 ) ;
const blob = new Blob ( [ json ] , {
type : "application/json"
} ) ;
const url = URL . createObjectURL ( blob ) ;
const a = $el ( "a" , {
href : url ,
download : ( nameInput . value || t . name ) + ".json" ,
style : { display : "none" } ,
parent : document . body
} ) ;
a . click ( ) ;
setTimeout ( function ( ) {
a . remove ( ) ;
window . URL . revokeObjectURL ( url ) ;
} , 0 ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} ) ,
$el ( "button" , {
textContent : "Delete" ,
style : {
fontSize : "12px" ,
color : "red" ,
fontWeight : "normal"
} ,
2024-08-16 19:25:02 +00:00
onclick : /* @__PURE__ */ _ _name ( ( e ) => {
2024-08-15 20:50:25 +00:00
const item = e . target . parentNode . parentNode ;
item . parentNode . removeChild ( item ) ;
this . templates . splice ( item . dataset . id * 1 , 1 ) ;
this . store ( ) ;
var that = this ;
setTimeout ( function ( ) {
that . element . querySelectorAll ( ".templateManagerRow" ) . forEach ( ( el , i2 ) => {
el . dataset . id = i2 . toString ( ) ;
} ) ;
} , 0 ) ;
2024-08-16 19:25:02 +00:00
} , "onclick" )
2024-08-15 20:50:25 +00:00
} )
] )
]
)
] ;
} )
)
) ;
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : id ,
setup ( ) {
const manage = new ManageTemplates ( ) ;
2024-08-30 23:32:10 +00:00
const clipboardAction = /* @__PURE__ */ _ _name ( async ( cb ) => {
2024-08-15 20:50:25 +00:00
const old = localStorage . getItem ( "litegrapheditor_clipboard" ) ;
2024-08-30 23:32:10 +00:00
await cb ( ) ;
2024-08-15 20:50:25 +00:00
localStorage . setItem ( "litegrapheditor_clipboard" , old ) ;
2024-08-30 23:32:10 +00:00
} , "clipboardAction" ) ;
2024-08-15 20:50:25 +00:00
const orig = LGraphCanvas . prototype . getCanvasMenuOptions ;
LGraphCanvas . prototype . getCanvasMenuOptions = function ( ) {
const options = orig . apply ( this , arguments ) ;
options . push ( null ) ;
options . push ( {
content : ` Save Selected as Template ` ,
disabled : ! Object . keys ( app . canvas . selected _nodes || { } ) . length ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
const name = prompt ( "Enter name" ) ;
2024-08-30 23:32:10 +00:00
if ( ! name ? . trim ( ) ) return ;
2024-08-15 20:50:25 +00:00
clipboardAction ( ( ) => {
app . canvas . copyToClipboard ( ) ;
let data = localStorage . getItem ( "litegrapheditor_clipboard" ) ;
data = JSON . parse ( data ) ;
const nodeIds = Object . keys ( app . canvas . selected _nodes ) ;
for ( let i = 0 ; i < nodeIds . length ; i ++ ) {
2024-09-05 22:56:01 +00:00
const node = app . graph . getNodeById ( nodeIds [ i ] ) ;
2024-08-30 23:32:10 +00:00
const nodeData = node ? . constructor . nodeData ;
2024-08-15 20:50:25 +00:00
let groupData = GroupNodeHandler . getGroupData ( node ) ;
if ( groupData ) {
groupData = groupData . nodeData ;
if ( ! data . groupNodes ) {
data . groupNodes = { } ;
}
data . groupNodes [ nodeData . name ] = groupData ;
data . nodes [ i ] . type = nodeData . name ;
}
}
manage . templates . push ( {
name ,
data : JSON . stringify ( data )
} ) ;
manage . store ( ) ;
} ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
const subItems = manage . templates . map ( ( t ) => {
return {
content : t . name ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-30 23:32:10 +00:00
clipboardAction ( async ( ) => {
2024-08-15 20:50:25 +00:00
const data = JSON . parse ( t . data ) ;
2024-08-30 23:32:10 +00:00
await GroupNodeConfig . registerFromWorkflow ( data . groupNodes , { } ) ;
2024-08-15 20:50:25 +00:00
localStorage . setItem ( "litegrapheditor_clipboard" , t . data ) ;
app . canvas . pasteFromClipboard ( ) ;
2024-08-30 23:32:10 +00:00
} ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ;
} ) ;
subItems . push ( null , {
content : "Manage" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => manage . show ( ) , "callback" )
2024-08-15 20:50:25 +00:00
} ) ;
options . push ( {
content : "Node Templates" ,
submenu : {
options : subItems
}
} ) ;
return options ;
} ;
}
} ) ;
app . registerExtension ( {
name : "Comfy.NoteNode" ,
registerCustomNodes ( ) {
2024-08-30 23:32:10 +00:00
class NoteNode extends LGraphNode {
static {
_ _name ( this , "NoteNode" ) ;
}
static category ;
color = LGraphCanvas . node _colors . yellow . color ;
bgcolor = LGraphCanvas . node _colors . yellow . bgcolor ;
groupcolor = LGraphCanvas . node _colors . yellow . groupcolor ;
isVirtualNode ;
collapsable ;
title _mode ;
constructor ( title ) {
super ( title ) ;
2024-08-15 20:50:25 +00:00
if ( ! this . properties ) {
this . properties = { text : "" } ;
}
ComfyWidgets . STRING (
2024-08-30 23:32:10 +00:00
// Should we extends LGraphNode? Yesss
2024-08-15 20:50:25 +00:00
this ,
"" ,
2024-08-30 23:32:10 +00:00
// @ts-expect-error
2024-08-15 20:50:25 +00:00
[ "" , { default : this . properties . text , multiline : true } ] ,
app
) ;
this . serialize _widgets = true ;
this . isVirtualNode = true ;
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
LiteGraph . registerNodeType (
"Note" ,
Object . assign ( NoteNode , {
title _mode : LiteGraph . NORMAL _TITLE ,
title : "Note" ,
collapsable : true
} )
) ;
NoteNode . category = "utils" ;
}
} ) ;
app . registerExtension ( {
name : "Comfy.RerouteNode" ,
registerCustomNodes ( app2 ) {
2024-08-30 23:32:10 +00:00
class RerouteNode extends LGraphNode {
static {
_ _name ( this , "RerouteNode" ) ;
}
static category ;
static defaultVisibility = false ;
constructor ( title ) {
super ( title ) ;
2024-08-15 20:50:25 +00:00
if ( ! this . properties ) {
this . properties = { } ;
}
2024-08-30 23:32:10 +00:00
this . properties . showOutputText = RerouteNode . defaultVisibility ;
2024-08-15 20:50:25 +00:00
this . properties . horizontal = false ;
this . addInput ( "" , "*" ) ;
this . addOutput ( this . properties . showOutputText ? "*" : "" , "*" ) ;
this . onAfterGraphConfigured = function ( ) {
requestAnimationFrame ( ( ) => {
this . onConnectionsChange ( LiteGraph . INPUT , null , true , null ) ;
} ) ;
} ;
this . onConnectionsChange = function ( type , index , connected , link _info ) {
this . applyOrientation ( ) ;
if ( connected && type === LiteGraph . OUTPUT ) {
const types = new Set (
this . outputs [ 0 ] . links . map ( ( l ) => app2 . graph . links [ l ] . type ) . filter ( ( t ) => t !== "*" )
) ;
if ( types . size > 1 ) {
const linksToDisconnect = [ ] ;
for ( let i = 0 ; i < this . outputs [ 0 ] . links . length - 1 ; i ++ ) {
const linkId = this . outputs [ 0 ] . links [ i ] ;
const link = app2 . graph . links [ linkId ] ;
linksToDisconnect . push ( link ) ;
}
for ( const link of linksToDisconnect ) {
const node = app2 . graph . getNodeById ( link . target _id ) ;
node . disconnectInput ( link . target _slot ) ;
}
}
}
let currentNode = this ;
let updateNodes = [ ] ;
let inputType = null ;
let inputNode = null ;
while ( currentNode ) {
updateNodes . unshift ( currentNode ) ;
const linkId = currentNode . inputs [ 0 ] . link ;
if ( linkId !== null ) {
const link = app2 . graph . links [ linkId ] ;
if ( ! link ) return ;
const node = app2 . graph . getNodeById ( link . origin _id ) ;
const type2 = node . constructor . type ;
if ( type2 === "Reroute" ) {
if ( node === this ) {
currentNode . disconnectInput ( link . target _slot ) ;
currentNode = null ;
} else {
currentNode = node ;
}
} else {
inputNode = currentNode ;
2024-08-30 23:32:10 +00:00
inputType = node . outputs [ link . origin _slot ] ? . type ? ? null ;
2024-08-15 20:50:25 +00:00
break ;
}
} else {
currentNode = null ;
break ;
}
}
const nodes = [ this ] ;
let outputType = null ;
while ( nodes . length ) {
currentNode = nodes . pop ( ) ;
const outputs = ( currentNode . outputs ? currentNode . outputs [ 0 ] . links : [ ] ) || [ ] ;
if ( outputs . length ) {
for ( const linkId of outputs ) {
const link = app2 . graph . links [ linkId ] ;
if ( ! link ) continue ;
const node = app2 . graph . getNodeById ( link . target _id ) ;
const type2 = node . constructor . type ;
if ( type2 === "Reroute" ) {
nodes . push ( node ) ;
updateNodes . push ( node ) ;
} else {
2024-08-30 23:32:10 +00:00
const nodeOutType = node . inputs && node . inputs [ link ? . target _slot ] && node . inputs [ link . target _slot ] . type ? node . inputs [ link . target _slot ] . type : null ;
2024-08-15 20:50:25 +00:00
if ( inputType && inputType !== "*" && nodeOutType !== inputType ) {
node . disconnectInput ( link . target _slot ) ;
} else {
outputType = nodeOutType ;
}
}
}
} else {
}
}
const displayType = inputType || outputType || "*" ;
const color = LGraphCanvas . link _type _colors [ displayType ] ;
let widgetConfig ;
let targetWidget ;
let widgetType ;
for ( const node of updateNodes ) {
node . outputs [ 0 ] . type = inputType || "*" ;
node . _ _outputType = displayType ;
node . outputs [ 0 ] . name = node . properties . showOutputText ? displayType : "" ;
node . size = node . computeSize ( ) ;
node . applyOrientation ( ) ;
for ( const l of node . outputs [ 0 ] . links || [ ] ) {
const link = app2 . graph . links [ l ] ;
if ( link ) {
link . color = color ;
if ( app2 . configuringGraph ) continue ;
const targetNode = app2 . graph . getNodeById ( link . target _id ) ;
2024-08-30 23:32:10 +00:00
const targetInput = targetNode . inputs ? . [ link . target _slot ] ;
if ( targetInput ? . widget ) {
2024-08-15 20:50:25 +00:00
const config = getWidgetConfig ( targetInput ) ;
if ( ! widgetConfig ) {
2024-08-30 23:32:10 +00:00
widgetConfig = config [ 1 ] ? ? { } ;
2024-08-15 20:50:25 +00:00
widgetType = config [ 0 ] ;
}
if ( ! targetWidget ) {
2024-08-30 23:32:10 +00:00
targetWidget = targetNode . widgets ? . find (
2024-08-15 20:50:25 +00:00
( w ) => w . name === targetInput . widget . name
) ;
}
const merged = mergeIfValid ( targetInput , [
config [ 0 ] ,
widgetConfig
] ) ;
if ( merged . customConfig ) {
widgetConfig = merged . customConfig ;
}
}
}
}
}
for ( const node of updateNodes ) {
if ( widgetConfig && outputType ) {
node . inputs [ 0 ] . widget = { name : "value" } ;
setWidgetConfig (
node . inputs [ 0 ] ,
2024-08-30 23:32:10 +00:00
[ widgetType ? ? displayType , widgetConfig ] ,
2024-08-15 20:50:25 +00:00
targetWidget
) ;
} else {
setWidgetConfig ( node . inputs [ 0 ] , null ) ;
}
}
if ( inputNode ) {
const link = app2 . graph . links [ inputNode . inputs [ 0 ] . link ] ;
if ( link ) {
link . color = color ;
}
}
} ;
this . clone = function ( ) {
2024-08-30 23:32:10 +00:00
const cloned = RerouteNode . prototype . clone . apply ( this ) ;
2024-08-15 20:50:25 +00:00
cloned . removeOutput ( 0 ) ;
cloned . addOutput ( this . properties . showOutputText ? "*" : "" , "*" ) ;
cloned . size = cloned . computeSize ( ) ;
return cloned ;
} ;
this . isVirtualNode = true ;
}
getExtraMenuOptions ( _ , options ) {
options . unshift (
{
content : ( this . properties . showOutputText ? "Hide" : "Show" ) + " Type" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
this . properties . showOutputText = ! this . properties . showOutputText ;
if ( this . properties . showOutputText ) {
this . outputs [ 0 ] . name = this . _ _outputType || this . outputs [ 0 ] . type ;
} else {
this . outputs [ 0 ] . name = "" ;
}
this . size = this . computeSize ( ) ;
this . applyOrientation ( ) ;
app2 . graph . setDirtyCanvas ( true , true ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ,
{
2024-08-30 23:32:10 +00:00
content : ( RerouteNode . defaultVisibility ? "Hide" : "Show" ) + " Type By Default" ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-30 23:32:10 +00:00
RerouteNode . setDefaultTextVisibility (
! RerouteNode . defaultVisibility
2024-08-15 20:50:25 +00:00
) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
} ,
{
// naming is inverted with respect to LiteGraphNode.horizontal
// LiteGraphNode.horizontal == true means that
// each slot in the inputs and outputs are laid out horizontally,
// which is the opposite of the visual orientation of the inputs and outputs as a node
content : "Set " + ( this . properties . horizontal ? "Horizontal" : "Vertical" ) ,
2024-08-16 19:25:02 +00:00
callback : /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
this . properties . horizontal = ! this . properties . horizontal ;
this . applyOrientation ( ) ;
2024-08-16 19:25:02 +00:00
} , "callback" )
2024-08-15 20:50:25 +00:00
}
) ;
}
applyOrientation ( ) {
this . horizontal = this . properties . horizontal ;
if ( this . horizontal ) {
this . inputs [ 0 ] . pos = [ this . size [ 0 ] / 2 , 0 ] ;
} else {
delete this . inputs [ 0 ] . pos ;
}
app2 . graph . setDirtyCanvas ( true , true ) ;
}
computeSize ( ) {
return [
this . properties . showOutputText && this . outputs && this . outputs . length ? Math . max (
75 ,
LiteGraph . NODE _TEXT _SIZE * this . outputs [ 0 ] . name . length * 0.6 + 40
) : 75 ,
26
] ;
}
static setDefaultTextVisibility ( visible ) {
2024-08-30 23:32:10 +00:00
RerouteNode . defaultVisibility = visible ;
2024-08-15 20:50:25 +00:00
if ( visible ) {
localStorage [ "Comfy.RerouteNode.DefaultVisibility" ] = "true" ;
} else {
delete localStorage [ "Comfy.RerouteNode.DefaultVisibility" ] ;
}
}
2024-08-30 23:32:10 +00:00
}
2024-08-15 20:50:25 +00:00
RerouteNode . setDefaultTextVisibility (
! ! localStorage [ "Comfy.RerouteNode.DefaultVisibility" ]
) ;
LiteGraph . registerNodeType (
"Reroute" ,
Object . assign ( RerouteNode , {
title _mode : LiteGraph . NO _TITLE ,
title : "Reroute" ,
collapsable : false
} )
) ;
RerouteNode . category = "utils" ;
}
} ) ;
app . registerExtension ( {
name : "Comfy.SaveImageExtraOutput" ,
2024-08-30 23:32:10 +00:00
async beforeRegisterNodeDef ( nodeType , nodeData , app2 ) {
if ( nodeData . name === "SaveImage" || nodeData . name === "SaveAnimatedWEBP" ) {
const onNodeCreated = nodeType . prototype . onNodeCreated ;
nodeType . prototype . onNodeCreated = function ( ) {
const r = onNodeCreated ? onNodeCreated . apply ( this , arguments ) : void 0 ;
const widget = this . widgets . find ( ( w ) => w . name === "filename_prefix" ) ;
widget . serializeValue = ( ) => {
return applyTextReplacements ( app2 , widget . value ) ;
2024-08-15 20:50:25 +00:00
} ;
2024-08-30 23:32:10 +00:00
return r ;
} ;
} else {
const onNodeCreated = nodeType . prototype . onNodeCreated ;
nodeType . prototype . onNodeCreated = function ( ) {
const r = onNodeCreated ? onNodeCreated . apply ( this , arguments ) : void 0 ;
if ( ! this . properties || ! ( "Node name for S&R" in this . properties ) ) {
this . addProperty ( "Node name for S&R" , this . constructor . type , "string" ) ;
}
return r ;
} ;
}
2024-08-15 20:50:25 +00:00
}
} ) ;
let touchZooming ;
let touchCount = 0 ;
app . registerExtension ( {
name : "Comfy.SimpleTouchSupport" ,
setup ( ) {
let zoomPos ;
let touchTime ;
let lastTouch ;
function getMultiTouchPos ( e ) {
return Math . hypot (
e . touches [ 0 ] . clientX - e . touches [ 1 ] . clientX ,
e . touches [ 0 ] . clientY - e . touches [ 1 ] . clientY
) ;
}
2024-08-16 19:25:02 +00:00
_ _name ( getMultiTouchPos , "getMultiTouchPos" ) ;
2024-08-15 20:50:25 +00:00
app . canvasEl . addEventListener (
"touchstart" ,
( e ) => {
touchCount ++ ;
lastTouch = null ;
2024-08-30 23:32:10 +00:00
if ( e . touches ? . length === 1 ) {
2024-08-15 20:50:25 +00:00
touchTime = /* @__PURE__ */ new Date ( ) ;
lastTouch = e . touches [ 0 ] ;
} else {
touchTime = null ;
2024-08-30 23:32:10 +00:00
if ( e . touches ? . length === 2 ) {
2024-08-15 20:50:25 +00:00
zoomPos = getMultiTouchPos ( e ) ;
app . canvas . pointer _is _down = false ;
}
}
} ,
true
) ;
app . canvasEl . addEventListener ( "touchend" , ( e ) => {
touchZooming = false ;
2024-08-30 23:32:10 +00:00
touchCount = e . touches ? . length ? ? touchCount - 1 ;
if ( touchTime && ! e . touches ? . length ) {
2024-08-15 20:50:25 +00:00
if ( ( /* @__PURE__ */ new Date ( ) ) . getTime ( ) - touchTime > 600 ) {
try {
e . constructor = CustomEvent ;
} catch ( error ) {
}
e . clientX = lastTouch . clientX ;
e . clientY = lastTouch . clientY ;
app . canvas . pointer _is _down = true ;
app . canvas . _mousedown _callback ( e ) ;
}
touchTime = null ;
}
} ) ;
app . canvasEl . addEventListener (
"touchmove" ,
( e ) => {
touchTime = null ;
2024-08-30 23:32:10 +00:00
if ( e . touches ? . length === 2 ) {
2024-08-15 20:50:25 +00:00
app . canvas . pointer _is _down = false ;
touchZooming = true ;
LiteGraph . closeAllContextMenus ( ) ;
2024-08-30 23:32:10 +00:00
app . canvas . search _box ? . close ( ) ;
2024-08-15 20:50:25 +00:00
const newZoomPos = getMultiTouchPos ( e ) ;
const midX = ( e . touches [ 0 ] . clientX + e . touches [ 1 ] . clientX ) / 2 ;
const midY = ( e . touches [ 0 ] . clientY + e . touches [ 1 ] . clientY ) / 2 ;
let scale = app . canvas . ds . scale ;
const diff = zoomPos - newZoomPos ;
if ( diff > 0.5 ) {
scale *= 1 / 1.07 ;
} else if ( diff < - 0.5 ) {
scale *= 1.07 ;
}
app . canvas . ds . changeScale ( scale , [ midX , midY ] ) ;
app . canvas . setDirty ( true , true ) ;
zoomPos = newZoomPos ;
}
} ,
true
) ;
}
} ) ;
const processMouseDown = LGraphCanvas . prototype . processMouseDown ;
LGraphCanvas . prototype . processMouseDown = function ( e ) {
if ( touchZooming || touchCount ) {
return ;
}
return processMouseDown . apply ( this , arguments ) ;
} ;
const processMouseMove = LGraphCanvas . prototype . processMouseMove ;
LGraphCanvas . prototype . processMouseMove = function ( e ) {
if ( touchZooming || touchCount > 1 ) {
return ;
}
return processMouseMove . apply ( this , arguments ) ;
} ;
app . registerExtension ( {
name : "Comfy.SlotDefaults" ,
suggestionsNumber : null ,
init ( ) {
LiteGraph . search _filter _enabled = true ;
LiteGraph . middle _click _slot _add _default _node = true ;
this . suggestionsNumber = app . ui . settings . addSetting ( {
id : "Comfy.NodeSuggestions.number" ,
2024-08-21 04:00:49 +00:00
category : [ "Comfy" , "Node Search Box" , "NodeSuggestions" ] ,
2024-08-15 20:50:25 +00:00
name : "Number of nodes suggestions" ,
2024-08-21 04:00:49 +00:00
tooltip : "Only for litegraph searchbox/context menu" ,
2024-08-15 20:50:25 +00:00
type : "slider" ,
attrs : {
min : 1 ,
max : 100 ,
step : 1
} ,
defaultValue : 5 ,
2024-08-16 19:25:02 +00:00
onChange : /* @__PURE__ */ _ _name ( ( newVal , oldVal ) => {
2024-08-15 20:50:25 +00:00
this . setDefaults ( newVal ) ;
2024-08-16 19:25:02 +00:00
} , "onChange" )
2024-08-15 20:50:25 +00:00
} ) ;
} ,
slot _types _default _out : { } ,
slot _types _default _in : { } ,
2024-08-30 23:32:10 +00:00
async beforeRegisterNodeDef ( nodeType , nodeData , app2 ) {
var nodeId = nodeData . name ;
var inputs = [ ] ;
inputs = nodeData [ "input" ] [ "required" ] ;
for ( const inputKey in inputs ) {
var input = inputs [ inputKey ] ;
if ( typeof input [ 0 ] !== "string" ) continue ;
var type = input [ 0 ] ;
if ( type in ComfyWidgets ) {
var customProperties = input [ 1 ] ;
if ( ! customProperties ? . forceInput ) continue ;
}
if ( ! ( type in this . slot _types _default _out ) ) {
this . slot _types _default _out [ type ] = [ "Reroute" ] ;
}
if ( this . slot _types _default _out [ type ] . includes ( nodeId ) ) continue ;
this . slot _types _default _out [ type ] . push ( nodeId ) ;
const lowerType = type . toLocaleLowerCase ( ) ;
if ( ! ( lowerType in LiteGraph . registered _slot _in _types ) ) {
LiteGraph . registered _slot _in _types [ lowerType ] = { nodes : [ ] } ;
}
LiteGraph . registered _slot _in _types [ lowerType ] . nodes . push (
nodeType . comfyClass
) ;
}
var outputs = nodeData [ "output" ] ;
for ( const key in outputs ) {
var type = outputs [ key ] ;
if ( ! ( type in this . slot _types _default _in ) ) {
this . slot _types _default _in [ type ] = [ "Reroute" ] ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
this . slot _types _default _in [ type ] . push ( nodeId ) ;
if ( ! ( type in LiteGraph . registered _slot _out _types ) ) {
LiteGraph . registered _slot _out _types [ type ] = { nodes : [ ] } ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
LiteGraph . registered _slot _out _types [ type ] . nodes . push ( nodeType . comfyClass ) ;
if ( ! LiteGraph . slot _types _out . includes ( type ) ) {
LiteGraph . slot _types _out . push ( type ) ;
}
}
var maxNum = this . suggestionsNumber . value ;
this . setDefaults ( maxNum ) ;
2024-08-15 20:50:25 +00:00
} ,
setDefaults ( maxNum ) {
LiteGraph . slot _types _default _out = { } ;
LiteGraph . slot _types _default _in = { } ;
for ( const type in this . slot _types _default _out ) {
LiteGraph . slot _types _default _out [ type ] = this . slot _types _default _out [ type ] . slice ( 0 , maxNum ) ;
}
for ( const type in this . slot _types _default _in ) {
LiteGraph . slot _types _default _in [ type ] = this . slot _types _default _in [ type ] . slice ( 0 , maxNum ) ;
}
}
} ) ;
function roundVectorToGrid ( vec ) {
vec [ 0 ] = LiteGraph . CANVAS _GRID _SIZE * Math . round ( vec [ 0 ] / LiteGraph . CANVAS _GRID _SIZE ) ;
vec [ 1 ] = LiteGraph . CANVAS _GRID _SIZE * Math . round ( vec [ 1 ] / LiteGraph . CANVAS _GRID _SIZE ) ;
return vec ;
}
2024-08-16 19:25:02 +00:00
_ _name ( roundVectorToGrid , "roundVectorToGrid" ) ;
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : "Comfy.SnapToGrid" ,
init ( ) {
app . ui . settings . addSetting ( {
id : "Comfy.SnapToGrid.GridSize" ,
2024-08-21 04:00:49 +00:00
category : [ "Comfy" , "Graph" , "GridSize" ] ,
2024-08-30 23:32:10 +00:00
name : "Snap to grid size" ,
2024-08-15 20:50:25 +00:00
type : "slider" ,
attrs : {
min : 1 ,
max : 500
} ,
tooltip : "When dragging and resizing nodes while holding shift they will be aligned to the grid, this controls the size of that grid." ,
defaultValue : LiteGraph . CANVAS _GRID _SIZE ,
onChange ( value ) {
LiteGraph . CANVAS _GRID _SIZE = + value ;
}
} ) ;
const onNodeMoved = app . canvas . onNodeMoved ;
app . canvas . onNodeMoved = function ( node ) {
2024-08-30 23:32:10 +00:00
const r = onNodeMoved ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
if ( app . shiftDown ) {
for ( const id2 in this . selected _nodes ) {
this . selected _nodes [ id2 ] . alignToGrid ( ) ;
}
}
return r ;
} ;
const onNodeAdded = app . graph . onNodeAdded ;
app . graph . onNodeAdded = function ( node ) {
const onResize = node . onResize ;
node . onResize = function ( ) {
if ( app . shiftDown ) {
roundVectorToGrid ( node . size ) ;
}
2024-08-30 23:32:10 +00:00
return onResize ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
} ;
2024-08-30 23:32:10 +00:00
return onNodeAdded ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
} ;
const origDrawNode = LGraphCanvas . prototype . drawNode ;
LGraphCanvas . prototype . drawNode = function ( node , ctx ) {
if ( app . shiftDown && this . node _dragged && node . id in this . selected _nodes ) {
const [ x , y ] = roundVectorToGrid ( [ ... node . pos ] ) ;
const shiftX = x - node . pos [ 0 ] ;
let shiftY = y - node . pos [ 1 ] ;
let w , h ;
if ( node . flags . collapsed ) {
w = node . _collapsed _width ;
h = LiteGraph . NODE _TITLE _HEIGHT ;
shiftY -= LiteGraph . NODE _TITLE _HEIGHT ;
} else {
w = node . size [ 0 ] ;
h = node . size [ 1 ] ;
let titleMode = node . constructor . title _mode ;
if ( titleMode !== LiteGraph . TRANSPARENT _TITLE && titleMode !== LiteGraph . NO _TITLE ) {
h += LiteGraph . NODE _TITLE _HEIGHT ;
shiftY -= LiteGraph . NODE _TITLE _HEIGHT ;
}
}
const f = ctx . fillStyle ;
ctx . fillStyle = "rgba(100, 100, 100, 0.5)" ;
ctx . fillRect ( shiftX , shiftY , w , h ) ;
ctx . fillStyle = f ;
}
return origDrawNode . apply ( this , arguments ) ;
} ;
let selectedAndMovingGroup = null ;
const groupMove = LGraphGroup . prototype . move ;
LGraphGroup . prototype . move = function ( deltax , deltay , ignore _nodes ) {
const v = groupMove . apply ( this , arguments ) ;
if ( ! selectedAndMovingGroup && app . canvas . selected _group === this && ( deltax || deltay ) ) {
selectedAndMovingGroup = this ;
}
if ( app . canvas . last _mouse _dragging === false && app . shiftDown ) {
this . recomputeInsideNodes ( ) ;
for ( const node of this . _nodes ) {
node . alignToGrid ( ) ;
}
LGraphNode . prototype . alignToGrid . apply ( this ) ;
}
return v ;
} ;
const drawGroups = LGraphCanvas . prototype . drawGroups ;
LGraphCanvas . prototype . drawGroups = function ( canvas , ctx ) {
if ( this . selected _group && app . shiftDown ) {
if ( this . selected _group _resizing ) {
roundVectorToGrid ( this . selected _group . size ) ;
} else if ( selectedAndMovingGroup ) {
const [ x , y ] = roundVectorToGrid ( [ ... selectedAndMovingGroup . pos ] ) ;
const f = ctx . fillStyle ;
const s = ctx . strokeStyle ;
ctx . fillStyle = "rgba(100, 100, 100, 0.33)" ;
ctx . strokeStyle = "rgba(100, 100, 100, 0.66)" ;
ctx . rect ( x , y , ... selectedAndMovingGroup . size ) ;
ctx . fill ( ) ;
ctx . stroke ( ) ;
ctx . fillStyle = f ;
ctx . strokeStyle = s ;
}
} else if ( ! this . selected _group ) {
selectedAndMovingGroup = null ;
}
return drawGroups . apply ( this , arguments ) ;
} ;
const onGroupAdd = LGraphCanvas . onGroupAdd ;
LGraphCanvas . onGroupAdd = function ( ) {
const v = onGroupAdd . apply ( app . canvas , arguments ) ;
if ( app . shiftDown ) {
const lastGroup = app . graph . _groups [ app . graph . _groups . length - 1 ] ;
if ( lastGroup ) {
roundVectorToGrid ( lastGroup . pos ) ;
roundVectorToGrid ( lastGroup . size ) ;
}
}
return v ;
} ;
}
} ) ;
app . registerExtension ( {
name : "Comfy.UploadImage" ,
2024-08-30 23:32:10 +00:00
async beforeRegisterNodeDef ( nodeType , nodeData , app2 ) {
if ( nodeData ? . input ? . required ? . image ? . [ 1 ] ? . image _upload === true ) {
nodeData . input . required . upload = [ "IMAGEUPLOAD" ] ;
}
2024-08-15 20:50:25 +00:00
}
} ) ;
const WEBCAM _READY = Symbol ( ) ;
app . registerExtension ( {
name : "Comfy.WebcamCapture" ,
getCustomWidgets ( app2 ) {
return {
WEBCAM ( node , inputName ) {
let res ;
node [ WEBCAM _READY ] = new Promise ( ( resolve ) => res = resolve ) ;
const container = document . createElement ( "div" ) ;
container . style . background = "rgba(0,0,0,0.25)" ;
container . style . textAlign = "center" ;
const video = document . createElement ( "video" ) ;
video . style . height = video . style . width = "100%" ;
2024-08-30 23:32:10 +00:00
const loadVideo = /* @__PURE__ */ _ _name ( async ( ) => {
2024-08-15 20:50:25 +00:00
try {
2024-08-30 23:32:10 +00:00
const stream = await navigator . mediaDevices . getUserMedia ( {
2024-08-15 20:50:25 +00:00
video : true ,
audio : false
} ) ;
container . replaceChildren ( video ) ;
setTimeout ( ( ) => res ( video ) , 500 ) ;
video . addEventListener ( "loadedmetadata" , ( ) => res ( video ) , false ) ;
video . srcObject = stream ;
video . play ( ) ;
} catch ( error ) {
const label = document . createElement ( "div" ) ;
label . style . color = "red" ;
label . style . overflow = "auto" ;
label . style . maxHeight = "100%" ;
label . style . whiteSpace = "pre-wrap" ;
if ( window . isSecureContext ) {
label . textContent = "Unable to load webcam, please ensure access is granted:\n" + error . message ;
} else {
label . textContent = "Unable to load webcam. A secure context is required, if you are not accessing ComfyUI on localhost (127.0.0.1) you will have to enable TLS (https)\n\n" + error . message ;
}
container . replaceChildren ( label ) ;
}
2024-08-30 23:32:10 +00:00
} , "loadVideo" ) ;
2024-08-15 20:50:25 +00:00
loadVideo ( ) ;
return { widget : node . addDOMWidget ( inputName , "WEBCAM" , container ) } ;
}
} ;
} ,
nodeCreated ( node ) {
if ( node . type , node . constructor . comfyClass !== "WebcamCapture" ) return ;
let video ;
const camera = node . widgets . find ( ( w2 ) => w2 . name === "image" ) ;
const w = node . widgets . find ( ( w2 ) => w2 . name === "width" ) ;
const h = node . widgets . find ( ( w2 ) => w2 . name === "height" ) ;
const captureOnQueue = node . widgets . find (
( w2 ) => w2 . name === "capture_on_queue"
) ;
const canvas = document . createElement ( "canvas" ) ;
2024-08-16 19:25:02 +00:00
const capture = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
canvas . width = w . value ;
canvas . height = h . value ;
const ctx = canvas . getContext ( "2d" ) ;
ctx . drawImage ( video , 0 , 0 , w . value , h . value ) ;
const data = canvas . toDataURL ( "image/png" ) ;
const img = new Image ( ) ;
img . onload = ( ) => {
node . imgs = [ img ] ;
app . graph . setDirtyCanvas ( true ) ;
requestAnimationFrame ( ( ) => {
2024-08-30 23:32:10 +00:00
node . setSizeForImage ? . ( ) ;
2024-08-15 20:50:25 +00:00
} ) ;
} ;
img . src = data ;
2024-08-16 19:25:02 +00:00
} , "capture" ) ;
2024-08-15 20:50:25 +00:00
const btn = node . addWidget (
"button" ,
"waiting for camera..." ,
"capture" ,
capture
) ;
btn . disabled = true ;
btn . serializeValue = ( ) => void 0 ;
2024-08-30 23:32:10 +00:00
camera . serializeValue = async ( ) => {
2024-08-15 20:50:25 +00:00
if ( captureOnQueue . value ) {
capture ( ) ;
2024-08-30 23:32:10 +00:00
} else if ( ! node . imgs ? . length ) {
2024-08-15 20:50:25 +00:00
const err = ` No webcam image captured ` ;
alert ( err ) ;
throw new Error ( err ) ;
}
2024-08-30 23:32:10 +00:00
const blob = await new Promise ( ( r ) => canvas . toBlob ( r ) ) ;
2024-08-15 20:50:25 +00:00
const name = ` ${ + /* @__PURE__ */ new Date ( ) } .png ` ;
const file2 = new File ( [ blob ] , name ) ;
const body = new FormData ( ) ;
body . append ( "image" , file2 ) ;
body . append ( "subfolder" , "webcam" ) ;
body . append ( "type" , "temp" ) ;
2024-08-30 23:32:10 +00:00
const resp = await api . fetchApi ( "/upload/image" , {
2024-08-15 20:50:25 +00:00
method : "POST" ,
body
} ) ;
if ( resp . status !== 200 ) {
const err = ` Error uploading camera image: ${ resp . status } - ${ resp . statusText } ` ;
alert ( err ) ;
throw new Error ( err ) ;
}
return ` webcam/ ${ name } [temp] ` ;
2024-08-30 23:32:10 +00:00
} ;
2024-08-15 20:50:25 +00:00
node [ WEBCAM _READY ] . then ( ( v ) => {
video = v ;
if ( ! w . value ) {
w . value = video . videoWidth || 640 ;
h . value = video . videoHeight || 480 ;
}
btn . disabled = false ;
btn . label = "capture" ;
} ) ;
}
} ) ;
function splitFilePath ( path ) {
const folder _separator = path . lastIndexOf ( "/" ) ;
if ( folder _separator === - 1 ) {
return [ "" , path ] ;
}
return [
path . substring ( 0 , folder _separator ) ,
path . substring ( folder _separator + 1 )
] ;
}
2024-08-16 19:25:02 +00:00
_ _name ( splitFilePath , "splitFilePath" ) ;
2024-08-15 20:50:25 +00:00
function getResourceURL ( subfolder , filename , type = "input" ) {
const params = [
"filename=" + encodeURIComponent ( filename ) ,
"type=" + type ,
"subfolder=" + subfolder ,
app . getRandParam ( ) . substring ( 1 )
] . join ( "&" ) ;
return ` /view? ${ params } ` ;
}
2024-08-16 19:25:02 +00:00
_ _name ( getResourceURL , "getResourceURL" ) ;
2024-08-30 23:32:10 +00:00
async function uploadFile ( audioWidget , audioUIWidget , file2 , updateNode , pasted = false ) {
try {
const body = new FormData ( ) ;
body . append ( "image" , file2 ) ;
if ( pasted ) body . append ( "subfolder" , "pasted" ) ;
const resp = await api . fetchApi ( "/upload/image" , {
method : "POST" ,
body
} ) ;
if ( resp . status === 200 ) {
const data = await resp . json ( ) ;
let path = data . name ;
if ( data . subfolder ) path = data . subfolder + "/" + path ;
if ( ! audioWidget . options . values . includes ( path ) ) {
audioWidget . options . values . push ( path ) ;
}
if ( updateNode ) {
audioUIWidget . element . src = api . apiURL (
getResourceURL ( ... splitFilePath ( path ) )
) ;
audioWidget . value = path ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
} else {
alert ( resp . status + " - " + resp . statusText ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
} catch ( error ) {
alert ( error ) ;
}
2024-08-15 20:50:25 +00:00
}
2024-08-16 19:25:02 +00:00
_ _name ( uploadFile , "uploadFile" ) ;
2024-08-15 20:50:25 +00:00
app . registerExtension ( {
name : "Comfy.AudioWidget" ,
2024-08-30 23:32:10 +00:00
async beforeRegisterNodeDef ( nodeType , nodeData ) {
if ( [ "LoadAudio" , "SaveAudio" , "PreviewAudio" ] . includes ( nodeType . comfyClass ) ) {
nodeData . input . required . audioUI = [ "AUDIO_UI" ] ;
}
2024-08-15 20:50:25 +00:00
} ,
getCustomWidgets ( ) {
return {
AUDIO _UI ( node , inputName ) {
const audio = document . createElement ( "audio" ) ;
audio . controls = true ;
audio . classList . add ( "comfy-audio" ) ;
audio . setAttribute ( "name" , "media" ) ;
const audioUIWidget = node . addDOMWidget (
inputName ,
/* name=*/
"audioUI" ,
audio
) ;
audioUIWidget . serialize = false ;
const isOutputNode = node . constructor . nodeData . output _node ;
if ( isOutputNode ) {
audioUIWidget . element . classList . add ( "empty-audio-widget" ) ;
const onExecuted = node . onExecuted ;
node . onExecuted = function ( message ) {
2024-08-30 23:32:10 +00:00
onExecuted ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
const audios = message . audio ;
if ( ! audios ) return ;
const audio2 = audios [ 0 ] ;
audioUIWidget . element . src = api . apiURL (
getResourceURL ( audio2 . subfolder , audio2 . filename , audio2 . type )
) ;
audioUIWidget . element . classList . remove ( "empty-audio-widget" ) ;
} ;
}
return { widget : audioUIWidget } ;
}
} ;
} ,
onNodeOutputsUpdated ( nodeOutputs ) {
for ( const [ nodeId , output ] of Object . entries ( nodeOutputs ) ) {
2024-09-05 22:56:01 +00:00
const node = app . graph . getNodeById ( nodeId ) ;
2024-08-15 20:50:25 +00:00
if ( "audio" in output ) {
const audioUIWidget = node . widgets . find (
( w ) => w . name === "audioUI"
) ;
const audio = output . audio [ 0 ] ;
audioUIWidget . element . src = api . apiURL (
getResourceURL ( audio . subfolder , audio . filename , audio . type )
) ;
audioUIWidget . element . classList . remove ( "empty-audio-widget" ) ;
}
}
}
} ) ;
app . registerExtension ( {
name : "Comfy.UploadAudio" ,
2024-08-30 23:32:10 +00:00
async beforeRegisterNodeDef ( nodeType , nodeData ) {
if ( nodeData ? . input ? . required ? . audio ? . [ 1 ] ? . audio _upload === true ) {
nodeData . input . required . upload = [ "AUDIOUPLOAD" ] ;
}
2024-08-15 20:50:25 +00:00
} ,
getCustomWidgets ( ) {
return {
AUDIOUPLOAD ( node , inputName ) {
const audioWidget = node . widgets . find (
( w ) => w . name === "audio"
) ;
const audioUIWidget = node . widgets . find (
( w ) => w . name === "audioUI"
) ;
2024-08-16 19:25:02 +00:00
const onAudioWidgetUpdate = /* @__PURE__ */ _ _name ( ( ) => {
2024-08-15 20:50:25 +00:00
audioUIWidget . element . src = api . apiURL (
getResourceURL ( ... splitFilePath ( audioWidget . value ) )
) ;
2024-08-16 19:25:02 +00:00
} , "onAudioWidgetUpdate" ) ;
2024-08-15 20:50:25 +00:00
if ( audioWidget . value ) {
onAudioWidgetUpdate ( ) ;
}
audioWidget . callback = onAudioWidgetUpdate ;
const onGraphConfigured = node . onGraphConfigured ;
node . onGraphConfigured = function ( ) {
2024-08-30 23:32:10 +00:00
onGraphConfigured ? . apply ( this , arguments ) ;
2024-08-15 20:50:25 +00:00
if ( audioWidget . value ) {
onAudioWidgetUpdate ( ) ;
}
} ;
const fileInput = document . createElement ( "input" ) ;
fileInput . type = "file" ;
fileInput . accept = "audio/*" ;
fileInput . style . display = "none" ;
fileInput . onchange = ( ) => {
if ( fileInput . files . length ) {
uploadFile ( audioWidget , audioUIWidget , fileInput . files [ 0 ] , true ) ;
}
} ;
const uploadWidget = node . addWidget (
"button" ,
inputName ,
/* value=*/
"" ,
( ) => {
fileInput . click ( ) ;
}
) ;
uploadWidget . label = "choose file to upload" ;
uploadWidget . serialize = false ;
return { widget : uploadWidget } ;
}
} ;
}
} ) ;
2024-09-05 22:56:01 +00:00
//# sourceMappingURL=index-CrROdkG4.js.map