Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebUI: Improve filter lists #21438

Merged
merged 5 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/webui/www/private/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ a.propButton img {
border-top: 1px solid var(--color-border-default);
}

.contextMenu .separatorBottom {
border-bottom: 1px solid var(--color-border-default);
}

.contextMenu li {
margin: 0;
padding: 0;
Expand All @@ -284,8 +288,7 @@ a.propButton img {
.contextMenu li.disabled {
background-color: transparent;
cursor: default;
filter: grayscale(1);
opacity: 0.6;
opacity: 0.5;
}

.contextMenu li.disabled a {
Expand Down
29 changes: 17 additions & 12 deletions src/webui/www/private/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -209,29 +209,34 @@ <h1 class="applicationTitle">qBittorrent Web User Interface <span class="version
<a href="#exportTorrent"><img src="images/edit-copy.svg" alt="QBT_TR(Export .torrent)QBT_TR[CONTEXT=TransferListWidget]"> QBT_TR(Export .torrent)QBT_TR[CONTEXT=TransferListWidget]</a>
</li>
</ul>
<ul id="statusesFilterMenu" class="contextMenu">
<li><a href="#startTorrents"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=StatusFilterWidget]">QBT_TR(Start torrents)QBT_TR[CONTEXT=StatusFilterWidget]</a></li>
<li><a href="#stopTorrents"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=StatusFilterWidget]">QBT_TR(Stop torrents)QBT_TR[CONTEXT=StatusFilterWidget]</a></li>
<li><a href="#deleteTorrents"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=StatusFilterWidget]">QBT_TR(Remove torrents)QBT_TR[CONTEXT=StatusFilterWidget]</a></li>
</ul>
<ul id="categoriesFilterMenu" class="contextMenu">
<li><a href="#createCategory"><img src="images/list-add.svg" alt="QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#createSubcategory"><img src="images/list-add.svg" alt="QBT_TR(Add subcategory...)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Add subcategory...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#editCategory"><img src="images/edit-rename.svg" alt="QBT_TR(Edit category...)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Edit category...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#deleteCategory"><img src="images/list-remove.svg" alt="QBT_TR(Remove category)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Remove category)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#deleteUnusedCategories"><img src="images/list-remove.svg" alt="QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li class="separator"><a href="#startTorrentsByCategory"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#stopTorrentsByCategory"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#deleteTorrentsByCategory"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#deleteUnusedCategories" class="separatorBottom"><img src="images/list-remove.svg" alt="QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#startTorrents"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#stopTorrents"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#deleteTorrents"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
</ul>
<ul id="tagsFilterMenu" class="contextMenu">
<li><a href="#createTag"><img src="images/list-add.svg" alt="QBT_TR(Add tag...)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Add tag...)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#deleteTag"><img src="images/list-remove.svg" alt="QBT_TR(Remove tag)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove tag)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#deleteUnusedTags"><img src="images/list-remove.svg" alt="QBT_TR(Remove unused tags)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove unused tags)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li class="separator"><a href="#startTorrentsByTag"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#stopTorrentsByTag"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#deleteTorrentsByTag"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#deleteUnusedTags" class="separatorBottom"><img src="images/list-remove.svg" alt="QBT_TR(Remove unused tags)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove unused tags)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#startTorrents"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#stopTorrents"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
<li><a href="#deleteTorrents"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TagFilterWidget]</a></li>
</ul>
<ul id="trackersFilterMenu" class="contextMenu">
<li><a href="#deleteTracker"><img src="images/edit-clear.svg" alt="QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li class="separator"><a href="#startTorrentsByTracker"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#stopTorrentsByTracker"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#deleteTorrentsByTracker"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#deleteTracker" class="separatorBottom"><img src="images/edit-clear.svg" alt="QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#startTorrents"><img src="images/torrent-start.svg" alt="QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Start torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#stopTorrents"><img src="images/torrent-stop.svg" alt="QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Stop torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
<li><a href="#deleteTorrents"><img src="images/list-remove.svg" alt="QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]"> QBT_TR(Remove torrents)QBT_TR[CONTEXT=TrackerFiltersList]</a></li>
</ul>
<ul id="torrentTrackersMenu" class="contextMenu">
<li><a href="#AddTracker"><img src="images/list-add.svg" alt="QBT_TR(Add trackers...)QBT_TR[CONTEXT=TrackerListWidget]"> QBT_TR(Add trackers...)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
Expand Down
21 changes: 12 additions & 9 deletions src/webui/www/private/scripts/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -641,12 +641,6 @@ window.addEventListener("DOMContentLoaded", () => {
trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All (%1))QBT_TR[CONTEXT=TrackerFiltersList]", torrentsTable.getRowSize()));
trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless (%1))QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount));

// Remove unused trackers
for (const [key, { trackerTorrentMap }] of trackerList) {
if (trackerTorrentMap.size === 0)
trackerList.delete(key);
}

