From 7e006b8cc5803ac9fdf08dbd6e8e2314dbc4c2e4 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Mon, 22 Apr 2024 18:25:48 -0400 Subject: [PATCH] fix filtering based on directory buttons. --- html/extra-networks-btn-dirs-view-item.html | 1 + javascript/extraNetworks.js | 177 +++++++++++++------- javascript/extraNetworksClusterize.js | 47 +++--- modules/ui_extra_networks.py | 4 +- 4 files changed, 146 insertions(+), 83 deletions(-) diff --git a/html/extra-networks-btn-dirs-view-item.html b/html/extra-networks-btn-dirs-view-item.html index c6847a3cb..63b2a64ea 100644 --- a/html/extra-networks-btn-dirs-view-item.html +++ b/html/extra-networks-btn-dirs-view-item.html @@ -1,4 +1,5 @@ \ No newline at end of file diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 45b726010..c0bb2b92e 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -3,6 +3,7 @@ ExtraNetworksClusterizeTreeList, ExtraNetworksClusterizeCardsList, waitForElement, + isString, isElement, isElementThrowError, requestGetPromise, @@ -83,6 +84,7 @@ class ExtraNetworksTab { sort_mode_str = ""; sort_dir_str = ""; filter_str = ""; + directory_filter_str = ""; show_prompt = true; show_neg_prompt = true; compact_prompt_en = false; @@ -128,7 +130,8 @@ class ExtraNetworksTab { this.setSortMode(sort_mode_elem.dataset.sortMode); this.setSortDir(sort_dir_elem.dataset.sortDir); - this.setFilterStr(this.txt_search_elem.value); + this.applyDirectoryFilter(); + this.applyFilter(); this.registerPrompt(); @@ -214,9 +217,14 @@ class ExtraNetworksTab { this.cards_list.setSortDir(this.sort_dir_str); } - setFilterStr(filter_str, is_dir) { + setFilterStr(filter_str) { this.filter_str = filter_str; - this.cards_list.setFilterStr(this.filter_str, is_dir === true); + this.cards_list.setFilterStr(this.filter_str); + } + + setDirectoryFilterStr(filter_str) { + this.directory_filter_str = filter_str; + this.cards_list.setDirectoryFilterStr(this.directory_filter_str); } movePrompt(show_prompt = true, show_neg_prompt = true) { @@ -284,7 +292,8 @@ class ExtraNetworksTab { // apply the previous sort/filter options this.setSortMode(this.sort_mode_str); this.setSortDir(this.sort_dir_str); - this.setFilterStr(this.filter_str); + this.applyDirectoryFilter(this.directory_filter_str); + this.applyFilter(this.filter_str); } async refresh() { @@ -311,23 +320,14 @@ class ExtraNetworksTab { this.cards_list.enable(false); } - applyFilter({is_dir = false} = {is_dir: false}) { - // We only want to filter/sort the cards list. - this.setFilterStr(this.txt_search_elem.value, is_dir); + applyDirectoryFilter(filter_str) { + filter_str = !isString(filter_str) ? "" : filter_str; + this.setDirectoryFilterStr(filter_str); + } - // If the search input has changed since selecting a button to populate it - // then we want to disable the button that previously populated the search input. - - // tree view buttons - let btn = this.container_elem.querySelector(".tree-list-item[data-selected='']"); - if (isElement(btn) && btn.dataset.path !== this.txt_search_elem.value && "selected" in btn.dataset) { - this.tree_list.onRowSelected(btn.dataset.divId, btn, false); - } - // dirs view buttons - btn = this.container_elem.querySelector(".extra-network-dirs-view-button[data-selected='']"); - if (isElement(btn) && btn.textContent.trim() !== this.txt_search_elem.value) { - delete btn.dataset.selected; - } + applyFilter(filter_str) { + filter_str = !isString(filter_str) ? this.txt_search_elem.value : filter_str; + this.setFilterStr(filter_str); } async waitForServerPageReady(max_attempts = EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS) { @@ -461,10 +461,10 @@ class ExtraNetworksTab { } } - updateSearch(text, ...args) { + updateSearch(text) { this.txt_search_elem.value = text; updateInput(this.txt_search_elem); - this.applyFilter(...args); + this.applyFilter(this.txt_search_elem.value); } autoSetTreeWidth() { @@ -496,6 +496,81 @@ class ExtraNetworksTab { // Mimicks resizeHandle.js::setLeftColGridTemplate(). row.style.gridTemplateColumns = `${max_width}px ${pad}px 1fr`; } + + setDirectoryButtons(source_elem) { + /** Sets the selected state of buttons in the tree and dirs views. + * + * Args: + * source_elem: If passed, the `data-selected` attribute of this element will + * be applied to the tree/dirs view buttons. This element MUST have a `data-path` + * attribute as this will be used to lookup matching tree/dirs buttons. If + * not passed, then the selected state is inferred from the existing DOM. + */ + // Checks if an element exists and is visible on the page. + const _exists = (elem) => { + return isElement(elem) && !isNullOrUndefined(elem.offsetParent); + }; + + // Removes `data-selected` attribute from all tree/dirs buttons. + const _reset = () => { + this.container_elem.querySelectorAll(".extra-network-dirs-view-button").forEach(elem => { + delete elem.dataset.selected; + }); + this.tree_list.content_elem.querySelectorAll(".tree-list-item").forEach(elem => { + delete elem.dataset.selected; + }); + }; + _reset.bind(this); + + let dirs_btn; + let tree_btn; + + if (_exists(source_elem)) { + const new_state = "selected" in source_elem.dataset; + dirs_btn = this.container_elem.querySelector(`.extra-network-dirs-view-button[data-path="${source_elem.dataset.path}"]`); + tree_btn = this.tree_list.content_elem.querySelector(`.tree-list-item[data-path="${source_elem.dataset.path}"]`); + + _reset(); + + if (new_state) { + if (_exists(dirs_btn)) { + dirs_btn.dataset.selected = ""; + } + if (_exists(tree_btn)) { + tree_btn.dataset.selected = ""; + } + } else { + if (_exists(dirs_btn)) { + delete dirs_btn.dataset.selected; + } + if (_exists(tree_btn)) { + delete tree_btn.dataset.selected; + } + this.applyDirectoryFilter(""); + } + } else { + dirs_btn = this.container_elem.querySelector(".extra-network-dirs-view-button[data-selected='']"); + tree_btn = this.tree_list.content_elem.querySelector(".tree-list-item[data-selected='']"); + if (_exists(dirs_btn)) { + tree_btn = this.tree_list.content_elem.querySelector(`.tree-list-item[data-path="${dirs_btn.dataset.path}"]`); + if (_exists(tree_btn) && "selected" in dirs_btn.dataset) { + _reset(); + dirs_btn.dataset.selected = ""; + tree_btn.dataset.selected = ""; + } + } else if (_exists(tree_btn)) { + dirs_btn = this.container_elem.querySelector(`.extra-network-dirs-view-button[data-path="${tree_btn.dataset.path}"]`); + if (_exists(dirs_btn) && "selected" in tree_btn.dataset) { + _reset(); + dirs_btn.dataset.selected = ""; + tree_btn.dataset.selected = ""; + } + } else { + _reset(); + this.applyDirectoryFilter(""); + } + } + } } // @@ -748,7 +823,6 @@ function extraNetworksBtnDirsViewItemOnClick(event, tabname_full) { /** Handles `onclick` events for buttons in the directory view. */ const tab = extra_networks_tabs[tabname_full]; const container_elem = tab.container_elem; - const txt_search_elem = tab.txt_search_elem; const _deselect_all_buttons = () => { container_elem.querySelectorAll( @@ -756,30 +830,18 @@ function extraNetworksBtnDirsViewItemOnClick(event, tabname_full) { ).forEach(elem => { delete elem.dataset.selected; }); - // deselect tree view rows - tab.tree_list.onRowSelected(); // empty params deselects all rows. }; const _select_button = (elem) => { _deselect_all_buttons(); // update search input with selected button's path. elem.dataset.selected = ""; - tab.updateSearch(elem.textContent.trim(), {is_dir: true}); - - // Select the corresponding tree view button. - if ("selected" in elem.dataset) { - const tree_row = tab.container_elem.querySelector(`.tree-list-item[data-path="${elem.textContent.trim()}"]`); - if (isElement(tree_row)) { - tab.tree_list.onRowSelected(tree_row.dataset.divId, tree_row); - } - } + tab.applyDirectoryFilter(elem.textContent.trim()); }; const _deselect_button = (elem) => { delete elem.dataset.selected; - tab.updateSearch(""); - // deselect tree view rows - tab.tree_list.onRowSelected(); // empty params deselects all rows. + tab.applyDirectoryFilter(""); }; if ("selected" in event.target.dataset) { @@ -788,6 +850,8 @@ function extraNetworksBtnDirsViewItemOnClick(event, tabname_full) { _select_button(event.target); } + tab.setDirectoryButtons(event.target); + event.stopPropagation(); } @@ -869,6 +933,8 @@ function extraNetworksControlTreeViewOnClick(event, tabname_full) { const resize_handle_row = tab.tree_list.scroll_elem.closest(".resize-handle-row"); resize_handle_row.classList.toggle("resize-handle-hidden", !show); + tab.setDirectoryButtons(); + event.stopPropagation(); } @@ -889,6 +955,8 @@ function extraNetworksControlDirsViewOnClick(event, tabname_full) { ".extra-network-content--dirs-view" ).classList.toggle("hidden", !show); + tab.setDirectoryButtons(); + event.stopPropagation(); } @@ -932,41 +1000,30 @@ function extraNetworksTreeFileOnClick(event, btn, tabname_full) { return; } -function extraNetworksTreeDirectoryOnClick(event, btn, tabname_full) { +async function extraNetworksTreeDirectoryOnClick(event, btn, tabname_full) { const true_targ = event.target; const div_id = btn.dataset.divId; const tab = extra_networks_tabs[tabname_full]; - const prev_selected_elem = gradioApp().querySelector(".tree-list-item[data-selected='']"); if (true_targ.matches(".tree-list-item-action--leading .tree-list-item-action-chevron")) { // If user clicks on the chevron, then we do not select the folder. - tab.tree_list.onRowExpandClick(div_id, btn); + await tab.tree_list.onRowExpandClick(div_id, btn); + tab.setDirectoryButtons(); } else if (true_targ.matches(".tree-list-item-action--trailing .tree-list-item-action-expand")) { - tab.tree_list.onExpandAllClick(div_id); + await tab.tree_list.onExpandAllClick(div_id); } else if (true_targ.matches(".tree-list-item-action--trailing .tree-list-item-action-collapse")) { - tab.tree_list.onCollapseAllClick(div_id); + await tab.tree_list.onCollapseAllClick(div_id); } else { // user clicked anywhere else on the row - tab.tree_list.onRowSelected(div_id, btn); - // Select the corresponding dirs view button. - if ("selected" in btn.dataset) { - tab.container_elem.querySelectorAll(".extra-network-dirs-view-button").forEach(elem => { - if (elem.textContent.trim() === btn.dataset.path) { - elem.dataset.selected = ""; - } else { - delete elem.dataset.selected; - } - }); - + await tab.tree_list.onRowSelected(div_id, btn); + tab.setDirectoryButtons(btn); + const filter_str = "selected" in btn.dataset ? btn.dataset.path : ""; + if (btn.dataset.treeEntryType === "dir") { + tab.applyDirectoryFilter(filter_str); + } else { + tab.updateSearch(filter_str); } - const search_txt = "selected" in btn.dataset ? btn.dataset.path : ""; - tab.updateSearch(search_txt, {is_dir: btn.dataset.treeEntryType === "dir" && search_txt !== ""}); - } - const selected_elem = gradioApp().querySelector(".tree-list-item[data-selected='']"); - if (isElement(prev_selected_elem) && !isElement(selected_elem)) { - // if a selected element was removed, clear filter. - tab.updateSearch(""); } } diff --git a/javascript/extraNetworksClusterize.js b/javascript/extraNetworksClusterize.js index cc553a707..be5b186fe 100644 --- a/javascript/extraNetworksClusterize.js +++ b/javascript/extraNetworksClusterize.js @@ -40,15 +40,15 @@ class ExtraNetworksClusterize extends Clusterize { tabname = ""; extra_networks_tabname = ""; - filter_as_dir = false; - // Override base class defaults default_sort_mode_str = "divId"; default_sort_dir_str = "ascending"; default_filter_str = ""; + default_directory_filter_str = ""; sort_mode_str = this.default_sort_mode_str; sort_dir_str = this.default_sort_dir_str; filter_str = this.default_filter_str; + directory_filter_str = this.default_directory_filter_str; constructor(args) { super(args); @@ -141,14 +141,21 @@ class ExtraNetworksClusterize extends Clusterize { this.sortData(); } - setFilterStr(filter_str, is_dir) { + setFilterStr(filter_str) { if (isString(filter_str) && this.filter_str !== filter_str) { this.filter_str = filter_str; } else if (isNullOrUndefined(filter_str)) { this.filter_str = this.default_filter_str; } + this.filterData(); + } - this.filter_as_dir = is_dir === true; + setDirectoryFilterStr(filter_str) { + if (isString(filter_str) && this.directory_filter_str !== filter_str) { + this.directory_filter_str = filter_str; + } else if (isNullOrUndefined(filter_str)) { + this.directory_filter_str = this.default_directory_filter_str; + } this.filterData(); } @@ -229,7 +236,6 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { ...args, no_data_text: "Directory is empty.", }); - } clear() { @@ -258,6 +264,9 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { /** Recursively sets the visibility of a div_id and its children. */ const this_obj = this.data_obj[div_id]; this_obj.visible = visible; + if (!visible && div_id === this.selected_div_id) { + this.selected_div_id = null; + } for (const child_id of this_obj.children) { this.#setVisibility(child_id, visible && this_obj.expanded); } @@ -271,7 +280,6 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { */ if (isNullOrUndefined(div_id) && isNullOrUndefined(elem)) { if (!isNullOrUndefined(this.selected_div_id) && keyExistsLogError(this.data_obj, this.selected_div_id)) { - this.data_obj[this.selected_div_id].selected = false; this.selected_div_id = null; for (const elem of this.content_elem.children) { delete elem.dataset.selected; @@ -288,20 +296,16 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { return; } - override = override === true; - if (!isNullOrUndefined(this.selected_div_id) && div_id !== this.selected_div_id) { const prev_elem = this.content_elem.querySelector(`[data-div-id="${this.selected_div_id}"]`); // deselect current selection if exists on page if (isElement(prev_elem)) { delete prev_elem.dataset.selected; - this.data_obj[prev_elem.dataset.divId].selected = false; + this.selected_div_id = null; } } - - elem.toggleAttribute("data-selected"); + elem.toggleAttribute("data-selected", override); this.selected_div_id = "selected" in elem.dataset ? div_id : null; - this.data_obj[elem.dataset.divId].selected = "selected" in elem.dataset; } getMaxRowWidth() { @@ -576,12 +580,15 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { /** Filters data by a string and returns number of items after filter. */ let n_visible = 0; for (const [div_id, v] of Object.entries(this.data_obj)) { - let visible; - if (this.filter_str && this.filter_as_dir) { - // Filtering as directory only shows direct children. Case sensitive - // comparison against the relative directory of each object. - visible = this.filter_str === v.rel_parent_dir; - } else if (v.search_only && this.filter_str.length >= 4) { + let visible = true; + // Filtering as directory only shows direct children. Case sensitive + // comparison against the relative directory of each object. + if (this.directory_filter_str && this.directory_filter_str !== v.rel_parent_dir) { + this.data_obj[div_id].visible = false; + continue; + } + + if (v.search_only && this.filter_str.length >= 4) { // Custom filter for items marked search_only=true. // TODO: Not ideal. This disregards any search_terms set on the model. // However the search terms are currently set up in a way that would @@ -594,9 +601,7 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { // All other filters treated case insensitive. visible = v.search_terms.toLowerCase().indexOf(this.filter_str.toLowerCase()) !== -1; } - if (v.search_only && this.filter_str.length < 4) { - visible = false; - } + this.data_obj[div_id].visible = visible; if (visible) { n_visible++; diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index c20c0b39e..89db499d6 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -503,8 +503,8 @@ class ExtraNetworksPage: search_only = any(x.startswith(".") for x in filename.split(os.sep)) self.cards[div_id] = CardListItem(div_id, card_html) self.cards[div_id].abspath = os.path.normpath(item.get("filename", "")) - for parent_dir in self.allowed_directories_for_previews(): - parent_dir = os.path.dirname(os.path.abspath(parent_dir)) + for path in self.allowed_directories_for_previews(): + parent_dir = os.path.dirname(os.path.abspath(path)) if self.cards[div_id].abspath.startswith(parent_dir): self.cards[div_id].relpath = os.path.relpath(self.cards[div_id].abspath, parent_dir) break