Finish cleanup.

This commit is contained in:
Sj-Si 2024-01-16 13:35:01 -05:00
parent 1fdc18e6a0
commit 4f96267033
11 changed files with 287 additions and 167 deletions

View File

@ -1,4 +0,0 @@
<div class="card-minimal" onclick={card_clicked} data-name="{name}">
<span class="name">{name}</span>
<span class="button-row">{copy_path_button}{metadata_button}{edit_button}</span>
</div>

View File

@ -1,9 +1,9 @@
<div class="card" style={style} onclick="{card_clicked}" data-name="{name}" {sort_keys}> <div class="card" style="{style}" onclick="{card_clicked}" data-name="{name}" {sort_keys}>
{background_image} {background_image}
<div class="button-row">{copy_path_button}{metadata_button}{edit_button}</div> <div class="button-row">{copy_path_button}{metadata_button}{edit_button}</div>
<div class='actions'> <div class="actions">
<div class='additional'>{search_terms}</div> <div class="additional">{search_terms}</div>
<span class='name'>{name}</span> <span class="name">{name}</span>
<span class='description'>{description}</span> <span class="description">{description}</span>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="edit-button card-button" <div class="edit-button card-button"
title="Edit metadata" title="Edit metadata"
onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{page_id}', '{name}')"> onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}', '{name}')">
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="metadata-button card-button" <div class="metadata-button card-button"
title="Show internal metadata" title="Show internal metadata"
onclick="extraNetworksRequestMetadata(event, '{page_id}', '{name}')"> onclick="extraNetworksRequestMetadata(event, '{extra_networks_tabname}', '{name}')">
</div> </div>

View File

@ -1,8 +1,8 @@
<div id='{tabname}_{network_type_id}_pane' class='extra-network-pane'> <div id='{tabname}_{extra_networks_tabname}_pane' class='extra-network-pane'>
<div id='{tabname}_{network_type_id}_tree' class='extra-network-tree'> <div id='{tabname}_{extra_networks_tabname}_tree' class='extra-network-tree'>
{tree_html} {tree_html}
</div> </div>
<div id='{tabname}_{network_type_id}_cards' class='extra-network-cards'> <div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards'>
{items_html} {items_html}
</div> </div>
</div> </div>

View File

@ -1,8 +1,7 @@
<span data-filterable-item-text hidden>{search_terms}</span> <span data-filterable-item-text hidden>{search_terms}</span>
<div class="tree-list-content {subclass}" <div class="tree-list-content {subclass}"
expanded="false"
type="button" type="button"
onclick="extraNetworksTreeOnClick(event, '{tabname}', '{tab_id}');{onclick_extra}" onclick="extraNetworksTreeOnClick(event, '{tabname}', '{extra_networks_tabname}');{onclick_extra}"
data-path="{data_path}" data-path="{data_path}"
data-hash="{data_hash}" data-hash="{data_hash}"
> >

View File

@ -2,36 +2,36 @@
<div class="tree-list-controls"> <div class="tree-list-controls">
<div class="tree-list-search"> <div class="tree-list-search">
<input <input
id="{tabname}_{tab_id}_extra_search" id="{tabname}_{extra_networks_tabname}_extra_search"
class="tree-list-search-text" class="tree-list-search-text"
type="search" type="search"
placeholder="Filter files" placeholder="Filter files"
> >
</div> </div>
<div <div
id="{tabname}_{tab_id}_extra_sort" id="{tabname}_{extra_networks_tabname}_extra_sort"
class="tree-list-sort" class="tree-list-sort"
data-sortmode="path" data-sortmode="path"
data-sortkey="sortPath-Ascending-640" data-sortkey="sortPath-Ascending-640"
title="Sort by path" title="Sort by path"
onclick="extraNetworksTreeSortOnClick(event, '{tabname}', '{tab_id}');" onclick="extraNetworksTreeSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="tree-list-sort-icon"></i> <i class="tree-list-sort-icon"></i>
</div> </div>
<div <div
id="{tabname}_{tab_id}_extra_sort_dir" id="{tabname}_{extra_networks_tabname}_extra_sort_dir"
class="tree-list-sort-dir" class="tree-list-sort-dir"
data-sortdir="Ascending" data-sortdir="Ascending"
title="Sort ascending" title="Sort ascending"
onclick="extraNetworksTreeSortDirOnClick(event, '{tabname}', '{tab_id}');" onclick="extraNetworksTreeSortDirOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="tree-list-sort-dir-icon"></i> <i class="tree-list-sort-dir-icon"></i>
</div> </div>
<div <div
id="{tabname}_{tab_id}_extra_refresh" id="{tabname}_{extra_networks_tabname}_extra_refresh"
class="tree-list-refresh" class="tree-list-refresh"
title="Refresh page" title="Refresh page"
onclick="extraNetworksTreeRefreshOnClick(event, '{tabname}', '{tab_id}');" onclick="extraNetworksTreeRefreshOnClick(event, '{tabname}', '{extra_networks_tabname}');"
> >
<i class="tree-list-refresh-icon"></i> <i class="tree-list-refresh-icon"></i>
</div> </div>

