Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
automatic1111
GitHub Repository: automatic1111/stable-diffusion-webui
Path: blob/master/javascript/extraNetworks.js
3055 views
1
function toggleCss(key, css, enable) {
2
var style = document.getElementById(key);
3
if (enable && !style) {
4
style = document.createElement('style');
5
style.id = key;
6
style.type = 'text/css';
7
document.head.appendChild(style);
8
}
9
if (style && !enable) {
10
document.head.removeChild(style);
11
}
12
if (style) {
13
style.innerHTML == '';
14
style.appendChild(document.createTextNode(css));
15
}
16
}
17
18
function setupExtraNetworksForTab(tabname) {
19
function registerPrompt(tabname, id) {
20
var textarea = gradioApp().querySelector("#" + id + " > label > textarea");
21
22
if (!activePromptTextarea[tabname]) {
23
activePromptTextarea[tabname] = textarea;
24
}
25
26
textarea.addEventListener("focus", function() {
27
activePromptTextarea[tabname] = textarea;
28
});
29
}
30
31
var tabnav = gradioApp().querySelector('#' + tabname + '_extra_tabs > div.tab-nav');
32
var controlsDiv = document.createElement('DIV');
33
controlsDiv.classList.add('extra-networks-controls-div');
34
tabnav.appendChild(controlsDiv);
35
tabnav.insertBefore(controlsDiv, null);
36
37
var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs');
38
this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) {
39
// tabname_full = {tabname}_{extra_networks_tabname}
40
var tabname_full = elem.id;
41
var search = gradioApp().querySelector("#" + tabname_full + "_extra_search");
42
var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir");
43
var refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh");
44
var currentSort = '';
45
46
// If any of the buttons above don't exist, we want to skip this iteration of the loop.
47
if (!search || !sort_dir || !refresh) {
48
return; // `return` is equivalent of `continue` but for forEach loops.
49
}
50
51
var applyFilter = function(force) {
52
var searchTerm = search.value.toLowerCase();
53
gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) {
54
var searchOnly = elem.querySelector('.search_only');
55
var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms, .description'), function(t) {
56
return t.textContent.toLowerCase();
57
}).join(" ");
58
59
var visible = text.indexOf(searchTerm) != -1;
60
if (searchOnly && searchTerm.length < 4) {
61
visible = false;
62
}
63
if (visible) {
64
elem.classList.remove("hidden");
65
} else {
66
elem.classList.add("hidden");
67
}
68
});
69
70
applySort(force);
71
};
72
73
var applySort = function(force) {
74
var cards = gradioApp().querySelectorAll('#' + tabname_full + ' div.card');
75
var parent = gradioApp().querySelector('#' + tabname_full + "_cards");
76
var reverse = sort_dir.dataset.sortdir == "Descending";
77
var activeSearchElem = gradioApp().querySelector('#' + tabname_full + "_controls .extra-network-control--sort.extra-network-control--enabled");
78
var sortKey = activeSearchElem ? activeSearchElem.dataset.sortkey : "default";
79
var sortKeyDataField = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
80
var sortKeyStore = sortKey + "-" + sort_dir.dataset.sortdir + "-" + cards.length;
81
82
if (sortKeyStore == currentSort && !force) {
83
return;
84
}
85
currentSort = sortKeyStore;
86
87
var sortedCards = Array.from(cards);
88
sortedCards.sort(function(cardA, cardB) {
89
var a = cardA.dataset[sortKeyDataField];
90
var b = cardB.dataset[sortKeyDataField];
91
if (!isNaN(a) && !isNaN(b)) {
92
return parseInt(a) - parseInt(b);
93
}
94
95
return (a < b ? -1 : (a > b ? 1 : 0));
96
});
97
98
if (reverse) {
99
sortedCards.reverse();
100
}
101
102
parent.innerHTML = '';
103
104
var frag = document.createDocumentFragment();
105
sortedCards.forEach(function(card) {
106
frag.appendChild(card);
107
});
108
parent.appendChild(frag);
109
};
110
111
search.addEventListener("input", function() {
112
applyFilter();
113
});
114
applySort();
115
applyFilter();
116
extraNetworksApplySort[tabname_full] = applySort;
117
extraNetworksApplyFilter[tabname_full] = applyFilter;
118
119
var controls = gradioApp().querySelector("#" + tabname_full + "_controls");
120
controlsDiv.insertBefore(controls, null);
121
122
if (elem.style.display != "none") {
123
extraNetworksShowControlsForPage(tabname, tabname_full);
124
}
125
});
126
127
registerPrompt(tabname, tabname + "_prompt");
128
registerPrompt(tabname, tabname + "_neg_prompt");
129
}
130
131
function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) {
132
if (!gradioApp().querySelector('.toprow-compact-tools')) return; // only applicable for compact prompt layout
133
134
var promptContainer = gradioApp().getElementById(tabname + '_prompt_container');
135
var prompt = gradioApp().getElementById(tabname + '_prompt_row');
136
var negPrompt = gradioApp().getElementById(tabname + '_neg_prompt_row');
137
var elem = id ? gradioApp().getElementById(id) : null;
138
139
if (showNegativePrompt && elem) {
140
elem.insertBefore(negPrompt, elem.firstChild);
141
} else {
142
promptContainer.insertBefore(negPrompt, promptContainer.firstChild);
143
}
144
145
if (showPrompt && elem) {
146
elem.insertBefore(prompt, elem.firstChild);
147
} else {
148
promptContainer.insertBefore(prompt, promptContainer.firstChild);
149
}
150
151
if (elem) {
152
elem.classList.toggle('extra-page-prompts-active', showNegativePrompt || showPrompt);
153
}
154
}
155
156
157
function extraNetworksShowControlsForPage(tabname, tabname_full) {
158
gradioApp().querySelectorAll('#' + tabname + '_extra_tabs .extra-networks-controls-div > div').forEach(function(elem) {
159
var targetId = tabname_full + "_controls";
160
elem.style.display = elem.id == targetId ? "" : "none";
161
});
162
}
163
164
165
function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate)
166
extraNetworksMovePromptToTab(tabname, '', false, false);
167
168
extraNetworksShowControlsForPage(tabname, null);
169
}
170
171
function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt, tabname_full) { // called from python when user selects an extra networks tab
172
extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt);
173
174
extraNetworksShowControlsForPage(tabname, tabname_full);
175
}
176
177
function applyExtraNetworkFilter(tabname_full) {
178
var doFilter = function() {
179
var applyFunction = extraNetworksApplyFilter[tabname_full];
180
181
if (applyFunction) {
182
applyFunction(true);
183
}
184
};
185
setTimeout(doFilter, 1);
186
}
187
188
function applyExtraNetworkSort(tabname_full) {
189
var doSort = function() {
190
extraNetworksApplySort[tabname_full](true);
191
};
192
setTimeout(doSort, 1);
193
}
194
195
var extraNetworksApplyFilter = {};
196
var extraNetworksApplySort = {};
197
var activePromptTextarea = {};
198
199
function setupExtraNetworks() {
200
setupExtraNetworksForTab('txt2img');
201
setupExtraNetworksForTab('img2img');
202
}
203
204
var re_extranet = /<([^:^>]+:[^:]+):[\d.]+>(.*)/;
205
var re_extranet_g = /<([^:^>]+:[^:]+):[\d.]+>/g;
206
207
var re_extranet_neg = /\(([^:^>]+:[\d.]+)\)/;
208
var re_extranet_g_neg = /\(([^:^>]+:[\d.]+)\)/g;
209
function tryToRemoveExtraNetworkFromPrompt(textarea, text, isNeg) {
210
var m = text.match(isNeg ? re_extranet_neg : re_extranet);
211
var replaced = false;
212
var newTextareaText;
213
var extraTextBeforeNet = opts.extra_networks_add_text_separator;
214
if (m) {
215
var extraTextAfterNet = m[2];
216
var partToSearch = m[1];
217
var foundAtPosition = -1;
218
newTextareaText = textarea.value.replaceAll(isNeg ? re_extranet_g_neg : re_extranet_g, function(found, net, pos) {
219
m = found.match(isNeg ? re_extranet_neg : re_extranet);
220
if (m[1] == partToSearch) {
221
replaced = true;
222
foundAtPosition = pos;
223
return "";
224
}
225
return found;
226
});
227
if (foundAtPosition >= 0) {
228
if (extraTextAfterNet && newTextareaText.substr(foundAtPosition, extraTextAfterNet.length) == extraTextAfterNet) {
229
newTextareaText = newTextareaText.substr(0, foundAtPosition) + newTextareaText.substr(foundAtPosition + extraTextAfterNet.length);
230
}
231
if (newTextareaText.substr(foundAtPosition - extraTextBeforeNet.length, extraTextBeforeNet.length) == extraTextBeforeNet) {
232
newTextareaText = newTextareaText.substr(0, foundAtPosition - extraTextBeforeNet.length) + newTextareaText.substr(foundAtPosition);
233
}
234
}
235
} else {
236
newTextareaText = textarea.value.replaceAll(new RegExp(`((?:${extraTextBeforeNet})?${text})`, "g"), "");
237
replaced = (newTextareaText != textarea.value);
238
}
239
240
if (replaced) {
241
textarea.value = newTextareaText;
242
return true;
243
}
244
245
return false;
246
}
247
248
function updatePromptArea(text, textArea, isNeg) {
249
if (!tryToRemoveExtraNetworkFromPrompt(textArea, text, isNeg)) {
250
textArea.value = textArea.value + opts.extra_networks_add_text_separator + text;
251
}
252
253
updateInput(textArea);
254
}
255
256
function cardClicked(tabname, textToAdd, textToAddNegative, allowNegativePrompt) {
257
if (textToAddNegative.length > 0) {
258
updatePromptArea(textToAdd, gradioApp().querySelector("#" + tabname + "_prompt > label > textarea"));
259
updatePromptArea(textToAddNegative, gradioApp().querySelector("#" + tabname + "_neg_prompt > label > textarea"), true);
260
} else {
261
var textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector("#" + tabname + "_prompt > label > textarea");
262
updatePromptArea(textToAdd, textarea);
263
}
264
}
265
266
function saveCardPreview(event, tabname, filename) {
267
var textarea = gradioApp().querySelector("#" + tabname + '_preview_filename > label > textarea');
268
var button = gradioApp().getElementById(tabname + '_save_preview');
269
270
textarea.value = filename;
271
updateInput(textarea);
272
273
button.click();
274
275
event.stopPropagation();
276
event.preventDefault();
277
}
278
279
function extraNetworksSearchButton(tabname, extra_networks_tabname, event) {
280
var searchTextarea = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
281
var button = event.target;
282
var text = button.classList.contains("search-all") ? "" : button.textContent.trim();
283
284
searchTextarea.value = text;
285
updateInput(searchTextarea);
286
}
287
288
function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) {
289
/**
290
* Processes `onclick` events when user clicks on files in tree.
291
*
292
* @param event The generated event.
293
* @param btn The clicked `tree-list-item` button.
294
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
295
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
296
*/
297
// NOTE: Currently unused.
298
return;
299
}
300
301
function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname) {
302
/**
303
* Processes `onclick` events when user clicks on directories in tree.
304
*
305
* Here is how the tree reacts to clicks for various states:
306
* unselected unopened directory: Directory is selected and expanded.
307
* unselected opened directory: Directory is selected.
308
* selected opened directory: Directory is collapsed and deselected.
309
* chevron is clicked: Directory is expanded or collapsed. Selected state unchanged.
310
*
311
* @param event The generated event.
312
* @param btn The clicked `tree-list-item` button.
313
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
314
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
315
*/
316
var ul = btn.nextElementSibling;
317
// This is the actual target that the user clicked on within the target button.
318
// We use this to detect if the chevron was clicked.
319
var true_targ = event.target;
320
321
function _expand_or_collapse(_ul, _btn) {
322
// Expands <ul> if it is collapsed, collapses otherwise. Updates button attributes.
323
if (_ul.hasAttribute("hidden")) {
324
_ul.removeAttribute("hidden");
325
_btn.dataset.expanded = "";
326
} else {
327
_ul.setAttribute("hidden", "");
328
delete _btn.dataset.expanded;
329
}
330
}
331
332
function _remove_selected_from_all() {
333
// Removes the `selected` attribute from all buttons.
334
var sels = document.querySelectorAll("div.tree-list-content");
335
[...sels].forEach(el => {
336
delete el.dataset.selected;
337
});
338
}
339
340
function _select_button(_btn) {
341
// Removes `data-selected` attribute from all buttons then adds to passed button.
342
_remove_selected_from_all();
343
_btn.dataset.selected = "";
344
}
345
346
function _update_search(_tabname, _extra_networks_tabname, _search_text) {
347
// Update search input with select button's path.
348
var search_input_elem = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
349
search_input_elem.value = _search_text;
350
updateInput(search_input_elem);
351
}
352
353
354
// If user clicks on the chevron, then we do not select the folder.
355
if (true_targ.matches(".tree-list-item-action--leading, .tree-list-item-action-chevron")) {
356
_expand_or_collapse(ul, btn);
357
} else {
358
// User clicked anywhere else on the button.
359
if ("selected" in btn.dataset && !(ul.hasAttribute("hidden"))) {
360
// If folder is select and open, collapse and deselect button.
361
_expand_or_collapse(ul, btn);
362
delete btn.dataset.selected;
363
_update_search(tabname, extra_networks_tabname, "");
364
} else if (!(!("selected" in btn.dataset) && !(ul.hasAttribute("hidden")))) {
365
// If folder is open and not selected, then we don't collapse; just select.
366
// NOTE: Double inversion sucks but it is the clearest way to show the branching here.
367
_expand_or_collapse(ul, btn);
368
_select_button(btn, tabname, extra_networks_tabname);
369
_update_search(tabname, extra_networks_tabname, btn.dataset.path);
370
} else {
371
// All other cases, just select the button.
372
_select_button(btn, tabname, extra_networks_tabname);
373
_update_search(tabname, extra_networks_tabname, btn.dataset.path);
374
}
375
}
376
}
377
378
function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) {
379
/**
380
* Handles `onclick` events for buttons within an `extra-network-tree .tree-list--tree`.
381
*
382
* Determines whether the clicked button in the tree is for a file entry or a directory
383
* then calls the appropriate function.
384
*
385
* @param event The generated event.
386
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
387
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
388
*/
389
var btn = event.currentTarget;
390
var par = btn.parentElement;
391
if (par.dataset.treeEntryType === "file") {
392
extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname);
393
} else {
394
extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname);
395
}
396
}
397
398
function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) {
399
/** Handles `onclick` events for Sort Mode buttons. */
400
401
var self = event.currentTarget;
402
var parent = event.currentTarget.parentElement;
403
404
parent.querySelectorAll('.extra-network-control--sort').forEach(function(x) {
405
x.classList.remove('extra-network-control--enabled');
406
});
407
408
self.classList.add('extra-network-control--enabled');
409
410
applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
411
}
412
413
function extraNetworksControlSortDirOnClick(event, tabname, extra_networks_tabname) {
414
/**
415
* Handles `onclick` events for the Sort Direction button.
416
*
417
* Modifies the data attributes of the Sort Direction button to cycle between
418
* ascending and descending sort directions.
419
*
420
* @param event The generated event.
421
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
422
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
423
*/
424
if (event.currentTarget.dataset.sortdir == "Ascending") {
425
event.currentTarget.dataset.sortdir = "Descending";
426
event.currentTarget.setAttribute("title", "Sort descending");
427
} else {
428
event.currentTarget.dataset.sortdir = "Ascending";
429
event.currentTarget.setAttribute("title", "Sort ascending");
430
}
431
applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
432
}
433
434
function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabname) {
435
/**
436
* Handles `onclick` events for the Tree View button.
437
*
438
* Toggles the tree view in the extra networks pane.
439
*
440
* @param event The generated event.
441
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
442
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
443
*/
444
var button = event.currentTarget;
445
button.classList.toggle("extra-network-control--enabled");
446
var show = !button.classList.contains("extra-network-control--enabled");
447
448
var pane = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_pane");
449
pane.classList.toggle("extra-network-dirs-hidden", show);
450
}
451
452
function extraNetworksControlRefreshOnClick(event, tabname, extra_networks_tabname) {
453
/**
454
* Handles `onclick` events for the Refresh Page button.
455
*
456
* In order to actually call the python functions in `ui_extra_networks.py`
457
* to refresh the page, we created an empty gradio button in that file with an
458
* event handler that refreshes the page. So what this function here does
459
* is it manually raises a `click` event on that button.
460
*
461
* @param event The generated event.
462
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
463
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
464
*/
465
var btn_refresh_internal = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_extra_refresh_internal");
466
btn_refresh_internal.dispatchEvent(new Event("click"));
467
}
468
469
var globalPopup = null;
470
var globalPopupInner = null;
471
472
function closePopup() {
473
if (!globalPopup) return;
474
globalPopup.style.display = "none";
475
}
476
477
function popup(contents) {
478
if (!globalPopup) {
479
globalPopup = document.createElement('div');
480
globalPopup.classList.add('global-popup');
481
482
var close = document.createElement('div');
483
close.classList.add('global-popup-close');
484
close.addEventListener("click", closePopup);
485
close.title = "Close";
486
globalPopup.appendChild(close);
487
488
globalPopupInner = document.createElement('div');
489
globalPopupInner.classList.add('global-popup-inner');
490
globalPopup.appendChild(globalPopupInner);
491
492
gradioApp().querySelector('.main').appendChild(globalPopup);
493
}
494
495
globalPopupInner.innerHTML = '';
496
globalPopupInner.appendChild(contents);
497
498
globalPopup.style.display = "flex";
499
}
500
501
var storedPopupIds = {};
502
function popupId(id) {
503
if (!storedPopupIds[id]) {
504
storedPopupIds[id] = gradioApp().getElementById(id);
505
}
506
507
popup(storedPopupIds[id]);
508
}
509
510
function extraNetworksFlattenMetadata(obj) {
511
const result = {};
512
513
// Convert any stringified JSON objects to actual objects
514
for (const key of Object.keys(obj)) {
515
if (typeof obj[key] === 'string') {
516
try {
517
const parsed = JSON.parse(obj[key]);
518
if (parsed && typeof parsed === 'object') {
519
obj[key] = parsed;
520
}
521
} catch (error) {
522
continue;
523
}
524
}
525
}
526
527
// Flatten the object
528
for (const key of Object.keys(obj)) {
529
if (typeof obj[key] === 'object' && obj[key] !== null) {
530
const nested = extraNetworksFlattenMetadata(obj[key]);
531
for (const nestedKey of Object.keys(nested)) {
532
result[`${key}/${nestedKey}`] = nested[nestedKey];
533
}
534
} else {
535
result[key] = obj[key];
536
}
537
}
538
539
// Special case for handling modelspec keys
540
for (const key of Object.keys(result)) {
541
if (key.startsWith("modelspec.")) {
542
result[key.replaceAll(".", "/")] = result[key];
543
delete result[key];
544
}
545
}
546
547
// Add empty keys to designate hierarchy
548
for (const key of Object.keys(result)) {
549
const parts = key.split("/");
550
for (let i = 1; i < parts.length; i++) {
551
const parent = parts.slice(0, i).join("/");
552
if (!result[parent]) {
553
result[parent] = "";
554
}
555
}
556
}
557
558
return result;
559
}
560
561
function extraNetworksShowMetadata(text) {
562
try {
563
let parsed = JSON.parse(text);
564
if (parsed && typeof parsed === 'object') {
565
parsed = extraNetworksFlattenMetadata(parsed);
566
const table = createVisualizationTable(parsed, 0);
567
popup(table);
568
return;
569
}
570
} catch (error) {
571
console.error(error);
572
}
573
574
var elem = document.createElement('pre');
575
elem.classList.add('popup-metadata');
576
elem.textContent = text;
577
578
popup(elem);
579
return;
580
}
581
582
function requestGet(url, data, handler, errorHandler) {
583
var xhr = new XMLHttpRequest();
584
var args = Object.keys(data).map(function(k) {
585
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]);
586
}).join('&');
587
xhr.open("GET", url + "?" + args, true);
588
589
xhr.onreadystatechange = function() {
590
if (xhr.readyState === 4) {
591
if (xhr.status === 200) {
592
try {
593
var js = JSON.parse(xhr.responseText);
594
handler(js);
595
} catch (error) {
596
console.error(error);
597
errorHandler();
598
}
599
} else {
600
errorHandler();
601
}
602
}
603
};
604
var js = JSON.stringify(data);
605
xhr.send(js);
606
}
607
608
function extraNetworksCopyCardPath(event) {
609
navigator.clipboard.writeText(event.target.getAttribute("data-clipboard-text"));
610
event.stopPropagation();
611
}
612
613
function extraNetworksRequestMetadata(event, extraPage) {
614
var showError = function() {
615
extraNetworksShowMetadata("there was an error getting metadata");
616
};
617
618
var cardName = event.target.parentElement.parentElement.getAttribute("data-name");
619
620
requestGet("./sd_extra_networks/metadata", {page: extraPage, item: cardName}, function(data) {
621
if (data && data.metadata) {
622
extraNetworksShowMetadata(data.metadata);
623
} else {
624
showError();
625
}
626
}, showError);
627
628
event.stopPropagation();
629
}
630
631
var extraPageUserMetadataEditors = {};
632
633
function extraNetworksEditUserMetadata(event, tabname, extraPage) {
634
var id = tabname + '_' + extraPage + '_edit_user_metadata';
635
636
var editor = extraPageUserMetadataEditors[id];
637
if (!editor) {
638
editor = {};
639
editor.page = gradioApp().getElementById(id);
640
editor.nameTextarea = gradioApp().querySelector("#" + id + "_name" + ' textarea');
641
editor.button = gradioApp().querySelector("#" + id + "_button");
642
extraPageUserMetadataEditors[id] = editor;
643
}
644
645
var cardName = event.target.parentElement.parentElement.getAttribute("data-name");
646
editor.nameTextarea.value = cardName;
647
updateInput(editor.nameTextarea);
648
649
editor.button.click();
650
651
popup(editor.page);
652
653
event.stopPropagation();
654
}
655
656
function extraNetworksRefreshSingleCard(page, tabname, name) {
657
requestGet("./sd_extra_networks/get-single-card", {page: page, tabname: tabname, name: name}, function(data) {
658
if (data && data.html) {
659
var card = gradioApp().querySelector(`#${tabname}_${page.replace(" ", "_")}_cards > .card[data-name="${name}"]`);
660
661
var newDiv = document.createElement('DIV');
662
newDiv.innerHTML = data.html;
663
var newCard = newDiv.firstElementChild;
664
665
newCard.style.display = '';
666
card.parentElement.insertBefore(newCard, card);
667
card.parentElement.removeChild(card);
668
}
669
});
670
}
671
672
window.addEventListener("keydown", function(event) {
673
if (event.key == "Escape") {
674
closePopup();
675
}
676
});
677
678
/**
679
* Setup custom loading for this script.
680
* We need to wait for all of our HTML to be generated in the extra networks tabs
681
* before we can actually run the `setupExtraNetworks` function.
682
* The `onUiLoaded` function actually runs before all of our extra network tabs are
683
* finished generating. Thus we needed this new method.
684
*
685
*/
686
687
var uiAfterScriptsCallbacks = [];
688
var uiAfterScriptsTimeout = null;
689
var executedAfterScripts = false;
690
691
function scheduleAfterScriptsCallbacks() {
692
clearTimeout(uiAfterScriptsTimeout);
693
uiAfterScriptsTimeout = setTimeout(function() {
694
executeCallbacks(uiAfterScriptsCallbacks);
695
}, 200);
696
}
697
698
onUiLoaded(function() {
699
var mutationObserver = new MutationObserver(function(m) {
700
let existingSearchfields = gradioApp().querySelectorAll("[id$='_extra_search']").length;
701
let neededSearchfields = gradioApp().querySelectorAll("[id$='_extra_tabs'] > .tab-nav > button").length - 2;
702
703
if (!executedAfterScripts && existingSearchfields >= neededSearchfields) {
704
mutationObserver.disconnect();
705
executedAfterScripts = true;
706
scheduleAfterScriptsCallbacks();
707
}
708
});
709
mutationObserver.observe(gradioApp(), {childList: true, subtree: true});
710
});
711
712
uiAfterScriptsCallbacks.push(setupExtraNetworks);
713
714