Compare commits

...

3 Commits

Author SHA1 Message Date
Sj-Si b79ea9c1b1
Merge fbefe7f4fe into ddb28b33a3 2024-05-03 23:21:28 +00:00
Sj-Si fbefe7f4fe Add option for default tree expanded depth. 2024-05-03 19:20:47 -04:00
Sj-Si b497467436 fix hidden directory options and their functionality. 2024-05-03 18:18:02 -04:00
5 changed files with 135 additions and 85 deletions

View File

@ -1,2 +1,2 @@
<button class='lg secondary gradio-button custom-button extra-network-dirs-view-button {extra_class}' title="{title}"
data-path="{path}">{label}</button>
{data_attributes}>{label}</button>

View File

@ -748,8 +748,12 @@ class ExtraNetworksTab {
await this.tree_list.onRowSelected();
}
// Don't use escaped path here since this is pure javascript beyond this point.
let directory_filter = source_elem.dataset.path;
if ("directoryFilterOverride" in source_elem.dataset) {
directory_filter = source_elem.dataset.directoryFilterOverride;
}
this.applyDirectoryFilter(
"selected" in source_elem.dataset ? source_elem.dataset.path : null,
"selected" in source_elem.dataset ? directory_filter : null,
"recurse" in source_elem.dataset,
);
return;
@ -770,8 +774,12 @@ class ExtraNetworksTab {
const div_id = source_is_tree ? source_elem.dataset.divId : other_elem.dataset.divId;
_set_recursion_depth(div_id, data_recurse);
// Don't use escaped path here since this is pure javascript beyond this point.
let directory_filter = source_elem.dataset.path;
if ("directoryFilterOverride" in source_elem.dataset) {
directory_filter = source_elem.dataset.directoryFilterOverride;
}
this.applyDirectoryFilter(
"selected" in source_elem.dataset ? source_elem.dataset.path : null,
"selected" in source_elem.dataset ? directory_filter : null,
"recurse" in source_elem.dataset,
);
}

View File