View File

@ -31,25 +31,15 @@ function setupExtraNetworksForTab(tabname) {
var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs'); var this_tab = gradioApp().querySelector('#' + tabname + '_extra_tabs');
this_tab.classList.add('extra-networks'); this_tab.classList.add('extra-networks');
this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) { this_tab.querySelectorAll(":scope > [id^='" + tabname + "_']").forEach(function(elem) {
var tab_id = elem.getAttribute("id"); var extra_networks_tabname = elem.id;
var search = gradioApp().querySelector("#" + tab_id + "_extra_search"); var search = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_search");
if (!search) { var sort_mode = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_sort");
return; // `continue` doesn't work in `forEach` loops. This is equivalent. var sort_dir = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_sort_dir");
} var refresh = gradioApp().querySelector("#" + extra_networks_tabname + "_extra_refresh");
var sort = gradioApp().querySelector("#" + tab_id + "_extra_sort"); // If any of the buttons above don't exist, we want to skip this iteration of the loop.
if (!sort) { if (!search || !sort_mode || !sort_dir || !refresh) {
return; // `continue` doesn't work in `forEach` loops. This is equivalent. return; // `return` is equivalent of `continue` but for forEach loops.
}
var sort_dir = gradioApp().querySelector("#" + tab_id + "_extra_sort_dir");
if (!sort_dir) {
return; // `continue` doesn't work in `forEach` loops. This is equivalent.
}
var refresh = gradioApp().querySelector("#" + tab_id + "_extra_refresh");
if (!refresh) {
return; // `continue` doesn't work in `forEach` loops. This is equivalent.
} }
var applyFilter = function() { var applyFilter = function() {
@ -78,14 +68,14 @@ function setupExtraNetworksForTab(tabname) {
var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card');
var reverse = sort_dir.dataset.sortdir == "Descending"; var reverse = sort_dir.dataset.sortdir == "Descending";
var sortKey = sort.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name";
sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1);
var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length;
if (sortKeyStore == sort.dataset.sortkey) { if (sortKeyStore == sort_mode.dataset.sortkey) {
return; return;
} }
sort.dataset.sortkey = sortKeyStore; sort_mode.dataset.sortkey = sortKeyStore;
cards.forEach(function(card) { cards.forEach(function(card) {
card.originalParentElement = card.parentElement; card.originalParentElement = card.parentElement;
@ -115,8 +105,8 @@ function setupExtraNetworksForTab(tabname) {
applySort(); applySort();
applyFilter(); applyFilter();
extraNetworksApplySort[tab_id] = applySort; extraNetworksApplySort[extra_networks_tabname] = applySort;
extraNetworksApplyFilter[tab_id] = applyFilter; extraNetworksApplyFilter[extra_networks_tabname] = applyFilter;
}); });
registerPrompt(tabname, tabname + "_prompt"); registerPrompt(tabname, tabname + "_prompt");
@ -148,14 +138,6 @@ function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePromp
} }
} }
function clearSearch(tabname) {
// Clear search box.
var tab_id = tabname + "_extra_search";
var searchTextarea = gradioApp().querySelector("#" + tab_id + ' > label > textarea');
searchTextarea.value = "";
updateInput(searchTextarea);
}
function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate) function extraNetworksUnrelatedTabSelected(tabname) { // called from python when user selects an unrelated tab (generate)
extraNetworksMovePromptToTab(tabname, '', false, false); extraNetworksMovePromptToTab(tabname, '', false, false);
@ -264,22 +246,20 @@ function saveCardPreview(event, tabname, filename) {
event.preventDefault(); event.preventDefault();
} }
function extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id) { function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) {
/** /**
* Processes `onclick` events when user clicks on files in tree. * Processes `onclick` events when user clicks on files in tree.
* *
* @param event The generated event. * @param event The generated event.
* @param btn The clicked `tree-list-item` button. * @param btn The clicked `tree-list-item` button.
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/ */
var par = btn.parentElement; // NOTE: Currently unused.
var search_id = tabname + "_" + tab_id + "_extra_search"; return;
var type = par.getAttribute("data-tree-entry-type");
var path = btn.getAttribute("data-path");
} }
function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) { function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname) {
/** /**
* Processes `onclick` events when user clicks on directories in tree. * Processes `onclick` events when user clicks on directories in tree.
* *
@ -292,7 +272,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) {
* @param event The generated event. * @param event The generated event.
* @param btn The clicked `tree-list-item` button. * @param btn The clicked `tree-list-item` button.
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/ */
var ul = btn.nextElementSibling; var ul = btn.nextElementSibling;
// This is the actual target that the user clicked on within the target button. // This is the actual target that the user clicked on within the target button.
@ -301,12 +281,12 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) {
function _expand_or_collapse(_ul, _btn) { function _expand_or_collapse(_ul, _btn) {
// Expands <ul> if it is collapsed, collapses otherwise. Updates button attributes. // Expands <ul> if it is collapsed, collapses otherwise. Updates button attributes.
if (_ul.hasAttribute("data-hidden")) { if (_ul.hasAttribute("hidden")) {
_ul.removeAttribute("data-hidden"); _ul.removeAttribute("hidden");
_btn.setAttribute("expanded", "true"); _btn.dataset.expanded = "";
} else { } else {
_ul.setAttribute("data-hidden", ""); _ul.setAttribute("hidden", "");
_btn.setAttribute("expanded", "false"); delete _btn.dataset.expanded;
} }
} }
@ -314,19 +294,19 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) {
// Removes the `selected` attribute from all buttons. // Removes the `selected` attribute from all buttons.
var sels = document.querySelectorAll("div.tree-list-content"); var sels = document.querySelectorAll("div.tree-list-content");
[...sels].forEach(el => { [...sels].forEach(el => {
el.removeAttribute("selected"); delete el.dataset.selected;
}); });
} }
function _select_button(_btn) { function _select_button(_btn) {
// Removes `selected` attribute from all buttons then adds to passed button. // Removes `data-selected` attribute from all buttons then adds to passed button.
_remove_selected_from_all(); _remove_selected_from_all();
_btn.setAttribute("selected", ""); _btn.dataset.selected = "";
} }
function _update_search(_tabname, _tab_id, _search_text) { function _update_search(_tabname, _extra_networks_tabname, _search_text) {
// Update search input with select button's path. // Update search input with select button's path.
var search_input_elem = gradioApp().querySelector("#" + tabname + "_" + tab_id + "_extra_search"); var search_input_elem = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search");
search_input_elem.value = _search_text; search_input_elem.value = _search_text;
updateInput(search_input_elem); updateInput(search_input_elem);
} }
@ -337,26 +317,26 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id) {
_expand_or_collapse(ul, btn); _expand_or_collapse(ul, btn);
} else { } else {
// User clicked anywhere else on the button. // User clicked anywhere else on the button.
if (btn.hasAttribute("selected") && !ul.hasAttribute("data-hidden")) { if ("selected" in btn.dataset && !(ul.hasAttribute("hidden"))) {
// If folder is select and open, collapse and deselect button. // If folder is select and open, collapse and deselect button.
_expand_or_collapse(ul, btn); _expand_or_collapse(ul, btn);
btn.removeAttribute("selected"); delete btn.dataset.selected;
_update_search(tabname, tab_id, ""); _update_search(tabname, extra_networks_tabname, "");
} else if (!(!btn.hasAttribute("selected") && !ul.hasAttribute("data-hidden"))) { } else if (!(!("selected" in btn.dataset) && !(ul.hasAttribute("hidden")))) {
// If folder is open and not selected, then we don't collapse; just select. // If folder is open and not selected, then we don't collapse; just select.
// NOTE: Double inversion sucks but it is the clearest way to show the branching here. // NOTE: Double inversion sucks but it is the clearest way to show the branching here.
_expand_or_collapse(ul, btn); _expand_or_collapse(ul, btn);
_select_button(btn, tabname, tab_id); _select_button(btn, tabname, extra_networks_tabname);
_update_search(tabname, tab_id, btn.getAttribute("data-path")); _update_search(tabname, extra_networks_tabname, btn.dataset.path);
} else { } else {
// All other cases, just select the button. // All other cases, just select the button.
_select_button(btn, tabname, tab_id); _select_button(btn, tabname, extra_networks_tabname);
_update_search(tabname, tab_id, btn.getAttribute("data-path")); _update_search(tabname, extra_networks_tabname, btn.dataset.path);
} }
} }
} }
function extraNetworksTreeOnClick(event, tabname, tab_id) { function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) {
/** /**
* Handles `onclick` events for buttons within an `extra-network-tree .tree-list--tree`. * Handles `onclick` events for buttons within an `extra-network-tree .tree-list--tree`.
* *
@ -365,20 +345,30 @@ function extraNetworksTreeOnClick(event, tabname, tab_id) {
* *
* @param event The generated event. * @param event The generated event.
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param tab_id The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/ */
var btn = event.currentTarget; var btn = event.currentTarget;
var par = btn.parentElement; var par = btn.parentElement;
if (par.getAttribute("data-tree-entry-type") === "file") { if (par.dataset.treeEntryType === "file") {
extraNetworksTreeProcessFileClick(event, btn, tabname, tab_id); extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname);
} else { } else {
extraNetworksTreeProcessDirectoryClick(event, btn, tabname, tab_id); extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_networks_tabname);
} }
} }
function extraNetworksTreeSortOnClick(event, tabname, tab_id) { function extraNetworksTreeSortOnClick(event, tabname, extra_networks_tabname) {
/**
* Handles `onclick` events for the Sort Mode button.
*
* Modifies the data attributes of the Sort Mode button to cycle between
* various sorting modes.
*
* @param event The generated event.
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/
var curr_mode = event.currentTarget.dataset.sortmode; var curr_mode = event.currentTarget.dataset.sortmode;
var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + tab_id + "_extra_sort_dir"); var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_sort_dir");
var sort_dir = el_sort_dir.dataset.sortdir; var sort_dir = el_sort_dir.dataset.sortdir;
if (curr_mode == "path") { if (curr_mode == "path") {
event.currentTarget.dataset.sortmode = "name"; event.currentTarget.dataset.sortmode = "name";
@ -397,23 +387,43 @@ function extraNetworksTreeSortOnClick(event, tabname, tab_id) {
event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640"; event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640";
event.currentTarget.setAttribute("title", "Sort by path"); event.currentTarget.setAttribute("title", "Sort by path");
} }
applyExtraNetworkSort(tabname + "_" + tab_id); applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
} }
function extraNetworksTreeSortDirOnClick(event, tabname, tab_id) { function extraNetworksTreeSortDirOnClick(event, tabname, extra_networks_tabname) {
var curr_dir = event.currentTarget.getAttribute("data-sortdir"); /**
if (curr_dir == "Ascending") { * Handles `onclick` events for the Sort Direction button.
*
* Modifies the data attributes of the Sort Direction button to cycle between
* ascending and descending sort directions.
*
* @param event The generated event.
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/
if (event.currentTarget.dataset.sortdir == "Ascending") {
event.currentTarget.dataset.sortdir = "Descending"; event.currentTarget.dataset.sortdir = "Descending";
event.currentTarget.setAttribute("title", "Sort descending"); event.currentTarget.setAttribute("title", "Sort descending");
} else { } else {
event.currentTarget.dataset.sortdir = "Ascending"; event.currentTarget.dataset.sortdir = "Ascending";
event.currentTarget.setAttribute("title", "Sort ascending"); event.currentTarget.setAttribute("title", "Sort ascending");
} }
applyExtraNetworkSort(tabname + "_" + tab_id); applyExtraNetworkSort(tabname + "_" + extra_networks_tabname);
} }
function extraNetworksTreeRefreshOnClick(event, tabname, tab_id) { function extraNetworksTreeRefreshOnClick(event, tabname, extra_networks_tabname) {
console.log("refresh clicked"); /**
* Handles `onclick` events for the Refresh Page button.
*
* In order to actually call the python functions in `ui_extra_networks.py`
* to refresh the page, we created an empty gradio button in that file with an
* event handler that refreshes the page. So what this function here does
* is it manually raises a `click` event on that button.
*
* @param event The generated event.
* @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc.
* @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc.
*/
var btn_refresh_internal = gradioApp().getElementById(tabname + "_extra_refresh_internal"); var btn_refresh_internal = gradioApp().getElementById(tabname + "_extra_refresh_internal");
btn_refresh_internal.dispatchEvent(new Event("click")); btn_refresh_internal.dispatchEvent(new Event("click"));
} }

