mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-20 13:23:34 -07:00
WebUI: Support creating new torrents
Implemented the torrent creator using WebAPI from #20366 in WebUI, the interface is mostly inspired by GUI and VueTorrent. Closes #5614. PR #22459.
This commit is contained in:
parent
055d82bda4
commit
f540381caf
10 changed files with 790 additions and 6 deletions
|
@ -32,6 +32,7 @@
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/bittorrent/torrentcreationmanager.h"
|
#include "base/bittorrent/torrentcreationmanager.h"
|
||||||
|
@ -88,6 +89,17 @@ namespace
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
QStringList parseUrls(const QString &urlsParam)
|
||||||
|
{
|
||||||
|
// Empty lines are preserved because they indicate new tracker tier and will be ignored in url seeds.
|
||||||
|
const QStringList encodedUrls = urlsParam.split(u'|');
|
||||||
|
QStringList urls;
|
||||||
|
urls.reserve(encodedUrls.size());
|
||||||
|
for (const QString &urlStr : encodedUrls)
|
||||||
|
urls << QUrl::fromPercentEncoding(urlStr.toLatin1());
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
QString taskStatusString(const std::shared_ptr<BitTorrent::TorrentCreationTask> task)
|
QString taskStatusString(const std::shared_ptr<BitTorrent::TorrentCreationTask> task)
|
||||||
{
|
{
|
||||||
if (task->isFailed())
|
if (task->isFailed())
|
||||||
|
@ -130,8 +142,8 @@ void TorrentCreatorController::addTaskAction()
|
||||||
.torrentFilePath = Path(params()[KEY_TORRENT_FILE_PATH]),
|
.torrentFilePath = Path(params()[KEY_TORRENT_FILE_PATH]),
|
||||||
.comment = params()[KEY_COMMENT],
|
.comment = params()[KEY_COMMENT],
|
||||||
.source = params()[KEY_SOURCE],
|
.source = params()[KEY_SOURCE],
|
||||||
.trackers = params()[KEY_TRACKERS].split(u'|'),
|
.trackers = parseUrls(params()[KEY_TRACKERS]),
|
||||||
.urlSeeds = params()[KEY_URL_SEEDS].split(u'|')
|
.urlSeeds = parseUrls(params()[KEY_URL_SEEDS])
|
||||||
};
|
};
|
||||||
|
|
||||||
bool const startSeeding = parseBool(params()[u"startSeeding"_s]).value_or(createTorrentParams.torrentFilePath.isEmpty());
|
bool const startSeeding = parseBool(params()[u"startSeeding"_s]).value_or(createTorrentParams.torrentFilePath.isEmpty());
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
color: var(--color-text-white);
|
color: var(--color-text-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
#transferList .stateIcon {
|
#transferList .stateIcon,
|
||||||
|
#torrentCreatorContentView .stateIcon {
|
||||||
background: left center / contain no-repeat;
|
background: left center / contain no-repeat;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
padding-left: 1.65em;
|
padding-left: 1.65em;
|
||||||
|
@ -74,6 +75,10 @@
|
||||||
background-image: url("../images/error.svg");
|
background-image: url("../images/error.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.stateRunning {
|
||||||
|
background-image: url("../images/torrent-start.svg");
|
||||||
|
}
|
||||||
|
|
||||||
&.stateUnknown {
|
&.stateUnknown {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,7 @@ input[type="number"],
|
||||||
input[type="password"],
|
input[type="password"],
|
||||||
input[type="button"],
|
input[type="button"],
|
||||||
button,
|
button,
|
||||||
|
textarea,
|
||||||
select {
|
select {
|
||||||
border: 1px solid var(--color-border-default);
|
border: 1px solid var(--color-border-default);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
1
src/webui/www/private/images/torrent-creator.svg
Normal file
1
src/webui/www/private/images/torrent-creator.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m74.453195 67.398077-.004-.0034c-.668397-.617885-1.533343-.76327-2.164719-.869616l-.04644-.0081c-.609164-.100288-.705418-.168269-.981393-.415288-.164239-.146058-.360114-.380289-.360114-.669039s.195875-.52298.359441-.669038l2.038175-1.808558c1.744655-1.541961 2.705855-3.611673 2.705855-5.828115s-.959181-4.286154-2.705899-5.828846c-2.41579-2.12625-5.721429-3.298077-9.313813-3.298077-4.152412 0-8.306842 1.572981-11.403145 4.315096-2.95293 2.609519-4.577143 6.105481-4.577143 9.843077s1.626232 7.233558 4.577143 9.842404c1.464013 1.295 3.196596 2.300577 5.150632 2.989808a17.91615 17.915288 0 0 0 5.847301 1.009615h.121833c4.105966 0 8.016058-1.384519 10.728688-3.796154.652916-.576154 1.019761-1.402019 1.03255-2.326154.01414-.95375-.36146-1.88125-1.004952-2.479615zm-22.145296-8.628846a2.1539497 2.1538462 0 1 1 2.15395 2.153846 2.1539497 2.1538462 0 0 1 -2.15395-2.153846zm2.692438 9.086538a2.1539497 2.1538462 0 1 1 2.153949-2.153846 2.1539497 2.1538462 0 0 1 -2.153949 2.153846zm2.692437-13.394231a2.1539497 2.1538462 0 1 1 2.153949 2.153847 2.1539497 2.1538462 0 0 1 -2.153949-2.153847zm4.307899 18.240385a3.2309246 3.2307692 0 1 1 3.230925-3.230769 3.2309246 3.2307692 0 0 1 -3.230925 3.230769zm4.846387-16.086538a2.1539497 2.1538462 0 1 1 2.15395-2.153847 2.1539497 2.1538462 0 0 1 -2.15395 2.153847z" fill="#1e90ff" stroke-width=".067309" transform="translate(-46 -46)"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -93,9 +93,10 @@
|
||||||
<li>
|
<li>
|
||||||
<a class="returnFalse">QBT_TR(Tools)QBT_TR[CONTEXT=MainWindow]</a>
|
<a class="returnFalse">QBT_TR(Tools)QBT_TR[CONTEXT=MainWindow]</a>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a id="torrentCreatorLink"><img class="MyMenuIcon" src="images/torrent-creator.svg" alt="QBT_TR(Torrent Creator)QBT_TR[CONTEXT=MainWindow]" width="16" height="16">QBT_TR(Torrent Creator)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||||
|
<li class="divider"><a id="manageCookiesLink"><img class="MyMenuIcon" src="images/browser-cookies.svg" alt="QBT_TR(Manage Cookies...)QBT_TR[CONTEXT=MainWindow]" width="16" height="16">QBT_TR(Manage Cookies...)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||||
<li><a id="preferencesLink"><img class="MyMenuIcon" src="images/configure.svg" alt="QBT_TR(Options...)QBT_TR[CONTEXT=MainWindow]" width="16" height="16">QBT_TR(Options...)QBT_TR[CONTEXT=MainWindow]</a></li>
|
<li><a id="preferencesLink"><img class="MyMenuIcon" src="images/configure.svg" alt="QBT_TR(Options...)QBT_TR[CONTEXT=MainWindow]" width="16" height="16">QBT_TR(Options...)QBT_TR[CONTEXT=MainWindow]</a></li>
|
||||||
<li><a id="registerMagnetHandlerLink"><img class="MyMenuIcon" src="images/torrent-magnet.svg" alt="QBT_TR(Register to handle magnet links...)QBT_TR[CONTEXT=HttpServer]" width="16" height="16">QBT_TR(Register to handle magnet links...)QBT_TR[CONTEXT=HttpServer]</a></li>
|
<li class="divider"><a id="registerMagnetHandlerLink"><img class="MyMenuIcon" src="images/torrent-magnet.svg" alt="QBT_TR(Register to handle magnet links...)QBT_TR[CONTEXT=HttpServer]" width="16" height="16">QBT_TR(Register to handle magnet links...)QBT_TR[CONTEXT=HttpServer]</a></li>
|
||||||
<li><a id="manageCookiesLink"><img class="MyMenuIcon" src="images/browser-cookies.svg" alt="QBT_TR(Manage Cookies...)QBT_TR[CONTEXT=MainWindow]" width="16" height="16">QBT_TR(Manage Cookies...)QBT_TR[CONTEXT=MainWindow]</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -51,7 +51,8 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
RssDownloaderRulesTable: RssDownloaderRulesTable,
|
RssDownloaderRulesTable: RssDownloaderRulesTable,
|
||||||
RssDownloaderFeedSelectionTable: RssDownloaderFeedSelectionTable,
|
RssDownloaderFeedSelectionTable: RssDownloaderFeedSelectionTable,
|
||||||
RssDownloaderArticlesTable: RssDownloaderArticlesTable,
|
RssDownloaderArticlesTable: RssDownloaderArticlesTable,
|
||||||
TorrentWebseedsTable: TorrentWebseedsTable
|
TorrentWebseedsTable: TorrentWebseedsTable,
|
||||||
|
TorrentCreationTasksTable: TorrentCreationTasksTable,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3392,6 +3393,225 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const TorrentCreationTasksTable = new Class({
|
||||||
|
Extends: DynamicTable,
|
||||||
|
|
||||||
|
initColumns: function() {
|
||||||
|
this.newColumn("state_icon", "", "QBT_TR(Status Icon)QBT_TR[CONTEXT=TorrentCreator]", 30, false);
|
||||||
|
this.newColumn("source_path", "", "QBT_TR(Source Path)QBT_TR[CONTEXT=TorrentCreator]", 200, true);
|
||||||
|
this.newColumn("progress", "", "QBT_TR(Progress)QBT_TR[CONTEXT=TorrentCreator]", 85, true);
|
||||||
|
this.newColumn("status", "", "QBT_TR(Status)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("torrent_format", "", "QBT_TR(Format)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("piece_size", "", "QBT_TR(Piece Size)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("private", "", "QBT_TR(Private)QBT_TR[CONTEXT=TorrentCreator]", 30, true);
|
||||||
|
this.newColumn("added_on", "", "QBT_TR(Added On)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("start_on", "", "QBT_TR(Started On)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("completion_on", "", "QBT_TR(Completed On)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("trackers", "", "QBT_TR(Trackers)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("web_seeds", "", "QBT_TR(Web Seeds)QBT_TR[CONTEXT=TorrentCreator]", 100, false);
|
||||||
|
this.newColumn("comment", "", "QBT_TR(Comment)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
this.newColumn("source", "", "QBT_TR(Source)QBT_TR[CONTEXT=TorrentCreator]", 100, false);
|
||||||
|
this.newColumn("error_message", "", "QBT_TR(Error Message)QBT_TR[CONTEXT=TorrentCreator]", 100, true);
|
||||||
|
|
||||||
|
this.columns["state_icon"].dataProperties[0] = "status";
|
||||||
|
this.columns["source_path"].dataProperties.push("status");
|
||||||
|
|
||||||
|
this.initColumnsFunctions();
|
||||||
|
},
|
||||||
|
|
||||||
|
initColumnsFunctions: function() {
|
||||||
|
const getStateIconClasses = (state) => {
|
||||||
|
let stateClass = "stateUnknown";
|
||||||
|
// normalize states
|
||||||
|
switch (state) {
|
||||||
|
case "Running":
|
||||||
|
stateClass = "stateRunning";
|
||||||
|
break;
|
||||||
|
case "Queued":
|
||||||
|
stateClass = "stateQueued";
|
||||||
|
break;
|
||||||
|
case "Finished":
|
||||||
|
stateClass = "stateStoppedUP";
|
||||||
|
break;
|
||||||
|
case "Failed":
|
||||||
|
stateClass = "stateError";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `stateIcon ${stateClass}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// state_icon
|
||||||
|
this.columns["state_icon"].updateTd = function(td, row) {
|
||||||
|
const state = this.getRowValue(row);
|
||||||
|
let div = td.firstElementChild;
|
||||||
|
if (div === null) {
|
||||||
|
div = document.createElement("div");
|
||||||
|
td.append(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.className = `${getStateIconClasses(state)} stateIconColumn`;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns["state_icon"].onVisibilityChange = (columnName) => {
|
||||||
|
// show state icon in name column only when standalone
|
||||||
|
// state icon column is hidden
|
||||||
|
this.updateColumn("name", true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// source_path
|
||||||
|
this.columns["source_path"].updateTd = function(td, row) {
|
||||||
|
const name = this.getRowValue(row, 0);
|
||||||
|
const state = this.getRowValue(row, 1);
|
||||||
|
let span = td.firstElementChild;
|
||||||
|
if (span === null) {
|
||||||
|
span = document.createElement("span");
|
||||||
|
td.append(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.className = this.isStateIconShown() ? getStateIconClasses(state) : "";
|
||||||
|
span.textContent = name;
|
||||||
|
td.title = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns["source_path"].isStateIconShown = () => !this.columns["state_icon"].isVisible();
|
||||||
|
|
||||||
|
// status
|
||||||
|
this.columns["status"].updateTd = function(td, row) {
|
||||||
|
const state = this.getRowValue(row);
|
||||||
|
if (!state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let status = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
|
||||||
|
switch (state) {
|
||||||
|
case "Queued":
|
||||||
|
status = "QBT_TR(Queued)QBT_TR[CONTEXT=TorrentCreator]";
|
||||||
|
break;
|
||||||
|
case "Running":
|
||||||
|
status = "QBT_TR(Running)QBT_TR[CONTEXT=TorrentCreator]";
|
||||||
|
break;
|
||||||
|
case "Finished":
|
||||||
|
status = "QBT_TR(Finished)QBT_TR[CONTEXT=TorrentCreator]";
|
||||||
|
break;
|
||||||
|
case "Failed":
|
||||||
|
status = "QBT_TR(Failed)QBT_TR[CONTEXT=TorrentCreator]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.textContent = status;
|
||||||
|
td.title = status;
|
||||||
|
};
|
||||||
|
|
||||||
|
// torrent_format
|
||||||
|
this.columns["torrent_format"].updateTd = function(td, row) {
|
||||||
|
const torrentFormat = this.getRowValue(row);
|
||||||
|
if (!torrentFormat)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let format = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
|
||||||
|
switch (torrentFormat) {
|
||||||
|
case "v1":
|
||||||
|
format = "V1";
|
||||||
|
break;
|
||||||
|
case "v2":
|
||||||
|
format = "V2";
|
||||||
|
break;
|
||||||
|
case "hybrid":
|
||||||
|
format = "QBT_TR(Hybrid)QBT_TR[CONTEXT=TorrentCreator]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.textContent = format;
|
||||||
|
td.title = format;
|
||||||
|
};
|
||||||
|
|
||||||
|
// progress
|
||||||
|
this.columns["progress"].updateTd = function(td, row) {
|
||||||
|
const progress = this.getRowValue(row);
|
||||||
|
|
||||||
|
const div = td.firstElementChild;
|
||||||
|
if (div !== null) {
|
||||||
|
if (td.resized) {
|
||||||
|
td.resized = false;
|
||||||
|
div.setWidth(progressColumnWidth - 5);
|
||||||
|
}
|
||||||
|
if (div.getValue() !== progress)
|
||||||
|
div.setValue(progress);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (progressColumnWidth < 0)
|
||||||
|
progressColumnWidth = td.offsetWidth;
|
||||||
|
td.append(new window.qBittorrent.ProgressBar.ProgressBar(progress, {
|
||||||
|
width: progressColumnWidth - 5
|
||||||
|
}));
|
||||||
|
td.resized = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.columns["progress"].staticWidth = 100;
|
||||||
|
this.columns["progress"].onResize = function(columnName) {
|
||||||
|
const pos = this.getColumnPos(columnName);
|
||||||
|
progressColumnWidth = -1;
|
||||||
|
for (const tr of this.getTrs()) {
|
||||||
|
const td = this.getRowCells(tr)[pos];
|
||||||
|
if (progressColumnWidth < 0)
|
||||||
|
progressColumnWidth = td.offsetWidth;
|
||||||
|
td.resized = true;
|
||||||
|
this.columns[columnName].updateTd(td, this.getRow(tr.rowId));
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
// piece_size
|
||||||
|
this.columns["piece_size"].updateTd = function(td, row) {
|
||||||
|
const pieceSize = this.getRowValue(row);
|
||||||
|
const size = (pieceSize === 0) ? "QBT_TR(N/A)QBT_TR[CONTEXT=TorrentCreator]" : window.qBittorrent.Misc.friendlyUnit(pieceSize, false);
|
||||||
|
td.textContent = size;
|
||||||
|
td.title = size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// private
|
||||||
|
this.columns["private"].updateTd = function(td, row) {
|
||||||
|
const isPrivate = this.getRowValue(row);
|
||||||
|
const string = isPrivate
|
||||||
|
? "QBT_TR(Yes)QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
|
: "QBT_TR(No)QBT_TR[CONTEXT=PropertiesWidget]";
|
||||||
|
td.textContent = string;
|
||||||
|
td.title = string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayDate = function(td, row) {
|
||||||
|
const val = this.getRowValue(row);
|
||||||
|
if (!val) {
|
||||||
|
td.textContent = "";
|
||||||
|
td.title = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const date = new Date(val).toLocaleString();
|
||||||
|
td.textContent = date;
|
||||||
|
td.title = date;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// added_on, start_on, completion_on
|
||||||
|
this.columns["added_on"].updateTd = displayDate;
|
||||||
|
this.columns["start_on"].updateTd = displayDate;
|
||||||
|
this.columns["completion_on"].updateTd = displayDate;
|
||||||
|
},
|
||||||
|
|
||||||
|
setupCommonEvents: function() {
|
||||||
|
this.parent();
|
||||||
|
this.dynamicTableDiv.addEventListener("dblclick", (e) => {
|
||||||
|
const tr = e.target.closest("tr");
|
||||||
|
if (!tr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.deselectAll();
|
||||||
|
this.selectRow(tr.rowId);
|
||||||
|
|
||||||
|
window.qBittorrent.TorrentCreator.exportTorrents();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return exports();
|
return exports();
|
||||||
})();
|
})();
|
||||||
Object.freeze(window.qBittorrent.DynamicTable);
|
Object.freeze(window.qBittorrent.DynamicTable);
|
||||||
|
|
|
@ -208,6 +208,32 @@ const initializeWindows = () => {
|
||||||
updateMainData();
|
updateMainData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addClickEvent("torrentCreator", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const id = "torrentCreatorPage";
|
||||||
|
new MochaUI.Window({
|
||||||
|
id: id,
|
||||||
|
icon: "images/torrent-creator.svg",
|
||||||
|
title: "QBT_TR(Torrent Creator)QBT_TR[CONTEXT=TorrentCreator]",
|
||||||
|
loadMethod: "xhr",
|
||||||
|
contentURL: "views/torrentcreator.html",
|
||||||
|
scrollbars: true,
|
||||||
|
maximizable: true,
|
||||||
|
paddingVertical: 0,
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
width: loadWindowWidth(id, 900),
|
||||||
|
height: loadWindowHeight(id, 400),
|
||||||
|
onResize: () => {
|
||||||
|
saveWindowSize(id);
|
||||||
|
},
|
||||||
|
onClose: () => {
|
||||||
|
window.qBittorrent.TorrentCreator.unload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addClickEvent("preferences", (e) => {
|
addClickEvent("preferences", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
233
src/webui/www/private/views/createtorrent.html
Normal file
233
src/webui/www/private/views/createtorrent.html
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
<style>
|
||||||
|
#createTorrentForm {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTorrentForm input[type="text"],
|
||||||
|
#createTorrentForm textarea,
|
||||||
|
#createTorrentForm table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTorrentForm fieldset {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sourcePathBox {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sourcePath {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTorrentButton {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<form id="createTorrentForm" autocorrect="off" autocapitalize="none">
|
||||||
|
<fieldset class="settings">
|
||||||
|
<legend>QBT_TR(Select file/folder to share:)QBT_TR[CONTEXT=TorrentCreator]</legend>
|
||||||
|
<div id="sourcePathBox">
|
||||||
|
<label for="sourcePath">QBT_TR(Path:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
<input type="text" id="sourcePath" name="sourcePath" class="pathDirectory">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="settings">
|
||||||
|
<legend>QBT_TR(Settings)QBT_TR[CONTEXT=TorrentCreator]</legend>
|
||||||
|
<div id="torrentFormatBox">
|
||||||
|
<label for="torrentFormat">QBT_TR(Torrent format:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
<select id="torrentFormat" name="format">
|
||||||
|
<option value="v2">V2</option>
|
||||||
|
<option selected value="hybrid">QBT_TR(Hybrid)QBT_TR[CONTEXT=TorrentCreator]</option>
|
||||||
|
<option value="v1">V1</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="pieceSize">QBT_TR(Piece size:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
<select id="pieceSize" name="pieceSize">
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="hidden" id="privateTorrentHidden" name="private" value="0">
|
||||||
|
<input type="checkbox" id="privateTorrent"><label for="privateTorrent">QBT_TR(Private
|
||||||
|
torrent (Won't distribute on DHT network))QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="hidden" id="startSeedingHidden" name="startSeeding" value="0">
|
||||||
|
<input type="checkbox" id="startSeeding"><label for="startSeeding">QBT_TR(Start
|
||||||
|
seeding
|
||||||
|
immediately)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
</div>
|
||||||
|
<fieldset id="optimizeAlignmentBox">
|
||||||
|
<legend><input type="hidden" id="optimizeAlignmentHidden" name="optimizeAlignment" value="0"><input type="checkbox" id="optimizeAlignment"><label for="optimizeAlignment">QBT_TR(Optimize
|
||||||
|
alignment)QBT_TR[CONTEXT=TorrentCreator]</label></legend>
|
||||||
|
<label for="paddedFileSizeLimit">QBT_TR(Align to piece boundary for files larger
|
||||||
|
than:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
<input type="number" id="paddedFileSizeLimit" name="paddedFileSizeLimit" disabled value="0">QBT_TR(KiB)QBT_TR[CONTEXT=OptionsDialog]
|
||||||
|
</fieldset>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="settings">
|
||||||
|
<legend>QBT_TR(Fields)QBT_TR[CONTEXT=TorrentCreator]</legend>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="trackerURLs">QBT_TR(Tracker URLs:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<textarea rows="7" type="text" id="trackerURLs" name="trackers"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="webSeedURLs">QBT_TR(Web seed URLs:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<textarea rows="7" type="text" id="webSeedURLs" name="urlSeeds"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="comments">QBT_TR(Comments:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<textarea rows="7" type="text" id="comments" name="comment"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label for="source">QBT_TR(Source:)QBT_TR[CONTEXT=TorrentCreator]</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="source" name="source">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
<button type="submit" id="createTorrentButton">QBT_TR(Create Torrent)QBT_TR[CONTEXT=TorrentCreator]</button>
|
||||||
|
</form>
|
||||||
|
<div id="download_spinner" class="mochaSpinner"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window.qBittorrent ??= {};
|
||||||
|
window.qBittorrent.CreateTorrent ??= (() => {
|
||||||
|
const exports = () => {
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
savePreferences: savePreferences,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatUrls = (urls) => {
|
||||||
|
return urls.split("\n").map(encodeURIComponent).join("|");
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSizeOption = (size) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = size;
|
||||||
|
option.textContent = (size === 0) ? "QBT_TR(Auto)QBT_TR[CONTEXT=TorrentCreator]" : window.qBittorrent.Misc.friendlyUnit(size, false);
|
||||||
|
return option;
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
const pieceSizeSelect = document.getElementById("pieceSize");
|
||||||
|
pieceSizeSelect.appendChild(createSizeOption(0));
|
||||||
|
for (let i = 4; i <= 17; ++i)
|
||||||
|
pieceSizeSelect.appendChild(createSizeOption(1024 << i));
|
||||||
|
|
||||||
|
const buildInfo = window.qBittorrent.Cache.buildInfo.get();
|
||||||
|
const libtorrentVersion = window.qBittorrent.Misc.parseVersion(buildInfo.libtorrent);
|
||||||
|
if (libtorrentVersion.valid) {
|
||||||
|
if (libtorrentVersion.major >= 2) {
|
||||||
|
document.getElementById("optimizeAlignmentBox").style.display = "none";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.getElementById("torrentFormatBox").style.display = "none";
|
||||||
|
document.getElementById("optimizeAlignment").addEventListener("change", (e) => {
|
||||||
|
document.getElementById("paddedFileSizeLimit").disabled = !e.target.checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("createTorrentForm").addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPreference();
|
||||||
|
window.qBittorrent.pathAutofill.attachPathAutofill();
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
document.getElementById("privateTorrentHidden").value = document.getElementById("privateTorrent").checked ? "true" : "false";
|
||||||
|
document.getElementById("startSeedingHidden").value = document.getElementById("startSeeding").checked ? "true" : "false";
|
||||||
|
document.getElementById("optimizeAlignmentHidden").value = document.getElementById("optimizeAlignment").checked ? "true" : "false";
|
||||||
|
|
||||||
|
document.getElementById("download_spinner").style.display = "block";
|
||||||
|
|
||||||
|
const formData = new FormData(document.getElementById("createTorrentForm"));
|
||||||
|
if (formData.has("trackers"))
|
||||||
|
formData.set("trackers", formatUrls(formData.get("trackers")));
|
||||||
|
if (formData.has("urlSeeds"))
|
||||||
|
formData.set("urlSeeds", formatUrls(formData.get("urlSeeds")));
|
||||||
|
|
||||||
|
fetch("api/v2/torrentcreator/addTask", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
alert("QBT_TR(Unable to create torrent.)QBT_TR[CONTEXT=TorrentCreator]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.qBittorrent.Client.closeWindow(document.getElementById("createTorrentPage"));
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const savePreferences = () => {
|
||||||
|
const preference = {
|
||||||
|
sourcePath: document.getElementById("sourcePath").value,
|
||||||
|
torrentFormat: document.getElementById("torrentFormat").value,
|
||||||
|
pieceSize: document.getElementById("pieceSize").value,
|
||||||
|
privateTorrent: document.getElementById("privateTorrent").checked,
|
||||||
|
startSeeding: document.getElementById("startSeeding").checked,
|
||||||
|
optimizeAlignment: document.getElementById("optimizeAlignment").checked,
|
||||||
|
paddedFileSizeLimit: document.getElementById("paddedFileSizeLimit").value,
|
||||||
|
trackerURLs: document.getElementById("trackerURLs").value,
|
||||||
|
webSeedURLs: document.getElementById("webSeedURLs").value,
|
||||||
|
comments: document.getElementById("comments").value,
|
||||||
|
source: document.getElementById("source").value,
|
||||||
|
};
|
||||||
|
LocalPreferences.set("torrent_creator", JSON.stringify(preference));
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPreference = () => {
|
||||||
|
const preference = JSON.parse(LocalPreferences.get("torrent_creator") ?? "{}");
|
||||||
|
document.getElementById("sourcePath").value = preference.sourcePath ?? "";
|
||||||
|
document.getElementById("torrentFormat").value = preference.torrentFormat ?? "hybrid";
|
||||||
|
document.getElementById("pieceSize").value = preference.pieceSize ?? 0;
|
||||||
|
document.getElementById("privateTorrent").checked = preference.privateTorrent ?? false;
|
||||||
|
document.getElementById("startSeeding").checked = preference.startSeeding ?? false;
|
||||||
|
document.getElementById("optimizeAlignment").checked = preference.optimizeAlignment ?? false;
|
||||||
|
document.getElementById("paddedFileSizeLimit").value = preference.paddedFileSizeLimit ?? 0;
|
||||||
|
document.getElementById("trackerURLs").value = preference.trackerURLs ?? "";
|
||||||
|
document.getElementById("webSeedURLs").value = preference.webSeedURLs ?? "";
|
||||||
|
document.getElementById("comments").value = preference.comments ?? "";
|
||||||
|
document.getElementById("source").value = preference.source ?? "";
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports();
|
||||||
|
})();
|
||||||
|
Object.freeze(window.qBittorrent.CreateTorrent);
|
||||||
|
|
||||||
|
window.qBittorrent.CreateTorrent.init();
|
||||||
|
</script>
|
282
src/webui/www/private/views/torrentcreator.html
Normal file
282
src/webui/www/private/views/torrentcreator.html
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
<style>
|
||||||
|
#torrentCreatorTopBar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addTaskButton {
|
||||||
|
padding: 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addTaskButton img {
|
||||||
|
margin: 0 5px -3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#torrentCreatorContainer {
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#torrentCreatorContentView {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 16px);
|
||||||
|
border: 1px solid var(--color-border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#torrentCreationTasksTableFixedHeaderDiv {
|
||||||
|
border-bottom: 1px solid var(--color-border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="torrentCreatorContainer">
|
||||||
|
<ul id="torrentCreationTasksTableMenu" class="contextMenu">
|
||||||
|
<li><a href="#exportTorrent"><img src="images/edit-copy.svg" alt="QBT_TR(Download Torrent)QBT_TR[CONTEXT=TorrentCreator]">
|
||||||
|
QBT_TR(Export Torrent)QBT_TR[CONTEXT=TorrentCreator]</a></li>
|
||||||
|
<li><a href="#deleteTask"><img src="images/list-remove.svg" alt="QBT_TR(Remove Task)QBT_TR[CONTEXT=TorrentCreator]">
|
||||||
|
QBT_TR(Remove Task)QBT_TR[CONTEXT=TorrentCreator]</a></li>
|
||||||
|
</ul>
|
||||||
|
<div id="torrentCreatorTopBar">
|
||||||
|
<button type="button" id="addTaskButton">
|
||||||
|
<img alt="QBT_TR(Create New Torrent)QBT_TR[CONTEXT=TorrentCreator]" src="images/list-add.svg" width="16" height="16">QBT_TR(Create New Torrent)QBT_TR[CONTEXT=TorrentCreator]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="torrentCreatorContentView">
|
||||||
|
<div id="torrentCreationTasksTableFixedHeaderDiv" class="dynamicTableFixedHeaderDiv">
|
||||||
|
<table class="dynamicTable" style="position:relative;">
|
||||||
|
<thead>
|
||||||
|
<tr class="dynamicTableHeader"></tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="torrentCreationTasksTableDiv" class="dynamicTableDiv">
|
||||||
|
<table class="dynamicTable">
|
||||||
|
<thead>
|
||||||
|
<tr class="dynamicTableHeader"></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window.qBittorrent ??= {};
|
||||||
|
window.qBittorrent.TorrentCreator ??= (() => {
|
||||||
|
const exports = () => {
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
unload: unload,
|
||||||
|
exportTorrents: exportTorrents,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let table;
|
||||||
|
let contextMenu;
|
||||||
|
let prevOffsetLeft;
|
||||||
|
let prevOffsetTop;
|
||||||
|
let timer = -1;
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
table = new window.qBittorrent.DynamicTable.TorrentCreationTasksTable();
|
||||||
|
|
||||||
|
contextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||||
|
targets: "#torrentCreationTasksTableDiv",
|
||||||
|
menu: "torrentCreationTasksTableMenu",
|
||||||
|
actions: {
|
||||||
|
deleteTask: () => {
|
||||||
|
const selectedTasks = table.selectedRowsIds();
|
||||||
|
if (selectedTasks.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!confirm("QBT_TR(Are you sure you want to delete selected tasks?)QBT_TR[CONTEXT=TorrentCreator]"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
selectedTasks.forEach(task => {
|
||||||
|
fetch("api/v2/torrentcreator/deleteTask", {
|
||||||
|
method: "POST",
|
||||||
|
body: new URLSearchParams({
|
||||||
|
taskID: task,
|
||||||
|
})
|
||||||
|
}).then((response) => {
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exportTorrent: exportTorrents,
|
||||||
|
},
|
||||||
|
offsets: calculateContextMenuOffsets(),
|
||||||
|
});
|
||||||
|
contextMenu.updateMenuItems = () => {
|
||||||
|
const selectedRows = table.selectedRowsIds();
|
||||||
|
switch (selectedRows.length) {
|
||||||
|
case 0:
|
||||||
|
contextMenu.hideItem("exportTorrent");
|
||||||
|
contextMenu.hideItem("deleteTask");
|
||||||
|
break;
|
||||||
|
case 1: {
|
||||||
|
const row = table.getRow(selectedRows[0]);
|
||||||
|
if (row.full_data.status === "Finished")
|
||||||
|
contextMenu.showItem("exportTorrent");
|
||||||
|
else
|
||||||
|
contextMenu.hideItem("exportTorrent");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
contextMenu.showItem("exportTorrent");
|
||||||
|
contextMenu.showItem("deleteTask");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
table.setup("torrentCreationTasksTableDiv", "torrentCreationTasksTableFixedHeaderDiv", contextMenu);
|
||||||
|
|
||||||
|
table.dynamicTableDiv.addEventListener("contextmenu", (e) => {
|
||||||
|
updateContextMenuOffset();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
document.getElementById("addTaskButton").addEventListener("click", (event) => {
|
||||||
|
showCreateTorrentPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
load();
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateContextMenuOffsets = () => {
|
||||||
|
prevOffsetLeft = document.getElementById("torrentCreatorPage").getBoundingClientRect().left;
|
||||||
|
prevOffsetTop = document.getElementById("torrentCreatorPage").getBoundingClientRect().top;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: -prevOffsetLeft,
|
||||||
|
y: -prevOffsetTop
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateContextMenuOffset = () => {
|
||||||
|
// only re-calculate if window has moved
|
||||||
|
if ((prevOffsetLeft !== document.getElementById("torrentCreatorPage").getBoundingClientRect().left) || (prevOffsetTop !== document.getElementById("torrentCreatorPage").getBoundingClientRect().top))
|
||||||
|
contextMenu.options.offsets = calculateContextMenuOffsets();
|
||||||
|
};
|
||||||
|
|
||||||
|
const showCreateTorrentPage = () => {
|
||||||
|
const id = "createTorrentPage";
|
||||||
|
|
||||||
|
new MochaUI.Window({
|
||||||
|
id: id,
|
||||||
|
icon: "images/list-add.svg",
|
||||||
|
title: "QBT_TR(Create New Torrent)QBT_TR[CONTEXT=TorrentCreator]",
|
||||||
|
loadMethod: "xhr",
|
||||||
|
contentURL: "views/createtorrent.html",
|
||||||
|
scrollbars: true,
|
||||||
|
maximizable: false,
|
||||||
|
closable: true,
|
||||||
|
paddingVertical: 0,
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
width: loadWindowWidth(id, 500),
|
||||||
|
height: loadWindowHeight(id, 600),
|
||||||
|
onResize: () => {
|
||||||
|
saveWindowSize(id);
|
||||||
|
},
|
||||||
|
onClose: () => {
|
||||||
|
window.qBittorrent.CreateTorrent.savePreferences();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportTorrents = async () => {
|
||||||
|
const selectedTasks = table.selectedRowsIds();
|
||||||
|
if (selectedTasks.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const task of selectedTasks) {
|
||||||
|
const row = table.getRow(task);
|
||||||
|
if (row.full_data.status !== "Finished")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const url = new URL("api/v2/torrentcreator/torrentFile", window.location);
|
||||||
|
url.search = new URLSearchParams({
|
||||||
|
taskID: task
|
||||||
|
});
|
||||||
|
|
||||||
|
// download response to file
|
||||||
|
await window.qBittorrent.Misc.downloadFile(url, `${task}.torrent`, "QBT_TR(Unable to export torrent file)QBT_TR[CONTEXT=TorrentCreator]");
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/53560991/automatic-file-downloads-limited-to-10-files-on-chrome-browser
|
||||||
|
await window.qBittorrent.Misc.sleep(200);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
syncTaskWithInterval(100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unload = () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = -1;
|
||||||
|
table = null;
|
||||||
|
contextMenu = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncTaskWithInterval = (interval) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(syncTaskData, interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncTaskData = () => {
|
||||||
|
fetch("api/v2/torrentcreator/status", {
|
||||||
|
method: "GET",
|
||||||
|
cache: "no-store"
|
||||||
|
}).then(async (response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
let error = "QBT_TR(Unable to load torrent creation tasks)QBT_TR[CONTEXT=TorrentCreator]";
|
||||||
|
const responseText = await response.text();
|
||||||
|
if (responseText.length > 0)
|
||||||
|
error += `: ${responseText}`;
|
||||||
|
alert(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Awaiting the json before clearing the table to prevent flickering
|
||||||
|
const responseJSON = await response.json();
|
||||||
|
|
||||||
|
const selectedTasks = table.selectedRowsIds();
|
||||||
|
table.clear();
|
||||||
|
|
||||||
|
if (responseJSON.length > 0) {
|
||||||
|
for (let i = 0; i < responseJSON.length; ++i) {
|
||||||
|
const status = responseJSON[i].status;
|
||||||
|
const row = {
|
||||||
|
rowId: responseJSON[i].taskID,
|
||||||
|
source_path: responseJSON[i].sourcePath,
|
||||||
|
progress: status === "Finished" ? 100 : responseJSON[i].progress,
|
||||||
|
status: status,
|
||||||
|
torrent_format: responseJSON[i].format,
|
||||||
|
piece_size: responseJSON[i].pieceSize,
|
||||||
|
private: responseJSON[i].private,
|
||||||
|
added_on: responseJSON[i].timeAdded,
|
||||||
|
start_on: responseJSON[i].timeStarted,
|
||||||
|
completion_on: responseJSON[i].timeFinished,
|
||||||
|
trackers: responseJSON[i].trackers,
|
||||||
|
comment: responseJSON[i].comment,
|
||||||
|
source: responseJSON[i].source,
|
||||||
|
error_message: responseJSON[i].errorMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
table.updateRowData(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.updateTable(false);
|
||||||
|
|
||||||
|
if (selectedTasks.length > 0)
|
||||||
|
table.reselectRows(selectedTasks);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
syncTaskWithInterval(serverSyncMainDataInterval);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports();
|
||||||
|
})();
|
||||||
|
Object.freeze(window.qBittorrent.TorrentCreator);
|
||||||
|
|
||||||
|
window.qBittorrent.TorrentCreator.init();
|
||||||
|
</script>
|
|
@ -370,6 +370,7 @@
|
||||||
<file>private/images/task-complete.svg</file>
|
<file>private/images/task-complete.svg</file>
|
||||||
<file>private/images/task-reject.svg</file>
|
<file>private/images/task-reject.svg</file>
|
||||||
<file>private/images/toolbox-divider.gif</file>
|
<file>private/images/toolbox-divider.gif</file>
|
||||||
|
<file>private/images/torrent-creator.svg</file>
|
||||||
<file>private/images/torrent-magnet.svg</file>
|
<file>private/images/torrent-magnet.svg</file>
|
||||||
<file>private/images/torrent-start-forced.svg</file>
|
<file>private/images/torrent-start-forced.svg</file>
|
||||||
<file>private/images/torrent-start.svg</file>
|
<file>private/images/torrent-start.svg</file>
|
||||||
|
@ -428,6 +429,7 @@
|
||||||
<file>private/views/confirmdeletion.html</file>
|
<file>private/views/confirmdeletion.html</file>
|
||||||
<file>private/views/confirmRecheck.html</file>
|
<file>private/views/confirmRecheck.html</file>
|
||||||
<file>private/views/cookies.html</file>
|
<file>private/views/cookies.html</file>
|
||||||
|
<file>private/views/createtorrent.html</file>
|
||||||
<file>private/views/filters.html</file>
|
<file>private/views/filters.html</file>
|
||||||
<file>private/views/installsearchplugin.html</file>
|
<file>private/views/installsearchplugin.html</file>
|
||||||
<file>private/views/log.html</file>
|
<file>private/views/log.html</file>
|
||||||
|
@ -441,6 +443,7 @@
|
||||||
<file>private/views/search.html</file>
|
<file>private/views/search.html</file>
|
||||||
<file>private/views/searchplugins.html</file>
|
<file>private/views/searchplugins.html</file>
|
||||||
<file>private/views/statistics.html</file>
|
<file>private/views/statistics.html</file>
|
||||||
|
<file>private/views/torrentcreator.html</file>
|
||||||
<file>private/views/transferlist.html</file>
|
<file>private/views/transferlist.html</file>
|
||||||
<file>public/css/login.css</file>
|
<file>public/css/login.css</file>
|
||||||
<file>public/css/noscript.css</file>
|
<file>public/css/noscript.css</file>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue