Compare commits

...

6 Commits

Author SHA1 Message Date
Thomas Piccirello
2ebe8595c9 Modify CategoryOptions serialization to JSON
When a category's download path option is set to "Default", its `downloadPath` is serialized into JSON as `undefined`. This results in the `downloadPath` field being omitted from `torrents/categories` and `torrents/maindata` payloads (as is expected with an `undefined` value).

The use of `undefined` here causes an issue in the WebUI. Specifically, when the category previously contained a value for this field (i.e. download path option set to either "Yes" or "No"), the `processMap` logic in `SyncController` does not detect the removal this field. This results in the category's new `downloadPath` not being properly sent to the client. By switching from `undefined` to `null`, we ensure that the `downloadPath` value is always included in the category's payload. This allows `processMap` to properly detect whenever the value changes.

This change is backwards compatible with existing categories.json files. Older qBittorrent versions should also be able to parse new categories.json files containing `null`.

More context: cd3fbfbf9b (r2173148696)

PR #22958.
2025-07-07 00:17:57 +08:00
Thomas Piccirello
c5a282a02f WebUI: Fix footer left alignment
Follow up to #22918.
PR #22947.
2025-07-07 00:04:46 +08:00
Thomas Piccirello
0e0b1d0962 WebUI: Limit window sizes to viewport size
This change makes the WebUI easier to use on small screens (e.g. mobile). In cases where the window's default size is larger than the user's screen, the window will be resized appropriate (see example below). Every window has been tested for compatibility. The only windows that don't support this are the multi file rename window and the RSS Downloader window.

Closes #19813.
PR #22919.
2025-07-06 23:58:07 +08:00
Thomas Piccirello
15b8a81f92 WebUI: Improve search page experience on mobile
This change better handles resizing of elements on the search page to ensure no controls are hidden at typical mobile screen sizes.
Improvements seen below:
- "Search" button is now accessible
- Content no longer overflows w/ "Search plugins..." button pushed offscreen
- Tabs overflow horizontally and are scrollable, rather than pushing down the search results table

PR #22916.
2025-07-06 23:50:54 +08:00
Thomas Piccirello
f8d44b5073 WebUI: Fix hiding of filters toolbar
The toolbar should be hidden when not on the "Transfers" tab. The hiding of this toolbar can result in resizing the panels, especially on mobile, so we should recompute panel heights.

PR #22915.
2025-07-06 23:45:45 +08:00
Thomas Piccirello
f42dd1b529 WebUI: Support horizontal scrolling of tabs
Previously, the tabs would overflow to the next line, often being hidden by other content. For example, the "Content" tab is now accessible on mobile.

PR #22914.
2025-07-06 23:40:25 +08:00
16 changed files with 95 additions and 47 deletions

View File

