@@ -14,6 +14,9 @@ import { ComfyDialog, $el } from "../../scripts/ui.js";
14
14
// To delete/rename:
15
15
// Right click the canvas
16
16
// Node templates -> Manage
17
+ //
18
+ // To rearrange:
19
+ // Open the manage dialog and Drag and drop elements using the "Name:" label as handle
17
20
18
21
const id = "Comfy.NodeTemplates" ;
19
22
@@ -22,6 +25,10 @@ class ManageTemplates extends ComfyDialog {
22
25
super ( ) ;
23
26
this . element . classList . add ( "comfy-manage-templates" ) ;
24
27
this . templates = this . load ( ) ;
28
+ this . draggedEl = null ;
29
+ this . saveVisualCue = null ;
30
+ this . emptyImg = new Image ( ) ;
31
+ this . emptyImg . src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=' ;
25
32
26
33
this . importInput = $el ( "input" , {
27
34
type : "file" ,
@@ -35,14 +42,11 @@ class ManageTemplates extends ComfyDialog {
35
42
36
43
createButtons ( ) {
37
44
const btns = super . createButtons ( ) ;
38
- btns [ 0 ] . textContent = "Cancel" ;
39
- btns . unshift (
40
- $el ( "button" , {
41
- type : "button" ,
42
- textContent : "Save" ,
43
- onclick : ( ) => this . save ( ) ,
44
- } )
45
- ) ;
45
+ btns [ 0 ] . textContent = "Close" ;
46
+ btns [ 0 ] . onclick = ( e ) => {
47
+ clearTimeout ( this . saveVisualCue ) ;
48
+ this . close ( ) ;
49
+ } ;
46
50
btns . unshift (
47
51
$el ( "button" , {
48
52
type : "button" ,
@@ -71,25 +75,6 @@ class ManageTemplates extends ComfyDialog {
71
75
}
72
76
}
73
77
74
- save ( ) {
75
- // Find all visible inputs and save them as our new list
76
- const inputs = this . element . querySelectorAll ( "input" ) ;
77
- const updated = [ ] ;
78
-
79
- for ( let i = 0 ; i < inputs . length ; i ++ ) {
80
- const input = inputs [ i ] ;
81
- if ( input . parentElement . style . display !== "none" ) {
82
- const t = this . templates [ i ] ;
83
- t . name = input . value . trim ( ) || input . getAttribute ( "data-name" ) ;
84
- updated . push ( t ) ;
85
- }
86
- }
87
-
88
- this . templates = updated ;
89
- this . store ( ) ;
90
- this . close ( ) ;
91
- }
92
-
93
78
store ( ) {
94
79
localStorage . setItem ( id , JSON . stringify ( this . templates ) ) ;
95
80
}
@@ -145,71 +130,155 @@ class ManageTemplates extends ComfyDialog {
145
130
super . show (
146
131
$el (
147
132
"div" ,
148
- {
149
- style : {
150
- display : "grid" ,
151
- gridTemplateColumns : "1fr auto" ,
152
- gap : "5px" ,
153
- } ,
154
- } ,
155
- this . templates . flatMap ( ( t ) => {
133
+ { } ,
134
+ this . templates . flatMap ( ( t , i ) => {
156
135
let nameInput ;
157
136
return [
158
137
$el (
159
- "label " ,
138
+ "div " ,
160
139
{
161
- textContent : "Name: " ,
140
+ dataset : { id : i } ,
141
+ className : "tempateManagerRow" ,
142
+ style : {
143
+ display : "grid" ,
144
+ gridTemplateColumns : "1fr auto" ,
145
+ border : "1px dashed transparent" ,
146
+ gap : "5px" ,
147
+ backgroundColor : "var(--comfy-menu-bg)"
148
+ } ,
149
+ ondragstart : ( e ) => {
150
+ this . draggedEl = e . currentTarget ;
151
+ e . currentTarget . style . opacity = "0.6" ;
152
+ e . currentTarget . style . border = "1px dashed yellow" ;
153
+ e . dataTransfer . effectAllowed = 'move' ;
154
+ e . dataTransfer . setDragImage ( this . emptyImg , 0 , 0 ) ;
155
+ } ,
156
+ ondragend : ( e ) => {
157
+ e . target . style . opacity = "1" ;
158
+ e . currentTarget . style . border = "1px dashed transparent" ;
159
+ e . currentTarget . removeAttribute ( "draggable" ) ;
160
+
161
+ // rearrange the elements in the localStorage
162
+ this . element . querySelectorAll ( '.tempateManagerRow' ) . forEach ( ( el , i ) => {
163
+ var prev_i = el . dataset . id ;
164
+
165
+ if ( el == this . draggedEl && prev_i != i ) {
166
+ [ this . templates [ i ] , this . templates [ prev_i ] ] = [ this . templates [ prev_i ] , this . templates [ i ] ] ;
167
+ }
168
+ el . dataset . id = i ;
169
+ } ) ;
170
+ this . store ( ) ;
171
+ } ,
172
+ ondragover : ( e ) => {
173
+ e . preventDefault ( ) ;
174
+ if ( e . currentTarget == this . draggedEl )
175
+ return ;
176
+
177
+ let rect = e . currentTarget . getBoundingClientRect ( ) ;
178
+ if ( e . clientY > rect . top + rect . height / 2 ) {
179
+ e . currentTarget . parentNode . insertBefore ( this . draggedEl , e . currentTarget . nextSibling ) ;
180
+ } else {
181
+ e . currentTarget . parentNode . insertBefore ( this . draggedEl , e . currentTarget ) ;
182
+ }
183
+ }
162
184
} ,
163
185
[
164
- $el ( "input" , {
165
- value : t . name ,
166
- dataset : { name : t . name } ,
167
- $ : ( el ) => ( nameInput = el ) ,
168
- } ) ,
169
- ]
170
- ) ,
171
- $el (
172
- "div" ,
173
- { } ,
174
- [
175
- $el ( "button" , {
176
- textContent : "Export" ,
177
- style : {
178
- fontSize : "12px" ,
179
- fontWeight : "normal" ,
180
- } ,
181
- onclick : ( e ) => {
182
- const json = JSON . stringify ( { templates : [ t ] } , null , 2 ) ; // convert the data to a JSON string
183
- const blob = new Blob ( [ json ] , { type : "application/json" } ) ;
184
- const url = URL . createObjectURL ( blob ) ;
185
- const a = $el ( "a" , {
186
- href : url ,
187
- download : ( nameInput . value || t . name ) + ".json" ,
188
- style : { display : "none" } ,
189
- parent : document . body ,
190
- } ) ;
191
- a . click ( ) ;
192
- setTimeout ( function ( ) {
193
- a . remove ( ) ;
194
- window . URL . revokeObjectURL ( url ) ;
195
- } , 0 ) ;
196
- } ,
197
- } ) ,
198
- $el ( "button" , {
199
- textContent : "Delete" ,
200
- style : {
201
- fontSize : "12px" ,
202
- color : "red" ,
203
- fontWeight : "normal" ,
204
- } ,
205
- onclick : ( e ) => {
206
- nameInput . value = "" ;
207
- e . target . parentElement . style . display = "none" ;
208
- e . target . parentElement . previousElementSibling . style . display = "none" ;
186
+ $el (
187
+ "label" ,
188
+ {
189
+ textContent : "Name: " ,
190
+ style : {
191
+ cursor : "grab" ,
192
+ } ,
193
+ onmousedown : ( e ) => {
194
+ // enable dragging only from the label
195
+ if ( e . target . localName == 'label' )
196
+ e . currentTarget . parentNode . draggable = 'true' ;
197
+ }
209
198
} ,
210
- } ) ,
199
+ [
200
+ $el ( "input" , {
201
+ value : t . name ,
202
+ dataset : { name : t . name } ,
203
+ style : {
204
+ transitionProperty : 'background-color' ,
205
+ transitionDuration : '0s' ,
206
+ } ,
207
+ onchange : ( e ) => {
208
+ clearTimeout ( this . saveVisualCue ) ;
209
+ var el = e . target ;
210
+ var row = el . parentNode . parentNode ;
211
+ this . templates [ row . dataset . id ] . name = el . value . trim ( ) || 'untitled' ;
212
+ this . store ( ) ;
213
+ el . style . backgroundColor = 'rgb(40, 95, 40)' ;
214
+ el . style . transitionDuration = '0s' ;
215
+ this . saveVisualCue = setTimeout ( function ( ) {
216
+ el . style . transitionDuration = '.7s' ;
217
+ el . style . backgroundColor = 'var(--comfy-input-bg)' ;
218
+ } , 15 ) ;
219
+ } ,
220
+ onkeypress : ( e ) => {
221
+ var el = e . target ;
222
+ clearTimeout ( this . saveVisualCue ) ;
223
+ el . style . transitionDuration = '0s' ;
224
+ el . style . backgroundColor = 'var(--comfy-input-bg)' ;
225
+ } ,
226
+ $ : ( el ) => ( nameInput = el ) ,
227
+ } )
228
+ ]
229
+ ) ,
230
+ $el (
231
+ "div" ,
232
+ { } ,
233
+ [
234
+ $el ( "button" , {
235
+ textContent : "Export" ,
236
+ style : {
237
+ fontSize : "12px" ,
238
+ fontWeight : "normal" ,
239
+ } ,
240
+ onclick : ( e ) => {
241
+ const json = JSON . stringify ( { templates : [ t ] } , null , 2 ) ; // convert the data to a JSON string
242
+ const blob = new Blob ( [ json ] , { type : "application/json" } ) ;
243
+ const url = URL . createObjectURL ( blob ) ;
244
+ const a = $el ( "a" , {
245
+ href : url ,
246
+ download : ( nameInput . value || t . name ) + ".json" ,
247
+ style : { display : "none" } ,
248
+ parent : document . body ,
249
+ } ) ;
250
+ a . click ( ) ;
251
+ setTimeout ( function ( ) {
252
+ a . remove ( ) ;
253
+ window . URL . revokeObjectURL ( url ) ;
254
+ } , 0 ) ;
255
+ } ,
256
+ } ) ,
257
+ $el ( "button" , {
258
+ textContent : "Delete" ,
259
+ style : {
260
+ fontSize : "12px" ,
261
+ color : "red" ,
262
+ fontWeight : "normal" ,
263
+ } ,
264
+ onclick : ( e ) => {
265
+ const item = e . target . parentNode . parentNode ;
266
+ item . parentNode . removeChild ( item ) ;
267
+ this . templates . splice ( item . dataset . id * 1 , 1 ) ;
268
+ this . store ( ) ;
269
+ // update the rows index, setTimeout ensures that the list is updated
270
+ var that = this ;
271
+ setTimeout ( function ( ) {
272
+ that . element . querySelectorAll ( '.tempateManagerRow' ) . forEach ( ( el , i ) => {
273
+ el . dataset . id = i ;
274
+ } ) ;
275
+ } , 0 ) ;
276
+ } ,
277
+ } ) ,
278
+ ]
279
+ ) ,
211
280
]
212
- ) ,
281
+ )
213
282
] ;
214
283
} )
215
284
)
0 commit comments