mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-29 05:48:47 +03:00
webui (js): feature: Added complete support for labels (add/set/reset/display/filter) #648
Changes: - added list of labels on the lower-left corner - added support to add/set/reset labels on context menu - added support to filter torrents by label
This commit is contained in:
parent
76d93c23b7
commit
4ae2f6c33b
9 changed files with 272 additions and 23 deletions
|
@ -396,4 +396,3 @@ void prefjson::setPreferences(const QString& json)
|
|||
// Save preferences
|
||||
pref->apply();
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "core/preferences.h"
|
||||
#include "btjson.h"
|
||||
#include "prefjson.h"
|
||||
#include "jsonutils.h"
|
||||
#include "core/bittorrent/session.h"
|
||||
#include "core/bittorrent/trackerentry.h"
|
||||
#include "core/bittorrent/torrentinfo.h"
|
||||
|
@ -677,8 +678,9 @@ void WebApplication::action_command_setLabel()
|
|||
if( m.contains("value") ) {
|
||||
QString label = m["value"].toString();
|
||||
if (!hash.isEmpty()) {
|
||||
QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash);
|
||||
QBtSession::instance()->setLabel(h, label);
|
||||
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
||||
if (torrent)
|
||||
torrent->setLabel(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<file>www/public/download.html</file>
|
||||
<file>www/public/downloadlimit.html</file>
|
||||
<file>www/public/filters.html</file>
|
||||
<file>www/public/newlabel.html</file>
|
||||
<file>www/public/preferences.html</file>
|
||||
<file>www/public/preferences_content.html</file>
|
||||
<file>www/public/properties.html</file>
|
||||
|
|
|
@ -105,6 +105,11 @@
|
|||
<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 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">QBT_TR(Label >)QBT_TR</a>
|
||||
<ul id="contextLabelList">
|
||||
</ul>
|
||||
</li>
|
||||
<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>
|
||||
<ul>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
QBT_TR(Torrents)QBT_TR
|
||||
<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="downloading_filter"><a href="#" onclick="setFilter('downloading');return false;"><img src="images/skin/downloading.png"/>QBT_TR(Downloading)QBT_TR</a></li>
|
||||
|
@ -7,4 +8,8 @@
|
|||
<li id="paused_filter"><a href="#" onclick="setFilter('paused');return false;"><img src="images/skin/paused.png"/>QBT_TR(Paused)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>
|
||||
</ul>
|
||||
</ul>
|
||||
<br/>
|
||||
QBT_TR(Labels)QBT_TR
|
||||
<ul id="filterLabelList" style="padding-left: 0px;">
|
||||
</ul>
|
||||
|
|
64
src/webui/www/public/newlabel.html
Normal file
64
src/webui/www/public/newlabel.html
Normal file
|
@ -0,0 +1,64 @@
|
|||
<!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){
|
||||
// check field
|
||||
var label_str = $('newLabel').value;
|
||||
if( label_str == null || label_str == "" ) {
|
||||
return false;
|
||||
}
|
||||
if( label_str.match("[\\\\/:?\"*<>|]") !== null ) {
|
||||
$('alert_div').style.display = "block";
|
||||
return false;
|
||||
}
|
||||
|
||||
new Event(e).stop();
|
||||
var hashes = new URI().getData('hashes');
|
||||
var hashesList = hashes.split(',');
|
||||
var label_json = JSON.stringify( { value : label_str } );
|
||||
|
||||
if (hashesList.length) {
|
||||
var counter = hashesList.length;
|
||||
hashesList.each(function(hash, index) {
|
||||
new Request({
|
||||
url: 'command/setLabel',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash,
|
||||
label_obj: label_json
|
||||
},
|
||||
onComplete: function() {
|
||||
if( --counter === 0 ) {
|
||||
window.parent.document.getElementById('newLabelPage').parentNode.removeChild(window.parent.document.getElementById('newLabelPage'));
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h1 class="vcenter">QBT_TR(Label)QBT_TR:
|
||||
<input type="text" name="label" id="newLabel" value="" maxlength="100" />
|
||||
</h1>
|
||||
<div id="alert_div" style="border: 1px #ff0000 solid; display: none; color: #ff0000; background-color: #ffdee8; width: 95%;">
|
||||
<b>QBT_TR(Invalid label name)QBT_TR</b>:
|
||||
<br/>
|
||||
QBT_TR(Please don't use any special characters in the label name.)QBT_TR
|
||||
<br/>
|
||||
</div>
|
||||
<input type="button" value="QBT_TR(Add)QBT_TR" id="newLabelButton"/>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
|
@ -30,24 +30,29 @@ var alternativeSpeedLimits = false;
|
|||
var queueing_enabled = true;
|
||||
var syncMainDataTimerPeriod = 1500;
|
||||
|
||||
selected_filter = getLocalStorageItem('selected_filter', 'all');
|
||||
selected_label = null;
|
||||
var LABELS_ALL = 1;
|
||||
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 () {
|
||||
if (getLocalStorageItem('any_label', '1') == '0')
|
||||
selected_label = getLocalStorageItem('selected_label', '');
|
||||
else
|
||||
selected_label = null;
|
||||
}
|
||||
selected_label = getLocalStorageItem('selected_label', LABELS_ALL);
|
||||
};
|
||||
loadSelectedLabel();
|
||||
|
||||
var saveSelectedLabel = function () {
|
||||
if (selected_label == null)
|
||||
localStorage.setItem('any_label', '1');
|
||||
else {
|
||||
localStorage.setItem('any_label', '0');
|
||||
localStorage.setItem('selected_label', selected_label);
|
||||
function genHash(string) {
|
||||
var hash = 0;
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
var c = string.charCodeAt(i);
|
||||
hash = (c + hash * 31) | 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
window.addEvent('load', function () {
|
||||
|
@ -90,6 +95,14 @@ window.addEvent('load', function () {
|
|||
resizeLimit : [100, 300]
|
||||
});
|
||||
|
||||
setLabelFilter = function( hash ) {
|
||||
selected_label = hash;
|
||||
localStorage.setItem('selected_label', selected_label);
|
||||
updateLabelList();
|
||||
if (typeof myTable.table != 'undefined')
|
||||
updateMainData();
|
||||
};
|
||||
|
||||
setFilter = function (f) {
|
||||
// Visually Select the right filter
|
||||
$("all_filter").removeClass("selectedFilter");
|
||||
|
@ -148,6 +161,97 @@ window.addEvent('load', function () {
|
|||
var syncMainDataLastResponseId = 0;
|
||||
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 || label.length === 0 ) {
|
||||
removeTorrentFromLabelList( torrent['hash'] );
|
||||
return false;
|
||||
}
|
||||
|
||||
var labelHash = genHash( label );
|
||||
if( label_list[labelHash] == null ) {
|
||||
console.log( "addTorrentToLabelList: warning, label not found. label=", label, " label_list=", label_list );
|
||||
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();">QBT_TR(New...)QBT_TR</a>'}));
|
||||
labelList.appendChild(new Element('li', {html: '<a href="javascript:resetLabelFN();">QBT_TR(Reset)QBT_TR</a>'}));
|
||||
|
||||
var first = true;
|
||||
Object.each(label_list, function (label) {
|
||||
var labelHash = genHash( label.name );
|
||||
var el = new Element('li', {html: '<a href="javascript:updateLabelFN(\'' + labelHash + '\');">' + label.name + '</a>'});
|
||||
if (first) {
|
||||
el.removeClass();
|
||||
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="images/oxygen/folder-documents.png"/>' +
|
||||
text + '(' + count + ')' + '</a>';
|
||||
|
||||
return new Element( 'li', { id: hash, html: html } );
|
||||
};
|
||||
|
||||
var allLabels = 0;
|
||||
Object.each( label_list, function( label ) {
|
||||
allLabels += label.torrents.length;
|
||||
});
|
||||
|
||||
var unlabelled = myTable.getRowIds().length - allLabels;
|
||||
labelList.appendChild( create_link( LABELS_ALL, 'QBT_TR(All Labels)QBT_TR', allLabels ) );
|
||||
labelList.appendChild( create_link( LABELS_UNLABELLED, 'QBT_TR(Unlabeled)QBT_TR', unlabelled ) );
|
||||
|
||||
Object.each( label_list, function( label ) {
|
||||
var labelHash = genHash( label.name );
|
||||
labelList.appendChild( create_link( labelHash, label.name, label.torrents.length ) );
|
||||
} );
|
||||
|
||||
var childrens = labelList.childNodes;
|
||||
for (var i in childrens) {
|
||||
if( childrens[i].id == selected_label ) {
|
||||
childrens[i].className = "selectedFilter";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var syncMainDataTimer;
|
||||
var syncMainData = function () {
|
||||
var url = new URI('sync/maindata');
|
||||
|
@ -165,15 +269,26 @@ window.addEvent('load', function () {
|
|||
$('error_div').set('html', '');
|
||||
if (response) {
|
||||
var full_update = (response['full_update'] == true);
|
||||
if (full_update)
|
||||
if (full_update) {
|
||||
myTable.rows.erase();
|
||||
if (response['rid'])
|
||||
label_list = {};
|
||||
Object.each( response['labels'], function( label ) {
|
||||
var labelHash = genHash( label );
|
||||
label_list[ labelHash ] = { name: label, torrents: [] };
|
||||
} );
|
||||
}
|
||||
if (response['rid']) {
|
||||
syncMainDataLastResponseId = response['rid'];
|
||||
if (response['torrents'])
|
||||
}
|
||||
if (response['torrents']) {
|
||||
for (var key in response['torrents']) {
|
||||
response['torrents'][key]['hash'] = key;
|
||||
myTable.updateRowData(response['torrents'][key]);
|
||||
myTable.updateRowData( response['torrents'][key] );
|
||||
addTorrentToLabelList( response['torrents'][key] );
|
||||
}
|
||||
updateLabelList();
|
||||
updateContextMenu();
|
||||
}
|
||||
if (response['torrents_removed'])
|
||||
response['torrents_removed'].each(function (hash) {
|
||||
myTable.removeRow(hash);
|
||||
|
|
|
@ -60,6 +60,7 @@ var dynamicTable = new Class({
|
|||
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('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'].dataProperties[0] = 'state';
|
||||
|
@ -279,10 +280,13 @@ var dynamicTable = new Class({
|
|||
break;
|
||||
}
|
||||
|
||||
if (labelName == null)
|
||||
if (labelName == LABELS_ALL && row['full_data'].label.length > 0)
|
||||
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 true;
|
||||
|
|
|
@ -309,6 +309,60 @@ initializeWindows = function() {
|
|||
}
|
||||
};
|
||||
|
||||
newLabelFN = function () {
|
||||
var h = myTable.selectedIds();
|
||||
if (h.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'newLabelPage',
|
||||
title: "QBT_TR(Torrent Label)QBT_TR",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'newlabel.html?hashes=' + h.join(','),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0,
|
||||
width: 424,
|
||||
height: 150
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
resetLabelFN = function () {
|
||||
var h = myTable.selectedIds();
|
||||
var label_json = JSON.stringify({value: ''});
|
||||
if (h.length) {
|
||||
h.each(function (hash, index) {
|
||||
new Request({
|
||||
url: 'command/setLabel',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash,
|
||||
label_obj: label_json
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateLabelFN = function (label_hash) {
|
||||
var label = label_list[label_hash].name;
|
||||
var label_json = JSON.stringify({value: label});
|
||||
var h = myTable.selectedIds();
|
||||
if (h.length) {
|
||||
h.each(function (hash, index) {
|
||||
new Request({
|
||||
url: 'command/setLabel',
|
||||
method: 'post',
|
||||
data: {
|
||||
hash: hash,
|
||||
label_obj: label_json
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
['pauseAll', 'resumeAll'].each(function(item) {
|
||||
addClickEvent(item, function(e) {
|
||||
new Event(e).stop();
|
||||
|
|
Loading…
Reference in a new issue