WebUI: Improve table overflow handling

This PR relies on flexbox to ensure all WebUI tables are the correct height without overflowing. Table headers are now always visible and JS-based dynamic resizing is no longer needed.

PR #21652.
This commit is contained in:
Thomas Piccirello 2024-11-03 00:11:30 -07:00 committed by GitHub
parent b083029841
commit dc30b9c2ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 65 additions and 73 deletions

View file

@ -246,6 +246,7 @@ li.divider {
}
.pad {
height: 100%;
padding: 8px;
}

View file

@ -32,6 +32,11 @@
vertical-align: middle;
}
#transferList #transferList_pad {
/* override for default mocha inline style */
display: flex !important;
}
tr.dynamicTableHeader {
cursor: pointer;
}
@ -82,7 +87,14 @@ tr.dynamicTableHeader {
padding-left: 25px;
}
div:has(> div.dynamicTableFixedHeaderDiv):not(.invisible) {
display: flex;
flex-direction: column;
height: 100%;
}
.dynamicTableFixedHeaderDiv {
flex-shrink: 0;
overflow: hidden;
}
@ -92,6 +104,7 @@ tr.dynamicTableHeader {
}
.dynamicTableDiv {
flex-grow: 1;
overflow: auto;
}

View file

@ -270,6 +270,14 @@ a.propButton img {
overflow: hidden auto;
}
.propertiesTabContent {
height: 100%;
> div {
height: 100%;
}
}
/* context menu specific */
.contextMenu {

View file

@ -452,7 +452,7 @@
</div>
<input id="closeButton" type="button" value="Close" style="float: right; width: 30%;">
</div>
<div id="torrentFiles" class="panel" style="position: absolute; top: 0; right: 0; bottom: 0; left: 228px; margin: 35px 10px 45px 20px; border-bottom: 0">
<div id="torrentFiles" class="panel" style="position: absolute; top: 0; right: 0; bottom: 0; left: 228px; margin: 35px 10px 45px 20px; border-bottom: 0; height: initial;">
<div id="bulkRenameFilesTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
<table class="dynamicTable">
<thead>

View file

@ -100,33 +100,6 @@ window.qBittorrent.DynamicTable ??= (() => {
tableDiv.addEventListener("scroll", () => {
tableElement.style.left = `${-tableDiv.scrollLeft}px`;
});
// if the table exists within a panel
const parentPanel = tableDiv.getParent(".panel");
if (parentPanel) {
const resizeFn = (entries) => {
const panel = entries[0].target;
let h = panel.getBoundingClientRect().height - tableFixedHeaderDiv.getBoundingClientRect().height;
tableDiv.style.height = `${h}px`;
// Workaround due to inaccurate calculation of elements heights by browser
let n = 2;
// is panel vertical scrollbar visible or does panel content not fit?
while (((panel.clientWidth !== panel.offsetWidth) || (panel.clientHeight !== panel.scrollHeight)) && (n > 0)) {
--n;
h -= 0.5;
tableDiv.style.height = `${h}px`;
}
};
const resizeDebouncer = window.qBittorrent.Misc.createDebounceHandler(100, (entries) => {
resizeFn(entries);
});
const resizeObserver = new ResizeObserver(resizeDebouncer);
resizeObserver.observe(parentPanel, { box: "border-box" });
}
},
setupHeaderEvents: function() {

View file

@ -205,10 +205,10 @@ window.qBittorrent.Search ??= (() => {
// unhide the results elements
if (numSearchTabs() >= 1) {
$("searchResultsNoSearches").style.display = "none";
$("searchResultsFilters").style.display = "block";
$("searchResultsTableContainer").style.display = "block";
$("searchTabsToolbar").style.display = "block";
$("searchResultsNoSearches").classList.add("invisible");
$("searchResultsFilters").classList.remove("invisible");
$("searchResultsTableContainer").classList.remove("invisible");
$("searchTabsToolbar").classList.remove("invisible");
}
// select new tab
@ -271,10 +271,10 @@ window.qBittorrent.Search ??= (() => {
$("numSearchResultsVisible").textContent = 0;
$("numSearchResultsTotal").textContent = 0;
$("searchResultsNoSearches").style.display = "block";
$("searchResultsFilters").style.display = "none";
$("searchResultsTableContainer").style.display = "none";
$("searchTabsToolbar").style.display = "none";
$("searchResultsNoSearches").classList.remove("invisible");
$("searchResultsFilters").classList.add("invisible");
$("searchResultsTableContainer").classList.add("invisible");
$("searchTabsToolbar").classList.add("invisible");
}
else if (isTabSelected && newTabToSelect) {
setActiveTab(newTabToSelect);
@ -670,9 +670,9 @@ window.qBittorrent.Search ??= (() => {
const searchPluginsEmpty = (searchPlugins.length === 0);
if (!searchPluginsEmpty) {
$("searchResultsNoPlugins").style.display = "none";
$("searchResultsNoPlugins").classList.add("invisible");
if (numSearchTabs() === 0)
$("searchResultsNoSearches").style.display = "block";
$("searchResultsNoSearches").classList.remove("invisible");
// sort plugins alphabetically
const allPlugins = searchPlugins.sort((left, right) => {

View file

@ -1,5 +1,6 @@
<style type="text/css">
#logTopBar {
flex-shrink: 0;
margin-top: 1em;
}
@ -22,13 +23,16 @@
}
#logView {
display: flex;
flex-direction: column;
height: 100%;
padding: 0 20px;
overflow: auto;
}
#logContentView {
display: block;
vertical-align: top;
flex-grow: 1;
overflow: auto;
}
#logMessageTableFixedHeaderDiv .dynamicTableHeader,
@ -227,7 +231,6 @@
tableInfo["peer"].instance.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu);
MUI.Panels.instances.LogPanel.contentEl.style.height = "100%";
$("logView").style.height = "inherit";
load();
};

View file

@ -2,13 +2,18 @@
#rssView {
padding: 20px 20px 0 20px;
height: calc(100% - 20px);
display: flex;
flex-direction: column;
}
#rssTopBar {
flex-shrink: 0;
}
#rssContentView {
display: table;
width: 100%;
height: calc(100% - 30px);
vertical-align: top;
flex-grow: 1;
overflow: auto;
}
#rssFeedFixedHeaderDiv .dynamicTableHeader,
@ -45,6 +50,7 @@
#rightRssColumn {
overflow: auto;
height: 100%;
}
#rssFeedTableDiv,
@ -53,17 +59,21 @@
}
#rssTorrentDetailsName {
flex-shrink: 0;
background-color: var(--color-background-blue);
padding: 0;
color: var(--color-text-white);
}
#rssTorrentDetailsDate {
flex-shrink: 1;
background-color: var(--color-background-default);
}
#rssDetailsView {
height: calc(100vh - 135px);
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
}
@ -85,6 +95,7 @@
}
#rssDescription {
flex-grow: 2;
width: 100%;
border: none;
}
@ -206,18 +217,6 @@
if (!pref.rss_processing_enabled)
$("rssFetchingDisabled").removeClass("invisible");
// recalculate heights
const nonPageHeight = $("rssTopBar").getBoundingClientRect().height
+ $("desktopHeader").getBoundingClientRect().height
+ $("desktopFooterWrapper").getBoundingClientRect().height + 20;
$("rssDetailsView").style.height = "calc(100vh - " + nonPageHeight + "px)";
const nonTableHeight = nonPageHeight + $("rssFeedFixedHeaderDiv").getBoundingClientRect().height;
$("rssFeedTableDiv").style.height = "calc(100vh - " + nonTableHeight + "px)";
$("rssArticleTableDiv").style.height = "calc(100vh - " + nonTableHeight + "px)";
$("rssContentView").style.height = "calc(100% - " + $("rssTopBar").getBoundingClientRect().height + "px)";
const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({
targets: ".rssFeedContextMenuTarget",
menu: "rssFeedMenu",
@ -451,11 +450,6 @@
const torrentDescription = document.createRange().createContextualFragment('<iframe sandbox id="rssDescription"></iframe>');
$("rssDetailsView").append(torrentDescription);
document.getElementById("rssDescription").srcdoc = '<html><head><link rel="stylesheet" type="text/css" href="css/style.css"></head><body>' + article.description + "</body></html>";
// calculate height to fill screen
document.getElementById("rssDescription").style.height =
"calc(100% - " + document.getElementById("rssTorrentDetailsName").offsetHeight + "px - "
+ document.getElementById("rssTorrentDetailsDate").offsetHeight + "px - 5px)";
}
};