@@ -1,5 +1,10 @@
# WebAPI Changelog
## 2.11.10
* [#22932](https://github.com/qbittorrent/qBittorrent/pull/22932)
* `torrents/categories` and `sync/maindata` now serialize categories' `downloadPath` to `null`, rather than `undefined`
## 2.11.9
* [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015)

View File

@@ -52,7 +52,7 @@ BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObj
QJsonObject BitTorrent::CategoryOptions::toJSON() const
{
QJsonValue downloadPathValue = QJsonValue::Undefined;
QJsonValue downloadPathValue = QJsonValue::Null;
if (downloadPath)
{
if (downloadPath->enabled)

View File

@@ -53,7 +53,7 @@
#include "base/utils/version.h"
#include "api/isessionmanager.h"
inline const Utils::Version<3, 2> API_VERSION {2, 11, 9};
inline const Utils::Version<3, 2> API_VERSION {2, 11, 10};
class APIController;
class AuthController;

View File

@@ -23,13 +23,16 @@ Required by:
}
.tab-menu {
display: flex;
font-size: 11px;
list-style-type: none;
overflow-x: auto;
padding: 0;
}
.tab-menu li {
cursor: pointer;
flex: 0 0 auto;
float: left;
}

View File

@@ -463,6 +463,10 @@ a.propButton img {
vertical-align: top;
}
#torrentsFilterToolbar.invisible {
display: none;
}
#torrentsFilterInput {
background-color: var(--color-background-default);
background-image: url("../images/edit-find.svg");

View File

@@ -276,9 +276,9 @@
<li><a href="#ToggleSelection"><img src="images/edit-rename.svg" alt="QBT_TR(Toggle Selection)QBT_TR[CONTEXT=PropertiesWidget]"> QBT_TR(Toggle Selection)QBT_TR[CONTEXT=PropertiesWidget]</a></li>
</ul>
<div id="desktopFooterWrapper">
<div id="desktopFooter" style="overflow-x: auto;">
<div id="desktopFooter" style="overflow-x: auto; overflow-y: hidden;">
<span id="error_div"></span>
<table style="margin-right: 5px; width: max-content;">
<table style="margin-right: 5px; margin-left: auto; width: max-content;">
<tbody>
<tr>
<td id="freeSpaceOnDisk"></td>

View File

@@ -1308,7 +1308,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("filtersColumn_handle").classList.add("invisible");
document.getElementById("mainColumn").classList.add("invisible");
document.getElementById("torrentsFilterToolbar").classList.add("invisible");
MochaUI.Desktop.resizePanels();
MochaUI.Desktop.setDesktopSize();
};
const showSearchTab = (() => {
@@ -1342,7 +1342,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
const hideSearchTab = () => {
document.getElementById("searchTabColumn").classList.add("invisible");
MochaUI.Desktop.resizePanels();
MochaUI.Desktop.setDesktopSize();
};
const showRssTab = (() => {
@@ -1380,7 +1380,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
const hideRssTab = () => {
document.getElementById("rssTabColumn").classList.add("invisible");
window.qBittorrent.Rss && window.qBittorrent.Rss.unload();
MochaUI.Desktop.resizePanels();
MochaUI.Desktop.setDesktopSize();
};
const showLogTab = (() => {
@@ -1417,7 +1417,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
const hideLogTab = () => {
document.getElementById("logTabColumn").classList.add("invisible");
MochaUI.Desktop.resizePanels();
MochaUI.Desktop.setDesktopSize();
window.qBittorrent.Log && window.qBittorrent.Log.unload();
};

View File

@@ -42,7 +42,9 @@ window.qBittorrent ??= {};
window.qBittorrent.Dialog ??= (() => {
const exports = () => {
return {
baseModalOptions: baseModalOptions
baseModalOptions: baseModalOptions,
limitWidthToViewport: limitWidthToViewport,
limitHeightToViewport: limitHeightToViewport
};
};
@@ -68,6 +70,13 @@ window.qBittorrent.Dialog ??= (() => {
deepFreezeSafe(obj);
};
const limitWidthToViewport = (width) => {
return Math.min(width, window.innerWidth - 20);
};
const limitHeightToViewport = (height) => {
return Math.min(height, window.innerHeight - 75);
};
const baseModalOptions = Object.assign(Object.create(null), {
addClass: "modalDialog",
collapsible: false,
@@ -86,7 +95,7 @@ window.qBittorrent.Dialog ??= (() => {
left: 5
},
resizable: true,
width: 480,
width: limitWidthToViewport(480),
onCloseComplete: () => {
// make sure overlay is properly hidden upon modal closing
document.getElementById("modalOverlay").style.display = "none";
@@ -156,11 +165,15 @@ const initializeWindows = () => {
LocalPreferences.set(`window_${windowId}_height`, size.y);
};
loadWindowWidth = (windowId, defaultValue) => {
loadWindowWidth = (windowId, defaultValue, limitToViewportWidth = true) => {
if (limitToViewportWidth)
defaultValue = window.qBittorrent.Dialog.limitWidthToViewport(defaultValue);
return LocalPreferences.get(`window_${windowId}_width`, defaultValue);
};
loadWindowHeight = (windowId, defaultValue) => {
loadWindowHeight = (windowId, defaultValue, limitToViewportHeight = true) => {
if (limitToViewportHeight)
defaultValue = window.qBittorrent.Dialog.limitHeightToViewport(defaultValue);
return LocalPreferences.get(`window_${windowId}_height`, defaultValue);
};
@@ -325,7 +338,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 424,
width: window.qBittorrent.Dialog.limitWidthToViewport(424),
height: 100
});
};
@@ -351,7 +364,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 424,
width: window.qBittorrent.Dialog.limitWidthToViewport(424),
height: 100
});
};
@@ -397,7 +410,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 424,
width: window.qBittorrent.Dialog.limitWidthToViewport(424),
height: 220
});
};
@@ -473,7 +486,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 424,
width: window.qBittorrent.Dialog.limitWidthToViewport(424),
height: 100
});
};
@@ -520,7 +533,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 424,
width: window.qBittorrent.Dialog.limitWidthToViewport(424),
height: 100
});
};
@@ -703,7 +716,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 130
});
};
@@ -733,7 +746,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 100
});
};
@@ -868,7 +881,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 150
});
};
@@ -909,7 +922,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 150
});
};
@@ -931,7 +944,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 150
});
};
@@ -954,7 +967,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 150
});
};
@@ -1017,7 +1030,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
width: window.qBittorrent.Dialog.limitWidthToViewport(250),
height: 100
});
};
@@ -1064,7 +1077,7 @@ const initializeWindows = () => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
width: window.qBittorrent.Dialog.limitWidthToViewport(250),
height: 100
});
updateMainData();
@@ -1117,7 +1130,7 @@ const initializeWindows = () => {
resizable: true,
maximizable: false,
padding: 10,
width: 424,
width: window.qBittorrent.Dialog.limitWidthToViewport(424),
height: 100,
onCloseComplete: () => {
updateMainData();

View File

@@ -503,7 +503,7 @@ window.qBittorrent.PropFiles ??= (() => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 400,
width: window.qBittorrent.Dialog.limitWidthToViewport(400),
height: 100
});
};

