WebUI: Add new Add Torrent experience

This PR uses the new APIs from #21015 to provide a WebUI Add Torrent experience more closely matching the GUI's.

New functionality:
- View torrent size, date, infohash, files, etc.
- Reprioritize and ignore files before adding
- Specify tags when adding torrent
- Specify save path for incomplete torrent

Closes #20557, closes #10997, closes #12499, closes #14201, closes #15071, closes #15718, closes #16207.
PR #21645.
This commit is contained in:
Thomas Piccirello
2025-08-09 03:34:38 -07:00
committed by GitHub
parent 02d72179fe
commit 02892d1250
18 changed files with 1582 additions and 1074 deletions

View File

@@ -44,6 +44,7 @@ window.qBittorrent.DynamicTable ??= (() => {
TorrentTrackersTable: TorrentTrackersTable,
BulkRenameTorrentFilesTable: BulkRenameTorrentFilesTable,
TorrentFilesTable: TorrentFilesTable,
AddTorrentFilesTable: AddTorrentFilesTable,
LogMessageTable: LogMessageTable,
LogPeerTable: LogPeerTable,
RssFeedTable: RssFeedTable,
@@ -66,6 +67,9 @@ window.qBittorrent.DynamicTable ??= (() => {
let DynamicTableHeaderContextMenuClass = null;
if (typeof LocalPreferences === "undefined")
window.LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
class DynamicTable {
setup(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu, useVirtualList = false) {
this.dynamicTableDivId = dynamicTableDivId;
@@ -2184,12 +2188,13 @@ window.qBittorrent.DynamicTable ??= (() => {
populateTable(root) {
this.fileTree.setRoot(root);
root.children.each((node) => {
this.#addNodeToTable(node, 0);
this.#addNodeToTable(node, 0, root);
});
}
#addNodeToTable(node, depth) {
#addNodeToTable(node, depth, parent) {
node.depth = depth;
node.parent = parent;
if (node.isFolder) {
const data = {
@@ -2212,7 +2217,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}
node.children.each((child) => {
this.#addNodeToTable(child, depth + 1);
this.#addNodeToTable(child, depth + 1, node);
});
}
@@ -2719,12 +2724,13 @@ window.qBittorrent.DynamicTable ??= (() => {
populateTable(root) {
this.fileTree.setRoot(root);
root.children.each((node) => {
this.#addNodeToTable(node, 0);
this.#addNodeToTable(node, 0, root);
});
}
#addNodeToTable(node, depth) {
#addNodeToTable(node, depth, parent) {
node.depth = depth;
node.parent = parent;
if (node.isFolder) {
if (!this.collapseState.has(node.rowId))
@@ -2735,7 +2741,7 @@ window.qBittorrent.DynamicTable ??= (() => {
checked: node.checked,
remaining: node.remaining,
progress: node.progress,
priority: window.qBittorrent.PropFiles.normalizePriority(node.priority),
priority: window.qBittorrent.TorrentContent.normalizePriority(node.priority),
availability: node.availability,
fileId: -1,
name: node.name
@@ -2752,7 +2758,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}
node.children.each((child) => {
this.#addNodeToTable(child, depth + 1);
this.#addNodeToTable(child, depth + 1, node);
});
}
@@ -2813,9 +2819,9 @@ window.qBittorrent.DynamicTable ??= (() => {
const downloadCheckbox = td.children[1];
if (downloadCheckbox === undefined)
td.append(window.qBittorrent.PropFiles.createDownloadCheckbox(id, row.full_data.fileId, value));
td.append(window.qBittorrent.TorrentContent.createDownloadCheckbox(id, row.full_data.fileId, value));
else
window.qBittorrent.PropFiles.updateDownloadCheckbox(downloadCheckbox, id, row.full_data.fileId, value);
window.qBittorrent.TorrentContent.updateDownloadCheckbox(downloadCheckbox, id, row.full_data.fileId, value);
};
this.columns["checked"].staticWidth = 50;
@@ -2887,16 +2893,18 @@ window.qBittorrent.DynamicTable ??= (() => {
this.columns["size"].updateTd = displaySize;
// progress
this.columns["progress"].updateTd = function(td, row) {
const value = Number(this.getRowValue(row));
if (this.columns["progress"] !== undefined) {
this.columns["progress"].updateTd = function(td, row) {
const value = Number(this.getRowValue(row));
const progressBar = td.firstElementChild;
if (progressBar === null)
td.append(new window.qBittorrent.ProgressBar.ProgressBar(value));
else
progressBar.setValue(value);
};
this.columns["progress"].staticWidth = 100;
const progressBar = td.firstElementChild;
if (progressBar === null)
td.append(new window.qBittorrent.ProgressBar.ProgressBar(value));
else
progressBar.setValue(value);
};
this.columns["progress"].staticWidth = 100;
}
// priority
this.columns["priority"].updateTd = function(td, row) {
@@ -2905,15 +2913,17 @@ window.qBittorrent.DynamicTable ??= (() => {
const priorityCombo = td.firstElementChild;
if (priorityCombo === null)
td.append(window.qBittorrent.PropFiles.createPriorityCombo(id, row.full_data.fileId, value));
td.append(window.qBittorrent.TorrentContent.createPriorityCombo(id, row.full_data.fileId, value));
else
window.qBittorrent.PropFiles.updatePriorityCombo(priorityCombo, id, row.full_data.fileId, value);
window.qBittorrent.TorrentContent.updatePriorityCombo(priorityCombo, id, row.full_data.fileId, value);
};
this.columns["priority"].staticWidth = 140;
// remaining, availability
this.columns["remaining"].updateTd = displaySize;
this.columns["availability"].updateTd = displayPercentage;
if (this.columns["remaining"] !== undefined)
this.columns["remaining"].updateTd = displaySize;
if (this.columns["availability"] !== undefined)
this.columns["availability"].updateTd = displayPercentage;
}
#sortNodesByColumn(root, column) {
@@ -3068,6 +3078,17 @@ window.qBittorrent.DynamicTable ??= (() => {
}
}
class AddTorrentFilesTable extends TorrentFilesTable {
initColumns() {
this.newColumn("checked", "", "", 50, true);
this.newColumn("name", "", "QBT_TR(Name)QBT_TR[CONTEXT=TrackerListWidget]", 190, true);
this.newColumn("size", "", "QBT_TR(Total Size)QBT_TR[CONTEXT=TrackerListWidget]", 75, true);
this.newColumn("priority", "", "QBT_TR(Download Priority)QBT_TR[CONTEXT=TrackerListWidget]", 140, true);
this.initColumnsFunctions();
}
}
class RssFeedTable extends DynamicTable {
initColumns() {
this.newColumn("state_icon", "", "", 30, true);
@@ -3246,7 +3267,8 @@ window.qBittorrent.DynamicTable ??= (() => {
if (!tr)
return;
showDownloadPage([this.getRow(tr.rowId).full_data.torrentURL]);
const { name, torrentURL } = this._this.rows.get(this.rowId).full_data;
qBittorrent.Client.createAddTorrentWindow(name, torrentURL);
});
}
updateRow(tr, fullUpdate) {