Browse Source

Merge pull request #9228 from Piccirello/addCategory

Add save path and category editing to WebUI
adaptive-webui-19844
Vladimir Golovnev 6 years ago committed by GitHub
parent
commit
28a6ac3197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      src/base/bittorrent/torrenthandle.cpp
  2. 14
      src/webui/api/synccontroller.cpp
  3. 27
      src/webui/api/torrentscontroller.cpp
  4. 1
      src/webui/api/torrentscontroller.h
  5. 3
      src/webui/extra_translations.h
  6. 3
      src/webui/www/private/filters.html
  7. 1
      src/webui/www/private/index.html
  8. 136
      src/webui/www/private/newcategory.html
  9. 22
      src/webui/www/private/scripts/client.js
  10. 8
      src/webui/www/private/scripts/contextmenu.js
  11. 11
      src/webui/www/private/scripts/misc.js
  12. 30
      src/webui/www/private/scripts/mocha-init.js

8
src/base/bittorrent/torrenthandle.cpp

@ -1210,12 +1210,8 @@ void TorrentHandle::setName(const QString &name) @@ -1210,12 +1210,8 @@ void TorrentHandle::setName(const QString &name)
bool TorrentHandle::setCategory(const QString &category)
{
if (m_category != category) {
if (!category.isEmpty()) {
if (!Session::isValidCategoryName(category)) return false;
if (!m_session->categories().contains(category))
if (!m_session->addCategory(category))
return false;
}
if (!category.isEmpty() && !m_session->categories().contains(category))
return false;
QString oldCategory = m_category;
m_category = category;

14
src/webui/api/synccontroller.cpp

@ -320,7 +320,7 @@ namespace @@ -320,7 +320,7 @@ namespace
// - "full_update": full data update flag
// - "torrents": dictionary contains information about torrents.
// - "torrents_removed": a list of hashes of removed torrents
// - "categories": list of categories
// - "categories": map of categories info
// - "categories_removed": list of removed categories
// - "server_state": map contains information about the state of the server
// The keys of the 'torrents' dictionary are hashes of torrents.
@ -399,9 +399,15 @@ void SyncController::maindataAction() @@ -399,9 +399,15 @@ void SyncController::maindataAction()
data["torrents"] = torrents;
QVariantList categories;
for (auto i = session->categories().cbegin(); i != session->categories().cend(); ++i)
categories << i.key();
QVariantHash categories;
const auto categoriesList = session->categories();
for (auto it = categoriesList.cbegin(); it != categoriesList.cend(); ++it) {
const auto key = it.key();
categories[key] = QVariantMap {
{"name", key},
{"savePath", it.value()}
};
}
data["categories"] = categories;

27
src/webui/api/torrentscontroller.cpp

@ -738,7 +738,7 @@ void TorrentsController::setLocationAction() @@ -738,7 +738,7 @@ void TorrentsController::setLocationAction()
const QString newLocation {params()["location"].trimmed()};
if (newLocation.isEmpty())
throw APIError(APIErrorType::BadParams, tr("Save path is empty"));
throw APIError(APIErrorType::BadParams, tr("Save path cannot be empty"));
// try to create the location if it does not exist
if (!QDir(newLocation).mkpath("."))
@ -809,6 +809,7 @@ void TorrentsController::setCategoryAction() @@ -809,6 +809,7 @@ void TorrentsController::setCategoryAction()
const QStringList hashes {params()["hashes"].split('|')};
const QString category {params()["category"].trimmed()};
applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent)
{
if (!torrent->setCategory(category))
@ -821,10 +822,30 @@ void TorrentsController::createCategoryAction() @@ -821,10 +822,30 @@ void TorrentsController::createCategoryAction()
checkParams({"category"});
const QString category {params()["category"].trimmed()};
if (!BitTorrent::Session::isValidCategoryName(category) && !category.isEmpty())
const QString savePath {params()["savePath"]};
if (category.isEmpty())
throw APIError(APIErrorType::BadParams, tr("Category cannot be empty"));
if (!BitTorrent::Session::isValidCategoryName(category))
throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
BitTorrent::Session::instance()->addCategory(category);
if (!BitTorrent::Session::instance()->addCategory(category, savePath))
throw APIError(APIErrorType::Conflict, tr("Unable to create category"));
}
void TorrentsController::editCategoryAction()
{
checkParams({"category", "savePath"});
const QString category {params()["category"].trimmed()};
const QString savePath {params()["savePath"]};
if (category.isEmpty())
throw APIError(APIErrorType::BadParams, tr("Category cannot be empty"));
if (!BitTorrent::Session::instance()->editCategory(category, savePath))
throw APIError(APIErrorType::Conflict, tr("Unable to edit category"));
}
void TorrentsController::removeCategoriesAction()

1
src/webui/api/torrentscontroller.h

@ -53,6 +53,7 @@ private slots: @@ -53,6 +53,7 @@ private slots:
void renameAction();
void setCategoryAction();
void createCategoryAction();
void editCategoryAction();
void removeCategoriesAction();
void addAction();
void deleteAction();

3
src/webui/extra_translations.h

@ -78,7 +78,8 @@ const char *QBT_WEBUI_TRANSLATIONS[] = { @@ -78,7 +78,8 @@ const char *QBT_WEBUI_TRANSLATIONS[] = {
QT_TRANSLATE_NOOP("HttpServer", "Set location"),
QT_TRANSLATE_NOOP("HttpServer", "Limit upload rate"),
QT_TRANSLATE_NOOP("HttpServer", "Limit download rate"),
QT_TRANSLATE_NOOP("HttpServer", "Rename torrent")
QT_TRANSLATE_NOOP("HttpServer", "Rename torrent"),
QT_TRANSLATE_NOOP("HttpServer", "Unable to create category")
};
const struct { const char *source; const char *comment; } QBT_WEBUI_COMMENTED_TRANSLATIONS[] = {

3
src/webui/www/private/filters.html

@ -30,6 +30,9 @@ @@ -30,6 +30,9 @@
CreateCategory: function(element, ref) {
createCategoryFN();
},
EditCategory: function(element, ref) {
editCategoryFN(element.id);
},
DeleteCategory: function(element, ref) {
removeCategoryFN(element.id);
},

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

@ -143,6 +143,7 @@ @@ -143,6 +143,7 @@
</ul>
<ul id="categoriesFilterMenu" class="contextMenu">
<li><a href="#CreateCategory"><img src="images/qbt-theme/list-add.svg" alt="QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Add category...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#EditCategory"><img src="images/qbt-theme/document-edit.svg" alt="QBT_TR(Edit category...)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Edit category...)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#DeleteCategory"><img src="images/qbt-theme/list-remove.svg" alt="QBT_TR(Remove category)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Remove category)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li><a href="#DeleteUnusedCategories"><img src="images/qbt-theme/list-remove.svg" alt="QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Remove unused categories)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>
<li class="separator"><a href="#StartTorrentsByCategory"><img src="images/qbt-theme/media-playback-start.svg" alt="QBT_TR(Resume torrents)QBT_TR[CONTEXT=CategoryFilterWidget]"/> QBT_TR(Resume torrents)QBT_TR[CONTEXT=CategoryFilterWidget]</a></li>

136
src/webui/www/private/newcategory.html

@ -7,12 +7,13 @@ @@ -7,12 +7,13 @@
<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 src="scripts/misc.js"></script>
<script>
var newCategoryKeyboardEvents = new Keyboard({
defaultEventType: 'keydown',
events: {
'enter': function(event) {
$('newCategoryButton').click();
$('categoryNameButton').click();
event.preventDefault();
}
}
@ -20,42 +21,99 @@ @@ -20,42 +21,99 @@
newCategoryKeyboardEvents.activate();
window.addEvent('domready', function() {
$('newCategory').focus();
$('newCategoryButton').addEvent('click', function(e) {
new Event(e).stop();
// check field
var categoryName = $('newCategory').value.trim();
if (categoryName == null || categoryName == "")
return false;
if (categoryName.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) {
alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR[CONTEXT=HttpServer]");
var uriAction = safeTrim(new URI().getData('action'));
var uriHashes = safeTrim(new URI().getData('hashes'));
var uriCategoryName = safeTrim(new URI().getData('categoryName'));
var uriSavePath = safeTrim(new URI().getData('savePath'));
if (uriAction === "create") {
$('categoryName').focus();
}
else if (uriAction === "edit") {
if (!uriCategoryName)
return false;
}
var hashesList = new URI().getData('hashes');
if (!hashesList) {
new Request({
url: 'api/v2/torrents/createCategory',
method: 'post',
data: {
category: categoryName
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
}
else {
new Request({
url: 'api/v2/torrents/setCategory',
method: 'post',
data: {
hashes: hashesList,
category: categoryName
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
$('categoryName').set('disabled', true);
$('categoryName').set('value', escapeHtml(uriCategoryName));
$('savePath').set('value', escapeHtml(uriSavePath));
$('savePath').focus();
}
$('categoryNameButton').addEvent('click', function(e) {
new Event(e).stop();
var savePath = $('savePath').value.trim();
var categoryName = $('categoryName').value.trim();
var verifyCategoryName = function(name) {
if ((name === null) || (name === ""))
return false;
if (name.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) {
alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR[CONTEXT=HttpServer]");
return false;
}
return true;
};
switch (uriAction) {
case "set":
if ((uriHashes === "") || !verifyCategoryName(categoryName))
return;
new Request({
url: 'api/v2/torrents/createCategory',
method: 'post',
data: {
category: categoryName,
savePath: savePath
},
onSuccess: function() {
new Request({
url: 'api/v2/torrents/setCategory',
method: 'post',
data: {
hashes: uriHashes,
category: categoryName
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
},
onError: function() {
alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + escapeHtml(categoryName));
}
}).send();
break;
case "create":
if (!verifyCategoryName(categoryName))
return;
new Request({
url: 'api/v2/torrents/createCategory',
method: 'post',
data: {
category: categoryName,
savePath: savePath
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
break;
case "edit":
new Request({
url: 'api/v2/torrents/editCategory',
method: 'post',
data: {
category: uriCategoryName, // category name can't be changed
savePath: savePath
},
onComplete: function() {
window.parent.closeWindows();
}
}).send();
break;
}
});
});
@ -65,9 +123,11 @@ @@ -65,9 +123,11 @@
<body>
<div style="padding: 10px 10px 0px 10px;">
<p style="font-weight: bold;">QBT_TR(Category)QBT_TR[CONTEXT=TransferListWidget]:</p>
<input type="text" id="newCategory" value="" maxlength="100" style="width: 220px;" />
<input type="text" id="categoryName" value="" maxlength="100" style="width: 220px;" />
<p style="font-weight: bold;">QBT_TR(Save path)QBT_TR[CONTEXT=TransferListWidget]:</p>
<input type="text" id="savePath" value="" style="width: 220px;" />
<div style="text-align: center; padding-top: 10px;">
<input type="button" value="QBT_TR(Add)QBT_TR[CONTEXT=HttpServer]" id="newCategoryButton" />
<input type="button" value="QBT_TR(Add)QBT_TR[CONTEXT=HttpServer]" id="categoryNameButton" />
</div>
</div>
</body>

22
src/webui/www/private/scripts/client.js

@ -327,13 +327,21 @@ window.addEvent('load', function() { @@ -327,13 +327,21 @@ window.addEvent('load', function() {
syncMainDataLastResponseId = response['rid'];
}
if (response['categories']) {
response['categories'].each(function(category) {
var categoryHash = genHash(category);
category_list[categoryHash] = {
name: category,
torrents: []
};
});
for (var key in response['categories']) {
var category = response['categories'][key];
var categoryHash = genHash(key);
if (category_list[categoryHash] !== undefined) {
// only the save path can change for existing categories
category_list[categoryHash].savePath = category.savePath;
}
else {
category_list[categoryHash] = {
name: category.name,
savePath: category.savePath,
torrents: []
};
}
}
update_categories = true;
}
if (response['categories_removed']) {

8
src/webui/www/private/scripts/contextmenu.js

@ -387,9 +387,13 @@ var CategoriesFilterContextMenu = new Class({ @@ -387,9 +387,13 @@ var CategoriesFilterContextMenu = new Class({
Extends: ContextMenu,
updateMenuItems: function() {
var id = this.options.element.id;
if (id != CATEGORIES_ALL && id != CATEGORIES_UNCATEGORIZED)
if ((id != CATEGORIES_ALL) && (id != CATEGORIES_UNCATEGORIZED)) {
this.showItem('EditCategory');
this.showItem('DeleteCategory');
else
}
else {
this.hideItem('EditCategory');
this.hideItem('DeleteCategory');
}
}
});

11
src/webui/www/private/scripts/misc.js

@ -120,3 +120,14 @@ function escapeHtml(str) { @@ -120,3 +120,14 @@ function escapeHtml(str) {
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
function safeTrim(value) {
try {
return value.trim();
}
catch (e) {
if (e instanceof TypeError)
return "";
throw e;
}
}

30
src/webui/www/private/scripts/mocha-init.js

@ -458,20 +458,21 @@ initializeWindows = function() { @@ -458,20 +458,21 @@ initializeWindows = function() {
};
torrentNewCategoryFN = function() {
var action = "set";
var hashes = torrentsTable.selectedRowsIds();
if (hashes.length) {
new MochaUI.Window({
id: 'newCategoryPage',
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
loadMethod: 'iframe',
contentURL: 'newcategory.html?hashes=' + hashes.join('|'),
contentURL: 'newcategory.html?action=' + action + '&hashes=' + hashes.join('|'),
scrollbars: false,
resizable: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
height: 100
height: 150
});
}
};
@ -494,18 +495,39 @@ initializeWindows = function() { @@ -494,18 +495,39 @@ initializeWindows = function() {
};
createCategoryFN = function() {
var action = "create";
new MochaUI.Window({
id: 'newCategoryPage',
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
loadMethod: 'iframe',
contentURL: 'newcategory.html',
contentURL: 'newcategory.html?action=' + action,
scrollbars: false,
resizable: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
height: 100
height: 150
});
updateMainData();
};
editCategoryFN = function(categoryHash) {
var action = "edit";
var categoryName = category_list[categoryHash].name;
var savePath = category_list[categoryHash].savePath;
new MochaUI.Window({
id: 'editCategoryPage',
title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
loadMethod: 'iframe',
contentURL: 'newcategory.html?action=' + action + '&categoryName=' + categoryName + '&savePath=' + savePath,
scrollbars: false,
resizable: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: 250,
height: 150
});
updateMainData();
};

Loading…
Cancel
Save