diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 9bcbdfbe2..7c09d5dbf 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -4,14 +4,41 @@ const re_extranet_neg = /\(([^:^>]+:[\d.]+)\)/; const re_extranet_g_neg = /\(([^:^>]+:[\d.]+)\)/g; const activePromptTextarea = {}; const clusterizers = {}; -const clusterizers_initial_load = {}; var globalPopup = null; var globalPopupInner = null; const storedPopupIds = {}; +const tab_setup_complete = {}; const extraPageUserMetadataEditors = {}; // A flag used by the `waitForBool` promise to determine when we first load Ui Options. const initialUiOptionsLoaded = { state: false }; +const debounce = (handler, timeout_ms) => { + /** Debounces a function call. + * + * NOTE: This will NOT work if called from within a class. + * It will drop `this` from scope. + * + * Repeated calls to the debounce handler will not call the handler until there are + * no new calls to the debounce handler for timeout_ms time. + * + * Example: + * function add(x, y) { return x + y; } + * let debounce_handler = debounce(add, 5000); + * let res; + * for (let i = 0; i < 10; i++) { + * res = debounce_handler(i, 100); + * } + * console.log("Result:", res); + * + * This example will print "Result: 109". + */ + let timer = null; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => handler(...args), timeout_ms); + }; +} + function waitForElement(selector) { /** Promise that waits for an element to exist in DOM. */ return new Promise(resolve => { @@ -85,7 +112,7 @@ function waitForValueInObject(o) { * Resolves when obj[k] == v */ return new Promise(resolve => { - waitForKeyInObject({ k: o.k, v: o.obj }).then(() => { + waitForKeyInObject({ k: o.k, obj: o.obj }).then(() => { (function _waitForValueInObject() { if (o.k in o.obj && o.obj[o.k] == o.v) { @@ -115,7 +142,6 @@ function toggleCss(key, css, enable) { } function extraNetworksRefreshTab(tabname_full) { - clusterizers_initial_load[tabname_full].state = false; // Reapply controls since they don't change on refresh. let btn_dirs_view = gradioApp().querySelector(`#${tabname_full}_extra_dirs_view`); let btn_tree_view = gradioApp().querySelector(`#${tabname_full}_extra_tree_view`); @@ -127,9 +153,8 @@ function extraNetworksRefreshTab(tabname_full) { div_dirs.classList.toggle("hidden", !btn_dirs_view.classList.contains("extra-network-control--enabled")); div_tree.classList.toggle("hidden", !btn_tree_view.classList.contains("extra-network-control--enabled")); - waitForKeyInObject({ k: tabname_full, v: clusterizers }).then(() => { - extraNetworkClusterizersOnTabLoad(tabname_full); - }); + waitForKeyInObject({ k: tabname_full, obj: clusterizers }) + .then(() => extraNetworkClusterizersOnTabLoad(tabname_full)); } function extraNetworksRegisterPromptForTab(tabname, id) { @@ -170,20 +195,13 @@ function extraNetworksSetupTabContent(tabname, pane, controls_div) { }), }; - clusterizers_initial_load[tabname_full] = { state: false }; - // Debounce search text input. This way we only search after user is done typing. - let typing_timer; - let done_typing_interval_ms = 250; - txt_search.addEventListener("keyup", () => { - clearTimeout(typing_timer); + let search_input_debounce = debounce(() => { if (txt_search.value) { - typing_timer = setTimeout( - () => extraNetworksApplyFilter(tabname_full), - done_typing_interval_ms, - ); + extraNetworksApplyFilter(tabname_full); } - }); + }, 250); + txt_search.addEventListener("keyup", search_input_debounce); // Update search filter whenever the search input's clear button is pressed. txt_search.addEventListener("extra-network-control--search-clear", () => extraNetworksApplyFilter(tabname_full)); @@ -194,24 +212,9 @@ function extraNetworksSetupTabContent(tabname, pane, controls_div) { if (pane.style.display != "none") { extraNetworksShowControlsForPage(tabname, tabname_full); } - - // Handle extra-network-pane resize events. - let resize_observer_timer; - let done_resize_observer_interval_ms = 250; - let resize_observer = new ResizeObserver((entries) => { - for (const entry of entries) { - if (entry.target === pane) { - clearTimeout(resize_observer_timer); - resize_observer_timer = setTimeout( - () => extraNetworksClusterizersOnResize(tabname_full), - done_resize_observer_interval_ms, - ); - break; - } - } - }); - resize_observer.observe(pane); - }) + }).then(() => { + tab_setup_complete[tabname_full] = true; + }); } function extraNetworksSetupTab(tabname) { @@ -277,21 +280,8 @@ function extraNetworksTabSelected(tabname, id, showPrompt, showNegativePrompt, t extraNetworksMovePromptToTab(tabname, id, showPrompt, showNegativePrompt); extraNetworksShowControlsForPage(tabname, tabname_full); - console.log("HERE!!!:", clusterizers[tabname_full].cards_list.scroll_elem.isConnected); - - Promise.all([ - waitForKeyInObject({ obj: clusterizers, k: tabname_full }), - waitForKeyInObject({ obj: clusterizers_initial_load, k: tabname_full }).then(() => { - waitForBool(clusterizers_initial_load[tabname_full]).then(() => { - Promise.all([ - clusterizers[tabname_full].tree_list.waitForElements(), - clusterizers[tabname_full].cards_list.waitForElements(), - ]).then(() => {return;}); - }); - }), - ]).then(() => { - extraNetworkClusterizersOnTabLoad(tabname_full); - }); + waitForKeyInObject({ k: tabname_full, obj: clusterizers }) + .then(() => extraNetworkClusterizersOnTabLoad(tabname_full)); } function extraNetworksApplyFilter(tabname_full) { @@ -302,6 +292,24 @@ function extraNetworksApplyFilter(tabname_full) { // We only want to filter/sort the cards list. clusterizers[tabname_full].cards_list.applyFilter(); + + // 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. + const txt_search = gradioApp().querySelector(`#${tabname_full}_extra_search`); + if (!txt_search) { + return; + } + + // tree view buttons + let btn = gradioApp().querySelector(`#${tabname_full}_tree_list_content_area .tree-list-item[data-selected=""]`); + if (btn && btn.dataset.path !== txt_search.value) { + delete btn.dataset.selected; + } + // dirs view buttons + btn = gradioApp().querySelector(`#${tabname_full}_dirs .extra-network-dirs-view-button[data-selected=""]`); + if (btn && btn.textContent.trim() !== txt_search.value) { + delete btn.dataset.selected; + } } function extraNetworksClusterizersEnable(tabname_full) { @@ -349,15 +357,21 @@ function extraNetworksClusterizersRebuild(tabname_full, force) { } function extraNetworkClusterizersOnTabLoad(tabname_full) { - /** Enables a tab's clusterizer, updates its data, and rebuilds it. */ - if (!(tabname_full in clusterizers)) { - return; - } - - extraNetworksClusterizersEnable(tabname_full); - for (const v of Object.values(clusterizers[tabname_full])) { - v.load(); - } + return new Promise(resolve => { + /** Enables a tab's clusterizer, updates its data, and rebuilds it. */ + if (!(tabname_full in clusterizers)) { + return resolve(); + } + + (async () => { + extraNetworksClusterizersEnable(tabname_full); + for (const v of Object.values(clusterizers[tabname_full])) { + await v.load(); + } + })().then(() => { + return resolve(); + }); + }); } function extraNetworksClusterizersOnResize(tabname_full) { @@ -365,23 +379,8 @@ function extraNetworksClusterizersOnResize(tabname_full) { } function extraNetworksSetupEventHandlers() { - // Handle window resizes. Delay of 500ms after resize before firing an event - // as a way of "debouncing" resizes. - var resize_timer; - var resize_timeout_ms = 500; - window.addEventListener("resize", () => { - clearTimeout(resize_timer); - resize_timer = setTimeout(() => {extraNetworksClusterizersOnResize();}, resize_timeout_ms); - }); - - // Handle resizeHandle resizes. Only fires on mouseup after resizing. - window.addEventListener("resizeHandleResized", (e) => { - e.stopPropagation(); - const tabname_full = e.target.closest(".extra-network-pane").id.replace("_pane", ""); - // Force update rows after resizing. - extraNetworksClusterizersOnResize(tabname_full); - }); - + // Handle doubleclick events from the resize handle. + // This will automatically fit the left pane to the size of its largest loaded row. window.addEventListener("resizeHandleDblClick", (e) => { e.stopPropagation(); const tabname_full = e.target.closest(".extra-network-pane").id.replace("_pane", ""); @@ -390,38 +389,19 @@ function extraNetworksSetupEventHandlers() { return; } - let max_width = 0; - const scroll_area = e.target.querySelector(".extra-network-tree"); - const content = e.target.querySelector(".extra-network-tree-content"); - const style = window.getComputedStyle(content, null); - const content_lpad = parseInt(style.getPropertyValue("padding-left")); - const content_rpad = parseInt(style.getPropertyValue("padding-right")); - content.querySelectorAll(".tree-list-item").forEach((row) => { - // Temporarily set the grid columns to maximize column width - // so we can calculate the full width of the row. - const row_style = window.getComputedStyle(row, null); - const prev_grid_template_columns = row.style.gridTemplateColumns; - const n_cols = row_style.getPropertyValue("grid-template-columns").split(" ").length; - row.style.gridTemplateColumns = `repeat(${n_cols}, max-content)`; - if (row.scrollWidth > max_width) { - max_width = row.scrollWidth; - } - //row.style.gridTemplateColumns = prev_grid_template_columns; - }); - if (max_width <= 0) { + // Shouldn't be possible, but if tree is hidden then we want to ignore this event + // since there wouldn't be any content to calculate row width. When hidden, + // the resize handle shouldn't exist on page but just in case... + if (gradioApp().getElementById(`${tabname_full}_tree_list_scroll_area`).parentElement.classList.contains("hidden")) { return; } - // Add the container's padding to the result. - max_width += content_lpad + content_rpad; - // Add the scrollbar's width to the result. Will add 0 if scrollbar isnt present. - max_width += scroll_area.offsetWidth - scroll_area.clientWidth; + // We know that the tree list is the left column. That is the only one we want to resize. + let max_width = clusterizers[tabname_full].tree_list.getMaxRowWidth(); // Add the resize handle's padding to the result and default to minLeftColWidth if necessary. max_width = Math.max(max_width + e.detail.pad, e.detail.minLeftColWidth); e.detail.setLeftColGridTemplate(e.target, max_width); - - extraNetworksClusterizersOnResize(tabname_full); }); } @@ -726,10 +706,10 @@ function extraNetworksControlTreeViewOnClick(event, tabname_full) { */ var button = event.currentTarget; button.classList.toggle("extra-network-control--enabled"); - var show = !button.classList.contains("extra-network-control--enabled"); + var show = button.classList.contains("extra-network-control--enabled"); - gradioApp().getElementById(`${tabname_full}_tree_list_scroll_area`).parentElement.classList.toggle("hidden", show); - extraNetworksClusterizersonResize(tabname_full); + gradioApp().getElementById(`${tabname_full}_tree_list_scroll_area`).parentElement.classList.toggle("hidden", !show); + clusterizers[tabname_full].tree_list.enable(show); } function extraNetworksControlDirsViewOnClick(event, tabname_full) { @@ -744,10 +724,9 @@ function extraNetworksControlDirsViewOnClick(event, tabname_full) { */ var button = event.currentTarget; button.classList.toggle("extra-network-control--enabled"); - var show = !button.classList.contains("extra-network-control--enabled"); + var show = button.classList.contains("extra-network-control--enabled"); - gradioApp().getElementById(`${tabname_full}_dirs`).classList.toggle("hidden", show); - extraNetworksClusterizersOnResize(tabname_full); + gradioApp().getElementById(`${tabname_full}_dirs`).classList.toggle("hidden", !show); } function extraNetworksControlRefreshOnClick(event, tabname_full) { @@ -763,9 +742,11 @@ function extraNetworksControlRefreshOnClick(event, tabname_full) { * @param tabname_full The full active tabname. * i.e. txt2img_lora, img2img_checkpoints, etc. */ + // reset states initialUiOptionsLoaded.state = false; - var btn_refresh_internal = gradioApp().getElementById(`${tabname_full}_extra_refresh_internal`); - btn_refresh_internal.dispatchEvent(new Event("click")); + tab_setup_complete = {}; + // Fire an event for this button click. + gradioApp().getElementById(`${tabname_full}_extra_refresh_internal`).dispatchEvent(new Event("click")); } function closePopup() { diff --git a/javascript/extraNetworksClusterizeList.js b/javascript/extraNetworksClusterizeList.js index 21d842d08..9b00a0014 100644 --- a/javascript/extraNetworksClusterizeList.js +++ b/javascript/extraNetworksClusterizeList.js @@ -42,6 +42,58 @@ const querySelectorLogError = x => { return elem; } +const 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 { + top: parseFloat(style.getPropertyValue(`${prop}-top`)), + left: parseFloat(style.getPropertyValue(`${prop}-left`)), + bottom: parseFloat(style.getPropertyValue(`${prop}-bottom`)), + right: parseFloat(style.getPropertyValue(`${prop}-right`)), + }; +} + +const getComputedMarginDims = elem => { + const dims = getComputedPropertyDims(elem, "margin"); + return { + width: dims.left + dims.right, + height: dims.top + dims.bottom, + }; +} + +const getComputedPaddingDims = elem => { + const dims = getComputedPropertyDims(elem, "padding"); + return { + width: dims.left + dims.right, + height: dims.top + dims.bottom, + }; +} + +const getComputedBorderDims = elem => { + // computed border will always start with the pixel width so thankfully + // the parseFloat() conversion will just give us the width and ignore the rest. + // Otherwise we'd have to use border--width instead. + const dims = getComputedPropertyDims(elem, "border"); + return { + width: dims.left + dims.right, + height: dims.top + dims.bottom, + }; +} + +const 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; + const margin = getComputedMarginDims(elem); + const padding = getComputedPaddingDims(elem); + const border = getComputedBorderDims(elem); + return { + width: width + margin.width + padding.width + border.width, + height: height + margin.height + padding.height + border.height, + } + +} + function compress(string) { /** Compresses a string into a base64 encoded GZipped string. */ const cs = new CompressionStream('gzip'); @@ -84,42 +136,17 @@ const getComputedValue = function (container, css_property) { ); }; -const calcColsPerRow = function (parent) { - // Returns the number of columns in a row of a flexbox. - //const parent = document.querySelector(selector); - const parent_width = getComputedValue(parent, "width"); - const parent_padding_left = getComputedValue(parent, "padding-left"); - const parent_padding_right = getComputedValue(parent, "padding-right"); +const calcColsPerRow = function (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); - const child = parent.firstElementChild; - const child_width = getComputedValue(child, "width"); - const child_margin_left = getComputedValue(child, "margin-left"); - const child_margin_right = getComputedValue(child, "margin-right"); - - var parent_width_no_padding = parent_width - parent_padding_left - parent_padding_right; - const child_width_with_margin = child_width + child_margin_left + child_margin_right; - parent_width_no_padding += child_margin_left + child_margin_right; - - return parseInt(parent_width_no_padding / child_width_with_margin); } -const calcRowsPerCol = function (container, parent) { - // Returns the number of columns in a row of a flexbox. - //const parent = document.querySelector(selector); - const parent_height = getComputedValue(container, "height"); - const parent_padding_top = getComputedValue(container, "padding-top"); - const parent_padding_bottom = getComputedValue(container, "padding-bottom"); - - const child = parent.firstElementChild; - const child_height = getComputedValue(child, "height"); - const child_margin_top = getComputedValue(child, "margin-top"); - const child_margin_bottom = getComputedValue(child, "margin-bottom"); - - var parent_height_no_padding = parent_height - parent_padding_top - parent_padding_bottom; - const child_height_with_margin = child_height + child_margin_top + child_margin_bottom; - parent_height_no_padding += child_margin_top + child_margin_bottom; - - return parseInt(parent_height_no_padding / child_height_with_margin); +const calcRowsPerCol = function (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); } class ExtraNetworksClusterize { @@ -166,6 +193,8 @@ class ExtraNetworksClusterize { this.resize_observer_timer = null; this.resize_observer_timeout_ms = 250; this.element_observer = null; + this.data_update_timer = null + this.data_update_timeout_ms = 1000; this.enabled = false; @@ -204,33 +233,35 @@ class ExtraNetworksClusterize { } load() { - return waitForElement(`#${this.data_id}`) - .then((elem) => this.data_elem = elem) - .then(() => this.parseJson(this.data_elem.dataset.json)) - .then(() => this.init()) - .then(() => this.repair()) - .then(() => this.applyFilter()); + return new Promise(resolve => { + waitForElement(`#${this.data_id}`) + .then((elem) => this.data_elem = elem) + .then(() => this.parseJson(this.data_elem.dataset.json)) + .then(() => { return resolve(); }); + }); } parseJson(encoded_str) { /** promise */ return new Promise(resolve => { // Skip parsing if the string hasnt actually updated. if (this.encoded_str === encoded_str) { - console.log("no change"); return resolve(); } - return resolve( - Promise.resolve(encoded_str) - .then(v => decompress(v)) - .then(v => JSON.parse(v)) - .then(v => this.updateJson(v)) - .then(() => {console.log("parse json done"); this.encoded_str = encoded_str; }) - ); + Promise.resolve(encoded_str) + .then(v => decompress(v)) + .then(v => JSON.parse(v)) + .then(v => this.updateJson(v)) + .then(() => this.encoded_str = encoded_str) + .then(() => this.init()) + .then(() => this.repair()) + .then(() => this.applyFilter()) + .then(() => { return resolve(); }); }); } updateJson(json) { /** promise */ /** Must be overridden by inherited class. */ + console.error("Base class method called. Must be overridden by child."); return new Promise(resolve => {return resolve();}); } @@ -285,12 +316,17 @@ class ExtraNetworksClusterize { return; } - this.refresh(); + this.refresh(true); // Rebuild with `force=false` so we only rebuild if dimensions change. this.rebuild(false); } + getMaxRowWidth() { + // impliment in subclasses + return; + } + recalculateDims() { let rebuild_required = false; let clear_before_return = false; @@ -312,12 +348,24 @@ class ExtraNetworksClusterize { clear_before_return = true; } + const child = this.content_elem.querySelector(":not(.clusterize-extra-row)"); + if (isNullOrUndefined(child)) { + if (clear_before_return) { + this.clear(); + return rebuild_required; + } + } + // Calculate the visible rows and colums for the clusterize-content area. - let n_cols = calcColsPerRow(this.content_elem); - let n_rows = calcRowsPerCol(this.scroll_elem, this.content_elem); + let n_cols = calcColsPerRow(this.content_elem, child); + let n_rows = calcRowsPerCol(this.scroll_elem, child); n_cols = (isNaN(n_cols) || n_cols <= 0) ? 1 : n_cols; n_rows = (isNaN(n_rows) || n_rows <= 0) ? 1 : n_rows; + // Add two extra rows to account for partial row visibility on top and bottom + // of the content element view region. + n_rows += 2; + if (n_cols != this.n_cols || n_rows != this.n_rows) { // Sizes have changed. Update the instance values. this.n_cols = n_cols; @@ -372,15 +420,12 @@ class ExtraNetworksClusterize { if (isNullOrUndefined(this.clusterize)) { // If we have already initialized, don't do it again. - console.log("rebuild:: init"); this.init(); } else if (this.recalculateDims() || force) { - console.log("rebuild:: full", this.scroll_id); this.destroy(); this.clusterize = null; this.init(); } else { - console.log("rebuild:: update", this.scroll_id); this.update(); } } @@ -419,7 +464,6 @@ class ExtraNetworksClusterize { } onResize(elem_id) { - console.log("element resized:", elem_id); this.updateRows(); } @@ -437,7 +481,6 @@ class ExtraNetworksClusterize { default: break; } - console.log("onElementAdded::", elem_id, document.body.contains(this.scroll_elem)); } onElementRemoved(elem_id) { @@ -454,48 +497,65 @@ class ExtraNetworksClusterize { default: break; } - console.log("onElementRemoved::", elem_id, document.body.contains(this.scroll_elem)); + } + + onElementUpdated(elem_id) { + switch(elem_id) { + case this.data_id: + waitForElement(`#${this.data_id}`).then((elem) => this.data_elem = elem); + break; + case this.scroll_id: + this.repair(); + break; + case this.content_id: + this.repair(); + break; + default: + break; + } } onDataChanged(data) { - console.log("onDataChanged::", this.data_id); this.parseJson(data); } setupElementObservers() { this.element_observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === "childList") { - // added - if (mutation.addedNodes.length > 0) { - for (const node of mutation.addedNodes) { - if (node.id === this.data_id || node.id === this.scroll_id || node.id === this.content_id) { - this.onElementAdded(node.id); - } - } - } - // removed - if (mutation.removedNodes.length > 0) { - for (const node of mutation.removedNodes) { - if (node.id === this.data_id || node.id === this.scroll_id || node.id === this.content_id) { - this.onElementRemoved(node.id); - } - } - } - } else if (mutation.type === "attributes") { - if (mutation.target.id === this.data_id && mutation.attributeName === "data-json") { - this.onDataChanged(mutation.target.dataset.json); - } - } + // don't waste time if this object isn't enabled. + if (!this.enabled) { + return; + } + + let data_elem = gradioApp().getElementById(this.data_id); + if (data_elem && data_elem !== this.data_elem) { + this.onElementUpdated(data_elem.id); + } else if (data_elem && data_elem.dataset.json !== this.encoded_str) { + // we don't want to get blasted with data updates so just wait for + // the data to settle down before updating. + clearTimeout(this.data_update_timer); + this.data_update_timer = setTimeout(() => { + this.onDataChanged(data_elem.dataset.json); + }, this.data_update_timeout_ms); + } + + let scroll_elem = gradioApp().getElementById(this.scroll_id); + if (scroll_elem && scroll_elem !== this.scroll_elem) { + this.onElementUpdated(scroll_elem.id); + } + + let content_elem = gradioApp().getElementById(this.content_id); + if (content_elem && content_elem !== this.content_elem) { + this.onElementUpdated(content_elem.id); } }); this.element_observer.observe(gradioApp(), {subtree: true, childList: true, attributes: true}); } setupResizeHandlers() { + // Handle element resizes. Delay of `resize_observer_timeout_ms` after resize + // before firing an event as a way of "debouncing" resizes. this.resize_observer = new ResizeObserver((entries) => { for (const entry of entries) { - console.log("resizeObserver:", entry.target.id); if (entry.target.id === this.scroll_id || entry.target.id === this.content_id) { clearTimeout(this.resize_observer_timer); this.resize_observer_timer = setTimeout(() => this.onResize(entry.id), this.resize_observer_timeout_ms); @@ -619,8 +679,6 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { this.data_obj[k].active = false; } } - //this.applyFilter(); - console.log("updateJson:: done", this.scroll_id); return resolve(); }); } @@ -642,6 +700,42 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { } } } + + getMaxRowWidth() { + if (!this.enabled) { + // Inactive list is not displayed on screen. Can't calculate size. + return false; + } + if (this.rowCount() === 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 += getComputedDims(child).width; + // Re-apply previous style. + child.style.cssText = prev_style; + } + max_width = Math.max(max_width, row_width); + } + if (max_width <= 0) { + return; + } + + // Add the container's padding to the result. + max_width += getComputedPaddingDims(this.content_elem).width; + // Add the scrollbar's width to the result. Will add 0 if scrollbar isnt present. + max_width += this.scroll_elem.offsetWidth - this.scroll_elem.clientWidth; + return max_width; + } } class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { @@ -664,9 +758,6 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { active: true, }; } - //this.applyFilter(); - console.log("updateJson:: done", this.scroll_id); - if (this.scroll_id.includes("textual")) { console.log(this.data_obj); } return resolve(); }); } @@ -770,4 +861,33 @@ class ExtraNetworksClusterizeCardsList extends ExtraNetworksClusterize { this.applySort(); this.updateRows(); } + + getMaxRowWidth() { + if (!this.enabled) { + // Inactive list is not displayed on screen. Can't calculate size. + return false; + } + if (this.rowCount() === 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++) { + row_width += getComputedDims(this.content_elem.children[i + j]).width; + } + max_width = Math.max(max_width, row_width); + } + if (max_width <= 0) { + return; + } + + // Add the container's padding to the result. + max_width += getComputedPaddingDims(this.content_elem).width; + // Add the scrollbar's width to the result. Will add 0 if scrollbar isnt present. + max_width += this.scroll_elem.offsetWidth - this.scroll_elem.clientWidth; + return max_width; + } }