fix some bugs. need to continue bug testing.

This commit is contained in:
Sj-Si 2024-04-14 14:43:31 -04:00
parent 0853c2b42c
commit 6b8374cc84
7 changed files with 316 additions and 161 deletions

View File

@ -47,6 +47,7 @@ class Clusterize {
#element_observer = null;
#element_observer_timer = null;
#pointer_events_set = false;
#on_scroll_bound;
constructor(args) {
for (const option of Object.keys(this.options)) {
@ -56,16 +57,16 @@ class Clusterize {
}
if (isNullOrUndefined(this.options.callbacks.initData)) {
this.options.callbacks.initData = this.initDataDefaultCallback;
this.options.callbacks.initData = this.initDataDefaultCallback.bind(this);
}
if (isNullOrUndefined(this.options.callbacks.fetchData)) {
this.options.callbacks.fetchData = this.fetchDataDefaultCallback;
this.options.callbacks.fetchData = this.fetchDataDefaultCallback.bind(this);
}
if (isNullOrUndefined(this.options.callbacks.sortData)) {
this.options.callbacks.sortData = this.sortDataDefaultCallback;
this.options.callbacks.sortData = this.sortDataDefaultCallback.bind(this);
}
if (isNullOrUndefined(this.options.callbacks.filterData)) {
this.options.callbacks.filterData = this.filterDataDefaultCallback;
this.options.callbacks.filterData = this.filterDataDefaultCallback.bind(this);
}
// detect ie9 and lower
@ -96,6 +97,8 @@ class Clusterize {
this.#scroll_top = this.scroll_elem.scrollTop;
this.#max_items = args.max_items;
this.#on_scroll_bound = this.#onScroll.bind(this);
}
// ==== PUBLIC FUNCTIONS ====
@ -114,7 +117,7 @@ class Clusterize {
await this.#insertToDOM();
this.scroll_elem.scrollTop = this.#scroll_top;
this.#setupEvent("scroll", this.scroll_elem, this.#onScroll);
this.#setupEvent("scroll", this.scroll_elem, this.#on_scroll_bound);
this.#setupElementObservers();
this.#setupResizeObservers();
@ -129,12 +132,13 @@ class Clusterize {
this.#html(this.#generateEmptyRow().join(""));
}
destroy() {
this.#teardownEvent("scroll", this.scroll_elem, this.#onScroll);
destroy() {
this.#teardownEvent("scroll", this.scroll_elem, this.#on_scroll_bound);
this.#teardownElementObservers();
this.#teardownResizeObservers();
this.clear();
this.#html(this.#generateEmptyRow().join(""));
this.setup_has_run = false;
}
@ -210,7 +214,7 @@ class Clusterize {
if (!this.enabled) {
return;
}
return await this.options.callbacks.initData.call(this);
return await this.options.callbacks.initData();
}
fetchDataDefaultCallback() {
@ -221,7 +225,7 @@ class Clusterize {
if (!this.enabled) {
return;
}
return await this.options.callbacks.fetchData.call(this, idx_start, idx_end);
return await this.options.callbacks.fetchData(idx_start, idx_end);
}
sortDataDefaultCallback() {
@ -236,7 +240,7 @@ class Clusterize {
this.#fixElementReferences();
// Sort is applied to the filtered data.
await this.options.callbacks.sortData.call(this);
await this.options.callbacks.sortData();
this.#recalculateDims();
await this.#insertToDOM();
}
@ -251,7 +255,7 @@ class Clusterize {
}
// Filter is applied to entire dataset.
const max_items = await this.options.callbacks.filterData.call(this);
const max_items = await this.options.callbacks.filterData();
await this.setMaxItems(max_items);
}
@ -287,10 +291,8 @@ class Clusterize {
}
// Get the first element that isn't one of our placeholder rows.
const node = this.content_elem.querySelector(
`${this.options.tag}:not(clusterize-extra-row):not(clusterize-no-data)`
);
if (!isElement(node)) {
const node = this.content_elem.querySelector(":scope > :not(.clusterize-extra-row,.clusterize-no-data)");
if (!isElementLogError(node)) {
return;
}
@ -307,10 +309,13 @@ class Clusterize {
// Update rows in block to match the number of elements that can fit in the view.
const content_padding = getComputedPaddingDims(this.content_elem);
let content_gap = parseFloat(getComputedProperty(this.content_elem, "gap"));
if (isNumber(content_gap)) {
this.options.item_width += content_gap;
this.options.item_height += content_gap;
const column_gap = parseFloat(getComputedProperty(this.content_elem, "column-gap"));
const row_gap = parseFloat(getComputedProperty(this.content_elem, "row-gap"));
if (isNumber(column_gap)) {
this.options.item_width += column_gap;
}
if (isNumber(row_gap)) {
this.options.item_height += row_gap;
}
const inner_width = this.scroll_elem.clientWidth - content_padding.width;
@ -594,17 +599,17 @@ class Clusterize {
#setupEvent(type, elem, listener) {
if (elem.addEventListener) {
return elem.addEventListener(type, event => listener.call(this), false);
return elem.addEventListener(type, listener, false);
} else {
return elem.attachEvent(`on${type}`, event => listener.call(this));
return elem.attachEvent(`on${type}`, listener);
}
}
#teardownEvent(type, elem, listener) {
if (elem.removeEventListener) {
return elem.removeEventListener(type, event => listener.call(this), false);
return elem.removeEventListener(type, listener, false);
} else {
return elem.detachEvent(`on${type}`, event => listener.call(this));
return elem.detachEvent(`on${type}`, listener);
}
}
}

View File

@ -16,7 +16,10 @@ var globalPopupInner = null;
const storedPopupIds = {};
const extraPageUserMetadataEditors = {};
const extra_networks_tabs = {};
// A flag used by the `waitForBool` promise to determine when we first load Ui Options.
/** Boolean flags used along with utils.js::waitForBool(). */
// Set true when extraNetworksSetup completes.
const extra_networks_setup_complete = {state: false};
// Set true when we first load the UI options.
const initialUiOptionsLoaded = {state: false};
class ExtraNetworksTab {
@ -32,6 +35,9 @@ class ExtraNetworksTab {
txt_prompt_elem;
txt_neg_prompt_elem;
active_prompt_elem;
sort_mode_str = "";
sort_dir_str = "";
filter_str = "";
show_prompt = true;
show_neg_prompt = true;
compact_prompt_en = false;
@ -70,6 +76,15 @@ class ExtraNetworksTab {
await this.setupTreeList();
await this.setupCardsList();
const sort_mode_elem = this.controls_elem.querySelector(".extra-network-control--sort-mode[data-selected='']");
isElementThrowError(sort_mode_elem);
const sort_dir_elem = this.controls_elem.querySelector(".extra-network-control--sort-dir");
isElementThrowError(sort_dir_elem);
this.setSortMode(sort_mode_elem.dataset.sortMode);
this.setSortDir(sort_dir_elem.dataset.sortDir);
this.setFilterStr(this.txt_search_elem.value.toLowerCase());
this.registerPrompt();
if (this.container_elem.style.display === "none") {
@ -79,6 +94,24 @@ class ExtraNetworksTab {
}
}
destroy() {
this.unload();
this.tree_list.destroy();
this.cards_list.destroy();
this.tree_list = null;
this.cards_list = null;
this.container_elem = null;
this.controls_elem = null;
this.txt_search_elem = null;
this.prompt_container_elem = null;
this.prompts_elem = null;
this.prompt_row_elem = null;
this.neg_prompt_row_elem = null;
this.txt_prompt_elem = null;
this.txt_neg_prompt_elem = null;
this.active_prompt_elem = null;
}
async registerPrompt() {
await Promise.all([
waitForElement(`#${this.tabname}_prompt > label > textarea`).then(elem => this.txt_prompt_elem = elem),
@ -100,8 +133,8 @@ class ExtraNetworksTab {
contentId: `${this.tabname_full}_tree_list_content_area`,
tag: "button",
callbacks: {
initData: this.onInitTreeData,
fetchData: this.onFetchTreeData,
initData: this.onInitTreeData.bind(this),
fetchData: this.onFetchTreeData.bind(this),
},
});
await this.tree_list.setup();
@ -118,13 +151,28 @@ class ExtraNetworksTab {
contentId: `${this.tabname_full}_cards_list_content_area`,
tag: "div",
callbacks: {
initData: this.onInitCardsData,
fetchData: this.onFetchCardsData,
initData: this.onInitCardsData.bind(this),
fetchData: this.onFetchCardsData.bind(this),
},
});
await this.cards_list.setup();
}
setSortMode(sort_mode_str) {
this.sort_mode_str = sort_mode_str;
this.cards_list.setSortMode(this.sort_mode_str);
}
setSortDir(sort_dir_str) {
this.sort_dir_str = sort_dir_str;
this.cards_list.setSortDir(this.sort_dir_str);
}
setFilterStr(filter_str) {
this.filter_str = filter_str;
this.cards_list.setFilterStr(this.filter_str);
}
movePrompt(show_prompt=true, show_neg_prompt=true) {
// This function only applies when compact prompt mode is enabled.
if (!this.compact_prompt_en) {
@ -142,8 +190,8 @@ class ExtraNetworksTab {
this.prompts_elem.classList.toggle("extra-page-prompts-active", show_neg_prompt || show_prompt);
}
async refreshSingleCard(name) {
await requestGetPromise(
refreshSingleCard(name) {
requestGet(
"./sd_extra_networks/get-single-card",
{
tabname: this.tabname,
@ -152,13 +200,7 @@ class ExtraNetworksTab {
},
(data) => {
if (data && data.html) {
const card = this.cards_list.content_elem.querySelector(`.card[data-name="${name}"]`);
const new_div = document.createElement("div");
new_div.innerHTML = data.html;
const new_card = new_div.firstElementChild;
new_card.style.display = "";
card.parentElement.insertBefore(new_card, card);
card.parentElement.removeChild(card);
this.cards_list.updateCard(name, data.html);
}
},
);
@ -176,6 +218,8 @@ class ExtraNetworksTab {
const btn_dirs_view = this.controls_elem.querySelector(".extra-network-control--dirs-view");
const btn_tree_view = this.controls_elem.querySelector(".extra-network-control--tree-view");
const div_dirs = this.container_elem.querySelector(".extra-network-content--dirs-view");
// We actually want to select the tree view's column in the resize-handle-row.
// This is what we actually show/hide, not the inner elements.
const div_tree = this.container_elem.querySelector(
`.extra-network-content.resize-handle-col:has(> #${this.tabname_full}_tree_list_scroll_area)`
);
@ -188,6 +232,10 @@ class ExtraNetworksTab {
this.tree_list.enable();
this.cards_list.enable();
await Promise.all([this.tree_list.load(true), this.cards_list.load(true)]);
// apply the previous sort/filter options
this.setSortMode(this.sort_mode_str);
this.setSortDir(this.sort_dir_str);
this.setFilterStr(this.filter_str);
}
async load(show_prompt, show_neg_prompt) {
@ -207,7 +255,7 @@ class ExtraNetworksTab {
applyFilter() {
// We only want to filter/sort the cards list.
this.cards_list.setFilterStr(this.txt_search_elem.value.toLowerCase());
this.setFilterStr(this.txt_search_elem.value.toLowerCase());
// 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.
@ -224,48 +272,80 @@ class ExtraNetworksTab {
}
}
async waitForServerPageReady() {
// We need to wait for the page to be ready before we can fetch data.
// After starting the server, on the first load of the page, if the user
// immediately clicks a tab, then we will try to load the card data before
// the server has even generated it.
// We use status 503 to indicate that the page isnt ready yet.
while (true) {
try {
await requestGetPromise(
"./sd_extra_networks/page-is-ready",
{extra_networks_tabname: this.extra_networks_tabname},
);
break;
} catch (error) {
if (error.status === 503) {
await new Promise(resolve => setTimeout(resolve, 250));
} else {
// We do not want to continue waiting if we get an unhandled error.
throw new Error("Error checking page readiness:", error);
}
}
}
}
async onInitCardsData() {
const res = await requestGetPromise(
"./sd_extra_networks/init-cards-data",
{
tabname: this.tabname,
extra_networks_tabname: this.extra_networks_tabname,
},
await this.waitForServerPageReady();
return JSON.parse(
await requestGetPromise(
"./sd_extra_networks/init-cards-data",
{
tabname: this.tabname,
extra_networks_tabname: this.extra_networks_tabname,
},
)
);
return JSON.parse(res);
}
async onInitTreeData() {
const res = await requestGetPromise(
"./sd_extra_networks/init-tree-data",
{
tabname: this.tabname,
extra_networks_tabname: this.extra_networks_tabname,
},
await this.waitForServerPageReady();
return JSON.parse(
await requestGetPromise(
"./sd_extra_networks/init-tree-data",
{
tabname: this.tabname,
extra_networks_tabname: this.extra_networks_tabname,
},
)
);
return JSON.parse(res);
}
async onFetchCardsData(div_ids) {
const res = await requestGetPromise(
"./sd_extra_networks/fetch-cards-data",
{
extra_networks_tabname: this.extra_networks_tabname,
div_ids: div_ids,
},
return JSON.parse(
await requestGetPromise(
"./sd_extra_networks/fetch-cards-data",
{
extra_networks_tabname: this.extra_networks_tabname,
div_ids: div_ids,
},
)
);
return JSON.parse(res);
}
async onFetchTreeData(div_ids) {
const res = await requestGetPromise(
"./sd_extra_networks/fetch-tree-data",
{
extra_networks_tabname: this.extra_networks_tabname,
div_ids: div_ids,
},
return JSON.parse(
await requestGetPromise(
"./sd_extra_networks/fetch-tree-data",
{
extra_networks_tabname: this.extra_networks_tabname,
div_ids: div_ids,
},
)
);
return JSON.parse(res);
}
updateSearch(text) {
@ -484,27 +564,13 @@ function extraNetworksShowMetadata(text) {
}
function extraNetworksRefreshSingleCard(tabname, extra_networks_tabname, name) {
requestGet(
"./sd_extra_networks/get-single-card",
{tabname: tabname, extra_networks_tabname: extra_networks_tabname, name: name},
(data) => {
if (data && data.html) {
const card = gradioApp().querySelector(`${tabname}_${extra_networks_tabname}_cards > .card[data-name="${name}"]`);
const new_div = document.createElement("div");
new_div.innerHTML = data.html;
const new_card = new_div.firstElementChild;
new_card.style.display = "";
card.parentElement.insertBefore(new_card, card);
card.parentElement.removeChild(card);
}
},
);
const tab = extra_networks_tabs[`${tabname}_${extra_networks_tabname}`];
tab.refreshSingleCard(name);
}
async function extraNetworksRefreshTab(tabname_full) {
/** called from python when user clicks the extra networks refresh tab button */
extra_networks_tabs[tabname_full].refresh();
await extra_networks_tabs[tabname_full].refresh();
}
// ==== EVENT HANDLING ====
@ -535,9 +601,10 @@ function extraNetworksUnrelatedTabSelected() {
async function extraNetworksTabSelected(tabname_full, show_prompt, show_neg_prompt) {
/** called from python when user selects an extra networks tab */
await waitForKeyInObject({obj: extra_networks_tabs, k: tabname_full});
for (const [k, v] of Object.entries(extra_networks_tabs)) {
if (k === tabname_full) {
v.load(show_prompt=show_prompt, show_neg_prompt=show_neg_prompt);
await v.load(show_prompt=show_prompt, show_neg_prompt=show_neg_prompt);
} else {
v.unload();
}
@ -563,6 +630,14 @@ function extraNetworksBtnDirsViewItemOnClick(event, tabname_full) {
// update search input with selected button's path.
elem.dataset.selected = "";
txt_search_elem.value = elem.textContent.trim();
// 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);
}
}
};
const _deselect_button = (elem) => {
@ -603,7 +678,7 @@ function extraNetworksControlSortModeOnClick(event, tabname_full) {
const sort_mode_str = event.currentTarget.dataset.sortMode.toLowerCase();
tab.cards_list.setSortMode(sort_mode_str);
tab.setSortMode(sort_mode_str);
}
function extraNetworksControlSortDirOnClick(event, tabname_full) {
@ -624,7 +699,7 @@ function extraNetworksControlSortDirOnClick(event, tabname_full) {
event.currentTarget.dataset.sortDir = sort_dir_str;
event.currentTarget.setAttribute("title", `Sort ${sort_dir_str}`);
tab.cards_list.setSortDir(sort_dir_str);
tab.setSortDir(sort_dir_str);
}
function extraNetworksControlTreeViewOnClick(event, tabname_full) {
@ -672,9 +747,6 @@ function extraNetworksControlRefreshOnClick(event, tabname_full) {
* event handler that refreshes the page. So what this function here does
* is it manually raises a `click` event on that button.
*/
// reset states
initialUiOptionsLoaded.state = false;
// We want to reset all tabs lists on refresh click so that the viewing area
// shows that it is loading new data.
for (tab of Object.values(extra_networks_tabs)) {
@ -721,6 +793,17 @@ function extraNetworksTreeDirectoryOnClick(event, btn, tabname_full) {
} 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;
}
});
}
tab.updateSearch("selected" in btn.dataset ? btn.dataset.path : "");
}
}
@ -839,13 +922,17 @@ async function extraNetworksSetupTab(tabname) {
}
async function extraNetworksSetup() {
extra_networks_setup_complete.state = false;
await waitForBool(initialUiOptionsLoaded);
await Promise.all([
extraNetworksSetupTab('txt2img'),
extraNetworksSetupTab('img2img'),
]);
extraNetworksSetupEventDelegators();
extra_networks_setup_complete.state = true;
}
onUiLoaded(extraNetworksSetup);

View File

@ -10,47 +10,6 @@ class NotImplementedError extends Error {
}
}
const LRU_MAX_ITEMS = 250;
class LRU {
constructor(max = LRU_MAX_ITEMS) {
this.max = max;
this.cache = new Map();
}
clear() {
this.cache.clear();
}
get(key) {
key = String(key);
let item = this.cache.get(key);
if (!isNullOrUndefined(item)) {
this.cache.delete(key);
this.cache.set(key, item);
}
return item;
}
set(key, val) {
key = String(key);
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size === this.max) {
this.cache.delete(this.first());
}
this.cache.set(key, val);
}
has(key) {
key = String(key);
return this.cache.has(key);
}
first() {
return this.cache.keys().next().value;
}
}
class ExtraNetworksClusterize extends Clusterize {
data_obj = {};
data_obj_keys_sorted = [];
@ -73,7 +32,6 @@ class ExtraNetworksClusterize extends Clusterize {
super(args);
this.tabname = getValueThrowError(args, "tabname");
this.extra_networks_tabname = getValueThrowError(args, "extra_networks_tabname");
this.lru = new LRU();
}
sortByDivId(data) {
@ -86,7 +44,7 @@ class ExtraNetworksClusterize extends Clusterize {
// 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);
await this.options.callbacks.sortData.call(this);
await this.options.callbacks.sortData();
}
async setup() {
@ -94,6 +52,12 @@ class ExtraNetworksClusterize extends Clusterize {
return;
}
if (this.lru instanceof LRUCache) {
this.lru.clear();
} else {
this.lru = new LRUCache();
}
await this.reinitData();
if (this.enabled) {
@ -101,6 +65,23 @@ class ExtraNetworksClusterize extends Clusterize {
}
}
destroy() {
if (this.lru instanceof LRUCache) {
this.lru.destroy();
this.lru = null;
}
this.data_obj = {};
this.data_obj_keys_sorted = [];
super.destroy();
}
clear() {
this.data_obj = {};
this.data_obj_keys_sorted = [];
this.lru.clear();
super.clear();
}
async load(force_init_data) {
if (!this.enabled) {
return;
@ -115,13 +96,6 @@ class ExtraNetworksClusterize extends Clusterize {
}
}
clear() {
this.data_obj = {};
this.data_obj_keys_sorted = [];
this.lru.clear();
super.clear();
}
setSortMode(sort_mode_str) {
if (this.sort_mode_str === sort_mode_str) {
return;
@ -173,6 +147,9 @@ class ExtraNetworksClusterize extends Clusterize {
}
async fetchDivIds(div_ids) {
if (isNullOrUndefinedLogError(this.lru)) {
return [];
}
const lru_keys = Array.from(this.lru.cache.keys());
const cached_div_ids = div_ids.filter(x => lru_keys.includes(x));
const missing_div_ids = div_ids.filter(x => !lru_keys.includes(x));
@ -182,7 +159,7 @@ class ExtraNetworksClusterize extends Clusterize {
if (missing_div_ids.length !== 0) {
Object.assign(
data,
await this.options.callbacks.fetchData.call(this, missing_div_ids),
await this.options.callbacks.fetchData(missing_div_ids),
);
}
@ -270,15 +247,17 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
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
const prev_elem = this.content_elem.querySelector(`div[data-div-id="${this.selected_div_id}"]`);
if (isElement(prev_elem)) {
delete prev_elem.dataset.selected;
this.data_obj[prev_elem.dataset.divId].selected = false;
}
}
elem.toggleAttribute("data-selected");
this.selected_div_id = "selected" in elem.dataset ? div_id : null;
this.data_obj[elem.dataset.divId].selected = "selected" in elem.dataset;
}
getMaxRowWidth() {
@ -344,7 +323,7 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize {
expanded: bool,
}
*/
this.data_obj = await this.options.callbacks.initData.call(this);
this.data_obj = await this.options.callbacks.initData();
}
async fetchData(idx_start, idx_end) {
@ -429,7 +408,29 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize {
sort_<mode>: string, (for various sort modes)
}
*/
this.data_obj = await this.options.callbacks.initData.call(this);
this.data_obj = await this.options.callbacks.initData();
}
updateCard(name, new_html) {
const parsed_html = htmlStringToElement(new_html);
const old_card = this.content_elem.querySelector(`.card[data-name="${name}"]`);
if (!isElementLogError(old_card)) {
return;
}
const div_id = old_card.dataset.divId;
// replace new html's data attributes with the current ones
for (const [k, v] of Object.entries(old_card.dataset)) {
parsed_html.dataset[k] = v;
}
// replace the element in DOM with our new element
old_card.replaceWith(parsed_html);
// update the internal cache with the new html
this.lru.set(String(div_id), new_html);
}
async fetchData(idx_start, idx_end) {

52
javascript/lru_cache.js Normal file
View File

@ -0,0 +1,52 @@
const LRU_CACHE_MAX_ITEMS_DEFAULT = 250;
class LRUCache {
/** Least Recently Used cache implementation.
*
* Source: https://stackoverflow.com/a/46432113
*/
constructor(max = LRU_CACHE_MAX_ITEMS_DEFAULT) {
isNumberThrowError(max);
this.max = max;
this.cache = new Map();
}
clear() {
this.cache.clear();
}
destroy() {
this.clear();
this.cache = null;
}
size() {
return this.cache.size;
}
get(key) {
let item = this.cache.get(key);
if (!isNullOrUndefined(item)) {
this.cache.delete(key);
this.cache.set(key, item);
}
return item;
}
set(key, val) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size === this.max) {
this.cache.delete(this.first());
}
this.cache.set(key, val);
}
has(key) {
return this.cache.has(key);
}
first() {
return this.cache.keys().next().value;
}
}

View File

@ -71,7 +71,6 @@ class TreeListItem(ListItem):
Attributes:
visible [bool]: Whether the item should be shown in the list.
expanded [bool]: Whether the item children should be shown.
selected [bool]: Whether the item is selected by user.
"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@ -79,7 +78,6 @@ class TreeListItem(ListItem):
self.node: Optional[DirectoryTreeNode] = None
self.visible: bool = False
self.expanded: bool = False
self.selected: bool = False
class DirectoryTreeNode:
@ -241,8 +239,23 @@ def init_cards_data(tabname: str = "", extra_networks_tabname: str = "") -> JSON
data = page.generate_cards_view_data(tabname)
if data is None:
return JSONResponse({}, status_code=503)
return JSONResponse(data, status_code=200)
def page_is_ready(extra_networks_tabname: str = "") -> JSONResponse:
page = get_page_by_name(extra_networks_tabname)
try:
items_list = [x for x in page.list_items()]
if len(page.items) == len(items_list):
return JSONResponse({}, status_code=200)
return JSONResponse({"error": "page not ready"}, status_code=503)
except Exception as exc:
return JSONResponse({"error": str(exc)}, status_code=500)
def get_metadata(extra_networks_tabname: str = "", item: str = "") -> JSONResponse:
try:
page = get_page_by_name(extra_networks_tabname)
@ -287,6 +300,7 @@ def add_pages_to_demo(app):
app.add_api_route("/sd_extra_networks/init-cards-data", init_cards_data, methods=["GET"])
app.add_api_route("/sd_extra_networks/fetch-tree-data", fetch_tree_data, methods=["GET"])
app.add_api_route("/sd_extra_networks/fetch-cards-data", fetch_cards_data, methods=["GET"])
app.add_api_route("/sd_extra_networks/page-is-ready", page_is_ready, methods=["GET"])
def quote_js(s):
s = s.replace('\\', '\\\\')
@ -593,8 +607,8 @@ class ExtraNetworksPage:
show_files = shared.opts.extra_networks_tree_view_show_files is True
for div_id, node in div_id_to_node.items():
self.tree[div_id] = TreeListItem(div_id, "")
self.tree[div_id].node = node
tree_item = TreeListItem(div_id, "")
tree_item.node = node
parent_id = None
if node.parent is not None:
parent_id = path_to_div_id.get(node.parent.abspath, None)
@ -603,8 +617,8 @@ class ExtraNetworksPage:
if show_files:
dir_is_empty = node.children == []
else:
dir_is_empty = not any(x.item.is_dir for x in node.children)
self.tree[div_id].html = self.build_tree_html_dict_row(
dir_is_empty = all(not x.is_dir for x in node.children)
tree_item.html = self.build_tree_html_dict_row(
tabname=tabname,
label=os.path.basename(node.abspath),
btn_type="dir",
@ -619,6 +633,7 @@ class ExtraNetworksPage:
"data-expanded": node.parent is None, # Expand root directories
},
)
self.tree[div_id] = tree_item
else: # file
if not show_files:
# Don't add file if files are disabled in the options.
@ -629,7 +644,7 @@ class ExtraNetworksPage:
onclick = html.escape(f"extraNetworksCardOnClick(event, '{tabname}_{self.extra_networks_tabname}');")
item_name = node.item.get("name", "").strip()
self.tree[div_id].html = self.build_tree_html_dict_row(
tree_item.html = self.build_tree_html_dict_row(
tabname=tabname,
label=html.escape(item_name),
btn_type="file",
@ -648,6 +663,7 @@ class ExtraNetworksPage:
item=node.item,
onclick_extra=onclick,
)
self.tree[div_id] = tree_item
res = {}
@ -740,7 +756,7 @@ 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)
self.tree_roots[abspath].build(tree_items if shared.opts.extra_networks_tree_view_show_files else {})
# Generate the html for displaying directory buttons
dirs_html = self.create_dirs_view_html(tabname)

View File

@ -16,14 +16,7 @@ def javascript_html():
script_js = os.path.join(script_path, "script.js")
head += f'<script type="text/javascript" src="{webpath(script_js)}"></script>\n'
# We want the utils.js script to be imported first since it can be used by multiple other
# scripts. This resolves dependency issues caused by html <script> ordering.
js_scripts = scripts.list_scripts("javascript", ".js")
js_utils_script = [x for x in js_scripts if x.filename == "utils.js"][0]
head += f'<script type="text/javascript" src="{webpath(js_utils_script.path)}"></script>\n'
# Now add all remaining .js scripts excluding the utils.js file.
for script in [x for x in js_scripts if x.filename != "utils.js"]:
for script in scripts.list_scripts("javascript", ".js"):
head += f'<script type="text/javascript" src="{webpath(script.path)}"></script>\n'
for script in scripts.list_scripts("javascript", ".mjs"):

View File

@ -1192,7 +1192,8 @@ body.resizing .resize-handle {
.clusterize-content {
flex: 1;
outline: 0;
gap: var(--spacing-sm);
/* need to manually set the gap to 0 to fix item dimension calcs. */
gap: 0;
counter-reset: clusterize-counter;
padding: var(--spacing-md);
}