View File

@ -155,7 +155,14 @@ class ExtraNetworksPage:
def __init__(self, title): def __init__(self, title):
self.title = title self.title = title
self.name = title.lower() self.name = title.lower()
self.id_page = self.name.replace(" ", "_") # This is the actual name of the extra networks tab (not txt2img/img2img).
self.extra_networks_tabname = self.name.replace(" ", "_")
self.allow_prompt = True
self.allow_negative_prompt = False
self.metadata = {}
self.items = {}
self.lister = util.MassFileLister()
# HTML Templates
self.pane_tpl = shared.html("extra-networks-pane.html") self.pane_tpl = shared.html("extra-networks-pane.html")
self.tree_tpl = shared.html("extra-networks-tree.html") self.tree_tpl = shared.html("extra-networks-tree.html")
self.card_tpl = shared.html("extra-networks-card.html") self.card_tpl = shared.html("extra-networks-card.html")
@ -163,11 +170,6 @@ class ExtraNetworksPage:
self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html") self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html")
self.btn_metadata_tpl = shared.html("extra-networks-metadata-button.html") self.btn_metadata_tpl = shared.html("extra-networks-metadata-button.html")
self.btn_edit_item_tpl = shared.html("extra-networks-edit-item-button.html") self.btn_edit_item_tpl = shared.html("extra-networks-edit-item-button.html")
self.allow_prompt = True
self.allow_negative_prompt = False
self.metadata = {}
self.items = {}
self.lister = util.MassFileLister()
def refresh(self): def refresh(self):
pass pass
@ -202,15 +204,17 @@ class ExtraNetworksPage:
item: dict, item: dict,
template: Optional[str] = None, template: Optional[str] = None,
) -> Union[str, dict]: ) -> Union[str, dict]:
"""Generates HTML for a single ExtraNetworks Item """Generates HTML for a single ExtraNetworks Item.
Args: Args:
tabname: The name of the active tab. tabname: The name of the active tab.
item: Dictionary containing item information. item: Dictionary containing item information.
template: Optional template string to use.
Returns: Returns:
HTML string generated for this item. If a template is passed: HTML string generated for this item.
Can be empty if the item is not meant to be shown. Can be empty if the item is not meant to be shown.
If no template is passed: A dictionary containing the generated item's attributes.
""" """
metadata = item.get("metadata") metadata = item.get("metadata")
if metadata: if metadata:
@ -244,14 +248,14 @@ class ExtraNetworksPage:
if metadata: if metadata:
btn_metadata = self.btn_metadata_tpl.format( btn_metadata = self.btn_metadata_tpl.format(
**{ **{
"page_id": self.id_page, "extra_networks_tabname": self.extra_networks_tabname,
"name": html.escape(item["name"]), "name": html.escape(item["name"]),
} }
) )
btn_edit_item = self.btn_edit_item_tpl.format( btn_edit_item = self.btn_edit_item_tpl.format(
**{ **{
"tabname": tabname, "tabname": tabname,
"page_id": self.id_page, "extra_networks_tabname": self.extra_networks_tabname,
"name": html.escape(item["name"]), "name": html.escape(item["name"]),
} }
) )
@ -307,9 +311,9 @@ class ExtraNetworksPage:
"search_only": " search_only" if search_only else "", "search_only": " search_only" if search_only else "",
"search_terms": search_terms_html, "search_terms": search_terms_html,
"sort_keys": sort_keys, "sort_keys": sort_keys,
"style": f"'display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%'", "style": f"display: none; {height}{width}; font-size: {shared.opts.extra_networks_card_text_scale*100}%",
"tabname": tabname, "tabname": tabname,
"tab_id": self.id_page, "extra_networks_tabname": self.extra_networks_tabname,
} }
if template: if template:
@ -317,7 +321,32 @@ class ExtraNetworksPage:
else: else:
return args return args
def create_tree_dir_item_html(self, tabname: str, dir_path: str, content: Optional[str] = None) -> Optional[str]: def create_tree_dir_item_html(
self,
tabname: str,
dir_path: str,
content: Optional[str] = None,
) -> Optional[str]:
"""Generates HTML for a directory item in the tree.
The generated HTML is of the format:
```html
<li class="tree-list-item tree-list-item--has-subitem">
<div class="tree-list-content tree-list-content-dir"></div>
<ul class="tree-list tree-list--subgroup">
{content}
</ul>
</li>
```
Args:
tabname: The name of the active tab.
dir_path: Path to the directory for this item.
content: Optional HTML string that will be wrapped by this <ul>.
Returns:
HTML formatted string.
"""
if not content: if not content:
return None return None
@ -326,7 +355,7 @@ class ExtraNetworksPage:
"search_terms": "", "search_terms": "",
"subclass": "tree-list-content-dir", "subclass": "tree-list-content-dir",
"tabname": tabname, "tabname": tabname,
"tab_id": self.id_page, "extra_networks_tabname": self.extra_networks_tabname,
"onclick_extra": "", "onclick_extra": "",
"data_path": dir_path, "data_path": dir_path,
"data_hash": "", "data_hash": "",
@ -337,10 +366,32 @@ class ExtraNetworksPage:
"action_list_item_action_trailing": "", "action_list_item_action_trailing": "",
} }
) )
ul = f"<ul class='tree-list tree-list--subgroup' data-hidden>{content}</ul>" ul = f"<ul class='tree-list tree-list--subgroup' hidden>{content}</ul>"
return f"<li class='tree-list-item tree-list-item--has-subitem' data-tree-entry-type='dir'>{btn + ul}</li>" return (
"<li class='tree-list-item tree-list-item--has-subitem' data-tree-entry-type='dir'>"
f"{btn + ul}"
"</li>"
)
def create_tree_file_item_html(self, tabname: str, item_name: str, item: dict) -> str: def create_tree_file_item_html(self, tabname: str, file_path: str, item: dict) -> str:
"""Generates HTML for a file item in the tree.
The generated HTML is of the format:
```html
<li class="tree-list-item tree-list-item--subitem">
<span data-filterable-item-text hidden></span>
<div class="tree-list-content tree-list-content-file"></div>
</li>
```
Args:
tabname: The name of the active tab.
file_path: The path to the file for this item.
item: Dictionary containing the item information.
Returns:
HTML formatted string.
"""
item_html_args = self.create_item_html(tabname, item) item_html_args = self.create_item_html(tabname, item)
action_buttons = "".join( action_buttons = "".join(
[ [
@ -355,9 +406,9 @@ class ExtraNetworksPage:
"search_terms": "", "search_terms": "",
"subclass": "tree-list-content-file", "subclass": "tree-list-content-file",
"tabname": tabname, "tabname": tabname,
"tab_id": self.id_page, "extra_networks_tabname": self.extra_networks_tabname,
"onclick_extra": item_html_args["card_clicked"], "onclick_extra": item_html_args["card_clicked"],
"data_path": item_name, "data_path": file_path,
"data_hash": item["shorthash"], "data_hash": item["shorthash"],
"action_list_item_action_leading": "<i class='tree-list-item-action-chevron'></i>", "action_list_item_action_leading": "<i class='tree-list-item-action-chevron'></i>",
"action_list_item_visual_leading": "🗎", "action_list_item_visual_leading": "🗎",
@ -366,11 +417,17 @@ class ExtraNetworksPage:
"action_list_item_action_trailing": action_buttons, "action_list_item_action_trailing": action_buttons,
} }
) )
return f"<li class='tree-list-item tree-list-item--subitem' data-tree-entry-type='file'>{btn}</li>" return (
"<li class='tree-list-item tree-list-item--subitem' data-tree-entry-type='file'>"
f"{btn}"
"</li>"
)
def create_tree_view_html(self, tabname: str) -> str: def create_tree_view_html(self, tabname: str) -> str:
"""Generates HTML for displaying folders in a tree view. """Generates HTML for displaying folders in a tree view.
The generated HTML uses `extra-networks-tree.html` as a template.
Args: Args:
tabname: The name of the active tab. tabname: The name of the active tab.
@ -379,7 +436,7 @@ class ExtraNetworksPage:
""" """
res = "" res = ""
# Generate HTML for the tree. # Setup the tree dictionary.
roots = self.allowed_directories_for_previews() roots = self.allowed_directories_for_previews()
tree_items = {v["filename"]: ExtraNetworksItem(v) for v in self.items.values()} tree_items = {v["filename"]: ExtraNetworksItem(v) for v in self.items.values()}
tree = get_tree([os.path.abspath(x) for x in roots], items=tree_items) tree = get_tree([os.path.abspath(x) for x in roots], items=tree_items)
@ -388,7 +445,17 @@ class ExtraNetworksPage:
return res return res
def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional[str]: def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional[str]:
"""Recursively builds HTML for a tree.""" """Recursively builds HTML for a tree.
Args:
data: Dictionary representing a directory tree. Can be NoneType.
Data keys should be absolute paths from the root and values
should be subdirectory trees or an ExtraNetworksItem.
Returns:
If data is not None: HTML string
Else: None
"""
if not data: if not data:
return None return None
@ -402,25 +469,36 @@ class ExtraNetworksPage:
else: else:
_dir_li.append(self.create_tree_dir_item_html(tabname, k, _build_tree(v))) _dir_li.append(self.create_tree_dir_item_html(tabname, k, _build_tree(v)))
# Directories should always be displayed before files. # Directories should always be displayed before files so we order them here.
return "".join(_dir_li) + "".join(_file_li) return "".join(_dir_li) + "".join(_file_li)
# Add each root directory to the tree. # Add each root directory to the tree.
for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])): for k, v in sorted(tree.items(), key=lambda x: shared.natural_sort_key(x[0])):
# If root is empty, append the "disabled" attribute to the template details tag.
item_html = self.create_tree_dir_item_html(tabname, k, _build_tree(v)) item_html = self.create_tree_dir_item_html(tabname, k, _build_tree(v))
if item_html: # Only add non-empty entries to the tree.
if item_html is not None:
res += item_html res += item_html
return self.tree_tpl.format( return self.tree_tpl.format(
**{ **{
"tabname": tabname, "tabname": tabname,
"tab_id": self.id_page, "extra_networks_tabname": self.extra_networks_tabname,
"tree": f"<ul class='tree-list tree-list--tree'>{res}</ul>" "tree": f"<ul class='tree-list tree-list--tree'>{res}</ul>"
} }
) )
def create_card_view_html(self, tabname): def create_card_view_html(self, tabname: str) -> str:
"""Generates HTML for the network Card View section for a tab.
This HTML goes into the `extra-networks-pane.html` <div> with
`id='{tabname}_{extra_networks_tabname}_cards`.
Args:
tabname: The name of the active tab.
Returns:
HTML formatted string.
"""
res = "" res = ""
self.items = {x["name"]: x for x in self.list_items()} self.items = {x["name"]: x for x in self.list_items()}
for item in self.items.values(): for item in self.items.values():
@ -433,20 +511,26 @@ class ExtraNetworksPage:
return res return res
def create_html(self, tabname): def create_html(self, tabname):
"""Generates an HTML string for the current pane.
The generated HTML uses `extra-networks-pane.html` as a template.
Args:
tabname: The name of the active tab.
Returns:
HTML formatted string.
"""
self.lister.reset() self.lister.reset()
self.metadata = {} self.metadata = {}
self.items = {x["name"]: x for x in self.list_items()} self.items = {x["name"]: x for x in self.list_items()}
tree_view_html = self.create_tree_view_html(tabname)
card_view_html = self.create_card_view_html(tabname)
network_type_id = self.id_page
return self.pane_tpl.format( return self.pane_tpl.format(
**{ **{
"tabname": tabname, "tabname": tabname,
"network_type_id": network_type_id, "extra_networks_tabname": self.extra_networks_tabname,
"tree_html": tree_view_html, "tree_html": self.create_tree_view_html(tabname),
"items_html": card_view_html, "items_html": self.create_card_view_html(tabname),
} }
) )
@ -561,16 +645,16 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
button_refresh = gr.Button("Refresh", elem_id=tabname+"_extra_refresh_internal", visible=False) button_refresh = gr.Button("Refresh", elem_id=tabname+"_extra_refresh_internal", visible=False)
for page in ui.stored_extra_pages: for page in ui.stored_extra_pages:
with gr.Tab(page.title, elem_id=f"{tabname}_{page.id_page}", elem_classes=["extra-page"]) as tab: with gr.Tab(page.title, elem_id=f"{tabname}_{page.extra_networks_tabname}", elem_classes=["extra-page"]) as tab:
with gr.Column(elem_id=f"{tabname}_{page.id_page}_prompts", elem_classes=["extra-page-prompts"]): with gr.Column(elem_id=f"{tabname}_{page.extra_networks_tabname}_prompts", elem_classes=["extra-page-prompts"]):
pass pass
elem_id = f"{tabname}_{page.id_page}_cards_html" elem_id = f"{tabname}_{page.extra_networks_tabname}_cards_html"
page_elem = gr.HTML('Loading...', elem_id=elem_id) page_elem = gr.HTML('Loading...', elem_id=elem_id)
ui.pages.append(page_elem) ui.pages.append(page_elem)
page_elem.change( page_elem.change(
fn=lambda: None, fn=lambda: None,
_js=f"function(){{applyExtraNetworkFilter({tabname}_{page.id_page}_extra_search); return []}}", _js=f"function(){{applyExtraNetworkFilter({tabname}_{page.extra_networks_tabname}_extra_search); return []}}",
inputs=[], inputs=[],
outputs=[], outputs=[],
) )

