2024-06-25 10:49:25 +00:00
// @ts-check
import { ComfyButton } from "../components/button.js" ;
import { prop , getStorageValue , setStorageValue } from "../../utils.js" ;
import { $el } from "../../ui.js" ;
import { api } from "../../api.js" ;
import { ComfyPopup } from "../components/popup.js" ;
import { createSpinner } from "../spinner.js" ;
import { ComfyWorkflow , trimJsonExt } from "../../workflows.js" ;
import { ComfyAsyncDialog } from "../components/asyncDialog.js" ;
export class ComfyWorkflowsMenu {
# first = true ;
element = $el ( "div.comfyui-workflows" ) ;
get open ( ) {
return this . popup . open ;
}
set open ( open ) {
this . popup . open = open ;
}
/ * *
* @ param { import ( "../../app.js" ) . ComfyApp } app
* /
constructor ( app ) {
this . app = app ;
this . # bindEvents ( ) ;
const classList = {
"comfyui-workflows-button" : true ,
"comfyui-button" : true ,
unsaved : getStorageValue ( "Comfy.PreviousWorkflowUnsaved" ) === "true" ,
running : false ,
} ;
this . buttonProgress = $el ( "div.comfyui-workflows-button-progress" ) ;
this . workflowLabel = $el ( "span.comfyui-workflows-label" , "" ) ;
this . button = new ComfyButton ( {
content : $el ( "div.comfyui-workflows-button-inner" , [ $el ( "i.mdi.mdi-graph" ) , this . workflowLabel , this . buttonProgress ] ) ,
icon : "chevron-down" ,
classList ,
} ) ;
this . element . append ( this . button . element ) ;
this . popup = new ComfyPopup ( { target : this . element , classList : "comfyui-workflows-popup" } ) ;
this . content = new ComfyWorkflowsContent ( app , this . popup ) ;
this . popup . children = [ this . content . element ] ;
this . popup . addEventListener ( "change" , ( ) => {
this . button . icon = "chevron-" + ( this . popup . open ? "up" : "down" ) ;
} ) ;
this . button . withPopup ( this . popup ) ;
this . unsaved = prop ( this , "unsaved" , classList . unsaved , ( v ) => {
classList . unsaved = v ;
this . button . classList = classList ;
setStorageValue ( "Comfy.PreviousWorkflowUnsaved" , v ) ;
} ) ;
}
# updateProgress = ( ) => {
const prompt = this . app . workflowManager . activePrompt ;
let percent = 0 ;
if ( this . app . workflowManager . activeWorkflow === prompt ? . workflow ) {
const total = Object . values ( prompt . nodes ) ;
const done = total . filter ( Boolean ) ;
percent = ( done . length / total . length ) * 100 ;
}
this . buttonProgress . style . width = percent + "%" ;
} ;
# updateActive = ( ) => {
const active = this . app . workflowManager . activeWorkflow ;
this . button . tooltip = active . path ;
this . workflowLabel . textContent = active . name ;
this . unsaved = active . unsaved ;
if ( this . # first ) {
this . # first = false ;
this . content . load ( ) ;
}
this . # updateProgress ( ) ;
} ;
# bindEvents ( ) {
this . app . workflowManager . addEventListener ( "changeWorkflow" , this . # updateActive ) ;
this . app . workflowManager . addEventListener ( "rename" , this . # updateActive ) ;
this . app . workflowManager . addEventListener ( "delete" , this . # updateActive ) ;
this . app . workflowManager . addEventListener ( "save" , ( ) => {
this . unsaved = this . app . workflowManager . activeWorkflow . unsaved ;
} ) ;
this . app . workflowManager . addEventListener ( "execute" , ( e ) => {
this . # updateProgress ( ) ;
} ) ;
api . addEventListener ( "graphChanged" , ( ) => {
this . unsaved = true ;
} ) ;
}
# getMenuOptions ( callback ) {
const menu = [ ] ;
const directories = new Map ( ) ;
for ( const workflow of this . app . workflowManager . workflows || [ ] ) {
const path = workflow . pathParts ;
if ( ! path ) continue ;
let parent = menu ;
let currentPath = "" ;
for ( let i = 0 ; i < path . length - 1 ; i ++ ) {
currentPath += "/" + path [ i ] ;
let newParent = directories . get ( currentPath ) ;
if ( ! newParent ) {
newParent = {
title : path [ i ] ,
has _submenu : true ,
submenu : {
options : [ ] ,
} ,
} ;
parent . push ( newParent ) ;
newParent = newParent . submenu . options ;
directories . set ( currentPath , newParent ) ;
}
parent = newParent ;
}
parent . push ( {
title : trimJsonExt ( path [ path . length - 1 ] ) ,
callback : ( ) => callback ( workflow ) ,
} ) ;
}
return menu ;
}
# getFavoriteMenuOptions ( callback ) {
const menu = [ ] ;
for ( const workflow of this . app . workflowManager . workflows || [ ] ) {
if ( workflow . isFavorite ) {
menu . push ( {
title : "⭐ " + workflow . name ,
callback : ( ) => callback ( workflow ) ,
} ) ;
}
}
return menu ;
}
/ * *
* @ param { import ( "../../app.js" ) . ComfyApp } app
* /
registerExtension ( app ) {
const self = this ;
app . registerExtension ( {
name : "Comfy.Workflows" ,
async beforeRegisterNodeDef ( nodeType ) {
function getImageWidget ( node ) {
const inputs = { ... node . constructor ? . nodeData ? . input ? . required , ... node . constructor ? . nodeData ? . input ? . optional } ;
for ( const input in inputs ) {
if ( inputs [ input ] [ 0 ] === "IMAGEUPLOAD" ) {
const imageWidget = node . widgets . find ( ( w ) => w . name === ( inputs [ input ] ? . [ 1 ] ? . widget ? ? "image" ) ) ;
if ( imageWidget ) return imageWidget ;
}
}
}
function setWidgetImage ( node , widget , img ) {
const url = new URL ( img . src ) ;
const filename = url . searchParams . get ( "filename" ) ;
const subfolder = url . searchParams . get ( "subfolder" ) ;
const type = url . searchParams . get ( "type" ) ;
const imageId = ` ${ subfolder ? subfolder + "/" : "" } ${ filename } [ ${ type } ] ` ;
widget . value = imageId ;
node . imgs = [ img ] ;
app . graph . setDirtyCanvas ( true , true ) ;
}
/ * *
* @ param { HTMLImageElement } img
* @ param { ComfyWorkflow } workflow
* /
async function sendToWorkflow ( img , workflow ) {
2024-07-14 06:04:40 +00:00
const openWorkflow = app . workflowManager . openWorkflows . find ( ( w ) => w . path === workflow . path ) ;
if ( openWorkflow ) {
workflow = openWorkflow ;
}
2024-06-25 10:49:25 +00:00
await workflow . load ( ) ;
let options = [ ] ;
const nodes = app . graph . computeExecutionOrder ( false ) ;
for ( const node of nodes ) {
const widget = getImageWidget ( node ) ;
if ( widget == null ) continue ;
if ( node . title ? . toLowerCase ( ) . includes ( "input" ) ) {
options = [ { widget , node } ] ;
break ;
} else {
options . push ( { widget , node } ) ;
}
}
if ( ! options . length ) {
alert ( "No image nodes have been found in this workflow!" ) ;
return ;
} else if ( options . length > 1 ) {
const dialog = new WidgetSelectionDialog ( options ) ;
const res = await dialog . show ( app ) ;
if ( ! res ) return ;
options = [ res ] ;
}
setWidgetImage ( options [ 0 ] . node , options [ 0 ] . widget , img ) ;
}
const getExtraMenuOptions = nodeType . prototype [ "getExtraMenuOptions" ] ;
nodeType . prototype [ "getExtraMenuOptions" ] = function ( _ , options ) {
const r = getExtraMenuOptions ? . apply ? . ( this , arguments ) ;
2024-07-14 06:04:40 +00:00
const setting = app . ui . settings . getSettingValue ( "Comfy.UseNewMenu" , false ) ;
if ( setting && setting != "Disabled" ) {
2024-06-25 10:49:25 +00:00
const t = /** @type { {imageIndex?: number, overIndex?: number, imgs: string[]} } */ /** @type {any} */ ( this ) ;
let img ;
if ( t . imageIndex != null ) {
// An image is selected so select that
img = t . imgs ? . [ t . imageIndex ] ;
} else if ( t . overIndex != null ) {
// No image is selected but one is hovered
img = t . img ? . s [ t . overIndex ] ;
}
if ( img ) {
let pos = options . findIndex ( ( o ) => o . content === "Save Image" ) ;
if ( pos === - 1 ) {
pos = 0 ;
} else {
pos ++ ;
}
options . splice ( pos , 0 , {
content : "Send to workflow" ,
has _submenu : true ,
submenu : {
options : [
{
callback : ( ) => sendToWorkflow ( img , app . workflowManager . activeWorkflow ) ,
title : "[Current workflow]" ,
} ,
... self . # getFavoriteMenuOptions ( sendToWorkflow . bind ( null , img ) ) ,
null ,
... self . # getMenuOptions ( sendToWorkflow . bind ( null , img ) ) ,
] ,
} ,
} ) ;
}
}
return r ;
} ;
} ,
} ) ;
}
}
export class ComfyWorkflowsContent {
element = $el ( "div.comfyui-workflows-panel" ) ;
treeState = { } ;
treeFiles = { } ;
/** @type { Map<ComfyWorkflow, WorkflowElement> } */
openFiles = new Map ( ) ;
/** @type {WorkflowElement} */
activeElement = null ;
/ * *
* @ param { import ( "../../app.js" ) . ComfyApp } app
* @ param { ComfyPopup } popup
* /
constructor ( app , popup ) {
this . app = app ;
this . popup = popup ;
this . actions = $el ( "div.comfyui-workflows-actions" , [
new ComfyButton ( {
content : "Default" ,
icon : "file-code" ,
iconSize : 18 ,
classList : "comfyui-button primary" ,
tooltip : "Load default workflow" ,
action : ( ) => {
popup . open = false ;
app . loadGraphData ( ) ;
app . resetView ( ) ;
} ,
} ) . element ,
new ComfyButton ( {
content : "Browse" ,
icon : "folder" ,
iconSize : 18 ,
tooltip : "Browse for an image or exported workflow" ,
action : ( ) => {
popup . open = false ;
app . ui . loadFile ( ) ;
} ,
} ) . element ,
new ComfyButton ( {
content : "Blank" ,
icon : "plus-thick" ,
iconSize : 18 ,
tooltip : "Create a new blank workflow" ,
action : ( ) => {
app . workflowManager . setWorkflow ( null ) ;
app . clean ( ) ;
app . graph . clear ( ) ;
app . workflowManager . activeWorkflow . track ( ) ;
popup . open = false ;
} ,
} ) . element ,
] ) ;
this . spinner = createSpinner ( ) ;
this . element . replaceChildren ( this . actions , this . spinner ) ;
this . popup . addEventListener ( "open" , ( ) => this . load ( ) ) ;
this . popup . addEventListener ( "close" , ( ) => this . element . replaceChildren ( this . actions , this . spinner ) ) ;
this . app . workflowManager . addEventListener ( "favorite" , ( e ) => {
const workflow = e [ "detail" ] ;
const button = this . treeFiles [ workflow . path ] ? . primary ;
if ( ! button ) return ; // Can happen when a workflow is renamed
button . icon = this . # getFavoriteIcon ( workflow ) ;
button . overIcon = this . # getFavoriteOverIcon ( workflow ) ;
this . updateFavorites ( ) ;
} ) ;
for ( const e of [ "save" , "open" , "close" , "changeWorkflow" ] ) {
// TODO: dont be lazy and just update the specific element
app . workflowManager . addEventListener ( e , ( ) => this . updateOpen ( ) ) ;
}
this . app . workflowManager . addEventListener ( "rename" , ( ) => this . load ( ) ) ;
this . app . workflowManager . addEventListener ( "execute" , ( e ) => this . # updateActive ( ) ) ;
}
async load ( ) {
await this . app . workflowManager . loadWorkflows ( ) ;
this . updateTree ( ) ;
this . updateFavorites ( ) ;
this . updateOpen ( ) ;
this . element . replaceChildren ( this . actions , this . openElement , this . favoritesElement , this . treeElement ) ;
}
updateOpen ( ) {
const current = this . openElement ;
this . openFiles . clear ( ) ;
this . openElement = $el ( "div.comfyui-workflows-open" , [
$el ( "h3" , "Open" ) ,
... this . app . workflowManager . openWorkflows . map ( ( w ) => {
const wrapper = new WorkflowElement ( this , w , {
primary : { element : $el ( "i.mdi.mdi-18px.mdi-progress-pencil" ) } ,
buttons : [
this . # getRenameButton ( w ) ,
new ComfyButton ( {
icon : "close" ,
iconSize : 18 ,
classList : "comfyui-button comfyui-workflows-file-action" ,
tooltip : "Close workflow" ,
action : ( e ) => {
e . stopImmediatePropagation ( ) ;
this . app . workflowManager . closeWorkflow ( w ) ;
} ,
} ) ,
] ,
} ) ;
if ( w . unsaved ) {
wrapper . element . classList . add ( "unsaved" ) ;
}
if ( w === this . app . workflowManager . activeWorkflow ) {
wrapper . element . classList . add ( "active" ) ;
}
this . openFiles . set ( w , wrapper ) ;
return wrapper . element ;
} ) ,
] ) ;
this . # updateActive ( ) ;
current ? . replaceWith ( this . openElement ) ;
}
updateFavorites ( ) {
const current = this . favoritesElement ;
const favorites = [ ... this . app . workflowManager . workflows . filter ( ( w ) => w . isFavorite ) ] ;
this . favoritesElement = $el ( "div.comfyui-workflows-favorites" , [
$el ( "h3" , "Favorites" ) ,
... favorites
. map ( ( w ) => {
return this . # getWorkflowElement ( w ) . element ;
} )
. filter ( Boolean ) ,
] ) ;
current ? . replaceWith ( this . favoritesElement ) ;
}
filterTree ( ) {
if ( ! this . filterText ) {
this . treeRoot . classList . remove ( "filtered" ) ;
// Unfilter whole tree
for ( const item of Object . values ( this . treeFiles ) ) {
item . element . parentElement . style . removeProperty ( "display" ) ;
this . showTreeParents ( item . element . parentElement ) ;
}
return ;
}
this . treeRoot . classList . add ( "filtered" ) ;
const searchTerms = this . filterText . toLocaleLowerCase ( ) . split ( " " ) ;
for ( const item of Object . values ( this . treeFiles ) ) {
const parts = item . workflow . pathParts ;
let termIndex = 0 ;
let valid = false ;
for ( const part of parts ) {
let currentIndex = 0 ;
do {
currentIndex = part . indexOf ( searchTerms [ termIndex ] , currentIndex ) ;
if ( currentIndex > - 1 ) currentIndex += searchTerms [ termIndex ] . length ;
} while ( currentIndex !== - 1 && ++ termIndex < searchTerms . length ) ;
if ( termIndex >= searchTerms . length ) {
valid = true ;
break ;
}
}
if ( valid ) {
item . element . parentElement . style . removeProperty ( "display" ) ;
this . showTreeParents ( item . element . parentElement ) ;
} else {
item . element . parentElement . style . display = "none" ;
this . hideTreeParents ( item . element . parentElement ) ;
}
}
}
hideTreeParents ( element ) {
// Hide all parents if no children are visible
if ( element . parentElement ? . classList . contains ( "comfyui-workflows-tree" ) === false ) {
for ( let i = 1 ; i < element . parentElement . children . length ; i ++ ) {
const c = element . parentElement . children [ i ] ;
if ( c . style . display !== "none" ) {
return ;
}
}
element . parentElement . style . display = "none" ;
this . hideTreeParents ( element . parentElement ) ;
}
}
showTreeParents ( element ) {
if ( element . parentElement ? . classList . contains ( "comfyui-workflows-tree" ) === false ) {
element . parentElement . style . removeProperty ( "display" ) ;
this . showTreeParents ( element . parentElement ) ;
}
}
updateTree ( ) {
const current = this . treeElement ;
const nodes = { } ;
let typingTimeout ;
this . treeFiles = { } ;
this . treeRoot = $el ( "ul.comfyui-workflows-tree" ) ;
this . treeElement = $el ( "section" , [
$el ( "header" , [
$el ( "h3" , "Browse" ) ,
$el ( "div.comfy-ui-workflows-search" , [
$el ( "i.mdi.mdi-18px.mdi-magnify" ) ,
$el ( "input" , {
placeholder : "Search" ,
value : this . filterText ? ? "" ,
oninput : ( e ) => {
this . filterText = e . target [ "value" ] ? . trim ( ) ;
clearTimeout ( typingTimeout ) ;
typingTimeout = setTimeout ( ( ) => this . filterTree ( ) , 250 ) ;
} ,
} ) ,
] ) ,
] ) ,
this . treeRoot ,
] ) ;
for ( const workflow of this . app . workflowManager . workflows ) {
if ( ! workflow . pathParts ) continue ;
let currentPath = "" ;
let currentRoot = this . treeRoot ;
for ( let i = 0 ; i < workflow . pathParts . length ; i ++ ) {
currentPath += ( currentPath ? "\\" : "" ) + workflow . pathParts [ i ] ;
const parentNode = nodes [ currentPath ] ? ? this . # createNode ( currentPath , workflow , i , currentRoot ) ;
nodes [ currentPath ] = parentNode ;
currentRoot = parentNode ;
}
}
current ? . replaceWith ( this . treeElement ) ;
this . filterTree ( ) ;
}
# expandNode ( el , workflow , thisPath , i ) {
const expanded = ! el . classList . toggle ( "closed" ) ;
if ( expanded ) {
let c = "" ;
for ( let j = 0 ; j <= i ; j ++ ) {
c += ( c ? "\\" : "" ) + workflow . pathParts [ j ] ;
this . treeState [ c ] = true ;
}
} else {
let c = thisPath ;
for ( let j = i + 1 ; j < workflow . pathParts . length ; j ++ ) {
c += ( c ? "\\" : "" ) + workflow . pathParts [ j ] ;
delete this . treeState [ c ] ;
}
delete this . treeState [ thisPath ] ;
}
}
# updateActive ( ) {
this . # removeActive ( ) ;
const active = this . app . workflowManager . activePrompt ;
if ( ! active ? . workflow ) return ;
const open = this . openFiles . get ( active . workflow ) ;
if ( ! open ) return ;
this . activeElement = open ;
const total = Object . values ( active . nodes ) ;
const done = total . filter ( Boolean ) ;
const percent = done . length / total . length ;
open . element . classList . add ( "running" ) ;
open . element . style . setProperty ( "--progress" , percent * 100 + "%" ) ;
open . primary . element . classList . remove ( "mdi-progress-pencil" ) ;
open . primary . element . classList . add ( "mdi-play" ) ;
}
# removeActive ( ) {
if ( ! this . activeElement ) return ;
this . activeElement . element . classList . remove ( "running" ) ;
this . activeElement . element . style . removeProperty ( "--progress" ) ;
this . activeElement . primary . element . classList . add ( "mdi-progress-pencil" ) ;
this . activeElement . primary . element . classList . remove ( "mdi-play" ) ;
}
/** @param {ComfyWorkflow} workflow */
# getFavoriteIcon ( workflow ) {
return workflow . isFavorite ? "star" : "file-outline" ;
}
/** @param {ComfyWorkflow} workflow */
# getFavoriteOverIcon ( workflow ) {
return workflow . isFavorite ? "star-off" : "star-outline" ;
}
/** @param {ComfyWorkflow} workflow */
# getFavoriteTooltip ( workflow ) {
return workflow . isFavorite ? "Remove this workflow from your favorites" : "Add this workflow to your favorites" ;
}
/** @param {ComfyWorkflow} workflow */
# getFavoriteButton ( workflow , primary ) {
return new ComfyButton ( {
icon : this . # getFavoriteIcon ( workflow ) ,
overIcon : this . # getFavoriteOverIcon ( workflow ) ,
iconSize : 18 ,
classList : "comfyui-button comfyui-workflows-file-action-favorite" + ( primary ? " comfyui-workflows-file-action-primary" : "" ) ,
tooltip : this . # getFavoriteTooltip ( workflow ) ,
action : ( e ) => {
e . stopImmediatePropagation ( ) ;
workflow . favorite ( ! workflow . isFavorite ) ;
} ,
} ) ;
}
/** @param {ComfyWorkflow} workflow */
# getDeleteButton ( workflow ) {
const deleteButton = new ComfyButton ( {
icon : "delete" ,
tooltip : "Delete this workflow" ,
classList : "comfyui-button comfyui-workflows-file-action" ,
iconSize : 18 ,
action : async ( e , btn ) => {
e . stopImmediatePropagation ( ) ;
if ( btn . icon === "delete-empty" ) {
btn . enabled = false ;
await workflow . delete ( ) ;
await this . load ( ) ;
} else {
btn . icon = "delete-empty" ;
btn . element . style . background = "red" ;
}
} ,
} ) ;
deleteButton . element . addEventListener ( "mouseleave" , ( ) => {
deleteButton . icon = "delete" ;
deleteButton . element . style . removeProperty ( "background" ) ;
} ) ;
return deleteButton ;
}
/** @param {ComfyWorkflow} workflow */
# getInsertButton ( workflow ) {
return new ComfyButton ( {
icon : "file-move-outline" ,
iconSize : 18 ,
tooltip : "Insert this workflow into the current workflow" ,
classList : "comfyui-button comfyui-workflows-file-action" ,
action : ( e ) => {
if ( ! this . app . shiftDown ) {
this . popup . open = false ;
}
e . stopImmediatePropagation ( ) ;
if ( ! this . app . shiftDown ) {
this . popup . open = false ;
}
workflow . insert ( ) ;
} ,
} ) ;
}
/** @param {ComfyWorkflow} workflow */
# getRenameButton ( workflow ) {
return new ComfyButton ( {
icon : "pencil" ,
tooltip : workflow . path ? "Rename this workflow" : "This workflow can't be renamed as it hasn't been saved." ,
classList : "comfyui-button comfyui-workflows-file-action" ,
iconSize : 18 ,
enabled : ! ! workflow . path ,
action : async ( e ) => {
e . stopImmediatePropagation ( ) ;
const newName = prompt ( "Enter new name" , workflow . path ) ;
if ( newName ) {
await workflow . rename ( newName ) ;
}
} ,
} ) ;
}
/** @param {ComfyWorkflow} workflow */
# getWorkflowElement ( workflow ) {
return new WorkflowElement ( this , workflow , {
primary : this . # getFavoriteButton ( workflow , true ) ,
buttons : [ this . # getInsertButton ( workflow ) , this . # getRenameButton ( workflow ) , this . # getDeleteButton ( workflow ) ] ,
} ) ;
}
/** @param {ComfyWorkflow} workflow */
# createLeafNode ( workflow ) {
const fileNode = this . # getWorkflowElement ( workflow ) ;
this . treeFiles [ workflow . path ] = fileNode ;
return fileNode ;
}
# createNode ( currentPath , workflow , i , currentRoot ) {
const part = workflow . pathParts [ i ] ;
const parentNode = $el ( "ul" + ( this . treeState [ currentPath ] ? "" : ".closed" ) , {
$ : ( el ) => {
el . onclick = ( e ) => {
this . # expandNode ( el , workflow , currentPath , i ) ;
e . stopImmediatePropagation ( ) ;
} ;
} ,
} ) ;
currentRoot . append ( parentNode ) ;
// Create a node for the current part and an inner UL for its children if it isnt a leaf node
const leaf = i === workflow . pathParts . length - 1 ;
let nodeElement ;
if ( leaf ) {
nodeElement = this . # createLeafNode ( workflow ) . element ;
} else {
nodeElement = $el ( "li" , [ $el ( "i.mdi.mdi-18px.mdi-folder" ) , $el ( "span" , part ) ] ) ;
}
parentNode . append ( nodeElement ) ;
return parentNode ;
}
}
class WorkflowElement {
/ * *
* @ param { ComfyWorkflowsContent } parent
* @ param { ComfyWorkflow } workflow
* /
constructor ( parent , workflow , { tagName = "li" , primary , buttons } ) {
this . parent = parent ;
this . workflow = workflow ;
this . primary = primary ;
this . buttons = buttons ;
this . element = $el (
tagName + ".comfyui-workflows-tree-file" ,
{
onclick : ( ) => {
workflow . load ( ) ;
this . parent . popup . open = false ;
} ,
title : this . workflow . path ,
} ,
[ this . primary ? . element , $el ( "span" , workflow . name ) , ... buttons . map ( ( b ) => b . element ) ]
) ;
}
}
class WidgetSelectionDialog extends ComfyAsyncDialog {
# options ;
/ * *
* @ param { Array < { widget : { name : string } , node : { pos : [ number , number ] , title : string , id : string , type : string } } > } options
* /
constructor ( options ) {
super ( ) ;
this . # options = options ;
}
show ( app ) {
this . element . classList . add ( "comfy-widget-selection-dialog" ) ;
return super . show (
$el ( "div" , [
$el ( "h2" , "Select image target" ) ,
$el (
"p" ,
"This workflow has multiple image loader nodes, you can rename a node to include 'input' in the title for it to be automatically selected, or select one below."
) ,
$el (
"section" ,
this . # options . map ( ( opt ) => {
return $el ( "div.comfy-widget-selection-item" , [
$el ( "span" , { dataset : { id : opt . node . id } } , ` ${ opt . node . title ? ? opt . node . type } ${ opt . widget . name } ` ) ,
$el (
"button.comfyui-button" ,
{
onclick : ( ) => {
app . canvas . ds . offset [ 0 ] = - opt . node . pos [ 0 ] + 50 ;
app . canvas . ds . offset [ 1 ] = - opt . node . pos [ 1 ] + 50 ;
app . canvas . selectNode ( opt . node ) ;
app . graph . setDirtyCanvas ( true , true ) ;
} ,
} ,
"Show"
) ,
$el (
"button.comfyui-button.primary" ,
{
onclick : ( ) => {
this . close ( opt ) ;
} ,
} ,
"Select"
) ,
] ) ;
} )
) ,
] )
) ;
}
}