import { app } from "../../scripts/app.js" ;
import { api } from "../../scripts/api.js"
import { ComfyDialog , $el } from "../../scripts/ui.js" ;
import { ComfyWidgets } from "../../scripts/widgets.js" ;
var update _comfyui _button = null ;
var fetch _updates _button = null ;
var update _all _button = null ;
var badge _mode = "none" ;
async function init _badge _mode ( ) {
api . fetchApi ( '/manager/badge_mode' )
. then ( response => response . text ( ) )
. then ( data => { badge _mode = data ; } )
}
await init _badge _mode ( ) ;
async function getCustomnodeMappings ( ) {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
const response = await api . fetchApi ( ` /customnode/getmappings?mode= ${ mode } ` ) ;
const data = await response . json ( ) ;
return data ;
}
async function getUnresolvedNodesInComponent ( ) {
try {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
const response = await api . fetchApi ( ` /component/get_unresolved ` ) ;
const data = await response . json ( ) ;
return data . nodes ;
}
catch {
return [ ] ;
}
}
async function getCustomNodes ( ) {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
var skip _update = "" ;
if ( ManagerMenuDialog . instance . update _check _checkbox . checked )
skip _update = "&skip_update=true" ;
const response = await api . fetchApi ( ` /customnode/getlist?mode= ${ mode } ${ skip _update } ` ) ;
const data = await response . json ( ) ;
return data ;
}
async function fetchNicknames ( ) {
const response1 = await api . fetchApi ( ` /customnode/getmappings?mode=local ` ) ;
const mappings = await response1 . json ( ) ;
let result = { } ;
for ( let i in mappings ) {
let item = mappings [ i ] ;
var nickname ;
if ( item [ 1 ] . title ) {
nickname = item [ 1 ] . title ;
}
else {
nickname = item [ 1 ] . title _aux ;
}
for ( let j in item [ 0 ] ) {
result [ item [ 0 ] [ j ] ] = nickname ;
}
}
return result ;
}
let nicknames = await fetchNicknames ( ) ;
async function getAlterList ( ) {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
var skip _update = "" ;
if ( ManagerMenuDialog . instance . update _check _checkbox . checked )
skip _update = "&skip_update=true" ;
const response = await api . fetchApi ( ` /alternatives/getlist?mode= ${ mode } ${ skip _update } ` ) ;
const data = await response . json ( ) ;
return data ;
}
async function getModelList ( ) {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
const response = await api . fetchApi ( ` /externalmodel/getlist?mode= ${ mode } ` ) ;
const data = await response . json ( ) ;
return data ;
}
async function install _checked _custom _node ( grid _rows , target _i , caller , mode ) {
if ( caller ) {
let failed = '' ;
caller . disableButtons ( ) ;
for ( let i in grid _rows ) {
if ( ! grid _rows [ i ] . checkbox . checked && i != target _i )
continue ;
var target ;
if ( grid _rows [ i ] . data . custom _node ) {
target = grid _rows [ i ] . data . custom _node ;
}
else {
target = grid _rows [ i ] . data ;
}
caller . startInstall ( target ) ;
try {
const response = await api . fetchApi ( ` /customnode/ ${ mode } ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( target )
} ) ;
if ( response . status == 400 ) {
app . ui . dialog . show ( ` ${ mode } failed: ${ target . title } ` ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
continue ;
}
const status = await response . json ( ) ;
app . ui . dialog . close ( ) ;
target . installed = 'True' ;
continue ;
}
catch ( exception ) {
failed += ` <BR> ${ target . title } ` ;
}
}
if ( failed != '' ) {
app . ui . dialog . show ( ` ${ mode } failed: ${ failed } ` ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
}
await caller . invalidateControl ( ) ;
caller . updateMessage ( '<BR>To apply the installed/disabled/enabled custom node, please restart ComfyUI.' ) ;
}
}
async function updateComfyUI ( ) {
let prev _text = update _comfyui _button . innerText ;
update _comfyui _button . innerText = "Updating ComfyUI..." ;
update _comfyui _button . disabled = true ;
update _comfyui _button . style . backgroundColor = "gray" ;
try {
const response = await api . fetchApi ( '/comfyui_manager/update_comfyui' ) ;
if ( response . status == 400 ) {
app . ui . dialog . show ( 'Failed to update ComfyUI.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
if ( response . status == 201 ) {
app . ui . dialog . show ( 'ComfyUI has been successfully updated.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
}
else {
app . ui . dialog . show ( 'ComfyUI is already up to date with the latest version.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
}
return true ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Failed to update ComfyUI / ${ exception } ` ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
finally {
update _comfyui _button . disabled = false ;
update _comfyui _button . innerText = prev _text ;
update _comfyui _button . style . backgroundColor = "" ;
}
}
async function fetchUpdates ( update _check _checkbox ) {
let prev _text = fetch _updates _button . innerText ;
fetch _updates _button . innerText = "Fetching updates..." ;
fetch _updates _button . disabled = true ;
fetch _updates _button . style . backgroundColor = "gray" ;
try {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
const response = await api . fetchApi ( ` /customnode/fetch_updates?mode= ${ mode } ` ) ;
if ( response . status != 200 && response . status != 201 ) {
app . ui . dialog . show ( 'Failed to fetch updates.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
if ( response . status == 201 ) {
app . ui . dialog . show ( 'There is an updated extension available.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
update _check _checkbox . checked = false ;
}
else {
app . ui . dialog . show ( 'All extensions are already up-to-date with the latest versions.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
}
return true ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Failed to update custom nodes / ${ exception } ` ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
finally {
fetch _updates _button . disabled = false ;
fetch _updates _button . innerText = prev _text ;
fetch _updates _button . style . backgroundColor = "" ;
}
}
async function updateAll ( update _check _checkbox ) {
let prev _text = update _all _button . innerText ;
update _all _button . innerText = "Updating all...(ComfyUI)" ;
update _all _button . disabled = true ;
update _all _button . style . backgroundColor = "gray" ;
try {
var mode = "url" ;
if ( ManagerMenuDialog . instance . local _mode _checkbox . checked )
mode = "local" ;
update _all _button . innerText = "Updating all..." ;
const response1 = await api . fetchApi ( '/comfyui_manager/update_comfyui' ) ;
const response2 = await api . fetchApi ( ` /customnode/update_all?mode= ${ mode } ` ) ;
if ( response1 . status != 200 && response2 . status != 201 ) {
app . ui . dialog . show ( 'Failed to update ComfyUI or several extensions.<BR><BR>See terminal log.<BR>' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
if ( response1 . status == 201 || response2 . status == 201 ) {
app . ui . dialog . show ( 'ComfyUI and all extensions have been updated to the latest version.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
}
else {
app . ui . dialog . show ( 'ComfyUI and all extensions are already up-to-date with the latest versions.' ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
}
return true ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Failed to update ComfyUI or several extensions / ${ exception } ` ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
finally {
update _all _button . disabled = false ;
update _all _button . innerText = prev _text ;
update _all _button . style . backgroundColor = "" ;
}
}
async function install _model ( target ) {
if ( ModelInstaller . instance ) {
ModelInstaller . instance . startInstall ( target ) ;
try {
const response = await api . fetchApi ( '/model/install' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( target )
} ) ;
const status = await response . json ( ) ;
app . ui . dialog . close ( ) ;
target . installed = 'True' ;
return true ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Install failed: ${ target . title } / ${ exception } ` ) ;
app . ui . dialog . element . style . zIndex = 9999 ;
return false ;
}
finally {
await ModelInstaller . instance . invalidateControl ( ) ;
ModelInstaller . instance . updateMessage ( "<BR>To apply the installed model, please click the 'Refresh' button on the main menu." ) ;
}
}
}
// -----
class CustomNodesInstaller extends ComfyDialog {
static instance = null ;
install _buttons = [ ] ;
message _box = null ;
data = null ;
clear ( ) {
this . install _buttons = [ ] ;
this . message _box = null ;
this . data = null ;
}
constructor ( ) {
super ( ) ;
this . search _keyword = '' ;
this . element = $el ( "div.comfy-modal" , { parent : document . body } , [ ] ) ;
}
startInstall ( target ) {
const self = CustomNodesInstaller . instance ;
self . updateMessage ( ` <BR><font color="green">Installing ' ${ target . title } '</font> ` ) ;
}
disableButtons ( ) {
for ( let i in this . install _buttons ) {
this . install _buttons [ i ] . disabled = true ;
this . install _buttons [ i ] . style . backgroundColor = 'gray' ;
}
}
apply _searchbox ( data ) {
let keyword = this . search _box . value . toLowerCase ( ) ;
for ( let i in this . grid _rows ) {
let data = this . grid _rows [ i ] . data ;
let content = data . author . toLowerCase ( ) + data . description . toLowerCase ( ) + data . title . toLowerCase ( ) ;
if ( this . filter && this . filter != '*' ) {
if ( this . filter != data . installed ) {
this . grid _rows [ i ] . control . style . display = 'none' ;
continue ;
}
}
if ( keyword == "" )
this . grid _rows [ i ] . control . style . display = null ;
else if ( content . includes ( keyword ) ) {
this . grid _rows [ i ] . control . style . display = null ;
}
else {
this . grid _rows [ i ] . control . style . display = 'none' ;
}
}
}
async filter _missing _node ( data ) {
const mappings = await getCustomnodeMappings ( ) ;
// build regex->url map
const regex _to _url = [ ] ;
for ( let i in data ) {
if ( data [ i ] [ 'nodename_pattern' ] ) {
let item = { regex : new RegExp ( data [ i ] . nodename _pattern ) , url : data [ i ] . files [ 0 ] } ;
regex _to _url . push ( item ) ;
}
}
// build name->url map
const name _to _url = { } ;
for ( const url in mappings ) {
const names = mappings [ url ] ;
for ( const name in names [ 0 ] ) {
name _to _url [ names [ 0 ] [ name ] ] = url ;
}
}
const registered _nodes = new Set ( ) ;
for ( let i in LiteGraph . registered _node _types ) {
registered _nodes . add ( LiteGraph . registered _node _types [ i ] . type ) ;
}
const missing _nodes = new Set ( ) ;
const nodes = app . graph . serialize ( ) . nodes ;
for ( let i in nodes ) {
const node _type = nodes [ i ] . type ;
if ( ! registered _nodes . has ( node _type ) ) {
const url = name _to _url [ node _type . trim ( ) ] ;
if ( url )
missing _nodes . add ( url ) ;
else {
for ( let j in regex _to _url ) {
if ( regex _to _url [ j ] . regex . test ( node _type ) ) {
missing _nodes . add ( regex _to _url [ j ] . url ) ;
}
}
}
}
}
let unresolved _nodes = await getUnresolvedNodesInComponent ( ) ;
for ( let i in unresolved _nodes ) {
let node _type = unresolved _nodes [ i ] ;
const url = name _to _url [ node _type ] ;
if ( url )
missing _nodes . add ( url ) ;
}
return data . filter ( node => node . files . some ( file => missing _nodes . has ( file ) ) ) ;
}
async invalidateControl ( ) {
this . clear ( ) ;
// splash
while ( this . element . children . length ) {
this . element . removeChild ( this . element . children [ 0 ] ) ;
}
const msg = $el ( 'div' , { id : 'custom-message' } ,
[ $el ( 'br' ) ,
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.' ,
$el ( 'br' ) ,
'NOTE: Update only checks for extensions that have been fetched.' ,
$el ( 'br' ) ] ) ;
msg . style . height = '100px' ;
msg . style . verticalAlign = 'middle' ;
msg . style . color = "var(--fg-color)" ;
this . element . appendChild ( msg ) ;
// invalidate
this . data = ( await getCustomNodes ( ) ) . custom _nodes ;
if ( this . is _missing _node _mode )
this . data = await this . filter _missing _node ( this . data ) ;
this . element . removeChild ( msg ) ;
while ( this . element . children . length ) {
this . element . removeChild ( this . element . children [ 0 ] ) ;
}
this . createHeaderControls ( ) ;
await this . createGrid ( ) ;
this . apply _searchbox ( this . data ) ;
this . createBottomControls ( ) ;
}
updateMessage ( msg ) {
this . message _box . innerHTML = msg ;
}
invalidate _checks ( is _checked , install _state ) {
if ( is _checked ) {
for ( let i in this . grid _rows ) {
let data = this . grid _rows [ i ] . data ;
let checkbox = this . grid _rows [ i ] . checkbox ;
let buttons = this . grid _rows [ i ] . buttons ;
checkbox . disabled = data . installed != install _state ;
if ( checkbox . disabled ) {
for ( let j in buttons ) {
buttons [ j ] . style . display = 'none' ;
}
}
else {
for ( let j in buttons ) {
buttons [ j ] . style . display = null ;
}
}
}
this . checkbox _all . disabled = false ;
}
else {
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
if ( checkbox . check )
return ; // do nothing
}
// every checkbox is unchecked -> enable all checkbox
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
let buttons = this . grid _rows [ i ] . buttons ;
checkbox . disabled = false ;
for ( let j in buttons ) {
buttons [ j ] . style . display = null ;
}
}
this . checkbox _all . checked = false ;
this . checkbox _all . disabled = true ;
}
}
check _all ( is _checked ) {
if ( is _checked ) {
// lookup first checked item's state
let check _state = null ;
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
if ( checkbox . checked ) {
check _state = this . grid _rows [ i ] . data . installed ;
}
}
if ( check _state == null )
return ;
// check only same state items
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
if ( this . grid _rows [ i ] . data . installed == check _state )
checkbox . checked = true ;
}
}
else {
// uncheck all
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
let buttons = this . grid _rows [ i ] . buttons ;
checkbox . checked = false ;
checkbox . disabled = false ;
for ( let j in buttons ) {
buttons [ j ] . style . display = null ;
}
}
this . checkbox _all . disabled = true ;
}
}
async createGrid ( ) {
var grid = document . createElement ( 'table' ) ;
grid . setAttribute ( 'id' , 'custom-nodes-grid' ) ;
this . grid _rows = { } ;
let self = this ;
var thead = document . createElement ( 'thead' ) ;
var tbody = document . createElement ( 'tbody' ) ;
var headerRow = document . createElement ( 'tr' ) ;
thead . style . position = "sticky" ;
thead . style . top = "0px" ;
thead . style . borderCollapse = "collapse" ;
thead . style . tableLayout = "fixed" ;
var header0 = document . createElement ( 'th' ) ;
header0 . style . width = "20px" ;
this . checkbox _all = $el ( "input" , { type : 'checkbox' , id : 'check_all' } , [ ] ) ;
header0 . appendChild ( this . checkbox _all ) ;
this . checkbox _all . checked = false ;
this . checkbox _all . disabled = true ;
this . checkbox _all . addEventListener ( 'change' , function ( ) { self . check _all . call ( self , self . checkbox _all . checked ) ; } ) ;
var header1 = document . createElement ( 'th' ) ;
header1 . innerHTML = ' ID ' ;
header1 . style . width = "20px" ;
var header2 = document . createElement ( 'th' ) ;
header2 . innerHTML = 'Author' ;
header2 . style . width = "150px" ;
var header3 = document . createElement ( 'th' ) ;
header3 . innerHTML = 'Name' ;
header3 . style . width = "20%" ;
var header4 = document . createElement ( 'th' ) ;
header4 . innerHTML = 'Description' ;
header4 . style . width = "60%" ;
// header4.classList.add('expandable-column');
var header5 = document . createElement ( 'th' ) ;
header5 . innerHTML = 'Install' ;
header5 . style . width = "130px" ;
header0 . style . position = "sticky" ;
header0 . style . top = "0px" ;
header1 . style . position = "sticky" ;
header1 . style . top = "0px" ;
header2 . style . position = "sticky" ;
header2 . style . top = "0px" ;
header3 . style . position = "sticky" ;
header3 . style . top = "0px" ;
header4 . style . position = "sticky" ;
header4 . style . top = "0px" ;
header5 . style . position = "sticky" ;
header5 . style . top = "0px" ;
thead . appendChild ( headerRow ) ;
headerRow . appendChild ( header0 ) ;
headerRow . appendChild ( header1 ) ;
headerRow . appendChild ( header2 ) ;
headerRow . appendChild ( header3 ) ;
headerRow . appendChild ( header4 ) ;
headerRow . appendChild ( header5 ) ;
headerRow . style . backgroundColor = "Black" ;
headerRow . style . color = "White" ;
headerRow . style . textAlign = "center" ;
headerRow . style . width = "100%" ;
headerRow . style . padding = "0" ;
grid . appendChild ( thead ) ;
grid . appendChild ( tbody ) ;
if ( this . data )
for ( var i = 0 ; i < this . data . length ; i ++ ) {
const data = this . data [ i ] ;
let dataRow = document . createElement ( 'tr' ) ;
let data0 = document . createElement ( 'td' ) ;
let checkbox = $el ( "input" , { type : 'checkbox' , id : ` check_ ${ i } ` } , [ ] ) ;
data0 . appendChild ( checkbox ) ;
checkbox . checked = false ;
checkbox . addEventListener ( 'change' , function ( ) { self . invalidate _checks . call ( self , checkbox . checked , data . installed ) ; } ) ;
var data1 = document . createElement ( 'td' ) ;
data1 . style . textAlign = "center" ;
data1 . innerHTML = i + 1 ;
var data2 = document . createElement ( 'td' ) ;
data2 . style . maxWidth = "100px" ;
data2 . className = "cm-node-author"
data2 . textContent = ` ${ data . author } ` ;
data2 . style . whiteSpace = "nowrap" ;
data2 . style . overflow = "hidden" ;
data2 . style . textOverflow = "ellipsis" ;
var data3 = document . createElement ( 'td' ) ;
data3 . style . maxWidth = "200px" ;
data3 . style . wordWrap = "break-word" ;
data3 . className = "cm-node-name"
data3 . innerHTML = ` <a href= ${ data . reference } target="_blank"><font color="skyblue"><b> ${ data . title } </b></font></a> ` ;
var data4 = document . createElement ( 'td' ) ;
data4 . innerHTML = data . description ;
data4 . className = "cm-node-desc"
var data5 = document . createElement ( 'td' ) ;
data5 . style . textAlign = "center" ;
var installBtn = document . createElement ( 'button' ) ;
installBtn . className = "cm-btn-install" ;
var installBtn2 = null ;
var installBtn3 = null ;
this . install _buttons . push ( installBtn ) ;
switch ( data . installed ) {
case 'Disabled' :
installBtn3 = document . createElement ( 'button' ) ;
installBtn3 . innerHTML = 'Enable' ;
installBtn3 . className = "cm-btn-enable" ;
installBtn3 . style . backgroundColor = 'blue' ;
installBtn3 . style . color = 'white' ;
this . install _buttons . push ( installBtn3 ) ;
installBtn . innerHTML = 'Uninstall' ;
installBtn . style . backgroundColor = 'red' ;
break ;
case 'Update' :
installBtn2 = document . createElement ( 'button' ) ;
installBtn2 . innerHTML = 'Update' ;
installBtn2 . className = "cm-btn-update" ;
installBtn2 . style . backgroundColor = 'blue' ;
installBtn2 . style . color = 'white' ;
this . install _buttons . push ( installBtn2 ) ;
installBtn3 = document . createElement ( 'button' ) ;
installBtn3 . innerHTML = 'Disable' ;
installBtn3 . className = "cm-btn-disable" ;
installBtn3 . style . backgroundColor = 'MediumSlateBlue' ;
installBtn3 . style . color = 'white' ;
this . install _buttons . push ( installBtn3 ) ;
installBtn . innerHTML = 'Uninstall' ;
installBtn . style . backgroundColor = 'red' ;
break ;
case 'True' :
installBtn3 = document . createElement ( 'button' ) ;
installBtn3 . innerHTML = 'Disable' ;
installBtn3 . className = "cm-btn-disable" ;
installBtn3 . style . backgroundColor = 'MediumSlateBlue' ;
installBtn3 . style . color = 'white' ;
this . install _buttons . push ( installBtn3 ) ;
installBtn . innerHTML = 'Uninstall' ;
installBtn . style . backgroundColor = 'red' ;
break ;
case 'False' :
installBtn . innerHTML = 'Install' ;
installBtn . style . backgroundColor = 'black' ;
installBtn . style . color = 'white' ;
break ;
default :
installBtn . innerHTML = 'Try Install' ;
installBtn . style . backgroundColor = 'Gray' ;
installBtn . style . color = 'white' ;
}
let j = i ;
if ( installBtn2 != null ) {
installBtn2 . style . width = "120px" ;
installBtn2 . addEventListener ( 'click' , function ( ) {
install _checked _custom _node ( self . grid _rows , j , CustomNodesInstaller . instance , 'update' ) ;
} ) ;
data5 . appendChild ( installBtn2 ) ;
}
if ( installBtn3 != null ) {
installBtn3 . style . width = "120px" ;
installBtn3 . addEventListener ( 'click' , function ( ) {
install _checked _custom _node ( self . grid _rows , j , CustomNodesInstaller . instance , 'toggle_active' ) ;
} ) ;
data5 . appendChild ( installBtn3 ) ;
}
installBtn . style . width = "120px" ;
installBtn . addEventListener ( 'click' , function ( ) {
if ( this . innerHTML == 'Uninstall' ) {
if ( confirm ( ` Are you sure uninstall ${ data . title } ? ` ) ) {
install _checked _custom _node ( self . grid _rows , j , CustomNodesInstaller . instance , 'uninstall' ) ;
}
}
else {
install _checked _custom _node ( self . grid _rows , j , CustomNodesInstaller . instance , 'install' ) ;
}
} ) ;
data5 . appendChild ( installBtn ) ;
dataRow . style . backgroundColor = "var(--bg-color)" ;
dataRow . style . color = "var(--fg-color)" ;
dataRow . style . textAlign = "left" ;
dataRow . appendChild ( data0 ) ;
dataRow . appendChild ( data1 ) ;
dataRow . appendChild ( data2 ) ;
dataRow . appendChild ( data3 ) ;
dataRow . appendChild ( data4 ) ;
dataRow . appendChild ( data5 ) ;
tbody . appendChild ( dataRow ) ;
let buttons = [ ] ;
if ( installBtn ) {
buttons . push ( installBtn ) ;
}
if ( installBtn2 ) {
buttons . push ( installBtn2 ) ;
}
if ( installBtn3 ) {
buttons . push ( installBtn3 ) ;
}
this . grid _rows [ i ] = { data : data , buttons : buttons , checkbox : checkbox , control : dataRow } ;
}
const panel = document . createElement ( 'div' ) ;
panel . style . width = "100%" ;
panel . appendChild ( grid ) ;
function handleResize ( ) {
const parentHeight = self . element . clientHeight ;
const gridHeight = parentHeight - 200 ;
grid . style . height = gridHeight + "px" ;
}
window . addEventListener ( "resize" , handleResize ) ;
grid . style . position = "relative" ;
grid . style . display = "inline-block" ;
grid . style . width = "100%" ;
grid . style . height = "100%" ;
grid . style . overflowY = "scroll" ;
this . element . style . height = "85%" ;
this . element . style . width = "80%" ;
this . element . appendChild ( panel ) ;
handleResize ( ) ;
}
createFilterCombo ( ) {
let combo = document . createElement ( "select" ) ;
combo . style . cssFloat = "left" ;
combo . style . fontSize = "14px" ;
combo . style . padding = "4px" ;
combo . style . background = "black" ;
combo . style . marginLeft = "2px" ;
combo . style . width = "199px" ;
combo . id = ` combo-manger-filter ` ;
combo . style . borderRadius = "15px" ;
let items =
[
{ value : '*' , text : 'Filter: all' } ,
{ value : 'Disabled' , text : 'Filter: disabled' } ,
{ value : 'Update' , text : 'Filter: update' } ,
{ value : 'True' , text : 'Filter: installed' } ,
{ value : 'False' , text : 'Filter: not-installed' } ,
] ;
items . forEach ( item => {
const option = document . createElement ( "option" ) ;
option . value = item . value ;
option . text = item . text ;
combo . appendChild ( option ) ;
} ) ;
let self = this ;
combo . addEventListener ( 'change' , function ( event ) {
self . filter = event . target . value ;
self . apply _searchbox ( ) ;
} ) ;
if ( self . filter ) {
combo . value = self . filter ;
}
return combo ;
}
createHeaderControls ( ) {
let self = this ;
this . search _box = $el ( 'input' , { type : 'text' , id : 'manager-customnode-search-box' , placeholder : 'input search keyword' , value : this . search _keyword } , [ ] ) ;
this . search _box . style . height = "25px" ;
this . search _box . onkeydown = ( event ) => {
if ( event . key === 'Enter' ) {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
}
if ( event . key === 'Escape' ) {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
}
} ;
let search _button = document . createElement ( "button" ) ;
search _button . innerHTML = "Search" ;
search _button . onclick = ( ) => {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
} ;
search _button . style . display = "inline-block" ;
let filter _control = this . createFilterCombo ( ) ;
filter _control . style . display = "inline-block" ;
let cell = $el ( 'td' , { width : '100%' } , [ filter _control , this . search _box , ' ' , search _button ] ) ;
let search _control = $el ( 'table' , { width : '100%' } ,
[
$el ( 'tr' , { } , [ cell ] )
]
) ;
cell . style . textAlign = "right" ;
this . element . appendChild ( search _control ) ;
}
async createBottomControls ( ) {
let close _button = document . createElement ( "button" ) ;
close _button . innerHTML = "Close" ;
close _button . onclick = ( ) => { this . close ( ) ; }
close _button . style . display = "inline-block" ;
this . message _box = $el ( 'div' , { id : 'custom-installer-message' } , [ $el ( 'br' ) , '' ] ) ;
this . message _box . style . height = '60px' ;
this . message _box . style . verticalAlign = 'middle' ;
this . element . appendChild ( this . message _box ) ;
this . element . appendChild ( close _button ) ;
}
async show ( is _missing _node _mode ) {
this . is _missing _node _mode = is _missing _node _mode ;
try {
this . invalidateControl ( ) ;
this . element . style . display = "block" ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Failed to get custom node list. / ${ exception } ` ) ;
}
}
}
// -----
class AlternativesInstaller extends ComfyDialog {
static instance = null ;
install _buttons = [ ] ;
message _box = null ;
data = null ;
clear ( ) {
this . install _buttons = [ ] ;
this . message _box = null ;
this . data = null ;
}
constructor ( ) {
super ( ) ;
this . search _keyword = '' ;
this . element = $el ( "div.comfy-modal" , { parent : document . body } , [ ] ) ;
}
startInstall ( target ) {
const self = AlternativesInstaller . instance ;
self . updateMessage ( ` <BR><font color="green">Installing ' ${ target . title } '</font> ` ) ;
}
disableButtons ( ) {
for ( let i in this . install _buttons ) {
this . install _buttons [ i ] . disabled = true ;
this . install _buttons [ i ] . style . backgroundColor = 'gray' ;
}
}
apply _searchbox ( data ) {
let keyword = this . search _box . value . toLowerCase ( ) ;
for ( let i in this . grid _rows ) {
let data1 = this . grid _rows [ i ] . data ;
let data2 = data1 . custom _node ;
if ( ! data2 )
continue ;
let content = data1 . tags . toLowerCase ( ) + data1 . description . toLowerCase ( ) + data2 . author . toLowerCase ( ) + data2 . description . toLowerCase ( ) + data2 . title . toLowerCase ( ) ;
if ( this . filter && this . filter != '*' ) {
if ( this . filter != data2 . installed ) {
this . grid _rows [ i ] . control . style . display = 'none' ;
continue ;
}
}
if ( keyword == "" )
this . grid _rows [ i ] . control . style . display = null ;
else if ( content . includes ( keyword ) ) {
this . grid _rows [ i ] . control . style . display = null ;
}
else {
this . grid _rows [ i ] . control . style . display = 'none' ;
}
}
}
async invalidateControl ( ) {
this . clear ( ) ;
// splash
while ( this . element . children . length ) {
this . element . removeChild ( this . element . children [ 0 ] ) ;
}
const msg = $el ( 'div' , { id : 'custom-message' } ,
[ $el ( 'br' ) ,
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.' ,
$el ( 'br' ) ,
'NOTE: Update only checks for extensions that have been fetched.' ,
$el ( 'br' ) ] ) ;
msg . style . height = '100px' ;
msg . style . verticalAlign = 'middle' ;
this . element . appendChild ( msg ) ;
// invalidate
this . data = ( await getAlterList ( ) ) . items ;
this . element . removeChild ( msg ) ;
while ( this . element . children . length ) {
this . element . removeChild ( this . element . children [ 0 ] ) ;
}
this . createHeaderControls ( ) ;
await this . createGrid ( ) ;
this . apply _searchbox ( this . data ) ;
this . createBottomControls ( ) ;
}
updateMessage ( msg ) {
this . message _box . innerHTML = msg ;
}
invalidate _checks ( is _checked , install _state ) {
if ( is _checked ) {
for ( let i in this . grid _rows ) {
let data = this . grid _rows [ i ] . data ;
let checkbox = this . grid _rows [ i ] . checkbox ;
let buttons = this . grid _rows [ i ] . buttons ;
checkbox . disabled = data . custom _node . installed != install _state ;
if ( checkbox . disabled ) {
for ( let j in buttons ) {
buttons [ j ] . style . display = 'none' ;
}
}
else {
for ( let j in buttons ) {
buttons [ j ] . style . display = null ;
}
}
}
this . checkbox _all . disabled = false ;
}
else {
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
if ( checkbox . check )
return ; // do nothing
}
// every checkbox is unchecked -> enable all checkbox
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
let buttons = this . grid _rows [ i ] . buttons ;
checkbox . disabled = false ;
for ( let j in buttons ) {
buttons [ j ] . style . display = null ;
}
}
this . checkbox _all . checked = false ;
this . checkbox _all . disabled = true ;
}
}
check _all ( is _checked ) {
if ( is _checked ) {
// lookup first checked item's state
let check _state = null ;
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
if ( checkbox . checked ) {
check _state = this . grid _rows [ i ] . data . custom _node . installed ;
}
}
if ( check _state == null )
return ;
// check only same state items
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
if ( this . grid _rows [ i ] . data . custom _node . installed == check _state )
checkbox . checked = true ;
}
}
else {
// uncheck all
for ( let i in this . grid _rows ) {
let checkbox = this . grid _rows [ i ] . checkbox ;
let buttons = this . grid _rows [ i ] . buttons ;
checkbox . checked = false ;
checkbox . disabled = false ;
for ( let j in buttons ) {
buttons [ j ] . style . display = null ;
}
}
this . checkbox _all . disabled = true ;
}
}
async createGrid ( ) {
var grid = document . createElement ( 'table' ) ;
grid . setAttribute ( 'id' , 'alternatives-grid' ) ;
this . grid _rows = { } ;
let self = this ;
var thead = document . createElement ( 'thead' ) ;
var tbody = document . createElement ( 'tbody' ) ;
var headerRow = document . createElement ( 'tr' ) ;
thead . style . position = "sticky" ;
thead . style . top = "0px" ;
thead . style . borderCollapse = "collapse" ;
thead . style . tableLayout = "fixed" ;
var header0 = document . createElement ( 'th' ) ;
header0 . style . width = "20px" ;
this . checkbox _all = $el ( "input" , { type : 'checkbox' , id : 'check_all' } , [ ] ) ;
header0 . appendChild ( this . checkbox _all ) ;
this . checkbox _all . checked = false ;
this . checkbox _all . disabled = true ;
this . checkbox _all . addEventListener ( 'change' , function ( ) { self . check _all . call ( self , self . checkbox _all . checked ) ; } ) ;
var header1 = document . createElement ( 'th' ) ;
header1 . innerHTML = ' ID ' ;
header1 . style . width = "20px" ;
var header2 = document . createElement ( 'th' ) ;
header2 . innerHTML = 'Tags' ;
header2 . style . width = "10%" ;
var header3 = document . createElement ( 'th' ) ;
header3 . innerHTML = 'Author' ;
header3 . style . width = "150px" ;
var header4 = document . createElement ( 'th' ) ;
header4 . innerHTML = 'Title' ;
header4 . style . width = "20%" ;
var header5 = document . createElement ( 'th' ) ;
header5 . innerHTML = 'Description' ;
header5 . style . width = "50%" ;
var header6 = document . createElement ( 'th' ) ;
header6 . innerHTML = 'Install' ;
header6 . style . width = "130px" ;
header1 . style . position = "sticky" ;
header1 . style . top = "0px" ;
header2 . style . position = "sticky" ;
header2 . style . top = "0px" ;
header3 . style . position = "sticky" ;
header3 . style . top = "0px" ;
header4 . style . position = "sticky" ;
header4 . style . top = "0px" ;
header5 . style . position = "sticky" ;
header5 . style . top = "0px" ;
thead . appendChild ( headerRow ) ;
headerRow . appendChild ( header0 ) ;
headerRow . appendChild ( header1 ) ;
headerRow . appendChild ( header2 ) ;
headerRow . appendChild ( header3 ) ;
headerRow . appendChild ( header4 ) ;
headerRow . appendChild ( header5 ) ;
headerRow . appendChild ( header6 ) ;
headerRow . style . backgroundColor = "Black" ;
headerRow . style . color = "White" ;
headerRow . style . textAlign = "center" ;
headerRow . style . width = "100%" ;
headerRow . style . padding = "0" ;
grid . appendChild ( thead ) ;
grid . appendChild ( tbody ) ;
if ( this . data )
for ( var i = 0 ; i < this . data . length ; i ++ ) {
const data = this . data [ i ] ;
var dataRow = document . createElement ( 'tr' ) ;
let data0 = document . createElement ( 'td' ) ;
let checkbox = $el ( "input" , { type : 'checkbox' , id : ` check_ ${ i } ` } , [ ] ) ;
data0 . appendChild ( checkbox ) ;
checkbox . checked = false ;
checkbox . addEventListener ( 'change' , function ( ) { self . invalidate _checks . call ( self , checkbox . checked , data . custom _node ? . installed ) ; } ) ;
var data1 = document . createElement ( 'td' ) ;
data1 . style . textAlign = "center" ;
data1 . innerHTML = i + 1 ;
var data2 = document . createElement ( 'td' ) ;
data2 . innerHTML = ` ${ data . tags } ` ;
var data3 = document . createElement ( 'td' ) ;
var data4 = document . createElement ( 'td' ) ;
if ( data . custom _node ) {
data3 . innerHTML = ` ${ data . custom _node . author } ` ;
data4 . innerHTML = ` <a href= ${ data . custom _node . reference } target="_blank"><font color="skyblue"><b> ${ data . custom _node . title } </b></font></a> ` ;
}
else {
data3 . innerHTML = ` Unknown ` ;
data4 . innerHTML = ` Unknown ` ;
}
var data5 = document . createElement ( 'td' ) ;
data5 . innerHTML = data . description ;
var data6 = document . createElement ( 'td' ) ;
data6 . style . textAlign = "center" ;
var installBtn = document . createElement ( 'button' ) ;
var installBtn2 = null ;
var installBtn3 = null ;
if ( data . custom _node ) {
this . install _buttons . push ( installBtn ) ;
switch ( data . custom _node . installed ) {
case 'Disabled' :
installBtn3 = document . createElement ( 'button' ) ;
installBtn3 . innerHTML = 'Enable' ;
installBtn3 . style . backgroundColor = 'blue' ;
installBtn3 . style . color = 'white' ;
this . install _buttons . push ( installBtn3 ) ;
installBtn . innerHTML = 'Uninstall' ;
installBtn . style . backgroundColor = 'red' ;
installBtn . style . color = 'white' ;
break ;
case 'Update' :
installBtn2 = document . createElement ( 'button' ) ;
installBtn2 . innerHTML = 'Update' ;
installBtn2 . style . backgroundColor = 'blue' ;
installBtn2 . style . color = 'white' ;
this . install _buttons . push ( installBtn2 ) ;
installBtn3 = document . createElement ( 'button' ) ;
installBtn3 . innerHTML = 'Disable' ;
installBtn3 . style . backgroundColor = 'MediumSlateBlue' ;
installBtn3 . style . color = 'white' ;
this . install _buttons . push ( installBtn3 ) ;
installBtn . innerHTML = 'Uninstall' ;
installBtn . style . backgroundColor = 'red' ;
installBtn . style . color = 'white' ;
break ;
case 'True' :
installBtn3 = document . createElement ( 'button' ) ;
installBtn3 . innerHTML = 'Disable' ;
installBtn3 . style . backgroundColor = 'MediumSlateBlue' ;
installBtn3 . style . color = 'white' ;
this . install _buttons . push ( installBtn3 ) ;
installBtn . innerHTML = 'Uninstall' ;
installBtn . style . backgroundColor = 'red' ;
installBtn . style . color = 'white' ;
break ;
case 'False' :
installBtn . innerHTML = 'Install' ;
installBtn . style . backgroundColor = 'black' ;
installBtn . style . color = 'white' ;
break ;
default :
installBtn . innerHTML = 'Try Install' ;
installBtn . style . backgroundColor = 'Gray' ;
installBtn . style . color = 'white' ;
}
let j = i ;
if ( installBtn2 != null ) {
installBtn2 . style . width = "120px" ;
installBtn2 . addEventListener ( 'click' , function ( ) {
install _checked _custom _node ( self . grid _rows , j , AlternativesInstaller . instance , 'update' ) ;
} ) ;
data6 . appendChild ( installBtn2 ) ;
}
if ( installBtn3 != null ) {
installBtn3 . style . width = "120px" ;
installBtn3 . addEventListener ( 'click' , function ( ) {
install _checked _custom _node ( self . grid _rows , j , AlternativesInstaller . instance , 'toggle_active' ) ;
} ) ;
data6 . appendChild ( installBtn3 ) ;
}
installBtn . style . width = "120px" ;
installBtn . addEventListener ( 'click' , function ( ) {
if ( this . innerHTML == 'Uninstall' ) {
if ( confirm ( ` Are you sure uninstall ${ data . title } ? ` ) ) {
install _checked _custom _node ( self . grid _rows , j , AlternativesInstaller . instance , 'uninstall' ) ;
}
}
else {
install _checked _custom _node ( self . grid _rows , j , AlternativesInstaller . instance , 'install' ) ;
}
} ) ;
data6 . appendChild ( installBtn ) ;
}
dataRow . style . backgroundColor = "var(--bg-color)" ;
dataRow . style . color = "var(--fg-color)" ;
dataRow . style . textAlign = "left" ;
dataRow . appendChild ( data0 ) ;
dataRow . appendChild ( data1 ) ;
dataRow . appendChild ( data2 ) ;
dataRow . appendChild ( data3 ) ;
dataRow . appendChild ( data4 ) ;
dataRow . appendChild ( data5 ) ;
dataRow . appendChild ( data6 ) ;
tbody . appendChild ( dataRow ) ;
let buttons = [ ] ;
if ( installBtn ) {
buttons . push ( installBtn ) ;
}
if ( installBtn2 ) {
buttons . push ( installBtn2 ) ;
}
if ( installBtn3 ) {
buttons . push ( installBtn3 ) ;
}
this . grid _rows [ i ] = { data : data , buttons : buttons , checkbox : checkbox , control : dataRow } ;
}
const panel = document . createElement ( 'div' ) ;
panel . style . width = "100%" ;
panel . appendChild ( grid ) ;
function handleResize ( ) {
const parentHeight = self . element . clientHeight ;
const gridHeight = parentHeight - 200 ;
grid . style . height = gridHeight + "px" ;
}
window . addEventListener ( "resize" , handleResize ) ;
grid . style . position = "relative" ;
grid . style . display = "inline-block" ;
grid . style . width = "100%" ;
grid . style . height = "100%" ;
grid . style . overflowY = "scroll" ;
this . element . style . height = "85%" ;
this . element . style . width = "80%" ;
this . element . appendChild ( panel ) ;
handleResize ( ) ;
}
createFilterCombo ( ) {
let combo = document . createElement ( "select" ) ;
combo . style . cssFloat = "left" ;
combo . style . fontSize = "14px" ;
combo . style . padding = "4px" ;
combo . style . background = "black" ;
combo . style . marginLeft = "2px" ;
combo . style . width = "199px" ;
combo . id = ` combo-manger-filter ` ;
combo . style . borderRadius = "15px" ;
let items =
[
{ value : '*' , text : 'Filter: all' } ,
{ value : 'Disabled' , text : 'Filter: disabled' } ,
{ value : 'Update' , text : 'Filter: update' } ,
{ value : 'True' , text : 'Filter: installed' } ,
{ value : 'False' , text : 'Filter: not-installed' } ,
] ;
items . forEach ( item => {
const option = document . createElement ( "option" ) ;
option . value = item . value ;
option . text = item . text ;
combo . appendChild ( option ) ;
} ) ;
let self = this ;
combo . addEventListener ( 'change' , function ( event ) {
self . filter = event . target . value ;
self . apply _searchbox ( ) ;
} ) ;
if ( self . filter ) {
combo . value = self . filter ;
}
return combo ;
}
createHeaderControls ( ) {
let self = this ;
this . search _box = $el ( 'input' , { type : 'text' , id : 'manager-alternode-search-box' , placeholder : 'input search keyword' , value : this . search _keyword } , [ ] ) ;
this . search _box . style . height = "25px" ;
this . search _box . onkeydown = ( event ) => {
if ( event . key === 'Enter' ) {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
}
if ( event . key === 'Escape' ) {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
}
} ;
let search _button = document . createElement ( "button" ) ;
search _button . innerHTML = "Search" ;
search _button . onclick = ( ) => {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
} ;
search _button . style . display = "inline-block" ;
let filter _control = this . createFilterCombo ( ) ;
filter _control . style . display = "inline-block" ;
let cell = $el ( 'td' , { width : '100%' } , [ filter _control , this . search _box , ' ' , search _button ] ) ;
let search _control = $el ( 'table' , { width : '100%' } ,
[
$el ( 'tr' , { } , [ cell ] )
]
) ;
cell . style . textAlign = "right" ;
this . element . appendChild ( search _control ) ;
}
async createBottomControls ( ) {
var close _button = document . createElement ( "button" ) ;
close _button . innerHTML = "Close" ;
close _button . onclick = ( ) => { this . close ( ) ; }
close _button . style . display = "inline-block" ;
this . message _box = $el ( 'div' , { id : 'alternatives-installer-message' } , [ $el ( 'br' ) , '' ] ) ;
this . message _box . style . height = '60px' ;
this . message _box . style . verticalAlign = 'middle' ;
this . element . appendChild ( this . message _box ) ;
this . element . appendChild ( close _button ) ;
}
async show ( ) {
try {
this . invalidateControl ( ) ;
this . element . style . display = "block" ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Failed to get alternatives list. / ${ exception } ` ) ;
console . error ( exception ) ;
}
}
}
// -----------
class ModelInstaller extends ComfyDialog {
static instance = null ;
install _buttons = [ ] ;
message _box = null ;
data = null ;
clear ( ) {
this . install _buttons = [ ] ;
this . message _box = null ;
this . data = null ;
}
constructor ( ) {
super ( ) ;
this . search _keyword = '' ;
this . element = $el ( "div.comfy-modal" , { parent : document . body } , [ ] ) ;
}
createControls ( ) {
return [
$el ( "button" , {
type : "button" ,
textContent : "Close" ,
onclick : ( ) => { this . close ( ) ; }
} )
] ;
}
startInstall ( target ) {
const self = ModelInstaller . instance ;
self . updateMessage ( ` <BR><font color="green">Installing ' ${ target . name } '</font> ` ) ;
for ( let i in self . install _buttons ) {
self . install _buttons [ i ] . disabled = true ;
self . install _buttons [ i ] . style . backgroundColor = 'gray' ;
}
}
apply _searchbox ( data ) {
let keyword = this . search _box . value . toLowerCase ( ) ;
for ( let i in this . grid _rows ) {
let data = this . grid _rows [ i ] . data ;
let content = data . name . toLowerCase ( ) + data . type . toLowerCase ( ) + data . base . toLowerCase ( ) + data . description . toLowerCase ( ) ;
if ( this . filter && this . filter != '*' ) {
if ( this . filter != data . installed ) {
this . grid _rows [ i ] . control . style . display = 'none' ;
continue ;
}
}
if ( keyword == "" )
this . grid _rows [ i ] . control . style . display = null ;
else if ( content . includes ( keyword ) ) {
this . grid _rows [ i ] . control . style . display = null ;
}
else {
this . grid _rows [ i ] . control . style . display = 'none' ;
}
}
}
async invalidateControl ( ) {
this . clear ( ) ;
this . data = ( await getModelList ( ) ) . models ;
while ( this . element . children . length ) {
this . element . removeChild ( this . element . children [ 0 ] ) ;
}
await this . createHeaderControls ( ) ;
if ( this . search _keyword ) {
this . search _box . value = this . search _keyword ;
}
await this . createGrid ( ) ;
await this . createBottomControls ( ) ;
this . apply _searchbox ( this . data ) ;
}
updateMessage ( msg ) {
this . message _box . innerHTML = msg ;
}
async createGrid ( models _json ) {
var grid = document . createElement ( 'table' ) ;
grid . setAttribute ( 'id' , 'external-models-grid' ) ;
var thead = document . createElement ( 'thead' ) ;
var tbody = document . createElement ( 'tbody' ) ;
var headerRow = document . createElement ( 'tr' ) ;
thead . style . position = "sticky" ;
thead . style . top = "0px" ;
thead . style . borderCollapse = "collapse" ;
thead . style . tableLayout = "fixed" ;
var header1 = document . createElement ( 'th' ) ;
header1 . innerHTML = ' ID ' ;
header1 . style . width = "20px" ;
var header2 = document . createElement ( 'th' ) ;
header2 . innerHTML = 'Type' ;
header2 . style . width = "100px" ;
var header3 = document . createElement ( 'th' ) ;
header3 . innerHTML = 'Base' ;
header3 . style . width = "100px" ;
var header4 = document . createElement ( 'th' ) ;
header4 . innerHTML = 'Name' ;
header4 . style . width = "30%" ;
var header5 = document . createElement ( 'th' ) ;
header5 . innerHTML = 'Filename' ;
header5 . style . width = "20%" ;
header5 . style . tableLayout = "fixed" ;
var header6 = document . createElement ( 'th' ) ;
header6 . innerHTML = 'Description' ;
header6 . style . width = "50%" ;
var header _down = document . createElement ( 'th' ) ;
header _down . innerHTML = 'Download' ;
header _down . style . width = "50px" ;
thead . appendChild ( headerRow ) ;
headerRow . appendChild ( header1 ) ;
headerRow . appendChild ( header2 ) ;
headerRow . appendChild ( header3 ) ;
headerRow . appendChild ( header4 ) ;
headerRow . appendChild ( header5 ) ;
headerRow . appendChild ( header6 ) ;
headerRow . appendChild ( header _down ) ;
headerRow . style . backgroundColor = "Black" ;
headerRow . style . color = "White" ;
headerRow . style . textAlign = "center" ;
headerRow . style . width = "100%" ;
headerRow . style . padding = "0" ;
grid . appendChild ( thead ) ;
grid . appendChild ( tbody ) ;
this . grid _rows = { } ;
if ( this . data )
for ( var i = 0 ; i < this . data . length ; i ++ ) {
const data = this . data [ i ] ;
var dataRow = document . createElement ( 'tr' ) ;
var data1 = document . createElement ( 'td' ) ;
data1 . style . textAlign = "center" ;
data1 . innerHTML = i + 1 ;
var data2 = document . createElement ( 'td' ) ;
data2 . innerHTML = ` ${ data . type } ` ;
var data3 = document . createElement ( 'td' ) ;
data3 . innerHTML = ` ${ data . base } ` ;
var data4 = document . createElement ( 'td' ) ;
data4 . className = "cm-node-name" ;
data4 . innerHTML = ` <a href= ${ data . reference } target="_blank"><font color="skyblue"><b> ${ data . name } </b></font></a> ` ;
var data5 = document . createElement ( 'td' ) ;
data5 . className = "cm-node-filename" ;
data5 . innerHTML = ` ${ data . filename } ` ;
data5 . style . wordBreak = "break-all" ;
var data6 = document . createElement ( 'td' ) ;
data6 . className = "cm-node-desc" ;
data6 . innerHTML = data . description ;
data6 . style . wordBreak = "break-all" ;
var data _install = document . createElement ( 'td' ) ;
var installBtn = document . createElement ( 'button' ) ;
data _install . style . textAlign = "center" ;
installBtn . innerHTML = 'Install' ;
this . install _buttons . push ( installBtn ) ;
switch ( data . installed ) {
case 'True' :
installBtn . innerHTML = 'Installed' ;
installBtn . style . backgroundColor = 'green' ;
installBtn . style . color = 'white' ;
installBtn . disabled = true ;
break ;
default :
installBtn . innerHTML = 'Install' ;
installBtn . style . backgroundColor = 'black' ;
installBtn . style . color = 'white' ;
break ;
}
installBtn . style . width = "100px" ;
installBtn . addEventListener ( 'click' , function ( ) {
install _model ( data ) ;
} ) ;
data _install . appendChild ( installBtn ) ;
dataRow . style . backgroundColor = "var(--bg-color)" ;
dataRow . style . color = "var(--fg-color)" ;
dataRow . style . textAlign = "left" ;
dataRow . appendChild ( data1 ) ;
dataRow . appendChild ( data2 ) ;
dataRow . appendChild ( data3 ) ;
dataRow . appendChild ( data4 ) ;
dataRow . appendChild ( data5 ) ;
dataRow . appendChild ( data6 ) ;
dataRow . appendChild ( data _install ) ;
tbody . appendChild ( dataRow ) ;
this . grid _rows [ i ] = { data : data , control : dataRow } ;
}
let self = this ;
const panel = document . createElement ( 'div' ) ;
panel . style . width = "100%" ;
panel . appendChild ( grid ) ;
function handleResize ( ) {
const parentHeight = self . element . clientHeight ;
const gridHeight = parentHeight - 200 ;
grid . style . height = gridHeight + "px" ;
}
window . addEventListener ( "resize" , handleResize ) ;
grid . style . position = "relative" ;
grid . style . display = "inline-block" ;
grid . style . width = "100%" ;
grid . style . height = "100%" ;
grid . style . overflowY = "scroll" ;
this . element . style . height = "85%" ;
this . element . style . width = "80%" ;
this . element . appendChild ( panel ) ;
handleResize ( ) ;
}
createFilterCombo ( ) {
let combo = document . createElement ( "select" ) ;
combo . style . cssFloat = "left" ;
combo . style . fontSize = "14px" ;
combo . style . padding = "4px" ;
combo . style . background = "black" ;
combo . style . marginLeft = "2px" ;
combo . style . width = "199px" ;
combo . id = ` combo-manger-filter ` ;
combo . style . borderRadius = "15px" ;
let items =
[
{ value : '*' , text : 'Filter: all' } ,
{ value : 'True' , text : 'Filter: installed' } ,
{ value : 'False' , text : 'Filter: not-installed' } ,
] ;
items . forEach ( item => {
const option = document . createElement ( "option" ) ;
option . value = item . value ;
option . text = item . text ;
combo . appendChild ( option ) ;
} ) ;
let self = this ;
combo . addEventListener ( 'change' , function ( event ) {
self . filter = event . target . value ;
self . apply _searchbox ( ) ;
} ) ;
return combo ;
}
createHeaderControls ( ) {
let self = this ;
this . search _box = $el ( 'input' , { type : 'text' , id : 'manager-model-search-box' , placeholder : 'input search keyword' , value : this . search _keyword } , [ ] ) ;
this . search _box . style . height = "25px" ;
this . search _box . onkeydown = ( event ) => {
if ( event . key === 'Enter' ) {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
}
if ( event . key === 'Escape' ) {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
}
} ;
let search _button = document . createElement ( "button" ) ;
search _button . innerHTML = "Search" ;
search _button . onclick = ( ) => {
self . search _keyword = self . search _box . value ;
self . apply _searchbox ( ) ;
} ;
search _button . style . display = "inline-block" ;
let filter _control = this . createFilterCombo ( ) ;
filter _control . style . display = "inline-block" ;
let cell = $el ( 'td' , { width : '100%' } , [ filter _control , this . search _box , ' ' , search _button ] ) ;
let search _control = $el ( 'table' , { width : '100%' } ,
[
$el ( 'tr' , { } , [ cell ] )
]
) ;
cell . style . textAlign = "right" ;
this . element . appendChild ( search _control ) ;
}
async createBottomControls ( ) {
var close _button = document . createElement ( "button" ) ;
close _button . innerHTML = "Close" ;
close _button . onclick = ( ) => { this . close ( ) ; }
close _button . style . display = "inline-block" ;
this . message _box = $el ( 'div' , { id : 'custom-download-message' } , [ $el ( 'br' ) , '' ] ) ;
this . message _box . style . height = '60px' ;
this . message _box . style . verticalAlign = 'middle' ;
this . element . appendChild ( this . message _box ) ;
this . element . appendChild ( close _button ) ;
}
async show ( ) {
try {
this . invalidateControl ( ) ;
this . element . style . display = "block" ;
}
catch ( exception ) {
app . ui . dialog . show ( ` Failed to get external model list. / ${ exception } ` ) ;
}
}
}
// -----------
class ManagerMenuDialog extends ComfyDialog {
static instance = null ;
local _mode _checkbox = null ;
createButtons ( ) {
this . local _mode _checkbox = $el ( "input" , { type : 'checkbox' , id : "use_local_db" } , [ ] )
const checkbox _text = $el ( "label" , { } , [ " Use local DB" ] )
checkbox _text . style . color = "var(--fg-color)" ;
checkbox _text . style . marginRight = "10px" ;
this . update _check _checkbox = $el ( "input" , { type : 'checkbox' , id : "skip_update_check" } , [ ] )
const uc _checkbox _text = $el ( "label" , { } , [ " Skip update check" ] )
uc _checkbox _text . style . color = "var(--fg-color)" ;
this . update _check _checkbox . checked = true ;
update _comfyui _button =
$el ( "button" , {
type : "button" ,
textContent : "Update ComfyUI" ,
onclick :
( ) => updateComfyUI ( )
} ) ;
fetch _updates _button =
$el ( "button" , {
type : "button" ,
textContent : "Fetch Updates" ,
onclick :
( ) => fetchUpdates ( this . update _check _checkbox )
} ) ;
update _all _button =
$el ( "button" , {
type : "button" ,
textContent : "Update All" ,
onclick :
( ) => updateAll ( this . update _check _checkbox )
} ) ;
// preview method
let preview _combo = document . createElement ( "select" ) ;
preview _combo . appendChild ( $el ( 'option' , { value : 'auto' , text : 'Preview method: Auto' } , [ ] ) ) ;
preview _combo . appendChild ( $el ( 'option' , { value : 'taesd' , text : 'Preview method: TAESD (slow)' } , [ ] ) ) ;
preview _combo . appendChild ( $el ( 'option' , { value : 'latent2rgb' , text : 'Preview method: Latent2RGB (fast)' } , [ ] ) ) ;
preview _combo . appendChild ( $el ( 'option' , { value : 'none' , text : 'Preview method: None (very fast)' } , [ ] ) ) ;
api . fetchApi ( '/manager/preview_method' )
. then ( response => response . text ( ) )
. then ( data => { preview _combo . value = data ; } )
preview _combo . addEventListener ( 'change' , function ( event ) {
api . fetchApi ( ` /manager/preview_method?value= ${ event . target . value } ` ) ;
} ) ;
// nickname
let badge _combo = document . createElement ( "select" ) ;
badge _combo . appendChild ( $el ( 'option' , { value : 'none' , text : 'Badge: None' } , [ ] ) ) ;
badge _combo . appendChild ( $el ( 'option' , { value : 'nick' , text : 'Badge: Nickname' } , [ ] ) ) ;
badge _combo . appendChild ( $el ( 'option' , { value : 'id_nick' , text : 'Badge: #ID Nickname' } , [ ] ) ) ;
api . fetchApi ( '/manager/badge_mode' )
. then ( response => response . text ( ) )
. then ( data => { badge _combo . value = data ; badge _mode = data ; } ) ;
badge _combo . addEventListener ( 'change' , function ( event ) {
api . fetchApi ( ` /manager/badge_mode?value= ${ event . target . value } ` ) ;
badge _mode = event . target . value ;
app . graph . setDirtyCanvas ( true ) ;
} ) ;
// channel
let channel _combo = document . createElement ( "select" ) ;
api . fetchApi ( '/manager/channel_url_list' )
. then ( response => response . json ( ) )
. then ( async data => {
try {
let urls = data . list ;
for ( let i in urls ) {
if ( urls [ i ] != '' ) {
let name _url = urls [ i ] . split ( '::' ) ;
channel _combo . appendChild ( $el ( 'option' , { value : name _url [ 0 ] , text : ` Channel: ${ name _url [ 0 ] } ` } , [ ] ) ) ;
}
}
channel _combo . addEventListener ( 'change' , function ( event ) {
api . fetchApi ( ` /manager/channel_url_list?value= ${ event . target . value } ` ) ;
} ) ;
channel _combo . value = data . selected ;
}
catch ( exception ) {
}
} ) ;
const res =
[
$el ( "tr.td" , { width : "100%" } , [ $el ( "font" , { size : 6 , color : "white" } , [ ` ComfyUI Manager Menu ` ] ) ] ) ,
$el ( "br" , { } , [ ] ) ,
$el ( "div" , { } , [ this . local _mode _checkbox , checkbox _text , this . update _check _checkbox , uc _checkbox _text ] ) ,
$el ( "br" , { } , [ ] ) ,
$el ( "button" , {
type : "button" ,
textContent : "Install Custom Nodes" ,
onclick :
( ) => {
if ( ! CustomNodesInstaller . instance )
CustomNodesInstaller . instance = new CustomNodesInstaller ( app ) ;
CustomNodesInstaller . instance . show ( false ) ;
}
} ) ,
$el ( "button" , {
type : "button" ,
textContent : "Install Missing Custom Nodes" ,
onclick :
( ) => {
if ( ! CustomNodesInstaller . instance )
CustomNodesInstaller . instance = new CustomNodesInstaller ( app ) ;
CustomNodesInstaller . instance . show ( true ) ;
}
} ) ,
$el ( "button" , {
type : "button" ,
textContent : "Install Models" ,
onclick :
( ) => {
if ( ! ModelInstaller . instance )
ModelInstaller . instance = new ModelInstaller ( app ) ;
ModelInstaller . instance . show ( ) ;
}
} ) ,
$el ( "br" , { } , [ ] ) ,
update _all _button ,
update _comfyui _button ,
fetch _updates _button ,
$el ( "br" , { } , [ ] ) ,
$el ( "button" , {
type : "button" ,
textContent : "Alternatives of A1111" ,
onclick :
( ) => {
if ( ! AlternativesInstaller . instance )
AlternativesInstaller . instance = new AlternativesInstaller ( app ) ;
AlternativesInstaller . instance . show ( ) ;
}
} ) ,
$el ( "br" , { } , [ ] ) ,
$el ( "button" , {
type : "button" ,
textContent : "ComfyUI Community Manual" ,
onclick : ( ) => { window . open ( "https://blenderneko.github.io/ComfyUI-docs/" , "comfyui-community-manual" ) ; }
} ) ,
$el ( "button" , {
type : "button" ,
textContent : "ComfyUI Workflow Gallery" ,
onclick : ( ) => { window . open ( "https://comfyworkflows.com/" , "comfyui-workflow-gallery" ) ; }
} ) ,
$el ( "button" , {
type : "button" ,
textContent : "ComfyUI Nodes Info" ,
onclick : ( ) => { window . open ( "https://ltdrdata.github.io/" , "comfyui-node-info" ) ; }
} ) ,
$el ( "br" , { } , [ ] ) ,
$el ( "hr" , { width : "100%" } , [ ] ) ,
preview _combo ,
badge _combo ,
channel _combo ,
$el ( "hr" , { width : "100%" } , [ ] ) ,
$el ( "br" , { } , [ ] ) ,
$el ( "button" , {
type : "button" ,
textContent : "Close" ,
onclick : ( ) => this . close ( )
} ) ,
$el ( "br" , { } , [ ] ) ,
] ;
res [ 0 ] . style . padding = "10px 10px 0 10px" ;
res [ 0 ] . style . backgroundColor = "black" ;
res [ 0 ] . style . textAlign = "center" ;
res [ 0 ] . style . height = "45px" ;
return res ;
}
constructor ( ) {
super ( ) ;
this . element = $el ( "div.comfy-modal" , { parent : document . body } ,
[ $el ( "div.comfy-modal-content" ,
[ ... this . createButtons ( ) ] ) ,
] ) ;
}
show ( ) {
this . element . style . display = "block" ;
}
}
class ShareDialog extends ComfyDialog {
static instance = null ;
createButtons ( ) {
this . is _nsfw _checkbox = $el ( "input" , { type : 'checkbox' , id : "is_nsfw" } , [ ] )
const is _nsfw _checkbox _text = $el ( "label" , { } , [ " Is this NSFW?" ] )
this . is _nsfw _checkbox . style . color = "var(--fg-color)" ;
this . is _nsfw _checkbox . checked = false ;
this . matrix _destination _checkbox = $el ( "input" , { type : 'checkbox' , id : "matrix_destination" } , [ ] )
const matrix _destination _checkbox _text = $el ( "label" , { } , [ " ComfyUI Matrix server" ] )
this . matrix _destination _checkbox . style . color = "var(--fg-color)" ;
this . matrix _destination _checkbox . checked = true ;
this . comfyworkflows _destination _checkbox = $el ( "input" , { type : 'checkbox' , id : "comfyworkflows_destination" } , [ ] )
const comfyworkflows _destination _checkbox _text = $el ( "label" , { } , [ " ComfyWorkflows.com" ] )
this . comfyworkflows _destination _checkbox . style . color = "var(--fg-color)" ;
this . comfyworkflows _destination _checkbox . checked = true ;
this . credits _input = $el ( "input" , {
type : "text" ,
placeholder : "This will be used to give you credits" ,
required : false ,
} , [ ] ) ;
this . title _input = $el ( "input" , {
type : "text" ,
// placeholder: "ex: Zombie animation - AnimateDiff",
required : false ,
} , [ ] ) ;
this . description _input = $el ( "textarea" , {
// placeholder: "ex: ",
required : false ,
} , [ ] ) ;
this . share _button = $el ( "button" , {
type : "button" ,
textContent : "Share" ,
} , [ ] ) ;
this . final _message = $el ( "div" , { } , [ ] ) ;
this . share _button . onclick = async ( ) => {
// alert("Title: " + this.title_input.value + "\nDescription: " + this.description_input.value + "\nCredits: " + this.credits_input.value + "\nNSFW: " + this.is_nsfw_checkbox.checked);
const prompt = await app . graphToPrompt ( ) ;
console . log ( prompt ) ;
const nodes = app . graph . _nodes ;
const destinations = [ ] ;
if ( this . matrix _destination _checkbox . checked ) {
destinations . push ( "matrix" ) ;
}
if ( this . comfyworkflows _destination _checkbox . checked ) {
destinations . push ( "comfyworkflows" ) ;
}
// if destinations includes matrix, make an api call to /manager/check_matrix to ensure that the user has configured their matrix settings
if ( destinations . includes ( "matrix" ) ) {
const response = await api . fetchApi ( ` /manager/check_matrix ` ) ;
if ( response . status != 200 ) {
alert ( "Please add your Matrix access token in a file called 'matrix_accesstoken' in the ComfyUI folder. To get your Matrix access token, go to https://app.element.io/, click on your profile, click on 'All settings', click on 'Help & About', and copy 'Access Token', and then click on 'Regenerate'." ) ;
// Reset state
this . matrix _destination _checkbox . checked = true ;
this . comfyworkflows _destination _checkbox . checked = true ;
this . share _button . textContent = "Share" ;
this . share _button . style . display = "inline-block" ;
this . final _message . innerHTML = "" ;
this . final _message . style . color = "white" ;
this . credits _input . value = "" ;
this . title _input . value = "" ;
this . description _input . value = "" ;
this . is _nsfw _checkbox . checked = false ;
this . close ( ) ;
return ;
}
}
const potential _outputs = [ ] ;
const potential _output _nodes = [ ] ;
// iterate over the array of nodes to find the ones that are marked as SaveImage
// TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc.
for ( let i = 0 ; i < nodes . length ; i ++ ) {
const node = nodes [ i ] ;
if ( node . type !== "SaveImage" ) {
continue ;
}
if ( node . type === "SaveImage" ) {
potential _output _nodes . push ( node ) ;
// check if node has an 'images' array property
if ( node . hasOwnProperty ( "images" ) && Array . isArray ( node . images ) ) {
// iterate over the images array and add each image to the potential_outputs array
for ( let j = 0 ; j < node . images . length ; j ++ ) {
potential _outputs . push ( { "type" : "image" , "image" : node . images [ j ] } ) ;
}
}
}
}
if ( potential _outputs . length === 0 ) {
if ( potential _output _nodes . length === 0 ) {
// todo: add support for other output node types (animatediff combine, etc.)
alert ( "No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt." ) ;
} else {
alert ( "To share this, first run a prompt. Once it's done, click 'Share'." ) ;
}
this . close ( ) ;
return ;
}
// Change the text of the share button to "Sharing..." to indicate that the share process has started
this . share _button . textContent = "Sharing..." ;
const response = await api . fetchApi ( ` /manager/share ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( {
share _destinations : destinations ,
credits : this . credits _input . value ,
title : this . title _input . value ,
description : this . description _input . value ,
is _nsfw : this . is _nsfw _checkbox . checked ,
prompt ,
potential _outputs ,
// potential_output_nodes
} )
} ) ;
if ( response . status != 200 ) {
alert ( "Failed to share your art. Please try again." ) ;
this . close ( ) ;
return ;
}
const response _json = await response . json ( ) ;
if ( response _json . comfyworkflows . url ) {
this . final _message . innerHTML = "Your art has been shared: <a href='" + response _json . comfyworkflows . url + "' target='_blank'>" + response _json . comfyworkflows . url + "</a>" ;
if ( response _json . matrix . success ) {
this . final _message . innerHTML += "<br>Your art has been shared in the ComfyUI Matrix server's #share channel!" ;
}
} else {
if ( response _json . matrix . success ) {
this . final _message . innerHTML = "Your art has been shared in the ComfyUI Matrix server's #share channel!" ;
}
}
this . final _message . style . color = "green" ;
// hide the share button
this . share _button . textContent = "Shared!" ;
this . share _button . style . display = "none" ;
// this.close();
}
const res =
[
$el ( "tr.td" , { width : "100%" } , [
$el ( "font" , { size : 6 , color : "white" } , [ ` Share your art ` ] ) ,
// $el("div", { size: 3, color: "white" }, [
// $el("a", {
// href: `https://comfyworkflows.com/?ref=cms`,
// target: `_blank`,
// color: "white",
// // style: `color:white;`
// }, `comfyworkflows.com`)
// ])
] ) ,
$el ( "p" , { size : 4 , color : "white" } , [ ` Get a public link for this art & workflow. ` ] ) ,
// $el("br", {}, []),
$el ( "div" , { } , [
$el ( "p" , { size : 3 , color : "white" } , [ ` Select where to share your art: ` ] ) ,
this . matrix _destination _checkbox ,
matrix _destination _checkbox _text ,
$el ( "br" , { } , [ ] ) ,
this . comfyworkflows _destination _checkbox ,
comfyworkflows _destination _checkbox _text ,
] ) ,
$el ( "h2" , {
textContent : "Your name/username (optional)" ,
size : 3 ,
color : "white"
} , [ ] ) ,
this . credits _input ,
$el ( "br" , { } , [ ] ) ,
$el ( "h2" , {
textContent : "Title (optional)" ,
size : 3 ,
color : "white"
} , [ ] ) ,
this . title _input ,
$el ( "br" , { } , [ ] ) ,
$el ( "h2" , {
textContent : "Description (optional)" ,
size : 3 ,
color : "white"
} , [ ] ) ,
this . description _input ,
$el ( "br" , { } , [ ] ) ,
$el ( "div" , { } , [ this . is _nsfw _checkbox , is _nsfw _checkbox _text ] ) ,
this . final _message ,
$el ( "br" , { } , [ ] ) ,
this . share _button ,
$el ( "button" , {
type : "button" ,
textContent : "Close" ,
onclick : ( ) => {
// Reset state
this . matrix _destination _checkbox . checked = true ;
this . comfyworkflows _destination _checkbox . checked = true ;
this . share _button . textContent = "Share" ;
this . share _button . style . display = "inline-block" ;
this . final _message . innerHTML = "" ;
this . final _message . style . color = "white" ;
this . credits _input . value = "" ;
this . title _input . value = "" ;
this . description _input . value = "" ;
this . is _nsfw _checkbox . checked = false ;
this . close ( )
}
} ) ,
$el ( "br" , { } , [ ] ) ,
] ;
res [ 0 ] . style . padding = "10px 10px 10px 10px" ;
res [ 0 ] . style . backgroundColor = "black" ; //"linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)";
res [ 0 ] . style . textAlign = "center" ;
res [ 0 ] . style . height = "45px" ;
return res ;
}
constructor ( ) {
super ( ) ;
this . element = $el ( "div.comfy-modal" , { parent : document . body } ,
[ $el ( "div.comfy-modal-content" ,
[ ... this . createButtons ( ) ] ) ,
] ) ;
}
show ( ) {
this . element . style . display = "block" ;
}
}
app . registerExtension ( {
name : "Comfy.ManagerMenu" ,
async setup ( ) {
const menu = document . querySelector ( ".comfy-menu" ) ;
const separator = document . createElement ( "hr" ) ;
separator . style . margin = "20px 0" ;
separator . style . width = "100%" ;
menu . append ( separator ) ;
const managerButton = document . createElement ( "button" ) ;
managerButton . textContent = "Manager" ;
managerButton . onclick = ( ) => {
if ( ! ManagerMenuDialog . instance )
ManagerMenuDialog . instance = new ManagerMenuDialog ( ) ;
ManagerMenuDialog . instance . show ( ) ;
}
menu . append ( managerButton ) ;
const shareButton = document . createElement ( "button" ) ;
shareButton . textContent = "Share" ;
shareButton . onclick = ( ) => {
if ( ! ShareDialog . instance )
ShareDialog . instance = new ShareDialog ( ) ;
ShareDialog . instance . show ( ) ;
}
// make the background color a gradient of blue to green
shareButton . style . background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)" ;
shareButton . style . color = "black" ;
// shareButton.style.border = "none";
// shareButton.style.borderRadius = "15px";
menu . append ( shareButton ) ;
} ,
async beforeRegisterNodeDef ( nodeType , nodeData , app ) {
const onDrawForeground = nodeType . prototype . onDrawForeground ;
nodeType . prototype . onDrawForeground = function ( ctx ) {
const r = onDrawForeground ? . apply ? . ( this , arguments ) ;
if ( ! this . flags . collapsed && badge _mode != 'none' && nodeType . title _mode != LiteGraph . NO _TITLE ) {
let text = "" ;
if ( badge _mode == 'id_nick' )
text = ` # ${ this . id } ` ;
if ( nicknames [ nodeData . name . trim ( ) ] ) {
let nick = nicknames [ nodeData . name . trim ( ) ] ;
if ( nick . length > 25 ) {
text += nick . substring ( 0 , 23 ) + ".." ;
}
else {
text += nick ;
}
}
if ( text != "" ) {
let fgColor = "white" ;
let bgColor = "#0F1F0F" ;
let visible = true ;
ctx . save ( ) ;
ctx . font = "12px sans-serif" ;
const sz = ctx . measureText ( text ) ;
ctx . fillStyle = bgColor ;
ctx . beginPath ( ) ;
ctx . roundRect ( this . size [ 0 ] - sz . width - 12 , - LiteGraph . NODE _TITLE _HEIGHT - 20 , sz . width + 12 , 20 , 5 ) ;
ctx . fill ( ) ;
ctx . fillStyle = fgColor ;
ctx . fillText ( text , this . size [ 0 ] - sz . width - 6 , - LiteGraph . NODE _TITLE _HEIGHT - 6 ) ;
ctx . restore ( ) ;
}
}
return r ;
} ;
} ,
async loadedGraphNode ( node , app ) {
if ( node . has _errors ) {
const onDrawForeground = node . onDrawForeground ;
node . onDrawForeground = function ( ctx ) {
const r = onDrawForeground ? . apply ? . ( this , arguments ) ;
if ( ! this . flags . collapsed && badge _mode != 'none' ) {
let text = "" ;
if ( badge _mode == 'id_nick' )
text = ` # ${ this . id } ` ;
if ( nicknames [ node . type . trim ( ) ] ) {
let nick = nicknames [ node . type . trim ( ) ] ;
if ( nick . length > 25 ) {
text += nick . substring ( 0 , 23 ) + ".." ;
}
else {
text += nick ;
}
}
if ( text != "" ) {
let fgColor = "white" ;
let bgColor = "#0F1F0F" ;
let visible = true ;
ctx . save ( ) ;
ctx . font = "12px sans-serif" ;
const sz = ctx . measureText ( text ) ;
ctx . fillStyle = bgColor ;
ctx . beginPath ( ) ;
ctx . roundRect ( this . size [ 0 ] - sz . width - 12 , - LiteGraph . NODE _TITLE _HEIGHT - 20 , sz . width + 12 , 20 , 5 ) ;
ctx . fill ( ) ;
ctx . fillStyle = fgColor ;
ctx . fillText ( text , this . size [ 0 ] - sz . width - 6 , - LiteGraph . NODE _TITLE _HEIGHT - 6 ) ;
ctx . restore ( ) ;
ctx . save ( ) ;
ctx . font = "bold 14px sans-serif" ;
const sz2 = ctx . measureText ( node . type ) ;
ctx . fillStyle = 'white' ;
ctx . fillText ( node . type , this . size [ 0 ] / 2 - sz2 . width / 2 , this . size [ 1 ] / 2 ) ;
ctx . restore ( ) ;
}
}
return r ;
} ;
}
}
} ) ;