Browse Source

Merge pull request #3444 from ngosang/webui_labels

[Web UI] Labels implementation
adaptive-webui-19844
sledgehammer999 9 years ago
parent
commit
4cecb585bc
  1. 1
      src/webui/btjson.cpp
  2. 21
      src/webui/webapplication.cpp
  3. 1
      src/webui/webapplication.h
  4. 1
      src/webui/webui.qrc
  5. 4
      src/webui/www/private/index.html
  6. 5
      src/webui/www/public/filters.html
  7. 47
      src/webui/www/public/newlabel.html
  8. 178
      src/webui/www/public/scripts/client.js
  9. 8
      src/webui/www/public/scripts/dynamicTable.js
  10. 36
      src/webui/www/public/scripts/mocha-init.js

1
src/webui/btjson.cpp

@ -256,6 +256,7 @@ private:
* - "seq_dl": Torrent sequential download state * - "seq_dl": Torrent sequential download state
* - "f_l_piece_prio": Torrent first last piece priority state * - "f_l_piece_prio": Torrent first last piece priority state
* - "force_start": Torrent force start state * - "force_start": Torrent force start state
* - "label": Torrent label
*/ */
QByteArray btjson::getTorrents(QString filter, QString label, QByteArray btjson::getTorrents(QString filter, QString label,
QString sortedColumn, bool reverse, int limit, int offset) QString sortedColumn, bool reverse, int limit, int offset)

21
src/webui/webapplication.cpp

