Browse Source

Add WebUI Trackers context menu

adaptive-webui-19844
Thomas Piccirello 6 years ago
parent
commit
33351e3d8d
  1. 79
      src/webui/api/torrentscontroller.cpp
  2. 2
      src/webui/api/torrentscontroller.h
  3. 11
      src/webui/www/private/addtrackers.html
  4. 6
      src/webui/www/private/css/style.css
  5. 65
      src/webui/www/private/edittracker.html
  6. 6
      src/webui/www/private/index.html
  7. 2
      src/webui/www/private/properties_content.html
  8. 115
      src/webui/www/private/scripts/prop-trackers.js
  9. 1
      src/webui/www/webui.qrc

79
src/webui/api/torrentscontroller.cpp

@ -377,7 +377,7 @@ void TorrentsController::trackersAction()
checkParams({"hash"}); checkParams({"hash"});
const QString hash {params()["hash"]}; const QString hash {params()["hash"]};
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); const BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent) if (!torrent)
throw APIError(APIErrorType::NotFound); throw APIError(APIErrorType::NotFound);
@ -613,15 +613,82 @@ void TorrentsController::addTrackersAction()
const QString hash = params()["hash"]; const QString hash = params()["hash"];
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash); BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent) { if (!torrent)
throw APIError(APIErrorType::NotFound);
QList<BitTorrent::TrackerEntry> trackers; QList<BitTorrent::TrackerEntry> trackers;
for (QString url : asConst(params()["urls"].split('\n'))) { for (const QString &urlStr : asConst(params()["urls"].split('\n'))) {
url = url.trimmed(); const QUrl url {urlStr.trimmed()};
if (!url.isEmpty()) if (url.isValid())
trackers << url; trackers << url.toString();
} }
torrent->addTrackers(trackers); torrent->addTrackers(trackers);
} }
void TorrentsController::editTrackerAction()
{
checkParams({"hash", "origUrl", "newUrl"});
const QString hash = params()["hash"];
const QString origUrl = params()["origUrl"];
const QString newUrl = params()["newUrl"];
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
const QUrl origTrackerUrl(origUrl);
const QUrl newTrackerUrl(newUrl);
if (origTrackerUrl == newTrackerUrl)
return;
if (!newTrackerUrl.isValid())
throw APIError(APIErrorType::BadParams, "New tracker URL is invalid");
QList<BitTorrent::TrackerEntry> trackers = torrent->trackers();
bool match = false;
for (BitTorrent::TrackerEntry &tracker : trackers) {
const QUrl trackerUrl(tracker.url());
if (trackerUrl == newTrackerUrl)
throw APIError(APIErrorType::Conflict, "New tracker URL already exists");
if (trackerUrl == origTrackerUrl) {
match = true;
BitTorrent::TrackerEntry newTracker(newTrackerUrl.toString());
newTracker.setTier(tracker.tier());
tracker = newTracker;
}
}
if (!match)
throw APIError(APIErrorType::Conflict, "Tracker not found");
torrent->replaceTrackers(trackers);
if (!torrent->isPaused())
torrent->forceReannounce();
}
void TorrentsController::removeTrackersAction()
{
checkParams({"hash", "urls"});
const QString hash = params()["hash"];
const QStringList urls = params()["urls"].split('|');
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (!torrent)
throw APIError(APIErrorType::NotFound);
QList<BitTorrent::TrackerEntry> remainingTrackers;
const QList<BitTorrent::TrackerEntry> trackers = torrent->trackers();
for (const BitTorrent::TrackerEntry &entry : trackers) {
if (!urls.contains(entry.url()))
remainingTrackers.push_back(entry);
}
if (remainingTrackers.size() == trackers.size())
throw APIError(APIErrorType::Conflict, "No trackers were removed");
torrent->replaceTrackers(remainingTrackers);
if (!torrent->isPaused())
torrent->forceReannounce();
} }
void TorrentsController::pauseAction() void TorrentsController::pauseAction()

2
src/webui/api/torrentscontroller.h