View File

@@ -145,7 +145,7 @@ window.qBittorrent.PropPeers ??= (() => {
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 260
});
},

View File

@@ -197,7 +197,7 @@ window.qBittorrent.PropTrackers ??= (() => {
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
width: window.qBittorrent.Dialog.limitWidthToViewport(500),
height: 260,
onCloseComplete: () => {
updateData();
@@ -222,7 +222,7 @@ window.qBittorrent.PropTrackers ??= (() => {
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
width: window.qBittorrent.Dialog.limitWidthToViewport(500),
height: 150,
onCloseComplete: () => {
updateData();

View File

@@ -159,7 +159,7 @@ window.qBittorrent.PropWebseeds ??= (() => {
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
width: window.qBittorrent.Dialog.limitWidthToViewport(500),
height: 260,
onCloseComplete: () => {
updateData();
@@ -188,7 +188,7 @@ window.qBittorrent.PropWebseeds ??= (() => {
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
width: window.qBittorrent.Dialog.limitWidthToViewport(500),
height: 150,
onCloseComplete: () => {
updateData();

View File

@@ -349,7 +349,7 @@
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 100
});
};
@@ -377,7 +377,7 @@
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 100
});
};
@@ -791,7 +791,7 @@
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 100
});
};
@@ -811,7 +811,7 @@
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 100
});
};
@@ -827,7 +827,7 @@
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 70
});
};
@@ -947,8 +947,8 @@
loadMethod: "xhr",
contentURL: "views/rssDownloader.html",
maximizable: false,
width: loadWindowWidth(id, 800),
height: loadWindowHeight(id, 650),
width: loadWindowWidth(id, 800, false),
height: loadWindowHeight(id, 650, false),
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveWindowSize(id);
}),

View File

@@ -536,7 +536,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 100
});
};
@@ -551,7 +551,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 100
});
};
@@ -574,7 +574,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
scrollbars: false,
resizable: false,
maximizable: false,
width: 360,
width: window.qBittorrent.Dialog.limitWidthToViewport(360),
height: 90
});
};
@@ -590,7 +590,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
scrollbars: false,
resizable: false,
maximizable: false,
width: 350,
width: window.qBittorrent.Dialog.limitWidthToViewport(350),
height: 85
});
};

View File

@@ -1,6 +1,6 @@
<style>
#searchPattern {
width: 300px;
width: 180px;
background-image: url("images/edit-find.svg");
background-repeat: no-repeat;
background-size: 1.5em;
@@ -70,6 +70,25 @@
margin: 0 5px -3px 0;
}
#searchResultsHeader {
height: 60px;
overflow: hidden;
}
#searchResultsHeaderContainer {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-top: 4px;
height: 24px;
}
@media (min-width: 760px) {
#searchPattern {
width: 300px;
}
}
@media (min-width: 1060px) {
#searchResultsGranularFilters {
display: inline-block;
@@ -82,13 +101,17 @@
#searchPattern {
width: 500px;
}
#searchResultsHeaderContainer {
margin: 20px 0 10px 0;
}
}
</style>
<div id="searchResults">
<div style="overflow: hidden; height: 60px;">
<div style="display: flex; flex-wrap: wrap; gap: 5px; margin: 20px 0 10px 0; height: 24px;">
<div id="searchResultsHeader">
<div id="searchResultsHeaderContainer">
<input type="search" id="searchPattern" class="searchInputField" placeholder="QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]" aria-label="QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]" autocorrect="off" autocomplete="off" autocapitalize="none" oninput="qBittorrent.Search.onSearchPatternChanged()">
<select id="categorySelect" class="searchInputField" aria-label="QBT_TR(Select category)QBT_TR[CONTEXT=SearchEngineWidget]" onchange="qBittorrent.Search.categorySelected()"></select>
<select id="pluginsSelect" class="searchInputField" aria-label="QBT_TR(Select plugins)QBT_TR[CONTEXT=SearchEngineWidget]" onchange="qBittorrent.Search.pluginSelected()"></select>

View File

@@ -124,7 +124,7 @@
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
width: window.qBittorrent.Dialog.limitWidthToViewport(500),
height: 120
});
};