fix bugs with multi-function buttons and recursive filters.

This commit is contained in:
Sj-Si 2024-04-30 14:27:13 -04:00
parent d912e720f7
commit 250e7673e8
5 changed files with 267 additions and 168 deletions

View file

@ -0,0 +1,11 @@
<div class="tree-list-item-action--chevron {extra_classes}">
<svg class="chevron-icon-single" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M 4 4 H 12 V 12" />
</svg>
<svg class="chevron-icon-double" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M 5 3 H 13 V 11" />
<path d="M 1 7 H 9 V 15" />
</svg>
</div>

View file

@ -540,11 +540,12 @@ class ExtraNetworksTab {
row.style.gridTemplateColumns = `${max_width}px ${pad}px 1fr`;
}
async setDirectoryButtons({source_elem, source_selector, source_class}={}) {
async setDirectoryButtons({source_elem, source_selector, source_class, reset_all}={}) {
// At least one argument must be specified.
if (isNullOrUndefined(source_elem)
&& isNullOrUndefined(source_selector)
&& isNullOrUndefined(source_class)) {
&& isNullOrUndefined(source_class)
&& isNullOrUndefined(reset_all)) {
console.error("At least one argument must be specified.")
return;
}
@ -596,6 +597,13 @@ class ExtraNetworksTab {
};
_set_recursion_depth.bind(this);
if (reset_all === true) {
_reset_all_buttons();
await this.tree_list.onRowSelected(); // no args deselects all.
this.applyDirectoryFilter();
return;
}
if (!_exists(source_elem) && isString(source_selector)) {
source_elem = this.container_elem.querySelector(source_selector);
}
@ -604,11 +612,17 @@ class ExtraNetworksTab {
source_elem = this.container_elem.querySelector(`${source_class}[data-selected]`);
}
// try to find any selected buttons to use as a source.
if (!_exists(source_elem)) {
source_elem = this.container_elem.querySelector("[data-selected]");
}
// If we got here with no source elem, then we will take this to mean that
// we are deselecting all.
if (!_exists(source_elem)) {
_reset_all_buttons();
await this.tree_list.onRowSelected(); // no args deselects all.
this.applyDirectoryFilter();
return;
}
@ -626,6 +640,10 @@ class ExtraNetworksTab {
} else {
await this.tree_list.onRowSelected();
}
this.applyDirectoryFilter(
"selected" in source_elem.dataset ? data_path : null,
"recurse" in source_elem.dataset,
);
return;
}
@ -643,6 +661,10 @@ class ExtraNetworksTab {
await this.tree_list.onRowSelected(source_is_tree ? source_elem : other_elem);
const div_id = source_is_tree ? source_elem.dataset.divId : other_elem.dataset.divId;
_set_recursion_depth(div_id, data_recurse);
this.applyDirectoryFilter(
"selected" in source_elem.dataset ? data_path : null,
"recurse" in source_elem.dataset,
);
}
}
@ -1129,11 +1151,11 @@ async function extraNetworksTreeDirectoryOnDblClick(event) {
// stopPropagation so we don't also trigger event on parent since this btn is nested.
event.stopPropagation();
const btn = event.target.closest(".tree-list-item");
if ("expanded" in btn.dataset) {
await extraNetworksBtnTreeViewCollapseOnClick(event);
} else {
await extraNetworksBtnTreeViewExpandOnClick(event);
}
const pane = btn.closest(".extra-network-pane");
const div_id = btn.dataset.divId;
const tab = extra_networks_tabs[pane.dataset.tabnameFull];
await tab.tree_list.toggleRowExpanded(div_id);
tab.setDirectoryButtons({source_class: ".tree-list-item"});
}
async function extraNetworksTreeDirectoryOnClick(event) {
@ -1141,48 +1163,36 @@ async function extraNetworksTreeDirectoryOnClick(event) {
if (event.target.closest(".tree-list-item-action")) {
return;
}
const btn = event.target.closest(".tree-list-item");
const pane = btn.closest(".extra-network-pane");
const tab = extra_networks_tabs[pane.dataset.tabnameFull];
tab.setDirectoryButtons({source_elem: btn});
}
async function extraNetworksTreeDirectoryChevronOnLongPress(event) {
// stopPropagation so we don't also trigger event on parent since this btn is nested.
event.stopPropagation();
const chevron = event.target.closest(".tree-list-item-action--chevron");
const btn = event.target.closest(".tree-list-item");
const pane = btn.closest(".extra-network-pane");
const div_id = btn.dataset.divId;
const tab = extra_networks_tabs[pane.dataset.tabnameFull];
if ("expanded" in btn.dataset) {
await tab.tree_list.collapseAllRows(div_id);
} else {
await tab.tree_list.expandAllRows(div_id);
}
tab.setDirectoryButtons({source_class: ".tree-list-item"});
}
async function extraNetworksBtnTreeViewChevronOnClick(event) {
// stopPropagation so we don't also trigger event on parent since this btn is nested.
event.stopPropagation();
const btn = event.target.closest(".tree-list-item-action-chevron");
const row = event.target.closest(".tree-list-item");
const pane = btn.closest(".extra-network-pane");
const div_id = row.dataset.divId;
const tab = extra_networks_tabs[pane.dataset.tabnameFull];
await tab.tree_list.onRowExpandClick(div_id, btn);
tab.setDirectoryButtons({source_class: ".tree-list-item"});
}
async function extraNetworksBtnTreeViewExpandOnClick(event) {
// stopPropagation so we don't also trigger event on parent since this btn is nested.
event.stopPropagation();
const btn = event.target.closest(".tree-list-item");
const pane = btn.closest(".extra-network-pane");
const div_id = btn.dataset.divId;
const tab = extra_networks_tabs[pane.dataset.tabnameFull];
await tab.tree_list.onExpandAllClick(div_id);
tab.setDirectoryButtons({source_class: ".tree-list-item"});
}
async function extraNetworksBtnTreeViewCollapseOnClick(event) {
// stopPropagation so we don't also trigger event on parent since this btn is nested.
event.stopPropagation();
const btn = event.target.closest(".tree-list-item");
const pane = btn.closest(".extra-network-pane");
const div_id = btn.dataset.divId;
const tab = extra_networks_tabs[pane.dataset.tabnameFull];
await tab.tree_list.onCollapseAllClick(div_id);
await tab.tree_list.toggleRowExpanded(div_id);
tab.setDirectoryButtons({source_class: ".tree-list-item"});
}
@ -1278,9 +1288,6 @@ function extraNetworksSetupEventDelegators() {
".copy-path-button": extraNetworksBtnCopyPathOnClick,
".edit-button": extraNetworksBtnEditMetadataOnClick,
".metadata-button": extraNetworksBtnShowMetadataOnClick,
".tree-list-item-action-chevron": extraNetworksBtnTreeViewChevronOnClick,
".tree-list-item-action-expand": extraNetworksBtnTreeViewExpandOnClick,
".tree-list-item-action-collapse": extraNetworksBtnTreeViewCollapseOnClick,
".extra-network-control--search-clear": extraNetworksControlSearchClearOnClick,
".extra-network-control--sort-mode": extraNetworksControlSortModeOnClick,
".extra-network-control--sort-dir": extraNetworksControlSortDirOnClick,
@ -1297,40 +1304,56 @@ function extraNetworksSetupEventDelegators() {
}
});
// effectively just a click but we need to handle separate from "click" events
// since the same elements are also handling long press events.
const short_press_event_map = {
".tree-list-item--dir": extraNetworksTreeDirectoryOnClick,
".extra-network-dirs-view-button": extraNetworksBtnDirsViewItemOnClick,
}
const short_press_ignore_map = {
".tree-list-item--dir": [".tree-list-item-action"],
}
const long_press_event_map = {
".tree-list-item--dir": extraNetworksTreeDirectoryOnLongPress,
".extra-network-dirs-view-button": extraNetworksBtnDirsViewItemOnLongPress,
};
const long_press_ignore_map = {
".tree-list-item--dir": [".tree-list-item-action"],
}
// Order in these maps matters since we may have separate events for both a div
// and for a child within that div however if the child is clicked then we wouldn't
// want to handle clicks for the parent as well. In this case, order the child's event
// before the parent and the parent will be ignored.
// Can add entries with handler=null to forcefully ignore specific event types.
const dbl_press_event_map = {
".tree-list-item--dir": extraNetworksTreeDirectoryOnDblClick,
}
const short_press_event_map = [
{
"selector": ".tree-list-item-action--chevron",
"handler": extraNetworksBtnTreeViewChevronOnClick,
},
{
"selector": ".tree-list-item--dir",
"negative": ".tree-list-item-action",
"handler": extraNetworksTreeDirectoryOnClick,
},
{
"selector": ".extra-network-dirs-view-button",
"handler": extraNetworksBtnDirsViewItemOnClick,
},
];
const dbl_press_ignore_map = {
".tree-list-item--dir": [".tree-list-item-action"],
}
const on_short_press = (event, elem, selector) => {
let ignores = short_press_ignore_map[selector];
ignores = ignores || [];
for (const ignore of Object.values(ignores)) {
if (event.target.closest(ignore)) {
return;
}
const long_press_event_map = [
{
"selector": ".tree-list-item-action--chevron",
"handler": extraNetworksTreeDirectoryChevronOnLongPress,
},
{
"selector": ".tree-list-item--dir",
"negative": ".tree-list-item-action",
"handler": extraNetworksTreeDirectoryOnLongPress,
},
{
"selector": ".extra-network-dirs-view-button",
"handler": extraNetworksBtnDirsViewItemOnLongPress,
},
];
const dbl_press_event_map = [
{
"selector": ".tree-list-item--dir",
"negative": ".tree-list-item-action",
"handler": extraNetworksTreeDirectoryOnDblClick,
},
];
const on_short_press = (event, elem, handler) => {
if (!handler) {
return;
}
elem.classList.remove("pressed");
// Toggle
if (elem.classList.contains("long-pressed")) {
elem.classList.remove("long-pressed");
@ -1342,18 +1365,13 @@ function extraNetworksSetupEventDelegators() {
}
elem.dispatchEvent(new Event("shortpress", event));
short_press_event_map[selector](event);
handler(event);
};
const on_long_press = (event, elem, selector) => {
let ignores = long_press_ignore_map[selector];
ignores = ignores || [];
for (const ignore of Object.values(ignores)) {
if (event.target.closest(ignore)) {
return;
}
const on_long_press = (event, elem, handler) => {
if (!handler) {
return;
}
elem.classList.remove("pressed");
// If long pressed, we deselect.
// Else we set as long pressed.
if (elem.classList.contains("short-pressed")) {
@ -1368,61 +1386,91 @@ function extraNetworksSetupEventDelegators() {
}
elem.dispatchEvent(new Event("longpress", event));
long_press_event_map[selector](event);
handler(event);
};
const on_dbl_press = (event, elem, selector) => {
let ignores = dbl_press_ignore_map[selector];
ignores = ignores || [];
for (const ignore of Object.values(ignores)) {
if (event.target.closest(ignore)) {
return;
}
const on_dbl_press = (event, elem, handler) => {
if (!handler) {
return;
}
elem.classList.remove("pressed");
dbl_press_event_map[selector](event);
handler(event);
}
let press_timer;
let press_time_ms = 800;
const event_maps = [
short_press_event_map,
long_press_event_map,
dbl_press_event_map,
];
const selectors = Object.keys(Object.assign({}, ...event_maps));
window.addEventListener("mousedown", event => {
for (const selector of selectors) {
const elem = event.target.closest(selector);
if (elem) {
for (const obj of short_press_event_map) {
const elem = event.target.closest(obj.selector);
const neg = obj.negative ? event.target.closest(obj.negative) : null;
if (elem && !neg) {
event.preventDefault();
event.stopPropagation();
elem.classList.add("pressed");
}
}
for (const obj of long_press_event_map) {
const elem = event.target.closest(obj.selector);
const neg = obj.negative ? event.target.closest(obj.negative) : null;
if (elem && !neg) {
event.preventDefault();
event.stopPropagation();
elem.classList.add("pressed");
press_timer = setTimeout(() => {
elem.classList.remove("pressed");
on_long_press(event, elem, obj.handler);
}, press_time_ms);
}
}
for (const obj of dbl_press_event_map) {
const elem = event.target.closest(obj.selector);
const neg = obj.negative ? event.target.closest(obj.negative) : null;
if (elem && !neg) {
event.preventDefault();
event.stopPropagation();
elem.classList.add("pressed");
press_timer = setTimeout((event) => {
on_long_press(event, elem, selector);
}, press_time_ms, event);
return;
}
}
});
window.addEventListener("mouseup", event => {
for (const selector of selectors) {
const elem = event.target.closest(selector);
if (elem) {
for (const obj of short_press_event_map) {
const elem = event.target.closest(obj.selector);
const neg = obj.negative ? event.target.closest(obj.negative) : null;
if (elem && !neg) {
event.preventDefault();
event.stopPropagation();
clearTimeout(press_timer);
if (elem.classList.contains("pressed")) {
if (event.detail === 1) {
on_short_press(event, elem, selector);
} else if (event.detail === 2) {
on_dbl_press(event, elem, selector);
if (event.detail === 1
|| !dbl_press_event_map.map(x => x.selector).includes(obj.selector)
) {
elem.classList.remove("pressed");
on_short_press(event, elem, obj.handler);
}
}
return;
}
}
if (event.detail === 2) {
for (const obj of dbl_press_event_map) {
const elem = event.target.closest(obj.selector);
const neg = obj.negative ? event.target.closest(obj.negative) : null;
if (elem && !neg) {
event.preventDefault();
event.stopPropagation();
clearTimeout(press_timer);
if (elem.classList.contains("pressed")) {
elem.classList.remove("pressed");
on_dbl_press(event, elem, obj.handler);
}
}
}
}
// long_press_event_map is handled by the timer setup in "mousedown" handlers.
});
}

View file

@ -347,7 +347,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
return max_width;
}
async onExpandAllClick(div_id) {
async expandAllRows(div_id) {
/** Recursively expands all directories below the passed div_id. */
if (!keyExistsLogError(this.data_obj, div_id)) {
return;
}
@ -372,7 +373,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
await this.sortData();
}
async onCollapseAllClick(div_id) {
async collapseAllRows(div_id) {
/** Recursively collapses all directories below the passed div_id. */
if (!keyExistsLogError(this.data_obj, div_id)) {
return;
}
@ -403,8 +405,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
await this.sortData();
}
async onRowExpandClick(div_id, elem) {
/** Expands or collapses a row to show/hide children. */
async toggleRowExpanded(div_id) {
/** Toggles a row between expanded and collapses states. */
if (!keyExistsLogError(this.data_obj, div_id)) {
return;
}
@ -584,11 +586,11 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
for (const [div_id, v] of Object.entries(this.data_obj)) {
let visible = true;
if (this.directory_filter_recurse) {
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.
if (this.directory_filter_str && !this.directory_filter_str.startsWith(v.rel_parent_dir)) {
this.data_obj[div_id].visible = false;
this.data_obj[div_id].visible = v.rel_parent_dir.startsWith(this.directory_filter_str);
if (!this.data_obj[div_id].visible) {
continue;
}
} else {

View file

@ -238,6 +238,7 @@ class ExtraNetworksPage:
self.btn_show_metadata_tpl = shared.html("extra-networks-btn-show-metadata.html")
self.btn_edit_metadata_tpl = shared.html("extra-networks-btn-edit-metadata.html")
self.btn_dirs_view_item_tpl = shared.html("extra-networks-btn-dirs-view-item.html")
self.btn_chevron_tpl = shared.html("extra-networks-btn-chevron.html")
def clear_data(self) -> None:
self.is_ready = False
@ -316,26 +317,19 @@ class ExtraNetworksPage:
# If not specified, title will just reflect the label
btn_title = btn_title.strip() if btn_title else f'"{label}"'
action_list_item_action_leading = "<i class='tree-list-item-action-chevron'></i>"
action_list_item_action_leading = self.btn_chevron_tpl.format(extra_classes="")
action_list_item_visual_leading = "🗀"
action_list_item_visual_trailing = ""
action_list_item_action_trailing = ""
if dir_is_empty:
action_list_item_action_leading = "<i class='tree-list-item-action-chevron' style='visibility: hidden'></i>"
action_list_item_action_leading = self.btn_chevron_tpl.format(extra_classes="invisible")
if btn_type == "file":
action_list_item_visual_leading = "🗎"
# Action buttons
if item is not None:
action_list_item_action_trailing += self.get_button_row(tabname, item)
else:
action_list_item_action_trailing += (
"<div class='button-row'>"
"<div class='tree-list-item-action-expand card-button' title='Expand All'></div>"
"<div class='tree-list-item-action-collapse card-button' title='Collapse All'></div>"
"</div>"
)
data_attributes_str = ""
for k, v in data_attributes.items():

126
style.css
View file

@ -31,6 +31,10 @@ div.gradio-container{
display: none !important;
}
.invisible {
visibility: hidden !important;
}
.compact{
background: transparent !important;
padding: 0 !important;
@ -1269,7 +1273,7 @@ body.resizing .resize-handle {
.resize-handle-row.resize-handle-hidden {
display: flex !important;
}
/*
/* Only recently supported in firefox. Switch to this eventually to simplify code.
.resize-handle-row:has(> .resize-handle-col.hidden) {
display: flex !important;
}
@ -1513,46 +1517,68 @@ body.resizing .resize-handle {
color: var(--button-secondary-text-color);
}
/* ==== TREE VIEW EXPAND/COLLAPSE BUTTONS ==== */
.tree-list-item-action-expand::before {
content: "≫";
}
.tree-list-item-action-expand {
transform: rotate(90deg);
}
.tree-list-item-action-collapse::before {
content: "≫";
}
.tree-list-item-action-collapse {
transform: rotate(-90deg);
}
/* ==== CHEVRON ICON ACTIONS ==== */
/* Define the animation for the arrow when it is clicked. */
.tree-list-item-action-chevron {
display: inline-flex;
.tree-list-item-action--chevron {
height: var(--button-large-text-size);
width: var(--button-large-text-size);
mask-image: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M 4 4 H 12 V 12"/></svg>');
mask-repeat: no-repeat;
mask-position: center center;
mask-size: 100%;
background: var(--input-placeholder-color);
background: transparent;
margin: 0 !important;
padding: 0 !important;
-ms-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
transition: transform 0.2s;
transition-delay: 0.3s;
z-index: 0;
}
.tree-list-item[data-expanded] .tree-list-item-action-chevron {
.tree-list-item[data-expanded] .tree-list-item-action--chevron {
-ms-transform: rotate(135deg);
-webkit-transform: rotate(135deg);
transform: rotate(135deg);
transition: transform 0.2s;
/* currently broken since we remove/add elements to clusterize.js list */
}
.tree-list-item[data-expanded] .tree-list-item-action--chevron.pressed {
-ms-transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.tree-list-item:not([data-expanded]) .tree-list-item-action--chevron.pressed {
-ms-transform: rotate(135deg);
-webkit-transform: rotate(135deg);
transform: rotate(135deg);
}
.tree-list-item-action--chevron .chevron-icon-single,
.tree-list-item-action--chevron .chevron-icon-double {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: all 0.1s ease;
transition-delay: 0.3s;
}
.tree-list-item-action--chevron:not(.pressed) .chevron-icon-single {
opacity: 1;
stroke: var(--input-placeholder-color);
}
.tree-list-item-action--chevron.pressed .chevron-icon-double {
opacity: 1;
stroke: var(--button-primary-border-color);
}
.tree-list-item-action--chevron.pressed .chevron-icon-single,
.tree-list-item-action--chevron:not(.pressed) .chevron-icon-double {
opacity: 0;
}
/* Text for button. */
.tree-list-item-label {
position: relative;
@ -1657,6 +1683,7 @@ body.resizing .resize-handle {
display: none;
}
/* Long-press buttons. Wipe L->R effect when button is held, then toggles color. */
.extra-network-dirs-view-button {
position: relative;
overflow: hidden;
@ -1671,10 +1698,21 @@ body.resizing .resize-handle {
}
.extra-network-dirs-view-button[data-selected] {
background: var(--button-primary-background-fill);
background: var(--button-secondary-background-fill);
}
.extra-network-dirs-view-button[data-selected]:not(.pressed):hover {
-webkit-transition: all 0.05s ease-in-out;
transition: all 0.05s ease-in-out;
background: var(--button-secondary-background-fill-hover);
}
.extra-network-dirs-view-button[data-recurse] {
background: var(--button-primary-background-fill);
border-color: var(--button-primary-border-color);
}
.extra-network-dirs-view-button[data-recurse]:not(.pressed):hover {
-webkit-transition: all 0.05s ease-in-out;
transition: all 0.05s ease-in-out;
background: var(--button-primary-background-fill-hover);
@ -1691,11 +1729,11 @@ body.resizing .resize-handle {
z-index: -1;
}
.extra-network-dirs-view-button:not([data-selected])::after {
.extra-network-dirs-view-button:not([data-recurse])::after {
background: var(--button-secondary-background-fill-hover);
}
.extra-network-dirs-view-button[data-selected]::after {
.extra-network-dirs-view-button[data-recurse]::after {
background: var(--button-primary-background-fill-hover);
}
@ -1704,16 +1742,16 @@ body.resizing .resize-handle {
transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.extra-network-dirs-view-button[data-recurse] {
box-shadow:
inset var(--spacing-sm) 0 0 0 var(--primary-700),
inset calc(-1 * var(--spacing-sm)) 0 0 0 var(--primary-700);
.extra-network-dirs-view-button[data-selected] {
outline: var(--spacing-xs) solid var(--button-primary-border-color);
outline-offset: calc(-1 * var(--spacing-xs));
}
.tree-list-item {
position: relative;
overflow: hidden;
z-index: 0;
user-select: none;
}
.tree-list-item:not(.pressed):hover {
@ -1726,25 +1764,31 @@ body.resizing .resize-handle {
background: var(--button-secondary-background-fill);
}
.tree-list-item .tree-list-item-action--leading::after {
.tree-list-item.pressed:hover {
-webkit-transition: all 0.05s ease-in-out;
transition: all 0.05s ease-in-out;
background: var(--button-secondary-background-fill);
}
.tree-list-item::after {
position: absolute;
content: "";
top: 0;
left: 0;
width: 0;
height: 100%;
background: var(--button-secondary-background-fill-hover);
transition: all 0.35s cubic-bezier(0.215, 0.61, 0.355, 1);
z-index: -1;
}
.tree-list-item.pressed .tree-list-item-action--leading::after {
.tree-list-item.pressed::after {
width: 100%;
transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.tree-list-item[data-recurse] .tree-list-item-action--leading .tree-list-item-action-chevron {
background: var(--button-primary-background-fill);
color: var(--primary-600);
.tree-list-item[data-recurse] .tree-list-item-action--chevron .chevron-icon-single {
stroke: var(--button-primary-border-color);
}
.tree-list-item-indent {