From 16495c203e377d11d6cca65e0b08094d9a920eb9 Mon Sep 17 00:00:00 2001 From: Sj-Si Date: Fri, 24 May 2024 15:20:30 -0400 Subject: [PATCH] Fix dblclick of resize grid handle. Fix resizing bugs. --- javascript/extraNetworks.js | 47 +++---- javascript/extraNetworksClusterize.js | 32 +++-- javascript/resizeGrid.js | 185 ++++++++++++++++++-------- style.css | 81 +++++------ 4 files changed, 206 insertions(+), 139 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index c643de368..952d77097 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -676,34 +676,24 @@ class ExtraNetworksTab { this.applyFilter(this.txt_search_elem.value); } - autoSetTreeWidth() { - const row = this.container_elem.querySelector(".resize-handle-row"); - if (!isElementLogError(row)) { + autoSetTreeWidth(handle_elem) { + const siblings = this.resize_grid.getSiblings(handle_elem); + const tree_item = siblings.prev; + // Only process if the prev element is our tree view. + if (tree_item.elem !== this.tree_list.scroll_elem.closest(".resize-grid--cell")) { + return; + } + let new_size_px = this.tree_list.getMaxRowWidth(); + if (!isNumber(new_size_px)) { return; } - const left_col = row.firstElementChild; - if (!isElementLogError(left_col)) { - return; - } - - // If the left column is hidden then we don't want to do anything. - if (left_col.classList.contains("hidden")) { - return; - } - - const pad = parseFloat(row.style.gridTemplateColumns.split(" ")[1]); - const min_left_col_width = parseFloat(left_col.style.flexBasis.slice(0, -2)); - // We know that the tree list is the left column. That is the only one we want to resize. - let max_width = this.tree_list.getMaxRowWidth(); - if (!isNumber(max_width)) { - return; - } - // Add the resize handle's padding to the result and default to minLeftColWidth if necessary. - max_width = Math.max(max_width + pad, min_left_col_width); - - // Mimicks resizeHandle.js::setLeftColGridTemplate(). - row.style.gridTemplateColumns = `${max_width}px ${pad}px 1fr`; + // Account for border dims on the container. + const div_tree = this.tree_list.scroll_elem.closest(".extra-network-content--tree-view"); + new_size_px += div_tree.offsetWidth - div_tree.clientWidth; + // Clamp the value to the min_size. + new_size_px = Math.max(tree_item.min_size, new_size_px); + tree_item.parent.resizeItem(tree_item, new_size_px); } async clearSelectedButtons({excluded_div_ids} = {}) { @@ -1612,11 +1602,12 @@ function extraNetworksSetupEventDelegators() { dbl_press_time_ms = 0; } - window.addEventListener("resizeHandleDblClick", event => { - // See resizeHandle.js::onDoubleClick() for event detail. + window.addEventListener("resize_grid_handle_dblclick", event => { + // See resizeGrid.js::ResizeGrid.setupEvents() for event detail. event.stopPropagation(); const pane = event.target.closest(".extra-network-pane"); - extra_networks_tabs[pane.dataset.tabnameFull].autoSetTreeWidth(); + const handle = event.target.closest(".resize-grid--handle"); + extra_networks_tabs[pane.dataset.tabnameFull].autoSetTreeWidth(handle); }); // Debounce search text input. This way we only search after user is done typing. diff --git a/javascript/extraNetworksClusterize.js b/javascript/extraNetworksClusterize.js index 7c5ed9fe7..b8899b5db 100644 --- a/javascript/extraNetworksClusterize.js +++ b/javascript/extraNetworksClusterize.js @@ -343,23 +343,33 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { let row_width = 0; for (let j = 0; j < this.options.cols_in_block; 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; + // Child first element is the indent div. Just use offset for this + // since we do some overlapping with ::after in CSS. + row_width += child.children[0].offsetWidth; + // Button is second element. We want entire scroll width of this one. + // But first we need to allow it to shrink to content. + const prev_css_text = child.children[1].cssText; + child.children[1].style.flex = "0 1 auto"; + row_width += child.children[1].scrollWidth; + // Add the button label's overflow to the width. + const lbl = child.querySelector(".tree-list-item-label"); + row_width += lbl.scrollWidth - lbl.offsetWidth; + // Revert changes to element style. + if (!prev_css_text) { + child.children[1].removeAttribute("style"); + } else { + child.children[1].cssText = prev_css_text; + } } - max_width = Math.max(max_width, row_width); + max_width = Math.max(row_width, max_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; + // Adds the scroll_elem's scrollbar and padding to the result. + // If scrollbar isn't visible, then only the element border/padding is added. + max_width += this.scroll_elem.offsetWidth - this.content_elem.offsetWidth; return max_width; } diff --git a/javascript/resizeGrid.js b/javascript/resizeGrid.js index cbbc8ccfe..1cf4883a9 100644 --- a/javascript/resizeGrid.js +++ b/javascript/resizeGrid.js @@ -535,26 +535,6 @@ class ResizeGridAxis extends ResizeGridItem { } } - getSiblings(handle_elem) { - /** Returns the nearest visible ResizeGridItems surrounding a ResizeGridHandle. - * - * Args: - * handle_elem (Element): The handle element in the grid to lookup. - * - * Returns: - * Object: Keys=(prev, next). Values are ResizeGridItems. - */ - let prev = this.getItem({elem: handle_elem.previousElementSibling}); - if (!prev.visible) { - prev = prev.parent.items.slice(0, this.items.indexOf(prev)).findLast(x => x.visible); - } - let next = this.getItem({elem: handle_elem.nextElementSibling}); - if (!next.visible) { - next = next.parent.items.slice(this.items.indexOf(next) + 1).findLast(x => x.visible); - } - return {prev: prev, next: next}; - } - updateVisibleHandles() { /** Sets the visibility of each ResizeGridHandle based on surrounding items. */ for (const item of this.items) { @@ -657,6 +637,78 @@ class ResizeGridAxis extends ResizeGridItem { } } + resizeItem(item, size_px) { + // Don't resize invisible items. + if (!item.visible) { + return; + } + // Don't resize item if it is the only visible item in the axis. + if (this.items.filter(x => x.visible).length === 1) { + return; + } + + if (size_px < item.min_size) { + console.error(`Requested size is too small: ${size_px} < ${item.min_size}`); + return; + } + + const dims = this.elem.getBoundingClientRect(); + let max_size = parseInt(this.axis === 0 ? dims.width : dims.height); + const vis_siblings = this.items.filter(x => x.visible && x !== item); + max_size -= vis_siblings.reduce((acc, obj) => { + return acc + obj.min_size + (obj.handle.visible ? obj.handle.pad_px : 0); + }, 0); + + if (size_px > max_size) { + console.error(`Requested size is too large: ${size_px} > ${max_size}`); + return; + } + + // Find the direct sibling of this item. + const idx = this.items.indexOf(item); + isNullOrUndefinedThrowError(item); // Indicates programmer error. + // Look after item. + let sibling = this.items.slice(idx + 1).find(x => x.visible); + if (isNullOrUndefined(sibling)) { + // No valid siblings after item, look before item. + sibling = this.items.slice(0, idx).findLast(x => x.visible); + isNullOrUndefinedThrowError(sibling); // Indicates programmer error. + } + const sibling_idx = this.items.indexOf(sibling); + + const _make_room = (sibling, others, tot_px) => { + let rem = tot_px; + // Shrink from the sibling first. + rem = sibling.shrink(rem, {limit_to_base: false}); + if (rem <= 0) { + return; + } + + // Shrink all other items next, starting from the end. + for (const other of others.slice().reverse()) { + rem = other.shrink(rem, {limit_to_base: false}); + if (rem <= 0) { + return; + } + } + + // This indicates a programmer error. + throw new Error(`No space for item. tot: ${tot_px}, rem: ${rem}`); + }; + + const curr_size = item.getSize(); + if (size_px < curr_size) { // shrink + item.shrink(curr_size - size_px, {limit_to_base: false}); + sibling.grow(-1); + } else if (size_px > curr_size) { // grow + const others = this.items.filter((x, i) => { + return x.visible && i !== idx && i !== sibling_idx; + }); + _make_room(sibling, others, size_px - curr_size); + item.setSize(size_px); + } + } + show({id, idx, elem, item} = {}) { /** Shows an item along this axis. * @@ -875,6 +927,7 @@ class ResizeGrid extends ResizeGridAxis { handle.elem.setPointerCapture(event.pointerId); // Temporarily set styles for elements. These are cleared on pointerup. + // Also cleared if dblclick is fired. // See `onMove()` comments for more info. prev.setSize(prev.getSize()); next.setSize(next.getSize()); @@ -888,6 +941,34 @@ class ResizeGrid extends ResizeGridAxis { } else { document.body.classList.add('resizing-row'); } + + if (!dblclick_timer) { + handle.elem.dataset.awaitDblClick = ''; + dblclick_timer = setTimeout( + (elem) => { + dblclick_timer = null; + delete elem.dataset.awaitDblClick; + }, + DBLCLICK_TIME_MS, + handle.elem + ); + } else if ('awaitDblClick' in handle.elem.dataset) { + clearTimeout(dblclick_timer); + dblclick_timer = null; + delete handle.elem.dataset.awaitDblClick; + handle.elem.dispatchEvent( + new CustomEvent('resize_grid_handle_dblclick', { + bubbles: true, + detail: this, + }) + ); + prev.render(); + next.render(); + + prev = null; + handle = null; + next = null; + } }, {signal: this.event_abort_controller.signal} ); @@ -918,13 +999,9 @@ class ResizeGrid extends ResizeGridAxis { window.addEventListener( 'pointerup', (event) => { - if ( - isNullOrUndefined(prev) || - isNullOrUndefined(handle) || - isNullOrUndefined(next) - ) { - return; - } + document.body.classList.remove('resizing'); + document.body.classList.remove('resizing-col'); + document.body.classList.remove('resizing-row'); if (event.target.hasPointerCapture(event.pointerId)) { event.target.releasePointerCapture(event.pointerId); @@ -933,6 +1010,15 @@ class ResizeGrid extends ResizeGridAxis { if (event.pointerType === 'mouse' && event.button !== 0) { return; } + + if ( + isNullOrUndefined(prev) || + isNullOrUndefined(handle) || + isNullOrUndefined(next) + ) { + return; + } + if (event.pointerType === 'touch') { touch_count--; } @@ -948,31 +1034,6 @@ class ResizeGrid extends ResizeGridAxis { prev.render(); next.render(); - document.body.classList.remove('resizing'); - document.body.classList.remove('resizing-col'); - document.body.classList.remove('resizing-row'); - - if (!dblclick_timer) { - handle.elem.dataset.awaitDblClick = ''; - dblclick_timer = setTimeout( - (elem) => { - dblclick_timer = null; - delete elem.dataset.awaitDblClick; - }, - DBLCLICK_TIME_MS, - handle.elem - ); - } else if ('awaitDblClick' in handle.elem.dataset) { - clearTimeout(dblclick_timer); - dblclick_timer = null; - delete handle.elem.dataset.awaitDblClick; - handle.elem.dispatchEvent( - new CustomEvent('resize_handle_dblclick', { - bubbles: true, - detail: this, - }) - ); - } prev = null; handle = null; next = null; @@ -1092,6 +1153,26 @@ class ResizeGrid extends ResizeGridAxis { } } + getSiblings(handle_elem) { + /** Returns the nearest visible ResizeGridItems surrounding a ResizeGridHandle. + * + * Args: + * handle_elem (Element): The handle element in the grid to lookup. + * + * Returns: + * Object: Keys=(prev, next). Values are ResizeGridItems. + */ + let prev = this.getItem({elem: handle_elem.previousElementSibling}); + if (!prev.visible) { + prev = prev.parent.items.slice(0, this.items.indexOf(prev)).findLast(x => x.visible); + } + let next = this.getItem({elem: handle_elem.nextElementSibling}); + if (!next.visible) { + next = next.parent.items.slice(this.items.indexOf(next) + 1).findLast(x => x.visible); + } + return {prev: prev, next: next}; + } + onMove(event, a, handle, b) { /** Handles pointermove events by calculating and setting new size of elements. * diff --git a/style.css b/style.css index 1d39f90c0..67b7de5e3 100644 --- a/style.css +++ b/style.css @@ -1269,6 +1269,7 @@ body.resizing.resize-grid-row { height: 100%; /* Use scroll instead of auto so that content size doesn't change when there is no content. */ overflow: clip scroll; + padding: var(--spacing-md) 0 var(--spacing-md) var(--spacing-md); } .clusterize-content { @@ -1277,7 +1278,6 @@ body.resizing.resize-grid-row { /* need to manually set the gap to 0 to fix item dimension calcs. */ gap: 0; counter-reset: clusterize-counter; - padding: var(--spacing-md); } .clusterize-extra-row { @@ -1533,33 +1533,33 @@ body.resizing.resize-grid-row { /* BUTTON ELEMENTS */ .tree-list-item { - display: grid; - grid-auto-rows: 1fr; - grid-template-columns: auto 1fr; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + width: 100%; + height: calc(var(--button-large-text-size) + (2 * var(--spacing-sm))); position: relative; - width: 100%; - padding: 0; - margin: 0; - user-select: none; - border: none; -} - -/*