// Sort trackers by hostname
const sortedList = [];
trackerList.forEach(({ host, trackerTorrentMap }, hash) => {
Expand Down Expand Up @@ -815,8 +809,17 @@ window.addEventListener("DOMContentLoaded", () => {
const host = window.qBittorrent.Misc.getHost(tracker);
const hash = window.qBittorrent.Misc.genHash(host);
const trackerListEntry = trackerList.get(hash);
if (trackerListEntry)
if (trackerListEntry) {
trackerListEntry.trackerTorrentMap.delete(tracker);
// Remove unused trackers
if (trackerListEntry.trackerTorrentMap.size === 0) {
trackerList.delete(hash);
if (selectedTracker === hash) {
selectedTracker = TRACKERS_ALL;
LocalPreferences.set("selected_tracker", selectedTracker.toString());
}
}
}
}
updateTrackers = true;
}
Expand Down Expand Up @@ -1647,15 +1650,15 @@ window.addEventListener("DOMContentLoaded", () => {
return;
if (event.target.isContentEditable)
return;
deleteFN();
deleteSelectedTorrentsFN();
event.preventDefault();
},
"shift+delete": (event) => {
if ((event.target.nodeName === "INPUT") || (event.target.nodeName === "TEXTAREA"))
return;
if (event.target.isContentEditable)
return;
deleteFN(true);
deleteSelectedTorrentsFN(true);
event.preventDefault();
}
}
Expand Down
45 changes: 42 additions & 3 deletions src/webui/www/private/scripts/contextmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ window.qBittorrent.ContextMenu ??= (() => {
return {
ContextMenu: ContextMenu,
TorrentsTableContextMenu: TorrentsTableContextMenu,
StatusesFilterContextMenu: StatusesFilterContextMenu,
CategoriesFilterContextMenu: CategoriesFilterContextMenu,
TagsFilterContextMenu: TagsFilterContextMenu,
TrackersFilterContextMenu: TrackersFilterContextMenu,
Expand Down Expand Up @@ -300,6 +301,31 @@ window.qBittorrent.ContextMenu ??= (() => {
}
});

const FilterListContextMenu = new Class({
Extends: ContextMenu,
initialize: function(options) {
this.parent(options);
this.torrentObserver = new MutationObserver((records, observer) => {
this.updateTorrentActions();
});
},

startTorrentObserver: function() {
this.torrentObserver.observe(torrentsTable.tableBody, { childList: true });
},

stopTorrentObserver: function() {
this.torrentObserver.disconnect();
},

updateTorrentActions: function() {
const torrentsVisible = torrentsTable.tableBody.children.length > 0;
this.setEnabled("startTorrents", torrentsVisible)
.setEnabled("stopTorrents", torrentsVisible)
.setEnabled("deleteTorrents", torrentsVisible);
}
});

const TorrentsTableContextMenu = new Class({
Extends: ContextMenu,

Expand Down Expand Up @@ -574,8 +600,15 @@ window.qBittorrent.ContextMenu ??= (() => {
}
});

const StatusesFilterContextMenu = new Class({
Extends: FilterListContextMenu,
updateMenuItems: function() {
this.updateTorrentActions();
}
});

const CategoriesFilterContextMenu = new Class({
Extends: ContextMenu,
Extends: FilterListContextMenu,
updateMenuItems: function() {
const id = Number(this.options.element.id);
if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
Expand All @@ -591,28 +624,34 @@ window.qBittorrent.ContextMenu ??= (() => {
this.hideItem("deleteCategory");
this.hideItem("createSubcategory");
}

this.updateTorrentActions();
}
});

const TagsFilterContextMenu = new Class({
Extends: ContextMenu,
Extends: FilterListContextMenu,
updateMenuItems: function() {
const id = Number(this.options.element.id);
if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
this.showItem("deleteTag");
else
this.hideItem("deleteTag");

this.updateTorrentActions();
}
});

const TrackersFilterContextMenu = new Class({
Extends: ContextMenu,
Extends: FilterListContextMenu,
updateMenuItems: function() {
const id = Number(this.options.element.id);
if ((id !== TRACKERS_ALL) && (id !== TRACKERS_TRACKERLESS))
this.showItem("deleteTracker");
else
this.hideItem("deleteTracker");

this.updateTorrentActions();
}
});

Expand Down
13 changes: 12 additions & 1 deletion src/webui/www/private/scripts/dynamicTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -1525,9 +1525,20 @@ window.qBittorrent.DynamicTable ??= (() => {

getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash, trackerHash) {
const rowsHashes = [];
const useRegex = document.getElementById("torrentsFilterRegexBox").checked;
const filterText = document.getElementById("torrentsFilterInput").value.trim().toLowerCase();
let filterTerms;
try {
filterTerms = (filterText.length > 0)
? (useRegex ? new RegExp(filterText) : filterText.split(" "))
: null;
}
catch (e) { // SyntaxError: Invalid regex pattern
return filteredRows;
}

for (const row of this.rows.values()) {
if (this.applyFilter(row, filterName, categoryHash, tagHash, trackerHash, null))
if (this.applyFilter(row, filterName, categoryHash, tagHash, trackerHash, filterTerms))
rowsHashes.push(row["rowId"]);
}

Expand Down
Loading
Loading