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-10-10 02:37:04 +00:00
import { c9 as ComfyDialog , ca as $el , cb as ComfyApp , k as app , z as LiteGraph , aP as LGraphCanvas , cc as DraggableList , bO as useToastStore , aq as useNodeDefStore , b4 as api , L as LGraphGroup , cd as KeyComboImpl , aT as useKeybindingStore , aL as useCommandStore , l as LGraphNode , ce as ComfyWidgets , cf as applyTextReplacements , aA as NodeSourceType , cg as NodeBadgeMode , h as useSettingStore , F as computed , w as watch , ch as BadgePosition , aR as LGraphBadge , au as _ } from "./index-DGAbdBYF.js" ;
import { g as getColorPalette , d as defaultColorPalette } from "./colorPalette-D5oi2-2V.js" ;
import { mergeIfValid , getWidgetConfig , setWidgetConfig } from "./widgetInputs-DdoWwzg5.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 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 ORDER = Symbol ( ) ;
2024-10-07 21:15:29 +00:00
const PREFIX$1 = "workflow" ;
const SEPARATOR$1 = ">" ;
2024-08-15 20:50:25 +00:00
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 ( ) {
2024-10-07 21:15:29 +00:00
this . groupNodeType = LiteGraph . registered _node _types [ ` ${ PREFIX$1 } ${ SEPARATOR$1 } ` + this . selectedGroup ] ;
2024-08-15 20:50:25 +00:00
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 ,
2024-10-07 21:15:29 +00:00
selected : ` ${ PREFIX$1 } ${ SEPARATOR$1 } ` + g === type ,
2024-08-15 20:50:25 +00:00
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-09-22 03:28:54 +00:00
const node = app . graph . nodes . find (
2024-10-07 21:15:29 +00:00
( n ) => n . type === ` ${ PREFIX$1 } ${ SEPARATOR$1 } ` + this . selectedGroup
2024-08-15 20:50:25 +00:00
) ;
if ( node ) {
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert (
2024-08-15 20:50:25 +00:00
"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 ] ;
2024-10-07 21:15:29 +00:00
LiteGraph . unregisterNodeType (
` ${ PREFIX$1 } ${ SEPARATOR$1 } ` + this . selectedGroup
) ;
2024-08-15 20:50:25 +00:00
}
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 ) {
2024-09-22 03:28:54 +00:00
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 ;
} , { } ) ;
}
2024-10-07 21:15:29 +00:00
const nodes = nodesByType [ ` ${ PREFIX$1 } ${ SEPARATOR$1 } ` + g ] ;
2024-08-15 20:50:25 +00:00
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 (
2024-10-07 21:15:29 +00:00
type ? groupNodes . find ( ( g ) => ` ${ PREFIX$1 } ${ SEPARATOR$1 } ` + g === type ) : groupNodes [ 0 ]
2024-08-15 20:50:25 +00:00
) ;
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 ( ) ;
2024-10-07 21:15:29 +00:00
const PREFIX = "workflow" ;
const SEPARATOR = ">" ;
2024-08-15 20:50:25 +00:00
const Workflow = {
InUse : {
Free : 0 ,
Registered : 1 ,
InWorkflow : 2
} ,
isInUseGroupNode ( name ) {
2024-10-07 21:15:29 +00:00
const id2 = ` ${ PREFIX } ${ SEPARATOR } ${ name } ` ;
2024-08-30 23:32:10 +00:00
if ( app . graph . extra ? . groupNodes ? . [ name ] ) {
2024-09-22 03:28:54 +00:00
if ( app . graph . nodes . find ( ( n ) => n . type === id2 ) ) {
2024-08-15 20:50:25 +00:00
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 :
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert (
2024-08-15 20:50:25 +00:00
"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-10-07 21:15:29 +00:00
async registerType ( source = PREFIX ) {
2024-08-30 23:32:10 +00:00
this . nodeDef = {
output : [ ] ,
output _name : [ ] ,
output _is _list : [ ] ,
output _is _hidden : [ ] ,
2024-10-07 21:15:29 +00:00
name : source + SEPARATOR + this . name ,
2024-08-30 23:32:10 +00:00
display _name : this . name ,
2024-10-07 21:15:29 +00:00
category : "group nodes" + ( SEPARATOR + source ) ,
2024-08-30 23:32:10 +00:00
input : { required : { } } ,
2024-09-22 03:28:54 +00:00
description : ` Group node combining ${ this . nodeData . nodes . map ( ( n ) => n . type ) . join ( ", " ) } ` ,
python _module : "custom_nodes." + this . name ,
2024-08-30 23:32:10 +00:00
[ 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 ;
2024-10-07 21:15:29 +00:00
await app . registerNodeDef ( ` ${ PREFIX } ${ SEPARATOR } ` + this . name , this . nodeDef ) ;
2024-09-22 03:28:54 +00:00
useNodeDefStore ( ) . addNodeDef ( 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" ) {
2024-09-22 03:28:54 +00:00
const [ sourceNodeId , _2 , targetNodeId , _ _ ] = link ;
2024-08-15 20:50:25 +00:00
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 ) {
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 ,
2024-10-07 21:15:29 +00:00
hint : ` (In group node ' ${ PREFIX } ${ SEPARATOR } ${ g } ') `
2024-08-30 23:32:10 +00:00
} ) ;
missingNodeTypes . push ( {
2024-10-07 21:15:29 +00:00
type : ` ${ PREFIX } ${ SEPARATOR } ` + g ,
2024-08-30 23:32:10 +00:00
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 ] )
] ;
2024-10-07 21:15:29 +00:00
const builder = new GroupNodeBuilder ( nodes ) ;
const nodeData = builder . getNodeData ( ) ;
groupNode [ GROUP ] . groupData . nodeData . links = nodeData . links ;
2024-08-15 20:50:25 +00:00
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 ;
2024-09-22 03:28:54 +00:00
this . node . getExtraMenuOptions = function ( _2 , 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 ) {
2024-09-22 03:28:54 +00:00
for ( const [ _2 , , targetNodeId , targetSlot ] of rerouteLinks [ "0" ] ) {
2024-08-15 20:50:25 +00:00
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 ( ) ;
2024-10-07 21:15:29 +00:00
const groupNode = LiteGraph . createNode ( ` ${ PREFIX } ${ SEPARATOR } ${ name } ` ) ;
2024-08-30 23:32:10 +00:00
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-10-07 21:15:29 +00:00
const replaceLegacySeparators = /* @__PURE__ */ _ _name ( ( nodes ) => {
for ( const node of nodes ) {
if ( typeof node . type === "string" && node . type . startsWith ( "workflow/" ) ) {
node . type = node . type . replace ( /^workflow\// , ` ${ PREFIX } ${ SEPARATOR } ` ) ;
}
}
} , "replaceLegacySeparators" ) ;
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 ) {
2024-10-07 21:15:29 +00:00
replaceLegacySeparators ( graphData . nodes ) ;
2024-08-30 23:32:10 +00:00
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-10-10 02:37:04 +00:00
if ( node . title && node [ GROUP ] ? . groupData ? . nodeData ) {
Workflow . storeGroupNode ( node . title , node [ GROUP ] . groupData . nodeData ) ;
}
2024-08-15 20:50:25 +00:00
}
} ,
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 ;
2024-09-22 03:28:54 +00:00
for ( var n of [ group . nodes , nodes ] ) {
2024-08-15 20:50:25 +00:00
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 ( ) ;
2024-09-22 03:28:54 +00:00
const nodesInGroup = group . nodes ;
2024-08-15 20:50:25 +00:00
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 ) {
2024-10-10 02:37:04 +00:00
if ( ! app . vueAppReady ) return ;
const keyCombo = KeyComboImpl . fromEvent ( event ) ;
if ( keyCombo . isModifier ) {
2024-08-30 23:32:10 +00:00
return ;
}
const target = event . composedPath ( ) [ 0 ] ;
2024-10-10 02:37:04 +00:00
if ( ! keyCombo . hasModifier && ( target . tagName === "TEXTAREA" || target . tagName === "INPUT" || target . tagName === "SPAN" && target . classList . contains ( "property_value" ) ) ) {
2024-08-30 23:32:10 +00:00
return ;
}
2024-10-10 02:37:04 +00:00
const keybindingStore = useKeybindingStore ( ) ;
const commandStore = useCommandStore ( ) ;
const keybinding = keybindingStore . getKeybinding ( keyCombo ) ;
if ( keybinding && keybinding . targetSelector !== "#graph-canvas" ) {
await commandStore . execute ( keybinding . commandId ) ;
2024-08-30 23:32:10 +00:00
event . preventDefault ( ) ;
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 ( ) ;
} ) ;
}
2024-08-16 19:25:02 +00:00
} , "keybindListener" ) ;
2024-10-10 02:37:04 +00:00
window . addEventListener ( "keydown" , keybindListener ) ;
2024-08-15 20:50:25 +00:00
}
} ) ;
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" ,
2024-10-10 02:37:04 +00:00
options : [
{ value : LiteGraph . STRAIGHT _LINK , text : "Straight" } ,
{ value : LiteGraph . LINEAR _LINK , text : "Linear" } ,
{ value : LiteGraph . SPLINE _LINK , text : "Spline" } ,
{ value : LiteGraph . HIDDEN _LINK , text : "Hidden" }
] ,
2024-08-30 23:32:10 +00:00
onChange ( value ) {
app2 . canvas . links _render _mode = + value ;
2024-10-10 02:37:04 +00:00
app2 . canvas . setDirty (
/* fg */
false ,
/* bg */
true
) ;
2024-08-30 23:32:10 +00:00
}
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-09-22 03:28:54 +00:00
var PointerType = /* @__PURE__ */ ( ( PointerType2 ) => {
PointerType2 [ "Arc" ] = "arc" ;
PointerType2 [ "Rect" ] = "rect" ;
return PointerType2 ;
} ) ( PointerType || { } ) ;
var CompositionOperation = /* @__PURE__ */ ( ( CompositionOperation2 ) => {
CompositionOperation2 [ "SourceOver" ] = "source-over" ;
CompositionOperation2 [ "DestinationOut" ] = "destination-out" ;
return CompositionOperation2 ;
} ) ( CompositionOperation || { } ) ;
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 ;
2024-09-22 03:28:54 +00:00
pointer _type ;
brush _pointer _type _select ;
2024-08-30 23:32:10 +00:00
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" ;
2024-09-22 03:28:54 +00:00
divElement . style . height = "25px" ;
2024-08-15 20:50:25 +00:00
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" ;
2024-09-22 03:28:54 +00:00
divElement . style . height = "25px" ;
2024-08-15 20:50:25 +00:00
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 ;
}
2024-09-22 03:28:54 +00:00
createPointerTypeSelect ( self ) {
const divElement = document . createElement ( "div" ) ;
divElement . id = "maskeditor-pointer-type" ;
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 = "25px" ;
divElement . style . padding = "1px 6px" ;
divElement . style . display = "flex" ;
divElement . style . position = "relative" ;
divElement . style . top = "2px" ;
divElement . style . pointerEvents = "auto" ;
const labelElement = document . createElement ( "label" ) ;
labelElement . textContent = "Pointer Type:" ;
const selectElement = document . createElement ( "select" ) ;
selectElement . style . borderRadius = "0" ;
selectElement . style . borderColor = "transparent" ;
selectElement . style . borderStyle = "unset" ;
selectElement . style . fontSize = "0.9em" ;
const optionArc = document . createElement ( "option" ) ;
optionArc . value = "arc" ;
optionArc . text = "Circle" ;
optionArc . selected = true ;
const optionRect = document . createElement ( "option" ) ;
optionRect . value = "rect" ;
optionRect . text = "Square" ;
selectElement . appendChild ( optionArc ) ;
selectElement . appendChild ( optionRect ) ;
selectElement . addEventListener ( "change" , ( event ) => {
const target = event . target ;
self . pointer _type = target . value ;
this . setBrushBorderRadius ( self ) ;
} ) ;
divElement . appendChild ( labelElement ) ;
divElement . appendChild ( selectElement ) ;
return divElement ;
}
setBrushBorderRadius ( self ) {
if ( self . pointer _type === "rect" ) {
this . brush . style . borderRadius = "0%" ;
this . brush . style . MozBorderRadius = "0%" ;
this . brush . style . WebkitBorderRadius = "0%" ;
} else {
this . brush . style . borderRadius = "50%" ;
this . brush . style . MozBorderRadius = "50%" ;
this . brush . style . WebkitBorderRadius = "50%" ;
}
}
2024-08-15 20:50:25 +00:00
setlayout ( imgCanvas , maskCanvas ) {
const self = this ;
2024-09-22 03:28:54 +00:00
self . pointer _type = "arc" ;
2024-08-15 20:50:25 +00:00
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 . position = "absolute" ;
brush . style . zIndex = "8889" ;
brush . style . pointerEvents = "none" ;
this . brush = brush ;
2024-09-22 03:28:54 +00:00
this . setBrushBorderRadius ( self ) ;
2024-08-15 20:50:25 +00:00
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 ( ) ;
}
}
) ;
2024-09-22 03:28:54 +00:00
this . brush _pointer _type _select = this . createPointerTypeSelect ( self ) ;
2024-08-15 20:50:25 +00:00
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 ) ;
2024-09-22 03:28:54 +00:00
bottom _panel . appendChild ( this . brush _pointer _type _select ) ;
2024-08-15 20:50:25 +00:00
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 ( ( ) => {
2024-09-22 03:28:54 +00:00
self . init _shape (
self ,
"source-over"
/* SourceOver */
) ;
self . draw _shape ( self , x , y , brush _size ) ;
2024-08-15 20:50:25 +00:00
self . lastx = x ;
self . lasty = y ;
} ) ;
else
requestAnimationFrame ( ( ) => {
2024-09-22 03:28:54 +00:00
self . init _shape (
self ,
"source-over"
/* SourceOver */
) ;
2024-08-15 20:50:25 +00:00
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 ;
2024-09-22 03:28:54 +00:00
self . draw _shape ( self , px , py , brush _size ) ;
2024-08-15 20:50:25 +00:00
}
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 ( ( ) => {
2024-09-22 03:28:54 +00:00
self . init _shape (
self ,
"destination-out"
/* DestinationOut */
) ;
self . draw _shape ( self , x2 , y2 , brush _size ) ;
2024-08-15 20:50:25 +00:00
self . lastx = x2 ;
self . lasty = y2 ;
} ) ;
else
requestAnimationFrame ( ( ) => {
2024-09-22 03:28:54 +00:00
self . init _shape (
self ,
"destination-out"
/* DestinationOut */
) ;
2024-08-15 20:50:25 +00:00
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 ;
2024-09-22 03:28:54 +00:00
self . draw _shape ( self , px , py , brush _size ) ;
2024-08-15 20:50:25 +00:00
}
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 ;
if ( ! event . altKey && event . button == 0 ) {
2024-09-22 03:28:54 +00:00
self . init _shape (
self ,
"source-over"
/* SourceOver */
) ;
2024-08-15 20:50:25 +00:00
} else {
2024-09-22 03:28:54 +00:00
self . init _shape (
self ,
"destination-out"
/* DestinationOut */
) ;
2024-08-15 20:50:25 +00:00
}
2024-09-22 03:28:54 +00:00
self . draw _shape ( self , x , y , brush _size ) ;
2024-08-15 20:50:25 +00:00
self . lastx = x ;
self . lasty = y ;
self . lasttime = performance . now ( ) ;
}
}
2024-09-22 03:28:54 +00:00
init _shape ( self , compositionOperation ) {
self . maskCtx . beginPath ( ) ;
if ( compositionOperation == "source-over" ) {
self . maskCtx . fillStyle = this . getMaskFillStyle ( ) ;
self . maskCtx . globalCompositeOperation = "source-over" ;
} else if ( compositionOperation == "destination-out" ) {
self . maskCtx . globalCompositeOperation = "destination-out" ;
}
}
draw _shape ( self , x , y , brush _size ) {
if ( self . pointer _type === "rect" ) {
self . maskCtx . rect (
x - brush _size ,
y - brush _size ,
brush _size * 2 ,
brush _size * 2
) ;
} else {
self . maskCtx . arc ( x , y , brush _size , 0 , Math . PI * 2 , false ) ;
}
self . maskCtx . fill ( ) ;
}
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 ) ;
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert ( 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 ) {
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert ( "No templates to export." ) ;
2024-08-15 20:50:25 +00:00
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 ,
"" ,
[ "" , { 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 ) ;
} ) ;
} ;
2024-10-10 02:37:04 +00:00
this . onConnectionsChange = ( type , index , connected , link _info ) => {
2024-08-15 20:50:25 +00:00
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-09-22 03:28:54 +00:00
if ( inputType && ! LiteGraph . isValidConnection ( inputType , nodeOutType ) ) {
2024-08-15 20:50:25 +00:00
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 ;
}
2024-09-22 03:28:54 +00:00
getExtraMenuOptions ( _2 , options ) {
2024-08-15 20:50:25 +00:00
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
}
) ;
2024-10-10 02:37:04 +00:00
return [ ] ;
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 ;
2024-09-22 03:28:54 +00:00
const inputs = nodeData [ "input" ] [ "required" ] ;
2024-08-30 23:32:10 +00:00
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 ) {
2024-09-22 03:28:54 +00:00
LiteGraph . CANVAS _GRID _SIZE = + value || 10 ;
2024-08-15 20:50:25 +00:00
}
} ) ;
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 ( ) ;
2024-09-22 03:28:54 +00:00
for ( const node of this . nodes ) {
2024-08-15 20:50:25 +00:00
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 ) {
2024-09-22 03:28:54 +00:00
const lastGroup = app . graph . groups [ app . graph . groups . length - 1 ] ;
2024-08-15 20:50:25 +00:00
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 ` ;
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert ( err ) ;
2024-08-15 20:50:25 +00:00
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 } ` ;
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert ( err ) ;
2024-08-15 20:50:25 +00:00
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 {
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert ( resp . status + " - " + resp . statusText ) ;
2024-08-15 20:50:25 +00:00
}
2024-08-30 23:32:10 +00:00
} catch ( error ) {
2024-10-10 02:37:04 +00:00
useToastStore ( ) . addAlert ( error ) ;
2024-08-30 23:32:10 +00:00
}
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" ,
2024-10-07 21:15:29 +00:00
audio ,
{ serialize : false }
2024-08-15 20:50:25 +00:00
) ;
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 ( ) ;
2024-10-07 21:15:29 +00:00
} ,
{ serialize : false }
2024-08-15 20:50:25 +00:00
) ;
uploadWidget . label = "choose file to upload" ;
return { widget : uploadWidget } ;
}
} ;
}
} ) ;
2024-09-22 03:28:54 +00:00
function getNodeSource ( node ) {
const nodeDef = node . constructor . nodeData ;
if ( ! nodeDef ) {
return null ;
}
const nodeDefStore = useNodeDefStore ( ) ;
return nodeDefStore . nodeDefsByName [ nodeDef . name ] ? . nodeSource ? ? null ;
}
_ _name ( getNodeSource , "getNodeSource" ) ;
function isCoreNode ( node ) {
return getNodeSource ( node ) ? . type === NodeSourceType . Core ;
}
_ _name ( isCoreNode , "isCoreNode" ) ;
function badgeTextVisible ( node , badgeMode ) {
return badgeMode === NodeBadgeMode . None || isCoreNode ( node ) && badgeMode === NodeBadgeMode . HideBuiltIn ;
}
_ _name ( badgeTextVisible , "badgeTextVisible" ) ;
function getNodeIdBadgeText ( node , nodeIdBadgeMode ) {
return badgeTextVisible ( node , nodeIdBadgeMode ) ? "" : ` # ${ node . id } ` ;
}
_ _name ( getNodeIdBadgeText , "getNodeIdBadgeText" ) ;
function getNodeSourceBadgeText ( node , nodeSourceBadgeMode ) {
const nodeSource = getNodeSource ( node ) ;
return badgeTextVisible ( node , nodeSourceBadgeMode ) ? "" : nodeSource ? . badgeText ? ? "" ;
}
_ _name ( getNodeSourceBadgeText , "getNodeSourceBadgeText" ) ;
function getNodeLifeCycleBadgeText ( node , nodeLifeCycleBadgeMode ) {
let text = "" ;
const nodeDef = node . constructor . nodeData ;
if ( ! nodeDef ) {
return "" ;
}
if ( nodeDef . deprecated ) {
text = "[DEPR]" ;
}
if ( nodeDef . experimental ) {
text = "[BETA]" ;
}
return badgeTextVisible ( node , nodeLifeCycleBadgeMode ) ? "" : text ;
}
_ _name ( getNodeLifeCycleBadgeText , "getNodeLifeCycleBadgeText" ) ;
class NodeBadgeExtension {
static {
_ _name ( this , "NodeBadgeExtension" ) ;
}
constructor ( nodeIdBadgeMode = null , nodeSourceBadgeMode = null , nodeLifeCycleBadgeMode = null , colorPalette = null ) {
this . nodeIdBadgeMode = nodeIdBadgeMode ;
this . nodeSourceBadgeMode = nodeSourceBadgeMode ;
this . nodeLifeCycleBadgeMode = nodeLifeCycleBadgeMode ;
this . colorPalette = colorPalette ;
}
name = "Comfy.NodeBadge" ;
init ( app2 ) {
const settingStore = useSettingStore ( ) ;
this . nodeSourceBadgeMode = computed (
( ) => settingStore . get ( "Comfy.NodeBadge.NodeSourceBadgeMode" )
) ;
this . nodeIdBadgeMode = computed (
( ) => settingStore . get ( "Comfy.NodeBadge.NodeIdBadgeMode" )
) ;
this . nodeLifeCycleBadgeMode = computed (
( ) => settingStore . get (
"Comfy.NodeBadge.NodeLifeCycleBadgeMode"
)
) ;
this . colorPalette = computed (
( ) => getColorPalette ( settingStore . get ( "Comfy.ColorPalette" ) )
) ;
watch ( this . nodeSourceBadgeMode , ( ) => {
app2 . graph . setDirtyCanvas ( true , true ) ;
} ) ;
watch ( this . nodeIdBadgeMode , ( ) => {
app2 . graph . setDirtyCanvas ( true , true ) ;
} ) ;
watch ( this . nodeLifeCycleBadgeMode , ( ) => {
app2 . graph . setDirtyCanvas ( true , true ) ;
} ) ;
}
nodeCreated ( node , app2 ) {
node . badgePosition = BadgePosition . TopRight ;
node . badge _enabled = true ;
const badge = computed (
2024-10-10 02:37:04 +00:00
( ) => new LGraphBadge ( {
2024-09-22 03:28:54 +00:00
text : _ . truncate (
[
getNodeIdBadgeText ( node , this . nodeIdBadgeMode . value ) ,
getNodeLifeCycleBadgeText (
node ,
this . nodeLifeCycleBadgeMode . value
) ,
getNodeSourceBadgeText ( node , this . nodeSourceBadgeMode . value )
] . filter ( ( s ) => s . length > 0 ) . join ( " " ) ,
{
length : 31
}
) ,
fgColor : this . colorPalette . value . colors . litegraph _base ? . BADGE _FG _COLOR || defaultColorPalette . colors . litegraph _base . BADGE _FG _COLOR ,
bgColor : this . colorPalette . value . colors . litegraph _base ? . BADGE _BG _COLOR || defaultColorPalette . colors . litegraph _base . BADGE _BG _COLOR
} )
) ;
node . badges . push ( ( ) => badge . value ) ;
}
}
app . registerExtension ( new NodeBadgeExtension ( ) ) ;
2024-10-10 02:37:04 +00:00
//# sourceMappingURL=index-BMC1ey-i.js.map