@ -39,6 +39,7 @@
#include "core/preferences.h" #include "core/preferences.h"
#include "btjson.h" #include "btjson.h"
#include "prefjson.h" #include "prefjson.h"
#include "jsonutils.h"
#include "core/bittorrent/session.h" #include "core/bittorrent/session.h"
#include "core/bittorrent/trackerentry.h" #include "core/bittorrent/trackerentry.h"
#include "core/bittorrent/torrentinfo.h" #include "core/bittorrent/torrentinfo.h"
@ -110,6 +111,7 @@ QMap<QString, QMap<QString, WebApplication::Action> > WebApplication::initialize
ADD_ACTION(command, topPrio); ADD_ACTION(command, topPrio);
ADD_ACTION(command, bottomPrio); ADD_ACTION(command, bottomPrio);
ADD_ACTION(command, recheck); ADD_ACTION(command, recheck);
ADD_ACTION(command, setLabel);
ADD_ACTION(version, api); ADD_ACTION(version, api);
ADD_ACTION(version, api_min); ADD_ACTION(version, api_min);
ADD_ACTION(version, qbittorrent); ADD_ACTION(version, qbittorrent);
@ -664,6 +666,25 @@ void WebApplication::action_command_recheck()
torrent->forceRecheck(); torrent->forceRecheck();
} }
void WebApplication::action_command_setLabel()
{
CHECK_URI(0);
CHECK_PARAMETERS("hashes" << "label");
QStringList hashes = request().posts["hashes"].split("|");
QString label = request().posts["label"].trimmed();
if (!Utils::Fs::isValidFileSystemName(label)) {
status(400, "Labels must not contain special characters");
return;
}
foreach (const QString &hash, hashes) {
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
if (torrent)
torrent->setLabel(label);
}
}
bool WebApplication::isPublicScope() bool WebApplication::isPublicScope()
{ {
return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO); return (scope_ == DEFAULT_SCOPE || scope_ == VERSION_INFO);

1
src/webui/webapplication.h

@ -86,6 +86,7 @@ private:
void action_command_topPrio(); void action_command_topPrio();
void action_command_bottomPrio(); void action_command_bottomPrio();
void action_command_recheck(); void action_command_recheck();
void action_command_setLabel();
void action_version_api(); void action_version_api();
void action_version_api_min(); void action_version_api_min();
void action_version_qbittorrent(); void action_version_qbittorrent();

1
src/webui/webui.qrc

@ -27,6 +27,7 @@
<file>www/public/download.html</file> <file>www/public/download.html</file>
<file>www/public/downloadlimit.html</file> <file>www/public/downloadlimit.html</file>
<file>www/public/filters.html</file> <file>www/public/filters.html</file>
<file>www/public/newlabel.html</file>
<file>www/public/preferences.html</file> <file>www/public/preferences.html</file>
<file>www/public/preferences_content.html</file> <file>www/public/preferences_content.html</file>
<file>www/public/properties.html</file> <file>www/public/properties.html</file>

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

@ -105,6 +105,10 @@
<li><a href="#Pause"><img src="theme/media-playback-pause" alt="QBT_TR(Pause)QBT_TR"/> QBT_TR(Pause)QBT_TR</a></li> <li><a href="#Pause"><img src="theme/media-playback-pause" alt="QBT_TR(Pause)QBT_TR"/> QBT_TR(Pause)QBT_TR</a></li>
<li><a href="#ForceStart"><img src="theme/media-seek-forward" alt="QBT_TR(Force Resume)QBT_TR"/> QBT_TR(Force Resume)QBT_TR</a></li> <li><a href="#ForceStart"><img src="theme/media-seek-forward" alt="QBT_TR(Force Resume)QBT_TR"/> QBT_TR(Force Resume)QBT_TR</a></li>
<li class="separator"><a href="#Delete"><img src="theme/list-remove" alt="QBT_TR(Delete)QBT_TR"/> QBT_TR(Delete)QBT_TR</a></li> <li class="separator"><a href="#Delete"><img src="theme/list-remove" alt="QBT_TR(Delete)QBT_TR"/> QBT_TR(Delete)QBT_TR</a></li>
<li class="separator">
<a href="#Label" class="arrow-right"><img src="theme/view-categories" alt="QBT_TR(Label)QBT_TR"/> QBT_TR(Label)QBT_TR</a>
<ul id="contextLabelList"></ul>
</li>
<li id="queueingMenuItems" class="separator"> <li id="queueingMenuItems" class="separator">
<a href="#priority" class="arrow-right"><span style="display: inline-block; width:16px"></span> QBT_TR(Priority)QBT_TR</a> <a href="#priority" class="arrow-right"><span style="display: inline-block; width:16px"></span> QBT_TR(Priority)QBT_TR</a>
<ul> <ul>

5
src/webui/www/public/filters.html

@ -1,3 +1,4 @@
QBT_TR(Torrents)QBT_TR
<ul class="filterList"> <ul class="filterList">
<li id="all_filter"><a href="#" onclick="setFilter('all');return false;"><img src="images/skin/filterall.png"/>QBT_TR(All)QBT_TR</a></li> <li id="all_filter"><a href="#" onclick="setFilter('all');return false;"><img src="images/skin/filterall.png"/>QBT_TR(All)QBT_TR</a></li>
<li id="downloading_filter"><a href="#" onclick="setFilter('downloading');return false;"><img src="images/skin/downloading.png"/>QBT_TR(Downloading)QBT_TR</a></li> <li id="downloading_filter"><a href="#" onclick="setFilter('downloading');return false;"><img src="images/skin/downloading.png"/>QBT_TR(Downloading)QBT_TR</a></li>
@ -8,3 +9,7 @@
<li id="active_filter"><a href="#" onclick="setFilter('active');return false;"><img src="images/skin/filteractive.png"/>QBT_TR(Active)QBT_TR</a></li> <li id="active_filter"><a href="#" onclick="setFilter('active');return false;"><img src="images/skin/filteractive.png"/>QBT_TR(Active)QBT_TR</a></li>
<li id="inactive_filter"><a href="#" onclick="setFilter('inactive');return false;"><img src="images/skin/filterinactive.png"/>QBT_TR(Inactive)QBT_TR</a></li> <li id="inactive_filter"><a href="#" onclick="setFilter('inactive');return false;"><img src="images/skin/filterinactive.png"/>QBT_TR(Inactive)QBT_TR</a></li>
</ul> </ul>
<br/>
QBT_TR(Labels)QBT_TR
<ul id="filterLabelList" class="filterList">
</ul>

47
src/webui/www/public/newlabel.html

@ -0,0 +1,47 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>QBT_TR(New Label)QBT_TR</title>
<link rel="stylesheet" href="css/style.css" type="text/css" />
<script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"></script>
<script type="text/javascript" src="scripts/mootools-1.2-more.js" charset="utf-8"></script>
<script type="text/javascript">
window.addEvent('domready', function() {
$('newLabel').focus();
$('newLabelButton').addEvent('click', function(e) {
new Event(e).stop();
// check field
var labelName = $('newLabel').value.trim();
if (labelName == null || labelName == "")
return false;
if (labelName.match("[\\\\/:?\"*<>|]") !== null) {
alert("QBT_TR(Invalid label name:\nPlease do not use any special characters in the label name.)QBT_TR");
return false;
}
var hashesList = new URI().getData('hashes');
new Request({
url: 'command/setLabel',
method: 'post',
data: {
hashes: hashesList,
label: labelName
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
});
});
</script>
</head>
<body>
<div style="padding: 10px 10px 0px 10px;">
<p style="font-weight: bold;">QBT_TR(Label)QBT_TR:</p>
<input type="text" id="newLabel" value="" maxlength="100" style="width: 220px;"/>
<div style="text-align: center;">
<input type="button" value="QBT_TR(Add)QBT_TR" id="newLabelButton"/>
</div>
</div>
</body>
</html>

178
src/webui/www/public/scripts/client.js

@ -30,24 +30,29 @@ var alternativeSpeedLimits = false;
var queueing_enabled = true; var queueing_enabled = true;
var syncMainDataTimerPeriod = 1500; var syncMainDataTimerPeriod = 1500;
selected_filter = getLocalStorageItem('selected_filter', 'all'); var LABELS_ALL = 1;
selected_label = null; var LABELS_UNLABELLED = 2;
var label_list = {};
var selected_label = LABELS_ALL;
var setLabelFilter = function(){};
var selected_filter = getLocalStorageItem('selected_filter', 'all');
var setFilter = function(){};
var loadSelectedLabel = function () { var loadSelectedLabel = function () {
if (getLocalStorageItem('any_label', '1') == '0') selected_label = getLocalStorageItem('selected_label', LABELS_ALL);
selected_label = getLocalStorageItem('selected_label', ''); };
else
selected_label = null;
}
loadSelectedLabel(); loadSelectedLabel();
var saveSelectedLabel = function () { function genHash(string) {
if (selected_label == null) var hash = 0;
localStorage.setItem('any_label', '1'); for (var i = 0; i < string.length; i++) {
else { var c = string.charCodeAt(i);
localStorage.setItem('any_label', '0'); hash = (c + hash * 31) | 0;
localStorage.setItem('selected_label', selected_label);
} }
return hash;
} }
window.addEvent('load', function () { window.addEvent('load', function () {
@ -90,6 +95,14 @@ window.addEvent('load', function () {
resizeLimit : [100, 300] resizeLimit : [100, 300]
}); });
setLabelFilter = function(hash) {
selected_label = hash;
localStorage.setItem('selected_label', selected_label);
highlightSelectedLabel();
if (typeof myTable.table != 'undefined')
updateMainData();
};
setFilter = function (f) { setFilter = function (f) {
// Visually Select the right filter // Visually Select the right filter
$("all_filter").removeClass("selectedFilter"); $("all_filter").removeClass("selectedFilter");
@ -148,6 +161,112 @@ window.addEvent('load', function () {
var syncMainDataLastResponseId = 0; var syncMainDataLastResponseId = 0;
var serverState = {}; var serverState = {};
var removeTorrentFromLabelList = function(hash) {
if (hash == null || hash == "")
return false;
var removed = false;
Object.each(label_list, function(label) {
if (Object.contains(label.torrents, hash)) {
removed = true;
label.torrents.splice(label.torrents.indexOf(hash), 1);
}
});
return removed;
};
var addTorrentToLabelList = function(torrent) {
var label = torrent['label'];
if (label == null)
return false;
if (label.length === 0) { // Empty label
removeTorrentFromLabelList(torrent['hash']);
return true;
}
var labelHash = genHash(label);
if (label_list[labelHash] == null) // This should not happen
label_list[labelHash] = {name: label, torrents: []};
if (!Object.contains(label_list[labelHash].torrents, torrent['hash'])) {
removeTorrentFromLabelList(torrent['hash']);
label_list[labelHash].torrents = label_list[labelHash].torrents.combine([torrent['hash']]);
return true;
}
return false;
};
var updateContextMenu = function () {
var labelList = $('contextLabelList');
labelList.empty();
labelList.appendChild(new Element('li', {html: '<a href="javascript:newLabelFN();"><img src="theme/list-add" alt="QBT_TR(New...)QBT_TR"/> QBT_TR(New...)QBT_TR</a>'}));
labelList.appendChild(new Element('li', {html: '<a href="javascript:updateLabelFN(0);"><img src="theme/edit-clear" alt="QBT_TR(Reset)QBT_TR"/> QBT_TR(Reset)QBT_TR</a>'}));
var sortedLabels = []
Object.each(label_list, function(label) {
sortedLabels.push(label.name);
});
sortedLabels.sort();
var first = true;
Object.each(sortedLabels, function(labelName) {
var labelHash = genHash(labelName);
var el = new Element('li', {html: '<a href="javascript:updateLabelFN(\'' + labelHash + '\');"><img src="theme/inode-directory"/> ' + labelName + '</a>'});
if (first) {
el.addClass('separator');
first = false;
}
labelList.appendChild(el);
});
};
var updateLabelList = function() {
var labelList = $('filterLabelList');
if (!labelList)
return;
labelList.empty();
var create_link = function(hash, text, count) {
var html = '<a href="#" onclick="setLabelFilter(' + hash + ');return false;">' +
'<img src="theme/inode-directory"/>' +
text + ' (' + count + ')' + '</a>';
return new Element('li', {id: hash, html: html});
};
var all = myTable.getRowIds().length;
var unlabelled = 0;
Object.each(myTable.rows, function(row) {
if (row['full_data'].label.length === 0)
unlabelled += 1;
});
labelList.appendChild(create_link(LABELS_ALL, 'QBT_TR(All)QBT_TR', all));
labelList.appendChild(create_link(LABELS_UNLABELLED, 'QBT_TR(Unlabeled)QBT_TR', unlabelled));
var sortedLabels = []
Object.each(label_list, function(label) {
sortedLabels.push(label.name);
});
sortedLabels.sort();
Object.each(sortedLabels, function(labelName) {
var labelHash = genHash(labelName);
var labelCount = label_list[labelHash].torrents.length;
labelList.appendChild(create_link(labelHash, labelName, labelCount));
});
highlightSelectedLabel();
};
var highlightSelectedLabel = function() {
var labelList = $('filterLabelList');
if (!labelList)
return;
var childrens = labelList.childNodes;
for (var i in childrens) {
if (childrens[i].id == selected_label)
childrens[i].className = "selectedFilter";
else
childrens[i].className = "";
}
}
var syncMainDataTimer; var syncMainDataTimer;
var syncMainData = function () { var syncMainData = function () {
var url = new URI('sync/maindata'); var url = new URI('sync/maindata');
@ -164,19 +283,42 @@ window.addEvent('load', function () {
onSuccess : function (response) { onSuccess : function (response) {
$('error_div').set('html', ''); $('error_div').set('html', '');
if (response) { if (response) {
var update_labels = false;
var full_update = (response['full_update'] == true); var full_update = (response['full_update'] == true);
if (full_update) if (full_update) {
myTable.rows.erase(); myTable.rows.erase();
if (response['rid']) label_list = {};
}
if (response['rid']) {
syncMainDataLastResponseId = response['rid']; syncMainDataLastResponseId = response['rid'];
if (response['torrents']) }
if (response['labels']) {
response['labels'].each(function(label) {
var labelHash = genHash(label);
label_list[labelHash] = {name: label, torrents: []};
});
update_labels = true;
}
if (response['labels_removed']) {
response['labels_removed'].each(function(label) {
var labelHash = genHash(label);
delete label_list[labelHash];
});
update_labels = true;
}
if (response['torrents']) {
for (var key in response['torrents']) { for (var key in response['torrents']) {
response['torrents'][key]['hash'] = key; response['torrents'][key]['hash'] = key;
myTable.updateRowData(response['torrents'][key]); myTable.updateRowData(response['torrents'][key]);
if (addTorrentToLabelList(response['torrents'][key]))
update_labels = true;
}
} }
if (response['torrents_removed']) if (response['torrents_removed'])
response['torrents_removed'].each(function (hash) { response['torrents_removed'].each(function (hash) {
myTable.removeRow(hash); myTable.removeRow(hash);
removeTorrentFromLabelList(hash);
update_labels = true; // Allways to update All label
}); });
myTable.updateTable(full_update); myTable.updateTable(full_update);
myTable.altRow(); myTable.altRow();
@ -186,6 +328,10 @@ window.addEvent('load', function () {
serverState[key] = tmp[key]; serverState[key] = tmp[key];
processServerState(); processServerState();
} }
if (update_labels) {
updateLabelList();
updateContextMenu();
}
} }
clearTimeout(syncMainDataTimer); clearTimeout(syncMainDataTimer);
syncMainDataTimer = syncMainData.delay(syncMainDataTimerPeriod); syncMainDataTimer = syncMainData.delay(syncMainDataTimerPeriod);

8
src/webui/www/public/scripts/dynamicTable.js

@ -60,6 +60,7 @@ var dynamicTable = new Class({
this.newColumn('upspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Up Speed)QBT_TR'); this.newColumn('upspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Up Speed)QBT_TR');
this.newColumn('eta', 'width: 100px; cursor: pointer', 'QBT_TR(ETA)QBT_TR'); this.newColumn('eta', 'width: 100px; cursor: pointer', 'QBT_TR(ETA)QBT_TR');
this.newColumn('ratio', 'width: 100px; cursor: pointer', 'QBT_TR(Ratio)QBT_TR'); this.newColumn('ratio', 'width: 100px; cursor: pointer', 'QBT_TR(Ratio)QBT_TR');
this.newColumn('label', 'width: 100px; cursor: pointer', 'QBT_TR(Label)QBT_TR');
this.columns['state_icon'].onclick = ''; this.columns['state_icon'].onclick = '';
this.columns['state_icon'].dataProperties[0] = 'state'; this.columns['state_icon'].dataProperties[0] = 'state';
@ -279,10 +280,13 @@ var dynamicTable = new Class({
break; break;
} }
if (labelName == null) if (labelName == LABELS_ALL)
return true; return true;
if (labelName != row['full_data'].label) if (labelName == LABELS_UNLABELLED && row['full_data'].label.length === 0)
return true;
if (labelName != genHash( row['full_data'].label) )
return false; return false;
return true; return true;

36
src/webui/www/public/scripts/mocha-init.js

@ -304,6 +304,42 @@ initializeWindows = function() {
} }
}; };
newLabelFN = function () {
var h = myTable.selectedIds();
if (h.length) {
new MochaUI.Window({
id: 'newLabelPage',
title: "QBT_TR(New Label)QBT_TR",
loadMethod: 'iframe',
contentURL: 'newlabel.html?hashes=' + h.join('|'),
scrollbars: false,
resizable: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
height: 100
});
}
};
updateLabelFN = function (labelHash) {
var labelName = '';
if (labelHash != 0)
var labelName = label_list[labelHash].name;
var h = myTable.selectedIds();
if (h.length) {
new Request({
url: 'command/setLabel',
method: 'post',
data: {
hashes: h.join("|"),
label: labelName
}
}).send();
}
};
['pauseAll', 'resumeAll'].each(function(item) { ['pauseAll', 'resumeAll'].each(function(item) {
addClickEvent(item, function(e) { addClickEvent(item, function(e) {
new Event(e).stop(); new Event(e).stop();

Loading…
Cancel
Save