From c8e21f6c8b8b5c3502c6d0f18a4f01bf06c753ab Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Fri, 3 May 2024 13:13:25 -0400 Subject: [PATCH] fix loading/nodata messages --- html/extra-networks-pane.html | 22 +++-- javascript/clusterize.js | 64 ++++++-------- javascript/extraNetworks.js | 123 +++++++++++++++++++------- javascript/extraNetworksClusterize.js | 20 ++--- modules/ui_extra_networks.py | 10 ++- style.css | 21 +++-- 6 files changed, 165 insertions(+), 95 deletions(-) diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 2b9b6a2fc..57c51b37b 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -126,23 +126,29 @@
+
{tree_list_loading_splash_content}
+
-
Loading...
-
No Data
-
+ class='extra-network-tree-content clusterize-content'>
+
{cards_list_loading_splash_content}
+
-
Loading...
-
{no_cards_html}
-
+ class='extra-network-cards-content clusterize-content'>
diff --git a/javascript/clusterize.js b/javascript/clusterize.js index f03530912..4fe4af4d5 100644 --- a/javascript/clusterize.js +++ b/javascript/clusterize.js @@ -19,24 +19,19 @@ class Clusterize { content_elem = null; scroll_id = null; content_id = null; - no_data_class_default = "clusterize-no-data"; - loading_class_default = "clusterize-loading"; options = { rows_in_block: 50, cols_in_block: 1, blocks_in_cluster: 5, + rows_in_cluster: 50 * 5, // default is rows_in_block * blocks_in_cluster tag: null, id_attr: "data-div-id", + no_data_class: "clusterize-no-data", + no_data_text: "No Data", show_no_data_row: true, - no_data_class: this.no_data_class_default, - loading_class: this.loading_class_default, keep_parity: true, callbacks: {}, }; - loading_html_default = `
Loading...
`; - no_data_html_default = `
No Data
`; - loading_html = null; - no_data_html = null; setup_has_run = false; enabled = false; #is_mac = null; @@ -105,9 +100,6 @@ class Clusterize { this.#max_items = args.max_items; this.#on_scroll_bound = this.#onScroll.bind(this); - - this.loading_html = args.loading_html; - this.no_data_html = args.no_data_html; } // ==== PUBLIC FUNCTIONS ==== @@ -133,13 +125,12 @@ class Clusterize { this.setup_has_run = true; } - clear(custom_html) { + clear() { if (!this.setup_has_run || !this.enabled) { return; } - custom_html = custom_html || this.no_data_html; - this.#html(custom_html); + this.#html(this.#generateEmptyRow().join("")); } destroy() { @@ -147,7 +138,7 @@ class Clusterize { this.#teardownElementObservers(); this.#teardownResizeObservers(); - this.#html(this.no_data_html); + this.#html(this.#generateEmptyRow().join("")); this.setup_has_run = false; } @@ -279,29 +270,13 @@ class Clusterize { if (this.#ie && this.#ie <= 9 && !this.options.tag) { this.options.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase(); } + // Temporarily add one row so that we can calculate row dimensions. if (this.content_elem.children.length <= 1) { cache.data = this.#html(rows[0]); } if (!this.options.tag) { this.options.tag = this.content_elem.children[0].tagName.toLowerCase(); } - - if (!isString(this.loading_html)) { - const loading_elem = this.content_elem.querySelector(`.${this.options.loading_class}`); - if (isElement(loading_elem)) { - this.loading_html = loading_elem.outerHTML; - loading_elem.remove(); - } - } - - if (!isString(this.no_data_html)) { - const no_data_elem = this.content_elem.querySelector(`.${this.options.no_data_class}`); - if (isElement(no_data_elem)) { - this.no_data_html = no_data_elem.outerHTML; - no_data_elem.remove(); - } - } - this.#recalculateDims(); } @@ -317,7 +292,7 @@ class Clusterize { // Get the first element that isn't one of our placeholder rows. const node = this.content_elem.querySelector( - `:scope > :not(.clusterize-extra-row,.${this.options.no_data_class},.${this.options.loading_class})` + `:scope > :not(.clusterize-extra-row,.${this.options.no_data_class})` ); if (!isElement(node)) { // dont attempt to compute dims if we have no data. @@ -368,6 +343,21 @@ class Clusterize { return prev_options !== JSON.stringify(this.options); } + #generateEmptyRow() { + const row = document.createElement(this.options.tag); + row.className = this.options.no_data_class; + const node = document.createTextNode(this.options.no_data_text); + if (this.options.tag === "tr") { + const td = document.createElement("td"); + td.colSpan = 100; + td.appendChild(node); + row.appendChild(td); + } else { + row.appendChild(node); + } + return [row.outerHTML]; + } + #getClusterNum() { this.options.scroll_top = this.scroll_elem.scrollTop; const cluster_divider = this.options.cluster_height - this.options.block_height; @@ -397,7 +387,7 @@ class Clusterize { top_offset: 0, bottom_offset: 0, rows_above: 0, - rows: this_cluster_rows.length ? this_cluster_rows : [this.no_data_html], + rows: this_cluster_rows.length ? this_cluster_rows : this.#generateEmptyRow(), }; } @@ -417,12 +407,13 @@ class Clusterize { if (!Array.isArray(rows) || !rows.length) { // This implies there is no data for this list. Not an error. // Errors should be handled in the fetchData callback, not here. - this.#html(this.no_data_html); + this.#html(this.#generateEmptyRow().join("")); return; } else { + this.#html(rows.join("")); this.#exploreEnvironment(rows, this.#cache); // Remove the temporary item from the data since we calculated its size. - this.#html(this.loading_html); + this.#html(this.#generateEmptyRow().join("")); } } @@ -472,6 +463,7 @@ class Clusterize { } else { content_elem.innerHTML = data; } + return content_elem.innerHTML; } #renderExtraTag(class_name, height) { diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 5be4bdf71..ee79d9fc0 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -111,10 +111,12 @@ class ExtraNetworksTab { refresh_in_progress = false; dirs_view_en = false; tree_view_en = false; - tree_list_no_data_html_bkp = null; - tree_list_loading_html_bkp = null; - cards_list_no_data_html_bkp = null; - cards_list_loading_html_bkp = null; + cards_list_loading_splash_elem = null; + cards_list_no_data_splash_elem = null; + tree_list_loading_splash_elem = null; + tree_list_no_data_splash_elem = null; + cards_list_splash_state = null; + tree_list_splash_state = null; constructor({tabname, extra_networks_tabname}) { this.tabname = tabname; this.extra_networks_tabname = extra_networks_tabname; @@ -137,6 +139,21 @@ class ExtraNetworksTab { waitForElement(`#${this.tabname_full}_cards_list_content_area`), ]); + this.cards_list_loading_splash_elem = document.getElementById( + `${this.tabname_full}_cards_list_loading_splash` + ); + this.cards_list_no_data_splash_elem = document.getElementById( + `${this.tabname_full}_cards_list_no_data_splash` + ); + this.tree_list_loading_splash_elem = document.getElementById( + `${this.tabname_full}_tree_list_loading_splash` + ); + this.tree_list_no_data_splash_elem = document.getElementById( + `${this.tabname_full}_tree_list_no_data_splash` + ); + + this.updateSplashState({cards_list_state: "loading", tree_list_state: "loading"}); + this.txt_search_elem = this.controls_elem.querySelector(".extra-network-control--search-text"); this.dirs_view_en = "selected" in this.controls_elem.querySelector( ".extra-network-control--dirs-view" @@ -153,26 +170,6 @@ class ExtraNetworksTab { this.controls_elem.id = `${this.tabname_full}_controls`; controls_div.insertBefore(this.controls_elem, null); - // create backups of the no-data and loading elements for tree/cards list - const tree_loading_elem = this.container_elem.querySelector( - ".extra-network-tree .clusterize-loading" - ); - const tree_no_data_elem = this.container_elem.querySelector( - ".extra-network-tree .clusterize-no-data" - ); - const cards_loading_elem = this.container_elem.querySelector( - ".extra-network-cards-content .clusterize-loading" - ); - const cards_no_data_elem = this.container_elem.querySelector( - ".extra-network-cards-content .clusterize-no-data" - ); - // need to store backup of these since they are removed on clusterize clear. - // we re-apply them next time we call setup. - this.cards_list_no_data_html_bkp = cards_no_data_elem.outerHTML; - this.cards_list_loading_html_bkp = cards_loading_elem.outerHTML; - this.tree_list_no_data_html_bkp = tree_no_data_elem.outerHTML; - this.tree_list_loading_html_bkp = tree_loading_elem.outerHTML; - await Promise.all([this.setupTreeList(), this.setupCardsList()]); const btn_dirs_view = this.controls_elem.querySelector(".extra-network-control--dirs-view"); @@ -256,8 +253,6 @@ class ExtraNetworksTab { initData: this.onInitTreeData.bind(this), fetchData: this.onFetchTreeData.bind(this), }, - no_data_html: this.tree_list_no_data_html_bkp, - loading_html: this.tree_list_loading_html_bkp, }); await this.tree_list.setup(); } @@ -276,8 +271,6 @@ class ExtraNetworksTab { initData: this.onInitCardsData.bind(this), fetchData: this.onFetchCardsData.bind(this), }, - no_data_html: this.cards_list_no_data_html_bkp, - loading_html: this.cards_list_loading_html_bkp, }); await this.cards_list.setup(); } @@ -345,11 +338,53 @@ class ExtraNetworksTab { this.controls_elem.classList.add("hidden"); } + updateSplashState({cards_list_state, tree_list_state} = {}) { + const _handle_state = (state_str, loading_elem, no_data_elem) => { + switch (state_str) { + case "loading": + no_data_elem.classList.add("hidden"); + loading_elem.classList.remove("hidden"); + break; + case "no_data": + loading_elem.classList.add("hidden"); + no_data_elem.classList.remove("hidden"); + break; + case "show": + no_data_elem.classList.add("hidden"); + loading_elem.classList.add("hidden"); + break; + default: + break; + } + }; + + if (isString(cards_list_state)) { + this.cards_list_splash_state = cards_list_state; + } + _handle_state( + cards_list_state, + this.cards_list_loading_splash_elem, + this.cards_list_no_data_splash_elem, + ); + + if (isString(tree_list_state)) { + this.tree_list_splash_state = tree_list_state; + } + _handle_state( + tree_list_state, + this.tree_list_loading_splash_elem, + this.tree_list_no_data_splash_elem, + ); + } + async #refresh() { + this.updateSplashState({cards_list_state: "loading", tree_list_state: "loading"}); + try { await this.waitForServerPageReady(); } catch (error) { console.error(`refresh error: ${error.message}`); + this.updateSplashState({cards_list_state: "no_data", tree_list_state: "no_data"}); return; } const btn_dirs_view = this.controls_elem.querySelector(".extra-network-control--dirs-view"); @@ -370,7 +405,7 @@ class ExtraNetworksTab { resize_handle_row.classList.toggle("resize-handle-hidden", div_tree.classList.contains("hidden")); await Promise.all([this.setupTreeList(), this.setupCardsList()]); - this.tree_list.enable(true); + this.tree_list.enable(this.tree_view_en); this.cards_list.enable(true); await Promise.all([this.tree_list.load(true), this.cards_list.load(true)]); // apply the previous sort/filter options @@ -391,8 +426,12 @@ class ExtraNetworksTab { async load(show_prompt, show_neg_prompt) { this.movePrompt(show_prompt, show_neg_prompt); + this.updateSplashState({ + cards_list_state: this.cards_list_splash_state, + tree_list_state: this.tree_list_splash_state, + }); this.showControls(); - this.tree_list.enable(true); + this.tree_list.enable(this.tree_view_en); this.cards_list.enable(true); await Promise.all([this.tree_list.load(), this.cards_list.load()]); } @@ -449,10 +488,12 @@ class ExtraNetworksTab { } async onInitCardsData() { + this.updateSplashState({cards_list_state: "loading"}); try { await this.waitForServerPageReady(); } catch (error) { console.error(`onInitCardsData error: ${error.message}`); + this.updateSplashState({cards_list_state: "no_data"}); return {}; } @@ -474,18 +515,26 @@ class ExtraNetworksTab { const opts = {timeout_ms: timeout_ms, response_handler: response_handler}; try { const response = await fetchWithRetryAndBackoff(url, payload, opts); + if (Object.keys(response.data).length === 0) { + this.updateSplashState({cards_list_state: "no_data"}); + } else { + this.updateSplashState({cards_list_state: "show"}); + } return response.data; } catch (error) { console.error(`onInitCardsData error: ${error.message}`); + this.updateSplashState({cards_list_state: "no_data"}); return {}; } } async onInitTreeData() { + this.updateSplashState({tree_list_state: "loading"}); try { await this.waitForServerPageReady(); } catch (error) { console.error(`onInitTreeData error: ${error.message}`); + this.updateSplashState({tree_list_state: "no_data"}); return {}; } @@ -507,9 +556,15 @@ class ExtraNetworksTab { const opts = {timeout_ms: timeout_ms, response_handler: response_handler}; try { const response = await fetchWithRetryAndBackoff(url, payload, opts); + if (Object.keys(response.data).length === 0) { + this.updateSplashState({tree_list_state: "no_data"}); + } else { + this.updateSplashState({tree_list_state: "show"}); + } return response.data; } catch (error) { console.error(`onInitTreeData error: ${error.message}`); + this.updateSplashState({tree_list_state: "no_data"}); return {}; } } @@ -1061,6 +1116,13 @@ async function extraNetworksControlTreeViewOnClick(event) { const resize_handle_row = tab.tree_list.scroll_elem.closest(".resize-handle-row"); resize_handle_row.classList.toggle("resize-handle-hidden", !tab.tree_view_en); + // If the tree list hasn't loaded yet, we need to force it to load. + // This can happen if tree view is disabled by default or before refresh. + // Then after refresh, enabling the tree view will require a load. + if (tab.tree_view_en && !tab.tree_list.initial_load) { + await tab.tree_list.load(); + } + if ((tab.tree_view_en && tab.dirs_view_en) || (!tab.tree_view_en && tab.dirs_view_en)) { tab.setDirectoryButtons({source_class: ".extra-network-dirs-view-button"}); } else if (tab.tree_view_en) { @@ -1117,6 +1179,7 @@ function extraNetworksControlRefreshOnClick(event) { const btn = event.target.closest(".extra-network-control--refresh"); const controls = btn.closest(".extra-network-controls"); const tab = extra_networks_tabs[controls.dataset.tabnameFull]; + tab.updateSplashState({cards_list_state: "loading", tree_list_state: "loading"}); // We want to reset tab lists on refresh click so that the viewing area // shows that it is loading new data. tab.tree_list.clear(); diff --git a/javascript/extraNetworksClusterize.js b/javascript/extraNetworksClusterize.js index 07b914b0f..d1b36c985 100644 --- a/javascript/extraNetworksClusterize.js +++ b/javascript/extraNetworksClusterize.js @@ -39,6 +39,7 @@ class ExtraNetworksClusterize extends Clusterize { sort_fn = this.default_sort_fn; tabname = ""; extra_networks_tabname = ""; + initial_load = false; // Override base class defaults default_sort_mode_str = "divId"; @@ -91,6 +92,7 @@ class ExtraNetworksClusterize extends Clusterize { } destroy() { + this.initial_load = false; this.data_obj = {}; this.data_obj_keys_sorted = []; if (this.lru instanceof LRUCache) { @@ -101,12 +103,13 @@ class ExtraNetworksClusterize extends Clusterize { } clear() { + this.initial_load = false; this.data_obj = {}; this.data_obj_keys_sorted = []; if (this.lru instanceof LRUCache) { this.lru.clear(); } - super.clear(this.loading_html); + super.clear(); } async load(force_init_data) { @@ -114,6 +117,7 @@ class ExtraNetworksClusterize extends Clusterize { return; } + this.initial_load = true; if (!this.setup_has_run) { await this.setup(); } else if (force_init_data) { @@ -174,17 +178,7 @@ class ExtraNetworksClusterize extends Clusterize { } idxRangeToDivIds(idx_start, idx_end) { - const n_items = idx_end - idx_start; - const div_ids = []; - for (const div_id of this.data_obj_keys_sorted.slice(idx_start)) { - if (this.data_obj[div_id].visible) { - div_ids.push(div_id); - } - if (div_ids.length >= n_items) { - break; - } - } - return div_ids; + return this.data_obj_keys_sorted.slice(idx_start, idx_end); } async fetchDivIds(div_ids) { @@ -272,7 +266,7 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { clear() { this.selected_div_id = null; - super.clear(this.loading_html); + super.clear(); } async onRowSelected(elem) { diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 1be7bdb6e..973fcd9ee 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -759,15 +759,18 @@ class ExtraNetworksPage: include_hidden=shared.opts.extra_networks_show_hidden_directories, ) + cards_list_loading_splash_content = "Loading..." no_cards_html_dirs = "".join( [f"
  • {x}
  • " for x in self.allowed_directories_for_previews()] ) - no_cards_html = ( + cards_list_no_data_splash_content = ( "
    " "

    Nothing here. Add some content to the following directories:

    " f"" "
    " ) + tree_list_loading_splash_content = "Loading..." + tree_list_no_data_splash_content = "No Data" # Now use tree roots to generate a mapping of div_ids to nodes. # Flatten roots into a single sorted list of nodes. @@ -804,7 +807,10 @@ class ExtraNetworksPage: "tree_view_style": f"flex-basis: {shared.opts.extra_networks_tree_view_default_width}px;", "cards_view_style": "flex-grow: 1;", "dirs_html": dirs_html, - "no_cards_html": no_cards_html, + "cards_list_loading_splash_content": cards_list_loading_splash_content, + "cards_list_no_data_splash_content": cards_list_no_data_splash_content, + "tree_list_loading_splash_content": tree_list_loading_splash_content, + "tree_list_no_data_splash_content": tree_list_no_data_splash_content, } ) diff --git a/style.css b/style.css index 0470a3dd5..0747e77d1 100644 --- a/style.css +++ b/style.css @@ -1217,11 +1217,6 @@ body.resizing .resize-handle { color: var(--input-placeholder-color) !important; } -.clusterize-loading { - text-align: center; - color: var(--input-placeholder-color) !important; -} - .extra-network-pane { display: flex; flex-direction: column; @@ -1233,6 +1228,7 @@ body.resizing .resize-handle { } .extra-network-content { + position: relative; display: flex; flex-wrap: nowrap; width: 100%; @@ -1822,4 +1818,17 @@ body.resizing .resize-handle { .tree-list-item-indent > *[data-selected]::after { border-left: 1px solid var(--button-primary-border-color); -} \ No newline at end of file +} + +.extra-network-list-splash { + display: block; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 99; + background: var(--body-background-fill); + padding: var(--block-label-padding); + text-align: center; +}