View file

@ -113,7 +113,7 @@
<span></span>
</div>
<div id="searchResultsNoSearches" style="display: none">
<div id="searchResultsNoSearches" class="invisible">
<table>
<tbody>
<tr>
@ -126,13 +126,13 @@
<span></span>
</div>
<div id="searchTabsToolbar" class="toolbarTabs" style="border-bottom: 1px solid var(--color-border-default); display: none">
<div id="searchTabsToolbar" class="toolbarTabs invisible" style="border-bottom: 1px solid var(--color-border-default);">
<ul id="searchTabs" class="tab-menu"></ul>
<div class="clear"></div>
</div>
<div id="searchResultsFilters" style="display: none;">
<input type="text" id="searchInNameFilter" placeholder="QBT_TR(Filter)QBT_TR[CONTEXT=SearchEngineWidget]" autocorrect="off" autocapitalize="none">
<div id="searchResultsFilters" class="invisible">
<input type="text" id="searchInNameFilter" placeholder="QBT_TR(Filter)QBT_TR[CONTEXT=SearchEngineWidget]" aria-label="QBT_TR(Filter)QBT_TR[CONTEXT=SearchEngineWidget]" autocorrect="off" autocapitalize="none">
<span>QBT_TR(Results)QBT_TR[CONTEXT=SearchEngineWidget] (QBT_TR(showing)QBT_TR[CONTEXT=SearchEngineWidget] <span id="numSearchResultsVisible" class="numSearchResults">0</span> QBT_TR(out of)QBT_TR[CONTEXT=SearchEngineWidget] <span id="numSearchResultsTotal" class="numSearchResults">0</span>):</span>
@ -146,14 +146,14 @@
<img id="searchResultsGranularFiltersWarning" src="images/dialog-warning.svg" title="QBT_TR(Increase window width to display additional filters)QBT_TR[CONTEXT=SearchEngineWidget]" alt="QBT_TR(Warning)QBT_TR[CONTEXT=SearchEngineWidget]" width="24" height="24">
<div id="searchResultsGranularFilters">
<span style="margin-left: 15px;">QBT_TR(Seeds:)QBT_TR[CONTEXT=SearchEngineWidget]</span>
<label for="searchMinSeedsFilter" style="margin-left: 15px;">QBT_TR(Seeds:)QBT_TR[CONTEXT=SearchEngineWidget]</label>
<input type="number" min="0" max="1000" id="searchMinSeedsFilter" value="0" onchange="qBittorrent.Search.searchSeedsFilterChanged()">
<span>QBT_TR(to)QBT_TR[CONTEXT=SearchEngineWidget]</span>
<label for="searchMaxSeedsFilter">QBT_TR(to)QBT_TR[CONTEXT=SearchEngineWidget]</label>
<input type="number" min="0" max="1000" id="searchMaxSeedsFilter" value="0" onchange="qBittorrent.Search.searchSeedsFilterChanged()">
<span style="margin-left: 15px;">QBT_TR(Size:)QBT_TR[CONTEXT=SearchEngineWidget]</span>
<label for="searchMinSizeFilter" style="margin-left: 15px;">QBT_TR(Size:)QBT_TR[CONTEXT=SearchEngineWidget]</label>
<input type="number" min="0" max="1000" step=".01" value="0.00" id="searchMinSizeFilter" onchange="qBittorrent.Search.searchSizeFilterChanged()">
<select id="searchMinSizePrefix" onchange="qBittorrent.Search.searchSizeFilterPrefixChanged()">
<select id="searchMinSizePrefix" onchange="qBittorrent.Search.searchSizeFilterPrefixChanged()" aria-label="QBT_TR(Min size prefix)QBT_TR[CONTEXT=SearchEngineWidget]">
<option value="0">QBT_TR(B)QBT_TR[CONTEXT=misc]</option>
<option value="1">QBT_TR(KiB)QBT_TR[CONTEXT=misc]</option>
<option value="2" selected>QBT_TR(MiB)QBT_TR[CONTEXT=misc]</option>
@ -162,9 +162,9 @@
<option value="5">QBT_TR(PiB)QBT_TR[CONTEXT=misc]</option>
<option value="6">QBT_TR(EiB)QBT_TR[CONTEXT=misc]</option>
</select>
<span>QBT_TR(to)QBT_TR[CONTEXT=SearchEngineWidget]</span>
<label for="searchMaxSizeFilter">QBT_TR(to)QBT_TR[CONTEXT=SearchEngineWidget]</label>
<input type="number" min="0" max="1000" step=".01" value="0.00" id="searchMaxSizeFilter" onchange="qBittorrent.Search.searchSizeFilterChanged()">
<select id="searchMaxSizePrefix" onchange="qBittorrent.Search.searchSizeFilterPrefixChanged()">
<select id="searchMaxSizePrefix" onchange="qBittorrent.Search.searchSizeFilterPrefixChanged()" aria-label="QBT_TR(Max size prefix)QBT_TR[CONTEXT=SearchEngineWidget]">
<option value="0">QBT_TR(B)QBT_TR[CONTEXT=misc]</option>
<option value="1">QBT_TR(KiB)QBT_TR[CONTEXT=misc]</option>
<option value="2">QBT_TR(MiB)QBT_TR[CONTEXT=misc]</option>
@ -177,7 +177,7 @@
</div>
</div>
<div id="searchResultsTableContainer" style="display: none">
<div id="searchResultsTableContainer" class="invisible">
<div id="searchResultsTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
<table class="dynamicTable unselectable" style="position:relative;">
<thead>