@ -59,6 +59,8 @@ private slots:
void addAction(); void addAction();
void deleteAction(); void deleteAction();
void addTrackersAction(); void addTrackersAction();
void editTrackerAction();
void removeTrackersAction();
void filePrioAction(); void filePrioAction();
void uploadLimitAction(); void uploadLimitAction();
void downloadLimitAction(); void downloadLimitAction();

11
src/webui/www/private/addtrackers.html

@ -9,6 +9,17 @@
<script src="scripts/lib/mootools-1.2-more.js"></script> <script src="scripts/lib/mootools-1.2-more.js"></script>
<script> <script>
window.addEvent('domready', function() { window.addEvent('domready', function() {
var setLocationKeyboardEvents = new Keyboard({
defaultEventType: 'keydown',
events: {
'enter': function(event) {
$('addTrackersButton').click();
event.preventDefault();
}
}
});
setLocationKeyboardEvents.activate();
$('trackersUrls').focus(); $('trackersUrls').focus();
$('addTrackersButton').addEvent('click', function(e) { $('addTrackersButton').addEvent('click', function(e) {
new Event(e).stop(); new Event(e).stop();

6
src/webui/www/private/css/style.css

@ -450,12 +450,6 @@ td.generalLabel {
line-height: 25px; line-height: 25px;
} }
#addTrackersPlus {
width: 16px;
cursor: pointer;
margin-bottom: -3px;
}
.unselectable { .unselectable {
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-user-select: none; -webkit-user-select: none;

65
src/webui/www/private/edittracker.html

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="${LANG}">
<head>
<meta charset="UTF-8" />
<title>QBT_TR(Tracker editing)QBT_TR[CONTEXT=TrackerListWidget]</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<script src="scripts/lib/mootools-1.2-core-yc.js"></script>
<script src="scripts/lib/mootools-1.2-more.js"></script>
<script>
'use strict';
window.addEvent('domready', function() {
var setLocationKeyboardEvents = new Keyboard({
defaultEventType: 'keydown',
events: {
'enter': function(event) {
$('editTrackerButton').click();
event.preventDefault();
}
}
});
setLocationKeyboardEvents.activate();
var currentUrl = new URI().getData('url');
if (!currentUrl)
return false;
var decodedUrl = decodeURIComponent(currentUrl);
$('trackerUrl').value = decodedUrl;
$('trackerUrl').focus();
$('editTrackerButton').addEvent('click', function(e) {
new Event(e).stop();
var hash = new URI().getData('hash');
new Request({
url: 'api/v2/torrents/editTracker',
method: 'post',
data: {
hash: hash,
origUrl: decodedUrl,
newUrl: $('trackerUrl').value
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
});
});
</script>
</head>
<body>
<div style="text-align: center;">
<br/>
<h2 class="vcenter">QBT_TR(Tracker URL:)QBT_TR[CONTEXT=TrackerListWidget]</h2>
<div style="text-align: center; padding-top: 10px;">
<input id="trackerUrl" style="width: 90%;" />
</div>
<br/>
<input type="button" value="QBT_TR(Edit)QBT_TR[CONTEXT=HttpServer]" id="editTrackerButton" />
</div>
</body>
</html>

6
src/webui/www/private/index.html

@ -158,6 +158,12 @@
<li><a href="#PauseTorrentsByCategory"><img src="images/qbt-theme/media-playback-pause.svg" alt="QBT_TR(Pause torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Pause torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li> <li><a href="#PauseTorrentsByCategory"><img src="images/qbt-theme/media-playback-pause.svg" alt="QBT_TR(Pause torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Pause torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#DeleteTorrentsByCategory"><img src="images/qbt-theme/edit-delete.svg" alt="QBT_TR(Delete torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Delete torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li> <li><a href="#DeleteTorrentsByCategory"><img src="images/qbt-theme/edit-delete.svg" alt="QBT_TR(Delete torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Delete torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
</ul> </ul>
<ul id="torrentTrackersMenu" class="contextMenu">
<li><a href="#AddTracker"><img src="images/qbt-theme/list-add.svg" alt="QBT_TR(Add a new tracker...)QBT_TR[CONTEXT=TrackerListWidget]"/> QBT_TR(Add a new tracker...)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li class="separator"><a href="#EditTracker"><img src="images/qbt-theme/document-edit.svg" alt="QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]"/> QBT_TR(Edit tracker URL...)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li><a href="#RemoveTracker"><img src="images/qbt-theme/list-remove.svg" alt="QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]"/> QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
<li><a href="#CopyTrackerUrl" id="CopyTrackerUrl"><img src="images/qbt-theme/edit-copy.svg" alt="QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]"/> QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]</a></li>
</ul>
<div id="desktopFooterWrapper"> <div id="desktopFooterWrapper">
<div id="desktopFooter"> <div id="desktopFooter">
<span id="error_div"></span> <span id="error_div"></span>

2
src/webui/www/private/properties_content.html

@ -85,7 +85,7 @@
<thead> <thead>
<tr> <tr>
<th style="width: 5%;">QBT_TR(#)QBT_TR[CONTEXT=TrackerListWidget]</th> <th style="width: 5%;">QBT_TR(#)QBT_TR[CONTEXT=TrackerListWidget]</th>
<th style="width: 30%;">QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget] <img src="images/qbt-theme/list-add.svg" id="addTrackersPlus" alt="Add Trackers" /></th> <th style="width: 30%;">QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget]</th>
<th style="width: 10%;">QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]</th> <th style="width: 10%;">QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]</th>
<th style="width: 5%;">QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]</th> <th style="width: 5%;">QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]</th>
<th style="width: 5%;">QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]</th> <th style="width: 5%;">QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]</th>

115
src/webui/www/private/scripts/prop-trackers.js

@ -2,9 +2,10 @@ var trackersDynTable = new Class({
initialize: function() {}, initialize: function() {},
setup: function(table) { setup: function(table, contextMenu) {
this.table = $(table); this.table = $(table);
this.rows = new Hash(); this.rows = new Hash();
this.contextMenu = contextMenu;
}, },
removeRow: function(url) { removeRow: function(url) {
@ -46,11 +47,13 @@ var trackersDynTable = new Class({
td.set('html', row[i]); td.set('html', row[i]);
td.injectInside(tr); td.injectInside(tr);
} }
this.contextMenu.addTarget(tr);
tr.injectInside(this.table); tr.injectInside(this.table);
}, }
}); });
var current_hash = ""; var current_hash = "";
var selectedTracker = "";
var loadTrackersDataTimer; var loadTrackersDataTimer;
var loadTrackersData = function() { var loadTrackersData = function() {
@ -61,13 +64,13 @@ var loadTrackersData = function() {
} }
var new_hash = torrentsTable.getCurrentTorrentHash(); var new_hash = torrentsTable.getCurrentTorrentHash();
if (new_hash === "") { if (new_hash === "") {
tTable.removeAllRows(); torrentTrackersTable.removeAllRows();
clearTimeout(loadTrackersDataTimer); clearTimeout(loadTrackersDataTimer);
loadTrackersDataTimer = loadTrackersData.delay(10000); loadTrackersDataTimer = loadTrackersData.delay(10000);
return; return;
} }
if (new_hash != current_hash) { if (new_hash != current_hash) {
tTable.removeAllRows(); torrentTrackersTable.removeAllRows();
current_hash = new_hash; current_hash = new_hash;
} }
var url = new URI('api/v2/torrents/trackers?hash=' + current_hash); var url = new URI('api/v2/torrents/trackers?hash=' + current_hash);
@ -82,6 +85,8 @@ var loadTrackersData = function() {
}, },
onSuccess: function(trackers) { onSuccess: function(trackers) {
$('error_div').set('html', ''); $('error_div').set('html', '');
torrentTrackersTable.removeAllRows();
if (trackers) { if (trackers) {
// Update Trackers data // Update Trackers data
trackers.each(function(tracker) { trackers.each(function(tracker) {
@ -96,12 +101,9 @@ var loadTrackersData = function() {
escapeHtml(tracker.msg) escapeHtml(tracker.msg)
]; ];
tTable.insertRow(row); torrentTrackersTable.insertRow(row);
}); });
} }
else {
tTable.removeAllRows();
}
clearTimeout(loadTrackersDataTimer); clearTimeout(loadTrackersDataTimer);
loadTrackersDataTimer = loadTrackersData.delay(10000); loadTrackersDataTimer = loadTrackersData.delay(10000);
} }
@ -113,11 +115,42 @@ var updateTrackersData = function() {
loadTrackersData(); loadTrackersData();
}; };
tTable = new trackersDynTable(); var torrentTrackersContextMenu = new ContextMenu({
tTable.setup($('trackersTable')); targets: '.torrentTrackersMenuTarget',
menu: 'torrentTrackersMenu',
actions: {
AddTracker: function(element, ref) {
addTrackerFN();
},
EditTracker: function(element, ref) {
editTrackerFN(element);
},
RemoveTracker: function(element, ref) {
removeTrackerFN(element);
}
},
offsets: {
x: -15,
y: 2
},
onShow: function() {
var element = this.options.element;
selectedTracker = element;
if (element.childNodes[1].innerText.indexOf("** [") === 0) {
this.hideItem('EditTracker');
this.hideItem('RemoveTracker');
this.hideItem('CopyTrackerUrl');
}
else {
this.showItem('EditTracker');
this.showItem('RemoveTracker');
this.showItem('CopyTrackerUrl');
}
this.options.element.firstChild.click();
}
});
// Add trackers code var addTrackerFN = function() {
$('addTrackersPlus').addEvent('click', function addTrackerDlg() {
if (current_hash.length === 0) return; if (current_hash.length === 0) return;
new MochaUI.Window({ new MochaUI.Window({
id: 'trackersPage', id: 'trackersPage',
@ -131,6 +164,62 @@ $('addTrackersPlus').addEvent('click', function addTrackerDlg() {
paddingVertical: 0, paddingVertical: 0,
paddingHorizontal: 0, paddingHorizontal: 0,
width: 500, width: 500,
height: 250 height: 250,
onCloseComplete: function() {
updateTrackersData();
}
});
};
var editTrackerFN = function(element) {
if (current_hash.length === 0) return;
var trackerUrl = encodeURIComponent(element.childNodes[1].innerText);
new MochaUI.Window({
id: 'trackersPage',
title: "QBT_TR(Tracker editing)QBT_TR[CONTEXT=TrackerListWidget]",
loadMethod: 'iframe',
contentURL: 'edittracker.html?hash=' + current_hash + '&url=' + trackerUrl,
scrollbars: true,
resizable: false,
maximizable: false,
closable: true,
paddingVertical: 0,
paddingHorizontal: 0,
width: 500,
height: 150,
onCloseComplete: function() {
updateTrackersData();
}
}); });
};
var removeTrackerFN = function(element) {
if (current_hash.length === 0) return;
var trackerUrl = element.childNodes[1].innerText;
new Request({
url: 'api/v2/torrents/removeTrackers',
method: 'post',
data: {
hash: current_hash,
urls: trackerUrl
},
onSuccess: function() {
updateTrackersData();
}
}).send();
};
torrentTrackersTable = new trackersDynTable();
torrentTrackersTable.setup($('trackersTable'), torrentTrackersContextMenu);
new ClipboardJS('#CopyTrackerUrl', {
text: function(trigger) {
if (selectedTracker) {
var url = selectedTracker.childNodes[1].innerText;
selectedTracker = "";
return url;
}
}
}); });

1
src/webui/www/webui.qrc

@ -11,6 +11,7 @@
<file>private/css/Window.css</file> <file>private/css/Window.css</file>
<file>private/download.html</file> <file>private/download.html</file>
<file>private/downloadlimit.html</file> <file>private/downloadlimit.html</file>
<file>private/edittracker.html</file>
<file>private/filters.html</file> <file>private/filters.html</file>
<file>private/index.html</file> <file>private/index.html</file>
<file>private/installsearchplugin.html</file> <file>private/installsearchplugin.html</file>

Loading…
Cancel
Save