@ -68,7 +68,8 @@ class ExtraNetworksClusterize extends Clusterize {
await this.initData();
// can't use super class' sort since it relies on setup being run first.
// but we do need to make sure to sort the new data before continuing.
await this.setMaxItems(Object.keys(this.data_obj).length);
const max_items = Object.keys(this.data_obj).filter(k => this.data_obj[k].visible).length;
await this.setMaxItems(max_items);
await this.refresh(true);
await this.options.callbacks.sortData();
}
@ -581,28 +582,26 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
if (this.directory_filter_str && this.directory_filter_recurse) {
// Filter as directory with recurse shows all nested children.
// Case sensitive comparison against the relative directory of each object.
this.data_obj[div_id].visible = v.rel_parent_dir.startsWith(this.directory_filter_str);
if (!this.data_obj[div_id].visible) {
v.visible = v.rel_parent_dir.startsWith(this.directory_filter_str);
if (!v.visible) {
continue;
}
} else {
// Filtering as directory without recurse 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;
v.visible = false;
continue;
}
}
if (v.search_only && this.filter_str.length >= 4) {
if (v.search_only) {
// 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
// reveal hidden models if the user searches for any visible parent
// directories. For example, searching for "Lora" would reveal a hidden
// model in "Lora/.hidden/model.safetensors" since that full path is
// included in the search terms.
visible = v.rel_parent_dir.toLowerCase().indexOf(this.filter_str.toLowerCase()) !== -1;
if (this.directory_filter_str || this.filter_str.length >= 4) {
visible = v.search_terms.toLowerCase().indexOf(this.filter_str.toLowerCase()) !== -1;
} else {
visible = false;
}
} else {
// All other filters treated case insensitive.
visible = v.search_terms.toLowerCase().indexOf(this.filter_str.toLowerCase()) !== -1;

View File

@ -250,8 +250,10 @@ options_templates.update(options_section(('interrogate', "Interrogate"), {
}))
options_templates.update(options_section(('extra_networks', "Extra Networks", "sd"), {
"extra_networks_show_hidden_directories": OptionInfo(True, "Show hidden directories").info("directory is hidden if its name starts with \".\".").needs_reload_ui(),
"extra_networks_hidden_models": OptionInfo("When searched", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info('"When searched" option will only show the item when the search string has 4 characters or more').needs_reload_ui(),
"extra_networks_show_hidden_directories_buttons": OptionInfo(False, "Show buttons for hidden directories in the directory and tree views.").info("a directory is hidden if its name starts with a period (\".\").").needs_reload_ui(),
"extra_networks_show_hidden_models_cards": OptionInfo("Never", "Show cards for models in hidden directories", gr.Radio, {"choices": ["Always", "When searched", "Never"]}).info("\"When searched\" will only show cards when the search string has 4 characters or more and the search string matches either the model name or the hidden directory name (or any of its subdirectories).").needs_reload_ui(),
"extra_networks_show_hidden_models_in_tree_view": OptionInfo(False, "Show entries for models inside hidden directories in the tree view.").info("This option only applies if the \"Show buttons for hidden directories\" option is enabled.").needs_reload_ui(),
"extra_networks_tree_view_expand_depth_default": OptionInfo(1, "Expand the tree view to this folder depth by default.").needs_reload_ui(),
"extra_networks_default_multiplier": OptionInfo(1.0, "Default multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 2.0, "step": 0.01}),
"extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks").info("in pixels"),
"extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks").info("in pixels"),

View File

@ -94,13 +94,22 @@ class DirectoryTreeNode:
self.root_dir = root_dir
self.abspath = abspath
self.parent = parent
self.id = ""
self.depth = 0
self.is_dir = False
self.item = None
self.relpath = os.path.relpath(self.abspath, self.root_dir)
self.children: list["DirectoryTreeNode"] = []
self.is_dir = os.path.isdir(self.abspath)
# If any parent dirs are hidden, this node is also considered hidden.
self.is_hidden = any(x.startswith(".") for x in self.abspath.split(os.sep))
self.id = ""
self.depth = 0
self.item = None
self.rel_from_hidden = None
if self.is_hidden:
# Get the relative path starting from the first hidden directory.
parts = self.relpath.split(os.sep)
idxs = [i for i, x in enumerate(parts) if x.startswith(".")]
if len(idxs) > 0:
self.rel_from_hidden = os.path.join(*parts[idxs[0] :])
# If a parent is passed, then we add this instance to the parent's children.
if self.parent is not None:
@ -110,24 +119,19 @@ class DirectoryTreeNode:
def add_child(self, child: "DirectoryTreeNode") -> None:
self.children.append(child)
def build(self, items: dict[str, dict], include_hidden: bool = False) -> None:
def build(self, items: dict[str, dict]) -> None:
"""Builds a tree of nodes as children of this instance.
Args:
items: A dictionary where keys are absolute filepaths for directories/files.
The values are dictionaries representing extra networks items.
include_hidden: Whether to include hidden directories in the tree.
"""
self.is_dir = os.path.isdir(self.abspath)
if self.is_dir:
for x in os.listdir(self.abspath):
child_path = os.path.join(self.abspath, x)
# Skip hidden directories if include_hidden is False
if os.path.isdir(child_path) and os.path.basename(child_path).startswith(".") and not include_hidden:
continue
# Add all directories but only add files if they are in the items dict.
if os.path.isdir(child_path) or child_path in items:
DirectoryTreeNode(self.root_dir, child_path, self).build(items, include_hidden)
DirectoryTreeNode(self.root_dir, child_path, self).build(items)
else:
self.item = items.get(self.abspath, None)
@ -420,14 +424,10 @@ class ExtraNetworksPage:
filename = os.path.normpath(item.get("filename", ""))
# if this is true, the item must not be shown in the default view,
# and must instead only be shown when searching for it
show_hidden_models = str(shared.opts.extra_networks_hidden_models).strip().lower()
if show_hidden_models == "always":
search_only = False
else:
# If any parent dirs are hidden, the model is also hidden.
search_only = any(x.startswith(".") for x in filename.split(os.sep))
if search_only and show_hidden_models == "never":
show_hidden_models = str(shared.opts.extra_networks_show_hidden_models_cards).strip().lower()
# If any parent dirs are hidden, the model is also hidden.
is_hidden = any(x.startswith(".") for x in filename.split(os.sep))
if show_hidden_models == "never" and is_hidden:
return ""
sort_keys = {}
@ -506,7 +506,17 @@ class ExtraNetworksPage:
# Mapping from the self.nodes div_ids to the sorted index.
div_id_to_idx[node.id] = i
show_hidden_cards = str(shared.opts.extra_networks_show_hidden_models_cards).strip().lower()
for node in nodes.values():
search_only = False
if show_hidden_cards == "always":
search_only = False
elif show_hidden_cards == "when searched":
search_only = node.is_hidden
elif "never" == show_hidden_cards and node.is_hidden:
# We never show hidden cards here so don't even add it to the results.
continue
card = CardListItem(node.id, "")
card.node = node
item = node.item
@ -518,13 +528,7 @@ class ExtraNetworksPage:
sort_keys["path"] = div_id_to_idx[node.id]
search_terms = item.get("search_terms", [])
show_hidden_models = str(shared.opts.extra_networks_hidden_models).strip().lower()
if show_hidden_models == "always":
search_only = False
else:
# If any parent dirs are hidden, the model is also hidden.
filename = os.path.normpath(item.get("filename", ""))
search_only = any(x.startswith(".") for x in filename.split(os.sep))
card.abspath = os.path.normpath(item.get("filename", ""))
for path in self.allowed_directories_for_previews():
parent_dir = os.path.dirname(os.path.abspath(path))
@ -534,15 +538,14 @@ class ExtraNetworksPage:
card.sort_keys = sort_keys
card.search_terms = " ".join(search_terms)
card.search_only = search_only
card.rel_parent_dir = os.path.dirname(card.relpath)
if card.search_only:
parents = card.relpath.split(os.sep)
idxs = [i for i, x in enumerate(parents) if x.startswith(".")]
if len(idxs) > 0:
card.rel_parent_dir = os.path.join(*parents[idxs[0]:])
else:
print(f"search_only is enabled but no hidden dir found: {card.abspath}")
if card.node.rel_from_hidden is not None:
card.rel_parent_dir = os.path.dirname(card.node.rel_from_hidden)
if card.search_only and card.node.rel_from_hidden is not None:
# Limit the ways of searching for `search_only` cards so that the user
# can't search for a parent to a hidden directory to see hidden cards.
card.search_terms = card.search_terms.replace(node.relpath, card.node.rel_from_hidden)
self.cards[node.id] = card
@ -595,15 +598,22 @@ class ExtraNetworksPage:
_res.extend(_gen_indents(node.parent))
return _res
show_hidden_dirs = shared.opts.extra_networks_show_hidden_directories_buttons
show_hidden_models = shared.opts.extra_networks_show_hidden_models_in_tree_view
expand_depth = int(shared.opts.extra_networks_tree_view_expand_depth_default)
for node in self.nodes.values():
tree_item = TreeListItem(node.id, "")
# If root node, expand and set visible.
if node.parent is None:
tree_item.expanded = True
tree_item.visible = True
if node.is_hidden and node.is_dir and not show_hidden_dirs:
continue
# If direct child of root node, set visible.
if node.parent is not None and node.parent.parent is None:
if node.is_hidden and not node.is_dir and show_hidden_dirs and not show_hidden_models:
continue
tree_item = TreeListItem(node.id, "")
if node.depth <= expand_depth:
tree_item.expanded = True
if node.depth <= expand_depth + 1:
tree_item.visible = True
tree_item.node = node
@ -616,12 +626,44 @@ class ExtraNetworksPage:
indent_html = "".join(indent_html)
indent_html = f"<div class='tree-list-item-indent'>{indent_html}</div>"
children = []
if node.is_dir: # directory
if show_files:
dir_is_empty = node.children == []
else:
dir_is_empty = all(not x.is_dir for x in node.children)
if node.is_hidden and not show_hidden_models:
dir_is_empty = all(not x.is_dir for x in node.children)
if not dir_is_empty:
for child in tree_item.node.children:
if child.is_dir:
if child.is_hidden:
if show_hidden_dirs:
children.append(child.id)
else:
children.append(child.id)
elif show_files:
if child.is_hidden:
if show_hidden_dirs and show_hidden_models:
children.append(child.id)
else:
children.append(child.id)
data_attributes = {
"data-div-id": f'"{node.id}"',
"data-parent-id": f'"{parent_id}"',
"data-tree-entry-type": "dir",
"data-depth": node.depth,
"data-path": f'"{node.relpath}"',
"data-expanded": tree_item.expanded,
}
if node.is_hidden:
data_attributes["data-directory-filter-override"] = f'"{node.rel_from_hidden}"'
tree_item.html = self.build_tree_html_row(
tabname=tabname,
label=os.path.basename(node.abspath),
@ -629,14 +671,7 @@ class ExtraNetworksPage:
btn_title=f'"{node.abspath}"',
dir_is_empty=dir_is_empty,
indent_html=indent_html,
data_attributes={
"data-div-id": f'"{node.id}"',
"data-parent-id": f'"{parent_id}"',
"data-tree-entry-type": "dir",
"data-depth": node.depth,
"data-path": f'"{node.relpath}"',
"data-expanded": node.parent is None, # Expand root directories
},
data_attributes=data_attributes,
)
self.tree[node.id] = tree_item
else: # file
@ -644,6 +679,9 @@ class ExtraNetworksPage:
# Don't add file if files are disabled in the options.
continue
if node.is_hidden and not show_hidden_models:
continue
item_name = node.item.get("name", "").strip()
data_path = os.path.normpath(node.item.get("filename", "").strip())
data_attributes = {
@ -673,11 +711,6 @@ class ExtraNetworksPage:
)
self.tree[node.id] = tree_item
if show_files:
children = [x.id for x in tree_item.node.children]
else:
children = [x.id for x in tree_item.node.children if x.is_dir]
res[node.id] = {
"parent": parent_id,
"children": children,
@ -697,6 +730,9 @@ class ExtraNetworksPage:
if not node.is_dir:
continue
if node.is_hidden and not shared.opts.extra_networks_show_hidden_directories_buttons:
continue
if node.parent is None:
label = node.relpath
else:
@ -704,14 +740,28 @@ class ExtraNetworksPage:
parts = [x for x in node.relpath.split(os.sep) if x]
label = os.path.join(*parts[1:])
data_attributes = {"data-path": f'"{node.relpath}"'}
if node.is_hidden:
data_attributes["data-directory-filter-override"] = f'"{node.rel_from_hidden}"'
data_attributes_str = ""
for k, v in data_attributes.items():
if isinstance(v, (bool,)):
# Boolean data attributes only need a key when true.
if v:
data_attributes_str += f"{k} "
elif v not in [None, "", "''", '""']:
data_attributes_str += f"{k}={v} "
res.append(
self.btn_dirs_view_item_tpl.format(
**{
"extra_class": "search-all" if node.relpath == "" else "",
"tabname_full": f"{tabname}_{self.extra_networks_tabname}",
"title": html.escape(node.abspath),
"path": html.escape(node.relpath),
"label": html.escape(label),
"data_attributes": data_attributes_str,
}
)
)
@ -754,15 +804,10 @@ class ExtraNetworksPage:
if not os.path.exists(abspath):
continue
self.tree_roots[abspath] = DirectoryTreeNode(os.path.dirname(abspath), abspath, None)
self.tree_roots[abspath].build(
tree_items,
include_hidden=shared.opts.extra_networks_show_hidden_directories,
)
self.tree_roots[abspath].build(tree_items)
cards_list_loading_splash_content = "Loading..."
no_cards_html_dirs = "".join(
[f"<li>{x}</li>" for x in self.allowed_directories_for_previews()]
)
no_cards_html_dirs = "".join([f"<li>{x}</li>" for x in self.allowed_directories_for_previews()])
cards_list_no_data_splash_content = (
"<div class='nocards'>"
"<h1>Nothing here. Add some content to the following directories:</h1>"
@ -1220,11 +1265,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
elem_id=f"{tabname_full}_extra_refresh_internal",
visible=False,
)
button_refresh.click(
fn=functools.partial(refresh, tabname_full),
inputs=[],
outputs=list(ui.pages.values()),
).then(
button_refresh.click(fn=functools.partial(refresh, tabname_full), inputs=[], outputs=list(ui.pages.values()),).then(
fn=lambda: None,
_js="setupAllResizeHandles",
).then(