View File

@ -14,7 +14,7 @@ class UserMetadataEditor:
self.ui = ui self.ui = ui
self.tabname = tabname self.tabname = tabname
self.page = page self.page = page
self.id_part = f"{self.tabname}_{self.page.id_page}_edit_user_metadata" self.id_part = f"{self.tabname}_{self.page.extra_networks_tabname}_edit_user_metadata"
self.box = None self.box = None

View File

@ -879,13 +879,6 @@ footer {
margin-bottom: 1em; margin-bottom: 1em;
} }
.extra-network-pane{
height: calc(100vh - 24rem);
overflow: clip scroll;
resize: vertical;
min-height: 52rem;
}
.extra-networks > div.tab-nav{ .extra-networks > div.tab-nav{
min-height: 3.4rem; min-height: 3.4rem;
} }
@ -1182,23 +1175,63 @@ body.resizing .resize-handle {
/* ========================= */ /* ========================= */
.extra-network-pane { .extra-network-pane {
display: flex; display: flex;
} height: calc(100vh - 24rem);
resize: vertical;
.extra-network-pane .extra-network-cards { min-height: 52rem;
display: block;
} }
.extra-network-pane .extra-network-tree { .extra-network-pane .extra-network-tree {
display: block; flex: 1;
flex-direction: column;
display: flex;
font-size: 1rem; font-size: 1rem;
min-width: 25%;
border: 1px solid var(--block-border-color); border: 1px solid var(--block-border-color);
}
.extra-network-pane .extra-network-cards {
flex: 3;
overflow: clip auto !important;
border: 1px solid var(--block-border-color);
}
.extra-network-pane .extra-network-tree .tree-list {
flex: 1;
display: flex;
flex-direction: column;
padding: 0;
width: 100%;
overflow: hidden; overflow: hidden;
} }
.extra-network-tree .tree-list { .extra-network-pane .extra-network-tree .tree-list .tree-list-container {
margin: 0 0.25rem; flex: 1;
padding: 0; overflow: clip auto !important;
width: 100%;
}
.extra-network-pane .extra-network-cards::-webkit-scrollbar,
.extra-network-pane .tree-list-container::-webkit-scrollbar {
background-color: transparent;
width: 16px;
}
.extra-network-pane .extra-network-cards::-webkit-scrollbar-track,
.extra-network-pane .tree-list-container::-webkit-scrollbar-track {
background-color: transparent;
background-clip: content-box;
}
.extra-network-pane .extra-network-cards::-webkit-scrollbar-thumb,
.extra-network-pane .tree-list-container::-webkit-scrollbar-thumb {
background-color: var(--border-color-primary);
border-radius: 16px;
border: 4px solid var(--background-fill-primary);
}
.extra-network-pane .extra-network-cards::-webkit-scrollbar-button,
.extra-network-pane .tree-list-container::-webkit-scrollbar-button {
display: none;
} }
.extra-network-tree .tree-list .tree-list-controls { .extra-network-tree .tree-list .tree-list-controls {
@ -1244,17 +1277,15 @@ body.resizing .resize-handle {
background-color: transparent; background-color: transparent;
} }
/* Directory <ul> visibility based on expanded attribute. */ /* Directory <ul> visibility based on data-expanded attribute. */
.extra-network-tree .tree-list-content[expanded=false]+.tree-list--subgroup { .extra-network-tree .tree-list-content+.tree-list--subgroup {
height: 0; height: 0;
overflow: hidden;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
} }
.extra-network-tree .tree-list-content[expanded=true]+.tree-list--subgroup { .extra-network-tree .tree-list-content[data-expanded]+.tree-list--subgroup {
height: auto; height: auto;
overflow: visible;
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
@ -1307,7 +1338,7 @@ body.resizing .resize-handle {
background-color: var(--neutral-800); background-color: var(--neutral-800);
} }
.dark .extra-network-tree div.tree-list-content[selected] { .dark .extra-network-tree div.tree-list-content[data-selected] {
background-color: var(--neutral-700); background-color: var(--neutral-700);
} }
@ -1317,20 +1348,20 @@ body.resizing .resize-handle {
background-color: var(--neutral-200); background-color: var(--neutral-200);
} }
.extra-network-tree div.tree-list-content[selected] { .extra-network-tree div.tree-list-content[data-selected] {
background-color: var(--neutral-300); background-color: var(--neutral-300);
} }
/* ==== CHEVRON ICON ACTIONS ==== */ /* ==== CHEVRON ICON ACTIONS ==== */
/* Define the animation for the arrow when it is clicked. */ /* Define the animation for the arrow when it is clicked. */
.extra-network-tree .tree-list-content-dir[expanded=false] .tree-list-item-action-chevron { .extra-network-tree .tree-list-content-dir .tree-list-item-action-chevron {
-ms-transform: rotate(135deg); -ms-transform: rotate(135deg);
-webkit-transform: rotate(135deg); -webkit-transform: rotate(135deg);
transform: rotate(135deg); transform: rotate(135deg);
transition: transform 0.2s; transition: transform 0.2s;
} }
.extra-network-tree .tree-list-content-dir[expanded=true] .tree-list-item-action-chevron { .extra-network-tree .tree-list-content-dir[data-expanded] .tree-list-item-action-chevron {
-ms-transform: rotate(225deg); -ms-transform: rotate(225deg);
-webkit-transform: rotate(225deg); -webkit-transform: rotate(225deg);
transform: rotate(225deg); transform: rotate(225deg);