diff --git a/javascript/clusterize.js b/javascript/clusterize.js index 595b4e351..a2b2a0ef2 100644 --- a/javascript/clusterize.js +++ b/javascript/clusterize.js @@ -17,12 +17,6 @@ class Clusterize { content_elem = null; scroll_id = null; content_id = null; - default_sort_mode_str = ""; - default_sort_dir_str = "ascending"; - default_filter_str = ""; - sort_mode_str = this.default_sort_mode_str; - sort_dir_str = this.default_sort_dir_str; - filter_str = this.default_filter_str; options = { rows_in_block: 50, cols_in_block: 1, @@ -32,13 +26,9 @@ class Clusterize { no_data_class: "clusterize-no-data", no_data_text: "No data", keep_parity: true, - callbacks: { - initData: this.initDataDefault, - fetchData: this.fetchDataDefault, - sortData: this.sortDataDefault, - filterData: this.filterDataDefault, - }, + callbacks: {}, }; + setup_has_run = false; #is_mac = null; #ie = null; #max_items = null; @@ -60,17 +50,17 @@ class Clusterize { } } - if (!isNullOrUndefined(this.options.callbacks.initData)) { - this.options.callbacks.initData = this.initDataDefault; + if (isNullOrUndefined(this.options.callbacks.initData)) { + this.options.callbacks.initData = this.initDataDefaultCallback; } - if (!isNullOrUndefined(this.options.callbacks.fetchData)) { - this.options.callbacks.fetchData = this.fetchDataDefault; + if (isNullOrUndefined(this.options.callbacks.fetchData)) { + this.options.callbacks.fetchData = this.fetchDataDefaultCallback; } - if (!isNullOrUndefined(this.options.callbacks.sortData)) { - this.options.callbacks.sortData = this.sortDataDefault; + if (isNullOrUndefined(this.options.callbacks.sortData)) { + this.options.callbacks.sortData = this.sortDataDefaultCallback; } - if (!isNullOrUndefined(this.options.callbacks.filterData)) { - this.options.callbacks.filterData = this.filterDataDefault; + if (isNullOrUndefined(this.options.callbacks.filterData)) { + this.options.callbacks.filterData = this.filterDataDefaultCallback; } // detect ie9 and lower @@ -105,15 +95,25 @@ class Clusterize { // ==== PUBLIC FUNCTIONS ==== async setup() { + if (this.setup_has_run) { + return; + } + await this.#insertToDOM(); this.scroll_elem.scrollTop = this.#scroll_top; this.#setupEvent("scroll", this.scroll_elem, this.#onScroll); this.#setupElementObservers(); this.#setupResizeObservers(); + + this.setup_has_run = true; } clear() { + if (!this.setup_has_run) { + return; + } + this.#html(this.#generateEmptyRow().join("")); } @@ -122,15 +122,25 @@ class Clusterize { this.#teardownElementObservers(); this.#teardownResizeObservers(); this.clear(); + + this.setup_has_run = false; } async refresh(force) { + if (!this.setup_has_run) { + return; + } + if (this.#getRowsHeight() || force) { await this.update() } } async update() { + if (!this.setup_has_run) { + return; + } + this.#scroll_top = this.scroll_elem.scrollTop; // fixes #39 if (this.#max_rows * this.options.item_height < this.#scroll_top) { @@ -151,6 +161,11 @@ class Clusterize { } async setMaxItems(max_items) { + if (!this.setup_has_run) { + this.#max_items = max_items; + return; + } + if (max_items === this.#max_items) { // No change. do nothing. return; @@ -161,60 +176,57 @@ class Clusterize { await this.refresh(true); // Apply sort to the updated data. - await this.sortData(this.sort_mode_str, this.sort_dir_str); - } - - async filterData(filter_str) { - if (this.filter_str === filter_str) { - return; - } - - this.filter_str = isNullOrUndefined(filter_str) ? this.default_filter_str : filter_str; - - // Filter is applied to entire dataset. - const max_items = await this.options.callbacks.filterData(this.filter_str); - // If the number of items changed after filter, we need to update the cluster. - if (max_items !== this.#max_items) { - this.#max_items = max_items; - this.refresh(true); - } - // Apply sort to the new filtered data. - await this.sortData(this.sort_mode_str, this.sort_dir_str); - } - - async sortData(sort_mode_str, sort_dir_str) { - if (this.sort_mode_str === sort_mode_str && this.sort_dir_str === sort_dir_str) { - return; - } - - this.sort_mode_str = isNullOrUndefined(sort_mode_str) ? this.default_sort_mode_str : sort_mode_str; - this.sort_dir_str = isNullOrUndefined(sort_dir_str) ? this.default_sort_dir_str : sort_dir_str; - - // Sort is applied to the filtered data. - await this.options.callbacks.sortData(this.sort_mode_str, this.sort_dir_str === "descending"); - await this.#insertToDOM(); + await this.sortData(); } // ==== PRIVATE FUNCTIONS ==== - initDataDefault() { + initDataDefaultCallback() { return Promise.resolve({}); } - fetchDataDefault() { + async initData() { + return await this.options.callbacks.initData.call(this); + } + + fetchDataDefaultCallback() { return Promise.resolve([]); } - sortDataDefault() { + async fetchData(idx_start, idx_end) { + return await this.options.callbacks.fetchData.call(this, idx_start, idx_end); + } + + sortDataDefaultCallback() { return Promise.resolve(); } - filterDataDefault() { + async sortData() { + if (!this.setup_has_run) { + return; + } + + // Sort is applied to the filtered data. + await this.options.callbacks.sortData.call(this); + await this.#insertToDOM(); + } + + filterDataDefaultCallback() { return Promise.resolve(0); } + async filterData() { + if (!this.setup_has_run) { + return; + } + + // Filter is applied to entire dataset. + const max_items = await this.options.callbacks.filterData.call(this); + await this.setMaxItems(max_items); + } + #exploreEnvironment(rows, cache) { this.options.content_tag = this.content_elem.tagName.toLowerCase(); - if (!rows.length) { + if (isNullOrUndefined(rows) || !rows.length) { return; } if (this.#ie && this.#ie <= 9 && !this.options.tag) { @@ -241,7 +253,11 @@ class Clusterize { return; } - const nodes = this.content_elem.querySelectorAll(":not(.clusterize-row)"); + const rows = this.content_elem.children; + if (!rows.length) { + return; + } + const nodes = rows[0].children; if (!nodes.length) { return; } @@ -269,6 +285,7 @@ class Clusterize { // Update rows in block to match the number of elements that can fit in the scroll element view. this.options.rows_in_block = calcRowsPerCol(this.scroll_elem, node); this.options.cols_in_block = calcColsPerRow(this.content_elem, node); + console.log("HERE:", this.scroll_elem, this.content_elem, node, this.options.rows_in_block, this.options.cols_in_block); this.options.block_height = this.options.item_height * this.options.rows_in_block; this.options.block_width = this.options.item_width * this.options.cols_in_block; @@ -323,18 +340,19 @@ class Clusterize { const idx_start = Math.max(0, rows_start * this.options.cols_in_block); const idx_end = rows_end * this.options.cols_in_block; - const this_cluster_rows = await this.options.callbacks.fetchData(idx_start, idx_end); + const this_cluster_rows = await this.fetchData(idx_start, idx_end); return { top_offset: top_offset, bottom_offset: bottom_offset, rows_above: rows_above, - rows: this_cluster_rows, + rows: Array.isArray(this_cluster_rows) ? this_cluster_rows : [], }; } async #insertToDOM() { if (!this.options.cluster_height || !this.options.cluster_width) { - const rows = await this.options.callbacks.fetchData(0, 1); + console.log("HERE"); + const rows = await this.fetchData(0, 1); this.#exploreEnvironment(rows, this.#cache); } diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 744906502..8d27f70a2 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -22,7 +22,7 @@ const initialUiOptionsLoaded = {state: false}; // -const popup = contents => { +function popup(contents) { if (!globalPopup) { globalPopup = document.createElement('div'); globalPopup.classList.add('global-popup'); @@ -44,45 +44,52 @@ const popup = contents => { globalPopupInner.appendChild(contents); globalPopup.style.display = "flex"; -}; +} -const popupId = id => { +function popupId(id) { if (!storedPopupIds[id]) { storedPopupIds[id] = gradioApp().getElementById(id); } popup(storedPopupIds[id]); -}; +} -const closePopup = () => { +function closePopup() { if (!globalPopup) return; globalPopup.style.display = "none"; -}; +} // ==== GENERAL EXTRA NETWORKS FUNCTIONS ==== -const extraNetworksClusterizersLoadTab = async ( - { - tabname_full = "", - selected = false, - fetch_data = false, +function extraNetworksClusterizersEnable(tabname_full) { + for (const [_tabname_full, tab_clusterizers] of Object.entries(clusterizers)) { + for (const v of Object.values(tab_clusterizers)) { + v.enable(_tabname_full === tabname_full); + } } -) => { +} + +async function extraNetworksClusterizersLoadTab({ + tabname_full, + selected = false, + fetch_data = false, +}) { if (!keyExistsLogError(clusterizers, tabname_full)) { return; } - for (const v of Object.values(clusterizers[tabname_full])) { - if (fetch_data) { - await v.initDataDefault(); - } else { - await v.refresh(true); - } + if (selected) { + extraNetworksClusterizersEnable(tabname_full); } -}; -const extraNetworksRegisterPromptForTab = (tabname, id) => { + for (const v of Object.values(clusterizers[tabname_full])) { + await v.load(fetch_data); + await v.refresh(true); + } +} + +function extraNetworksRegisterPromptForTab(tabname, id) { var textarea = gradioApp().querySelector(`#${id} > label > textarea`); if (!activePromptTextarea[tabname]) { @@ -92,9 +99,9 @@ const extraNetworksRegisterPromptForTab = (tabname, id) => { textarea.addEventListener("focus", function() { activePromptTextarea[tabname] = textarea; }); -}; +} -const extraNetworksMovePromptToTab = (tabname, id, showPrompt, showNegativePrompt) => { +function extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt) { if (!gradioApp().querySelector('.toprow-compact-tools')) return; // only applicable for compact prompt layout var promptContainer = gradioApp().getElementById(`${tabname}_prompt_container`); @@ -117,16 +124,16 @@ const extraNetworksMovePromptToTab = (tabname, id, showPrompt, showNegativePromp if (elem) { elem.classList.toggle('extra-page-prompts-active', showNegativePrompt || showPrompt); } -}; +} -const extraNetworksShowControlsForPage = (tabname, tabname_full) => { +function extraNetworksShowControlsForPage(tabname, tabname_full) { gradioApp().querySelectorAll(`#${tabname}_extra_tabs .extra-network-controls-div > div`).forEach((elem) => { let show = `${tabname_full}_controls` === elem.id; elem.classList.toggle("hidden", !show); }); -}; +} -const extraNetworksRemoveFromPrompt = (textarea, text, is_neg) => { +function extraNetworksRemoveFromPrompt(textarea, text, is_neg) { let match = text.match(is_neg ? re_extranet_neg : re_extranet); let replaced = false; let res; @@ -167,17 +174,17 @@ const extraNetworksRemoveFromPrompt = (textarea, text, is_neg) => { } return false; -}; +} -const extraNetworksUpdatePrompt = (textarea, text, is_neg) => { +function extraNetworksUpdatePrompt(textarea, text, is_neg) { if (!extraNetworksRemoveFromPrompt(textarea, text, is_neg)) { textarea.value = textarea.value + opts.extra_networks_add_text_separator + text; } updateInput(textarea); -}; +} -const extraNetworksSaveCardPreview = (event, tabname, filename) => { +function extraNetworksSaveCardPreview(event, tabname, filename) { const textarea = gradioApp().querySelector(`#${tabname}_preview_filename > label > textarea`); const button = gradioApp().getElementById(`${tabname}_save_preview`); @@ -188,9 +195,9 @@ const extraNetworksSaveCardPreview = (event, tabname, filename) => { event.stopPropagation(); event.preventDefault(); -}; +} -const extraNetworksFlattenMetadata = obj => { +function extraNetworksFlattenMetadata(obj) { const result = {}; // Convert any stringified JSON objects to actual objects @@ -239,9 +246,9 @@ const extraNetworksFlattenMetadata = obj => { } return result; -}; +} -const extraNetworksShowMetadata = text => { +function extraNetworksShowMetadata(text) { try { let parsed = JSON.parse(text); if (parsed && typeof parsed === 'object') { @@ -260,9 +267,9 @@ const extraNetworksShowMetadata = text => { popup(elem); return; -}; +} -const extraNetworksRefreshSingleCard = (tabname, extra_networks_tabname, name) => { +function extraNetworksRefreshSingleCard(tabname, extra_networks_tabname, name) { requestGet( "./sd_extra_networks/get-single-card", {tabname: tabname, extra_networks_tabname: extra_networks_tabname, name: name}, @@ -279,9 +286,9 @@ const extraNetworksRefreshSingleCard = (tabname, extra_networks_tabname, name) = } }, ); -}; +} -const extraNetworksRefreshTab = async tabname_full => { +async function extraNetworksRefreshTab(tabname_full) { /** called from python when user clicks the extra networks refresh tab button */ // Reapply controls since they don't change on refresh. const controls = gradioApp().getElementById(`${tabname_full}_controls`); @@ -305,9 +312,9 @@ const extraNetworksRefreshTab = async tabname_full => { fetch_data: true, }); } -}; +} -const extraNetworksAutoSetTreeWidth = pane => { +function extraNetworksAutoSetTreeWidth(pane) { if (!isElementLogError(pane)) { return; } @@ -343,14 +350,14 @@ const extraNetworksAutoSetTreeWidth = pane => { // Mimicks resizeHandle.js::setLeftColGridTemplate(). row.style.gridTemplateColumns = `${max_width}px ${pad}px 1fr`; -}; +} -const extraNetworksApplyFilter = tabname_full => { +function extraNetworksApplyFilter(tabname_full) { if (!keyExistsLogError(clusterizers, tabname_full)) { return; } - const pane = gradioApp.getElementById(`${tabname_full}_pane`); + const pane = gradioApp().getElementById(`${tabname_full}_pane`); if (!isElementLogError(pane)) { return; } @@ -361,7 +368,7 @@ const extraNetworksApplyFilter = tabname_full => { } // We only want to filter/sort the cards list. - clusterizers[tabname_full].cards_list.filterData(txt_search.value.toLowerCase()); + clusterizers[tabname_full].cards_list.setFilterStr(txt_search.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. @@ -375,70 +382,72 @@ const extraNetworksApplyFilter = tabname_full => { if (isElement(btn) && btn.textContent.trim() !== txt_search.value) { delete btn.dataset.selected; } -}; +} // ==== EVENT HANDLING ==== -const extraNetworksInitCardsData = async () => { - return await requestGetPromise( +async function extraNetworksInitCardsData(tabname, extra_networks_tabname) { + const res = await requestGetPromise( "./sd_extra_networks/init-cards-data", { tabname: tabname, extra_networks_tabname: extra_networks_tabname, }, ); -}; + return JSON.parse(res); +} -const extraNetworksInitTreeData = async () => { - return await requestGetPromise( +async function extraNetworksInitTreeData(tabname, extra_networks_tabname) { + const res = await requestGetPromise( "./sd_extra_networks/init-tree-data", { tabname: tabname, extra_networks_tabname: extra_networks_tabname, }, ); -}; + return JSON.parse(res); +} -const extraNetworksOnInitData = async class_name => { +async function extraNetworksOnInitData(tabname, extra_networks_tabname, class_name) { if (class_name === "ExtraNetworksClusterizeTreeList") { - return await extraNetworksInitCardsData(); + return await extraNetworksInitTreeData(tabname, extra_networks_tabname); } else if (class_name === "ExtraNetworksClusterizeCardsList") { - return await extraNetworksInitTreeData(); + return await extraNetworksInitCardsData(tabname, extra_networks_tabname); } -}; +} -const extraNetworksFetchCardsData = async (extra_networks_tabname, div_ids) => { - return await requestGetPromise( +async function extraNetworksFetchCardsData(extra_networks_tabname, div_ids) { + const res = await requestGetPromise( "./sd_extra_networks/fetch-cards-data", { extra_networks_tabname: extra_networks_tabname, div_ids: div_ids, }, ); -}; + return JSON.parse(res); +} -const extraNetworksFetchTreeData = async (extra_networks_tabname, div_ids) => { - return await requestGetPromise( +async function extraNetworksFetchTreeData(extra_networks_tabname, div_ids) { + const res = await requestGetPromise( "./sd_extra_networks/fetch-tree-data", { extra_networks_tabname: extra_networks_tabname, div_ids: div_ids, }, ); -}; + return JSON.parse(res); +} -const extraNetworksOnFetchData = async (class_name, extra_networks_tabname, div_ids) => { +async function extraNetworksOnFetchData(class_name, extra_networks_tabname, div_ids) { if (class_name === "ExtraNetworksClusterizeTreeList") { - return await extraNetworksFetchCardsData(extra_networks_tabname, div_ids); - } else if (class_name === "ExtraNetworksClusterizeCardsList") { return await extraNetworksFetchTreeData(extra_networks_tabname, div_ids); + } else if (class_name === "ExtraNetworksClusterizeCardsList") { + return await extraNetworksFetchCardsData(extra_networks_tabname, div_ids); } -}; +} -const extraNetworksFetchMetadata = (extra_networks_tabname, card_name) => { - const _showError = () => { - extraNetworksShowMetadata("there was an error getting metadata"); - }; +function extraNetworksFetchMetadata(extra_networks_tabname, card_name) { + const _showError = () => { extraNetworksShowMetadata("there was an error getting metadata"); }; requestGet( "./sd_extra_networks/metadata", @@ -452,40 +461,51 @@ const extraNetworksFetchMetadata = (extra_networks_tabname, card_name) => { }, _showError, ); -}; +} -const extraNetworksUnrelatedTabSelected = tabname => { +function extraNetworksUnrelatedTabSelected(tabname) { /** called from python when user selects an unrelated tab (generate) */ extraNetworksMovePromptToTab(tabname, '', false, false); extraNetworksShowControlsForPage(tabname, null); -}; +} -const extraNetworksTabSelected = async (tabname, id, showPrompt, showNegativePrompt, tabname_full) => { +async function extraNetworksTabSelected( + tabname, + id, + showPrompt, + showNegativePrompt, + tabname_full, +) { /** called from python when user selects an extra networks tab */ extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt); extraNetworksShowControlsForPage(tabname, tabname_full); await waitForKeyInObject({k: tabname_full, obj: clusterizers}); - await extraNetworksClusterizersLoadTab(tabname_full); -}; + await extraNetworksClusterizersLoadTab({ + tabname_full: tabname_full, + selected: true, + fetch_data: false, + }); +} -const extraNetworksBtnDirsViewItemOnClick = (event, tabname_full) => { +function extraNetworksBtnDirsViewItemOnClick(event, tabname_full) { /** Handles `onclick` events for buttons in the directory view. */ const txt_search = gradioApp().querySelector(`#${tabname_full}_controls .extra-network-control--search-text`); + const _deselect_all_buttons = () => { gradioApp().querySelectorAll(".extra-network-dirs-view-button").forEach((elem) => { delete elem.dataset.selected; }); }; - const _select_button = elem => { + const _select_button = (elem) => { _deselect_all_buttons(); // Update search input with select button's path. elem.dataset.selected = ""; txt_search.value = elem.textContent.trim(); }; - const _deselect_button = elem => { + const _deselect_button = (elem) => { delete elem.dataset.selected; txt_search.value = ""; }; @@ -498,9 +518,9 @@ const extraNetworksBtnDirsViewItemOnClick = (event, tabname_full) => { updateInput(txt_search); extraNetworksApplyFilter(tabname_full); -}; +} -const extraNetworksControlSearchClearOnClick = (event, tabname_full) => { +function extraNetworksControlSearchClearOnClick(event, tabname_full) { /** Dispatches custom event when the `clear` button in a search input is clicked. */ let clear_btn = event.target.closest(".extra-network-control--search-clear"); let txt_search = clear_btn.previousElementSibling; @@ -511,9 +531,9 @@ const extraNetworksControlSearchClearOnClick = (event, tabname_full) => { {bubbles: true, detail: {tabname_full: tabname_full}}, ) ); -}; +} -const extraNetworksControlSortModeOnClick = (event, tabname_full) => { +function extraNetworksControlSortModeOnClick(event, tabname_full) { /** Handles `onclick` events for Sort Mode buttons. */ event.currentTarget.parentElement.querySelectorAll('.extra-network-control--sort-mode').forEach(elem => { delete elem.dataset.selected; @@ -526,11 +546,10 @@ const extraNetworksControlSortModeOnClick = (event, tabname_full) => { } const sort_mode_str = event.currentTarget.dataset.sortMode.toLowerCase(); - clusterizers[tabname_full].cards_list.sort_mode_str = sort_mode_str; - extraNetworksApplyFilter(tabname_full); -}; + clusterizers[tabname_full].cards_list.setSortMode(sort_mode_str); +} -const extraNetworksControlSortDirOnClick = (event, tabname_full) => { +function extraNetworksControlSortDirOnClick(event, tabname_full) { /** Handles `onclick` events for the Sort Direction button. * * Modifies the data attributes of the Sort Direction button to cycle between @@ -550,11 +569,10 @@ const extraNetworksControlSortDirOnClick = (event, tabname_full) => { return; } - clusterizers[tabname_full].cards_list.sort_dir_str = sort_dir_str; - extraNetworksApplyFilter(tabname_full); -}; + clusterizers[tabname_full].cards_list.setSortDir(sort_dir_str); +} -const extraNetworksControlTreeViewOnClick = (event, tabname_full) => { +function extraNetworksControlTreeViewOnClick(event, tabname_full) { /** Handles `onclick` events for the Tree View button. * * Toggles the tree view in the extra networks pane. @@ -572,10 +590,10 @@ const extraNetworksControlTreeViewOnClick = (event, tabname_full) => { return; } clusterizers[tabname_full].tree_list.scroll_elem.parentElement.classList.toggle("hidden", !show); - //clusterizers[tabname_full].tree_list.enable(show); -}; + clusterizers[tabname_full].tree_list.enable(show); +} -const extraNetworksControlDirsViewOnClick = (event, tabname_full) => { +function extraNetworksControlDirsViewOnClick(event, tabname_full) { /** Handles `onclick` events for the Dirs View button. * * Toggles the directory view in the extra networks pane. @@ -589,9 +607,9 @@ const extraNetworksControlDirsViewOnClick = (event, tabname_full) => { const pane = gradioApp().getElementById(`${tabname_full}_pane`); pane.querySelector(".extra-network-content--dirs-view").classList.toggle("hidden", !show); -}; +} -const extraNetworksControlRefreshOnClick = (event, tabname_full) => { +function extraNetworksControlRefreshOnClick(event, tabname_full) { /** Handles `onclick` events for the Refresh Page button. * * In order to actually call the python functions in `ui_extra_networks.py` @@ -612,9 +630,9 @@ const extraNetworksControlRefreshOnClick = (event, tabname_full) => { // Fire an event for this button click. gradioApp().getElementById(`${tabname_full}_extra_refresh_internal`).dispatchEvent(new Event("click")); -}; +} -const extraNetworksCardOnClick = (event, tabname) => { +function extraNetworksCardOnClick(event, tabname) { const elem = event.currentTarget; const prompt_elem = gradioApp().querySelector(`#${tabname}_prompt > label > textarea`); const neg_prompt_elem = gradioApp().querySelector(`#${tabname}_neg_prompt > label > textarea`); @@ -626,17 +644,17 @@ const extraNetworksCardOnClick = (event, tabname) => { } else { extraNetworksUpdatePrompt(prompt_elem, elem.dataset.prompt); } -}; +} -const extraNetworksTreeFileOnClick = (event, btn, tabname_full) => { +function extraNetworksTreeFileOnClick(event, btn, tabname_full) { return; -}; +} -const extraNetworksTreeDirectoryOnClick = (event, btn, tabname_full) => { +function extraNetworksTreeDirectoryOnClick(event, btn, tabname_full) { return; -}; +} -const extraNetworksTreeOnClick = (event, tabname_full) => { +function extraNetworksTreeOnClick(event, tabname_full) { const btn = event.target.closest(".tree-list-item"); if (!isElementLogError(btn)) { return; @@ -649,14 +667,14 @@ const extraNetworksTreeOnClick = (event, tabname_full) => { } event.stopPropagation(); -}; +} -const extraNetworksBtnShowMetadataOnClick = (event, extra_networks_tabname, card_name) => { +function extraNetworksBtnShowMetadataOnClick(event, extra_networks_tabname, card_name) { extraNetworksFetchMetadata(extra_networks_tabname, card_name); event.stopPropagation(); -}; +} -const extraNetworksBtnEditMetadataOnClick = (event, tabname_full, card_name) => { +function extraNetworksBtnEditMetadataOnClick(event, tabname_full, card_name) { const id = `${tabname_full}_edit_user_metadata`; let editor = extraPageUserMetadataEditors[id]; if (isNullOrUndefined(editor)) { @@ -673,16 +691,16 @@ const extraNetworksBtnEditMetadataOnClick = (event, tabname_full, card_name) => editor.button.click(); popup(editor.page); -}; +} -const extraNetworksBtnCopyPathOnClick = (event, path) => { +function extraNetworksBtnCopyPathOnClick(event, path) { copyToClipboard(path); event.stopPropagation(); -}; +} // ==== MAIN SETUP ==== -const extraNetworksSetupEventDelegators = () => { +function extraNetworksSetupEventDelegators() { /** Sets up event delegators for all extraNetworks tabs. * * These event handlers are not tied to any specific elements on the page. @@ -724,20 +742,15 @@ const extraNetworksSetupEventDelegators = () => { closePopup(); } }); -}; +} -const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => { +async function extraNetworksSetupTabContent(tabname, pane, controls_div) { const tabname_full = pane.id; const extra_networks_tabname = tabname_full.replace(`${tabname}_`, ""); const controls = await waitForElement(`#${tabname_full}_pane .extra-network-controls`); - const tree_scroll_elem = await waitForElement(`#${tabname_full}_tree_list_scroll_area`); - const tree_content_elem = await waitForElement(`#${tabname_full}_tree_list_content_area`); - const cards_scroll_elem = await waitForElement(`#${tabname_full}_cards_list_scroll_area`); - const cards_content_elem = await waitForElement(`#${tabname_full}_cards_list_content_area`); await waitForElement(`#${tabname_full}_pane .extra-network-content--dirs-view`); - console.log("BEFORE:", tree_scroll_elem, cards_scroll_elem); controls.id = `${tabname_full}_controls`; controls_div.insertBefore(controls, null); @@ -745,8 +758,8 @@ const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => { tree_list: new ExtraNetworksClusterizeTreeList({ tabname: tabname, extra_networks_tabname: extra_networks_tabname, - scrollElem: tree_scroll_elem, - contentElem: tree_content_elem, + scrollId: `${tabname_full}_tree_list_scroll_area`, + contentId: `${tabname_full}_tree_list_content_area`, tag: "div", callbacks: { initData: extraNetworksOnInitData, @@ -756,8 +769,8 @@ const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => { cards_list: new ExtraNetworksClusterizeCardsList({ tabname: tabname, extra_networks_tabname: extra_networks_tabname, - scrollElem: cards_scroll_elem, - contentElem: cards_content_elem, + scrollId: `${tabname_full}_cards_list_scroll_area`, + contentId: `${tabname_full}_cards_list_content_area`, tag: "div", callbacks: { initData: extraNetworksOnInitData, @@ -766,18 +779,15 @@ const extraNetworksSetupTabContent = async (tabname, pane, controls_div) => { }), }; + await clusterizers[tabname_full].tree_list.setup(); + await clusterizers[tabname_full].cards_list.setup(); + if (pane.style.display !== "none") { extraNetworksShowControlsForPage(tabname, tabname_full); } +} - await extraNetworksClusterizersLoadTab({ - tabname_full: tabname_full, - selected: false, - fetch_data: true, - }); -}; - -const extraNetworksSetupTab = async (tabname) => { +async function extraNetworksSetupTab(tabname) { let controls_div; const this_tab = await waitForElement(`#${tabname}_extra_tabs`); @@ -793,15 +803,15 @@ const extraNetworksSetupTab = async (tabname) => { } extraNetworksRegisterPromptForTab(tabname, `${tabname}_prompt`); extraNetworksRegisterPromptForTab(tabname, `${tabname}_neg_prompt`); -}; +} -const extraNetworksSetup = async () => { +async function extraNetworksSetup() { await waitForBool(initialUiOptionsLoaded); extraNetworksSetupTab('txt2img'); extraNetworksSetupTab('img2img'); extraNetworksSetupEventDelegators(); -}; +} onUiLoaded(extraNetworksSetup); onOptionsChanged(() => initialUiOptionsLoaded.state = true); diff --git a/javascript/extraNetworksClusterize.js b/javascript/extraNetworksClusterize.js index 37f7214bb..79c9d766a 100644 --- a/javascript/extraNetworksClusterize.js +++ b/javascript/extraNetworksClusterize.js @@ -18,6 +18,7 @@ class ExtraNetworksClusterize extends Clusterize { sort_fn = this.default_sort_fn; tabname = ""; extra_networks_tabname = ""; + enabled = false; // Override base class defaults default_sort_mode_str = "divId"; @@ -27,17 +28,54 @@ class ExtraNetworksClusterize extends Clusterize { sort_dir_str = this.default_sort_dir_str; filter_str = this.default_filter_str; - constructor(...args) { - super(...args); - - // finish initialization - this.tabname = getValueThrowError(...args, "tabname"); - this.extra_networks_tabname = getValueThrowError(...args, "extra_networks_tabname"); + constructor(args) { + super(args); + this.tabname = getValueThrowError(args, "tabname"); + this.extra_networks_tabname = getValueThrowError(args, "extra_networks_tabname"); } - sortByDivId() { + sortByDivId(data) { /** Sort data_obj keys (div_id) as numbers. */ - this.data_obj_keys_sorted = Object.keys(this.data_obj).sort(INT_COLLATOR.compare); + return Object.keys(data).sort(INT_COLLATOR.compare); + } + + async reinitData() { + 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.options.callbacks.sortData.call(this); + await this.setMaxItems(Object.keys(this.data_obj).length); + } + + async setup() { + if (this.setup_has_run) { + return; + } + + await this.reinitData(); + + if (this.enabled) { + await super.setup(); + } + } + + async load(force_init_data) { + if (!this.enabled) { + return; + } + + if (!this.setup_has_run) { + await this.setup(); + } else if (force_init_data) { + await this.reinitData(); + } else { + await this.refresh(true); + } + } + + enable(state) { + // if no state is passed, we enable by default. + this.enabled = state !== false; } clear() { @@ -46,31 +84,54 @@ class ExtraNetworksClusterize extends Clusterize { super.clear(); } - async initDataDefault() { - /**Fetches the initial data. - * - * This data should be minimal and only contain div IDs and other necessary - * information such as sort keys and terms for filtering. - */ - throw new NotImplementedError(); - } + setSortMode(sort_mode_str) { + if (this.sort_mode_str === sort_mode_str) { + return; + } - async fetchDataDefault(idx_start, idx_end) { - throw new NotImplementedError(); - } - - async sortDataDefault(sort_mode_str, sort_dir_str) { this.sort_mode_str = sort_mode_str; - this.sort_dir_str = sort_dir_str; - this.sort_reverse = sort_dir_str === "descending"; + this.sortData(); + } + setSortDir(sort_dir_str) { + const reverse = (sort_dir_str === "descending"); + if (this.sort_reverse === reverse) { + return; + } + + this.sort_dir_str = sort_dir_str; + this.sort_reverse = reverse; + this.sortData(); + } + + setFilterStr(filter_str) { + if (isString(filter_str) && this.filter_str !== filter_str.toLowerCase()) { + this.filter_str = filter_str.toLowerCase(); + } else if (isNullOrUndefined(this.filter_str)) { + this.filter_str = this.default_filter_str; + } else { + return; + } + + this.filterData(); + } + + async initDataDefaultCallback() { + throw new NotImplementedError(); + } + + async fetchDataDefaultCallback() { + throw new NotImplementedError(); + } + + async sortDataDefaultCallback() { this.data_obj_keys_sorted = this.sort_fn(this.data_obj); if (this.sort_reverse) { this.data_obj_keys_sorted = this.data_obj_keys_sorted.reverse(); } } - async filterDataDefault(filter_str) { + async filterDataDefaultCallback() { throw new NotImplementedError(); } } @@ -79,8 +140,10 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { selected_div_id = null; constructor(args) { - args.no_data_text = "Directory is empty."; - super(args); + super({ + ...args, + no_data_text: "Directory is empty.", + }); } @@ -147,6 +210,42 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { this.selected_div_id = "selected" in elem.dataset ? div_id : null; } + getMaxRowWidth() { + /** Calculates the width of the widest row in the list. */ + if (!this.enabled) { + // Inactive list is not displayed on screen. Can't calculate size. + return false; + } + if (this.content_elem.children.length === 0) { + // If there is no data then just skip. + return false; + } + + let max_width = 0; + for (let i = 0; i < this.content_elem.children.length; i += this.n_cols) { + let row_width = 0; + for (let j = 0; j < this.n_cols; j++) { + const child = this.content_elem.children[i + j]; + const child_style = window.getComputedStyle(child, null); + const prev_style = child.style.cssText; + const n_cols = child_style.getPropertyValue("grid-template-columns").split(" ").length; + child.style.gridTemplateColumns = `repeat(${n_cols}, max-content)`; + row_width += child.scrollWidth; + // Restore previous style. + child.style.cssText = prev_style; + } + max_width = Math.max(max_width, row_width); + } + if (max_width <= 0) { + return; + } + + // Adds the scroll element's border and the scrollbar's width to the result. + // If scrollbar isn't visible, then only the element border is added. + max_width += this.scroll_elem.offsetWidth - this.scroll_elem.clientWidth; + return max_width; + } + async onRowExpandClick(div_id, elem) { /** Expands or collapses a row to show/hide children. */ if (!keyExistsLogError(this.data_obj, div_id)){ @@ -165,7 +264,7 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { await this.setMaxItems(Object.values(this.data_obj).filter(v => v.visible).length); } - async initDataDefault() { + async initData() { /*Expects an object like the following: { parent: null or div_id, @@ -174,11 +273,18 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { expanded: bool, } */ - console.log("BLAH:", this.options.callbacks.initData); - this.data_obj = await this.options.callbacks.initData(this.constructor.name); + this.data_obj = await this.options.callbacks.initData.call( + this, + this.tabname, + this.extra_networks_tabname, + this.constructor.name, + ); } - async fetchDataDefault(idx_start, idx_end) { + async fetchData(idx_start, idx_end) { + if (!this.enabled) { + return []; + } const n_items = idx_end - idx_start; const div_ids = []; for (const div_id of this.data_obj_keys_sorted.slice(idx_start)) { @@ -190,7 +296,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { } } - const data = await this.options.callbacks.fetchData( + const data = await this.options.callbacks.fetchData.call( + this, this.constructor.name, this.extra_networks_tabname, div_ids, @@ -202,8 +309,8 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { const text_size = style.getPropertyValue("--button-large-text-size"); const res = []; - for (const [div_id, item] of Object.entries(data)) { - const parsed_html = htmlStringToElement(item); + for (const [div_id, html_str] of Object.entries(data)) { + const parsed_html = htmlStringToElement(html_str); const depth = Number(parsed_html.dataset.depth); parsed_html.style.paddingLeft = `calc(${depth} * ${text_size})`; parsed_html.style.boxShadow = this.getBoxShadow(depth); @@ -216,14 +323,10 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { res.push(parsed_html.outerHTML); } - return rows; + return res; } - async sortDataDefault(sort_mode, sort_dir) { - throw new NotImplementedError(); - } - - async filterDataDefault(filter_str) { + async filterDataDefaultCallback() { // just return the number of visible objects in our data. return Object.values(this.data_obj).filter(v => v.visible).length; } @@ -231,8 +334,10 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { constructor(args) { - args.no_data_text = "No files matching filter."; - super(args); + super({ + ...args, + no_data_text: "No files matching filter.", + }); } sortByName(data) { @@ -259,18 +364,25 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { }); } - async initDataDefault() { + async initData() { /*Expects an object like the following: { search_keys: array of strings, sort_: string, (for various sort modes) } */ - console.log("HERE:", this.options.callbacks); - this.data_obj = await this.options.callbacks.initData(this.constructor.name); + this.data_obj = await this.options.callbacks.initData.call( + this, + this.tabname, + this.extra_networks_tabname, + this.constructor.name, + ); } - async fetchDataDefault(idx_start, idx_end) { + async fetchData(idx_start, idx_end) { + if (!this.enabled) { + return; + } const n_items = idx_end - idx_start; const div_ids = []; for (const div_id of this.data_obj_keys_sorted.slice(idx_start)) { @@ -282,7 +394,8 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { } } - const data = await this.options.callbacks.fetchData( + const data = await this.options.callbacks.fetchData.call( + this, this.constructor.name, this.extra_networks_tabname, div_ids, @@ -291,35 +404,29 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { return Object.values(data); } - async sortDataDefault(sort_mode_str, sort_dir_str) { - switch (sort_mode_str) { + async sortData() { + switch (this.sort_mode_str) { case "name": this.sort_fn = this.sortByName; break; case "path": this.sort_fn = this.sortByPath; break; - case "created": + case "date_created": this.sort_fn = this.sortByDateCreated; break; - case "modified": + case "date_modified": this.sort_fn = this.sortByDateModified; break; default: this.sort_fn = this.default_sort_fn; break; } - await super.sortDataDefault(sort_mode_str, sort_dir_str) + await super.sortData() } - async filterDataDefault(filter_str) { + async filterDataDefaultCallback() { /** Filters data by a string and returns number of items after filter. */ - if (isString(filter_str)) { - this.filter_str = filter_str.toLowerCase(); - } else if (isNullOrUndefined(this.filter_str)) { - this.filter_str = this.default_filter_str; - } - let n_visible = 0; for (const [div_id, v] of Object.entries(this.data_obj)) { let visible = v.search_terms.indexOf(this.filter_str) != -1; diff --git a/javascript/utils.js b/javascript/utils.js index 88694c93c..99427fc6b 100644 --- a/javascript/utils.js +++ b/javascript/utils.js @@ -1,156 +1,161 @@ -/** Helper functions for checking types and simplifying logging/error handling. */ +/** Collators used for sorting. */ +const INT_COLLATOR = new Intl.Collator([], {numeric: true}); +const STR_COLLATOR = new Intl.Collator("en", {numeric: true, sensitivity: "base"}); -const isNumber = x => typeof x === "number" && isFinite(x); -const isNumberLogError = x => { +/** Helper functions for checking types and simplifying logging/error handling. */ +function isNumber(x) { return typeof x === "number" && isFinite(x); } +function isNumberLogError(x) { if (isNumber(x)) { return true; } console.error(`expected number, got: ${typeof x}`); return false; -}; -const isNumberThrowError = x => { +} +function isNumberThrowError(x) { if (isNumber(x)) { return; } throw new Error(`expected number, got: ${typeof x}`); -}; +} -const isString = x => typeof x === "string" || x instanceof String; -const isStringLogError = x => { +function isString(x) { return typeof x === "string" || x instanceof String; } +function isStringLogError(x) { if (isString(x)) { return true; } console.error(`expected string, got: ${typeof x}`); return false; -}; -const isStringThrowError = x => { +} +function isStringThrowError(x) { if (isString(x)) { return; } throw new Error(`expected string, got: ${typeof x}`); -}; +} -const isNull = x => x === null; -const isUndefined = x => typeof x === "undefined" || x === undefined; +function isNull(x) { return x === null; } +function isUndefined(x) { return typeof x === "undefined" || x === undefined; } // checks both null and undefined for simplicity sake. -const isNullOrUndefined = x => isNull(x) || isUndefined(x); -const isNullOrUndefinedLogError = x => { +function isNullOrUndefined(x) { return isNull(x) || isUndefined(x); } +function isNullOrUndefinedLogError(x) { if (isNullOrUndefined(x)) { console.error("Variable is null/undefined."); return true; } return false; -}; -const isNullOrUndefinedThrowError = x => { +} +function isNullOrUndefinedThrowError(x) { if (!isNullOrUndefined(x)) { return; } throw new Error("Variable is null/undefined."); -}; +} -const isElement = x => x instanceof Element; -const isElementLogError = x => { +function isElement(x) { return x instanceof Element; } +function isElementLogError(x) { if (isElement(x)) { return true; } console.error(`expected element type, got: ${typeof x}`); return false; -}; -const isElementThrowError = x => { +} +function isElementThrowError(x) { if (isElement(x)) { return; } throw new Error(`expected element type, got: ${typeof x}`); -}; +} -const isFunction = x => typeof x === "function"; -const isFunctionLogError = x => { +function isFunction(x) { return typeof x === "function"; } +function isFunctionLogError(x) { if (isFunction(x)) { return true; } console.error(`expected function type, got: ${typeof x}`); return false; -}; -const isFunctionThrowError = x => { +} +function isFunctionThrowError(x) { if (isFunction(x)) { return; } throw new Error(`expected function type, got: ${typeof x}`); -}; +} -const isObject = x => typeof x === "object" && !Array.isArray(x); -const isObjectLogError = x => { +function isObject(x) { return typeof x === "object" && !Array.isArray(x); } +function isObjectLogError(x) { if (isObject(x)) { return true; } console.error(`expected object type, got: ${typeof x}`); return false; -}; -const isObjectThrowError = x => { +} +function isObjectThrowError(x) { if (isObject(x)) { return; } throw new Error(`expected object type, got: ${typeof x}`); -}; +} -const keyExists = (obj, k) => isObject(obj) && isString(k) && k in obj; -const keyExistsLogError = (obj, k) => { +function keyExists(obj, k) { + return isObject(obj) && isString(k) && k in obj; +} +function keyExistsLogError(obj, k) { if (keyExists(obj, k)) { return true; } console.error(`key does not exist in object: ${k}`); return false; -}; -const keyExistsThrowError = (obj, k) => { +} +function keyExistsThrowError(obj, k) { if (keyExists(obj, k)) { return; } throw new Error(`key does not exist in object: ${k}`) -}; +} -const getValue = (obj, k) => { +function getValue(obj, k) { /** Returns value of object for given key if it exists, otherwise returns null. */ if (keyExists(obj, k)) { return obj[k]; } return null; -}; -const getValueLogError = (obj, k) => { +} +function getValueLogError(obj, k) { if (keyExistsLogError(obj, k)) { return obj[k]; } return null; -}; -const getValueThrowError = (obj, k) => { +} +function getValueThrowError(obj, k) { keyExistsThrowError(obj, k); return obj[k]; -}; +} -const getElementByIdLogError = selector => { +function getElementByIdLogError(selector) { const elem = gradioApp().getElementById(selector); isElementLogError(elem); return elem; -}; -const getElementByIdThrowError = selector => { +} +function getElementByIdThrowError(selector) { const elem = gradioApp().getElementById(selector); isElementThrowError(elem); return elem; -}; +} -const querySelectorLogError = selector => { +function querySelectorLogError(selector) { const elem = gradioApp().querySelector(selector); isElementLogError(elem); return elem; -}; -const querySelectorThrowError = selector => { +} +function querySelectorThrowError(selector) { const elem = gradioApp().querySelector(selector); isElementThrowError(elem); return elem; -}; +} /** Functions for getting dimensions of elements. */ -const getComputedPropertyDims = (elem, prop) => { +function getComputedPropertyDims(elem, prop) { /** Returns the top/left/bottom/right float dimensions of an element for the specified property. */ const style = window.getComputedStyle(elem, null); return { @@ -159,27 +164,27 @@ const getComputedPropertyDims = (elem, prop) => { bottom: parseFloat(style.getPropertyValue(`${prop}-bottom`)), right: parseFloat(style.getPropertyValue(`${prop}-right`)), }; -}; +} -const getComputedMarginDims = elem => { +function getComputedMarginDims(elem) { /** Returns the width/height of the computed margin of an element. */ const dims = getComputedPropertyDims(elem, "margin"); return { width: dims.left + dims.right, height: dims.top + dims.bottom, }; -}; +} -const getComputedPaddingDims = elem => { +function getComputedPaddingDims(elem) { /** Returns the width/height of the computed padding of an element. */ const dims = getComputedPropertyDims(elem, "padding"); return { width: dims.left + dims.right, height: dims.top + dims.bottom, }; -}; +} -const getComputedBorderDims = elem => { +function getComputedBorderDims(elem) { /** Returns the width/height of the computed border of an element. */ // computed border will always start with the pixel width so thankfully // the parseFloat() conversion will just give us the width and ignore the rest. @@ -189,9 +194,9 @@ const getComputedBorderDims = elem => { width: dims.left + dims.right, height: dims.top + dims.bottom, }; -}; +} -const getComputedDims = elem => { +function getComputedDims(elem) { /** Returns the full width and height of an element including its margin, padding, and border. */ const width = elem.scrollWidth; const height = elem.scrollHeight; @@ -202,24 +207,24 @@ const getComputedDims = elem => { width: width + margin.width + padding.width + border.width, height: height + margin.height + padding.height + border.height, }; -}; +} -const calcColsPerRow = function (parent, child) { +function calcColsPerRow(parent, child) { /** Calculates the number of columns of children that can fit in a parent's visible width. */ const parent_inner_width = parent.offsetWidth - getComputedPaddingDims(parent).width; return parseInt(parent_inner_width / getComputedDims(child).width, 10); -}; +} -const calcRowsPerCol = function (parent, child) { +function calcRowsPerCol(parent, child) { /** Calculates the number of rows of children that can fit in a parent's visible height. */ const parent_inner_height = parent.offsetHeight - getComputedPaddingDims(parent).height; return parseInt(parent_inner_height / getComputedDims(child).height, 10); -}; +} /** Functions for asynchronous operations. */ -const debounce = (handler, timeout_ms) => { +function debounce(handler, timeout_ms) { /** Debounces a function call. * * NOTE: This will NOT work if called from within a class. @@ -244,9 +249,9 @@ const debounce = (handler, timeout_ms) => { clearTimeout(timer); timer = setTimeout(() => handler(...args), timeout_ms); }; -}; +} -const waitForElement = selector => { +function waitForElement(selector) { /** Promise that waits for an element to exist in DOM. */ return new Promise(resolve => { if (document.querySelector(selector)) { @@ -265,9 +270,9 @@ const waitForElement = selector => { subtree: true }); }); -}; +} -const waitForBool = o => { +function waitForBool(o) { /** Promise that waits for a boolean to be true. * * `o` must be an Object of the form: @@ -283,9 +288,9 @@ const waitForBool = o => { setTimeout(_waitForBool, 100); })(); }); -}; +} -const waitForKeyInObject = o => { +function waitForKeyInObject(o) { /** Promise that waits for a key to exist in an object. * * `o` must be an Object of the form: @@ -304,9 +309,9 @@ const waitForKeyInObject = o => { setTimeout(_waitForKeyInObject, 100); })(); }); -}; +} -const waitForValueInObject = o => { +function waitForValueInObject(o) { /** Promise that waits for a key value pair in an Object. * * `o` must be an Object of the form: @@ -329,11 +334,11 @@ const waitForValueInObject = o => { })(); }); }); -}; +} /** Requests */ -const requestGet = (url, data, handler, errorHandler) => { +function requestGet(url, data, handler, errorHandler) { var xhr = new XMLHttpRequest(); var args = Object.keys(data).map(function (k) { return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]); @@ -357,9 +362,9 @@ const requestGet = (url, data, handler, errorHandler) => { }; var js = JSON.stringify(data); xhr.send(js); -}; +} -const requestGetPromise = (url, data) => { +function requestGetPromise(url, data) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); let args = Object.keys(data).map(k => { @@ -367,39 +372,40 @@ const requestGetPromise = (url, data) => { }).join("&"); xhr.open("GET", url + "?" + args, true); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - try { - resolve(xhr.responseText); - } catch (error) { - reject(error); - } - } else { - reject({ status: this.status, statusText: xhr.statusText }); - } + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + resolve(xhr.responseText); + } else { + reject({status: xhr.status, response: xhr.responseText}); } }; - xhr.send(JSON.stringify(data)); + + xhr.onerror = () => { + reject({status: xhr.status, response: xhr.responseText}); + }; + const payload = JSON.stringify(data); + xhr.send(payload); }); -}; +} /** Misc helper functions. */ -const clamp = (x, min, max) => Math.max(min, Math.min(x, max)); +function clamp(x, min, max) { + return Math.max(min, Math.min(x, max)); +} -const getStyle = (prop, elem) => { +function getStyle(prop, elem) { return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; -}; +} -const htmlStringToElement = function (str) { +function htmlStringToElement(s) { /** Converts an HTML string into an Element type. */ let parser = new DOMParser(); - let tmp = parser.parseFromString(str, "text/html"); + let tmp = parser.parseFromString(s, "text/html"); return tmp.body.firstElementChild; -}; +} -const toggleCss = (key, css, enable) => { +function toggleCss(key, css, enable) { var style = document.getElementById(key); if (enable && !style) { style = document.createElement('style'); @@ -414,10 +420,10 @@ const toggleCss = (key, css, enable) => { style.innerHTML == ''; style.appendChild(document.createTextNode(css)); } -}; +} -const copyToClipboard = s => { +function copyToClipboard(s) { /** Copies the passed string to the clipboard. */ isStringThrowError(s); navigator.clipboard.writeText(s); -}; \ No newline at end of file +} diff --git a/modules/ui_common.py b/modules/ui_common.py index 31b5492ea..82d424c1a 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -342,7 +342,7 @@ def setup_dialog(button_show, dialog, *, button_close=None): fn=lambda: gr.update(visible=True), inputs=[], outputs=[dialog], - ).then(fn=None, _js="function(){ popupId('" + dialog.elem_id + "'); }") + ).then(fn=None, _js=f"function(){{popupId('{dialog.elem_id}');}}") if button_close: button_close.click(fn=None, _js="closePopup") diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 51937b73d..a48226fdb 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -56,13 +56,14 @@ class CardListItem(ListItem): Attributes: visible [bool]: Whether the item should be shown in the list. sort_keys [dict]: Nested dict where keys are sort modes and values are sort keys. + search_terms [str]: String containing multiple search terms joined with spaces. """ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.visible: bool = False self.sort_keys = {} - self.search_terms = [] + self.search_terms = "" class TreeListItem(ListItem): @@ -92,6 +93,7 @@ class DirectoryTreeNode: self.root_dir = root_dir self.parent = parent + self.depth = 0 self.is_dir = False self.item = None self.relpath = os.path.relpath(self.abspath, self.root_dir) @@ -99,6 +101,7 @@ class DirectoryTreeNode: # If a parent is passed, then we add this instance to the parent's children. if self.parent is not None: + self.depth = self.parent.depth + 1 self.parent.add_child(self) def add_child(self, child: "DirectoryTreeNode") -> None: @@ -258,15 +261,12 @@ def init_tree_data(tabname: str = "", extra_networks_tabname: str = "") -> JSONR def fetch_tree_data( extra_networks_tabname: str = "", - div_ids: Optional[list[str]] = None, + div_ids: str = "", ) -> JSONResponse: page = get_page_by_name(extra_networks_tabname) - if div_ids is None: - return JSONResponse({}) - res = {} - for div_id in div_ids: + for div_id in div_ids.split(","): if div_id in page.tree: res[div_id] = page.tree[div_id].html @@ -275,15 +275,12 @@ def fetch_tree_data( def fetch_cards_data( extra_networks_tabname: str = "", - div_ids: Optional[list[str]] = None, + div_ids: str = "", ) -> JSONResponse: page = get_page_by_name(extra_networks_tabname) - if div_ids is None: - return JSONResponse({}) - res = {} - for div_id in div_ids: + for div_id in div_ids.split(","): if div_id in page.cards: res[div_id] = page.cards[div_id].html @@ -336,12 +333,10 @@ def add_pages_to_demo(app): app.add_api_route("/sd_extra_networks/cover-images", fetch_cover_images, methods=["GET"]) app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"]) app.add_api_route("/sd_extra_networks/get-single-card", get_single_card, methods=["GET"]) - #app.add_api_route("/sd_extra_networks/init-tree-data", init_tree_data, methods=["GET"]) - #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/init-tree-data", init_tree_data, methods=["GET"]) + 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"]) def quote_js(s): s = s.replace('\\', '\\\\') @@ -521,7 +516,7 @@ class ExtraNetworksPage: style += f"width: {shared.opts.extra_networks_card_width}px;" background_image = None - preview = html.escape(item.get("preview", "")) + preview = html.escape(item.get("preview", "") or "") if preview: background_image = f'' @@ -558,7 +553,7 @@ class ExtraNetworksPage: description = "" if shared.opts.extra_networks_card_show_desc: - description = item.get("description", "") + description = item.get("description", "") or "" if not shared.opts.extra_networks_card_description_is_html: description = html.escape(description) @@ -571,7 +566,7 @@ class ExtraNetworksPage: "data-prompt": item.get("prompt", "").strip(), "data-neg-prompt": item.get("negative_prompt", "").strip(), "data-allow-neg": self.allow_negative_prompt, - **{f"data-sort-{sort_mode}": sort_key for sort_mode, sort_key in sort_keys}, + **{f"data-sort-{sort_mode}": sort_key for sort_mode, sort_key in sort_keys.items()}, } data_attributes_str = "" @@ -606,7 +601,7 @@ class ExtraNetworksPage: search_terms = item.get("search_terms", []) self.cards[div_id] = CardListItem(div_id, card_html) self.cards[div_id].sort_keys = sort_keys - self.cards[div_id].search_terms = search_terms + self.cards[div_id].search_terms = " ".join(search_terms) # Sort cards for all sort modes sort_modes = ["name", "path", "date_created", "date_modified"] @@ -621,6 +616,7 @@ class ExtraNetworksPage: res[div_id] = { **{f"sort_{mode}": key for mode, key in card_item.sort_keys.items()}, "search_terms": card_item.search_terms, + "visible": True, } return res @@ -707,21 +703,21 @@ class ExtraNetworksPage: self.tree[div_id].visible = True # Set all direct children to active for child_node in self.tree[div_id].node.children: - self.tree[child_node.id].visible = True + self.tree[path_to_div_id[child_node.abspath]].visible = True for div_id, tree_item in self.tree.items(): # Expand root nodes and make them visible. expanded = tree_item.node.parent is None visible = tree_item.node.parent is None - parent = None + parent_id = None if tree_item.node.parent is not None: - parent = path_to_div_id[tree_item.node.parent.abspath] + parent_id = path_to_div_id[tree_item.node.parent.abspath] # Direct children of root nodes should be visible by default. - if tree_item.node.parent.node is None: + if self.tree[parent_id].node.parent is None: visible = True res[div_id] = { - "parent": parent, + "parent": parent_id, "children": [path_to_div_id[child.abspath] for child in tree_item.node.children], "visible": visible, "expanded": expanded, @@ -952,23 +948,28 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): for tab in unrelated_tabs: tab.select( fn=None, - _js=f"fujnction(){{extraNetworksUnrelatedTabSelected('{tabname}');}}", + _js=f"function(){{extraNetworksUnrelatedTabSelected('{tabname}');}}", inputs=[], outputs=[], show_progress=False, ) for page, tab in zip(ui.stored_extra_pages, related_tabs): - jscode = ( - "function(){extraNetworksTabSelected(" - f"'{tabname}', " - f"'{tabname}_{page.extra_networks_tabname}_prompts', " - f"{str(page.allow_prompt).lower()}, " - f"{str(page.allow_negative_prompt).lower()}, " - f"'{tabname}_{page.extra_networks_tabname}'" - ");}" + tab.select( + fn=None, + _js=( + "function(){extraNetworksTabSelected(" + f"'{tabname}', " + f"'{tabname}_{page.extra_networks_tabname}_prompts', " + f"{str(page.allow_prompt).lower()}, " + f"{str(page.allow_negative_prompt).lower()}, " + f"'{tabname}_{page.extra_networks_tabname}'" + ");}" + ), + inputs=[], + outputs=[], + show_progress=False, ) - tab.select(fn=None, _js=jscode, inputs=[], outputs=[], show_progress=False) def refresh(): for pg in ui.stored_extra_pages: @@ -986,7 +987,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): _js='setupAllResizeHandles' ).then( fn=lambda: None, - _js=f"function(){{ extraNetworksRefreshTab('{tabname}_{page.extra_networks_tabname}'); }}", + _js=f"function(){{extraNetworksRefreshTab('{tabname}_{page.extra_networks_tabname}');}}", ) def create_html(): diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index fde093700..626104cad 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -148,7 +148,18 @@ class UserMetadataEditor: def setup_save_handler(self, button, func, components): button\ .click(fn=func, inputs=[self.edit_name_input, *components], outputs=[])\ - .then(fn=None, _js="function(name){closePopup(); extraNetworksRefreshSingleCard(" + json.dumps(self.page.name) + "," + json.dumps(self.tabname) + ", name);}", inputs=[self.edit_name_input], outputs=[]) + .then( + fn=None, + _js=( + "function(name){" + "closePopup(); " + "extraNetworksRefreshSingleCard(" + f"'{self.tabname}', '{self.page.extra_networks_tabname}', name" + ");}" + ), + inputs=[self.edit_name_input], + outputs=[], + ) def create_editor(self): self.create_default_editor_elems() @@ -199,7 +210,12 @@ class UserMetadataEditor: outputs=[self.html_preview, self.html_status] ).then( fn=None, - _js="function(name){extraNetworksRefreshSingleCard(" + json.dumps(self.page.name) + "," + json.dumps(self.tabname) + ", name);}", + _js=( + "function(name){" + "extraNetworksRefreshSingleCard(" + f"'{self.tabname}', '{self.page.extra_networks_tabname}', name" + ");}" + ), inputs=[self.edit_name_input], outputs=[] ) diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py index f71b40c41..4358edb3a 100644 --- a/modules/ui_prompt_styles.py +++ b/modules/ui_prompt_styles.py @@ -120,4 +120,8 @@ class UiPromptStyles: inputs=[self.main_ui_prompt, self.main_ui_negative_prompt, self.dropdown], outputs=[self.main_ui_prompt, self.main_ui_negative_prompt, self.dropdown], show_progress=False, - ).then(fn=None, _js="function(){update_"+self.tabname+"_tokens(); closePopup();}", show_progress=False) + ).then( + fn=None, + _js=f"function(){{update_{self.tabname}_tokens(); closePopup();}}", + show_progress=False, + )