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)
bool TorrentHandle::setCategory(const QString &category) bool TorrentHandle::setCategory(const QString &category)
{ {
if (m_category != category) { if (m_category != category) {
if (!category.isEmpty()) { if (!category.isEmpty() && !m_session->categories().contains(category))
if (!Session::isValidCategoryName(category)) return false; return false;
if (!m_session->categories().contains(category))
if (!m_session->addCategory(category))
return false;
}
QString oldCategory = m_category; QString oldCategory = m_category;
m_category = category; m_category = category;

14
src/webui/api/synccontroller.cpp

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

27
src/webui/api/torrentscontroller.cpp

@ -738,7 +738,7 @@ void TorrentsController::setLocationAction()
const QString newLocation {params()["location"].trimmed()}; const QString newLocation {params()["location"].trimmed()};
if (newLocation.isEmpty()) 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 // try to create the location if it does not exist
if (!QDir(newLocation).mkpath(".")) if (!QDir(newLocation).mkpath("."))
@ -809,6 +809,7 @@ void TorrentsController::setCategoryAction()
const QStringList hashes {params()["hashes"].split('|')}; const QStringList hashes {params()["hashes"].split('|')};
const QString category {params()["category"].trimmed()}; const QString category {params()["category"].trimmed()};
applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent) applyToTorrents(hashes, [category](BitTorrent::TorrentHandle *torrent)
{ {
if (!torrent->setCategory(category)) if (!torrent->setCategory(category))
@ -821,10 +822,30 @@ void TorrentsController::createCategoryAction()
checkParams({"category"}); checkParams({"category"});
const QString category {params()["category"].trimmed()}; 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")); 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() void TorrentsController::removeCategoriesAction()

1
src/webui/api/torrentscontroller.h

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

3
src/webui/extra_translations.h

@ -78,7 +78,8 @@ const char *QBT_WEBUI_TRANSLATIONS[] = {
QT_TRANSLATE_NOOP("HttpServer", "Set location"), QT_TRANSLATE_NOOP("HttpServer", "Set location"),
QT_TRANSLATE_NOOP("HttpServer", "Limit upload rate"), QT_TRANSLATE_NOOP("HttpServer", "Limit upload rate"),
QT_TRANSLATE_NOOP("HttpServer", "Limit download 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[] = { const struct { const char *source; const char *comment; } QBT_WEBUI_COMMENTED_TRANSLATIONS[] = {

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

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

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

@ -143,6 +143,7 @@
</ul> </ul>
<ul id="categoriesFilterMenu" class="contextMenu"> <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="#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="#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><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> <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 @@
<link rel="stylesheet" href="css/style.css" type="text/css" /> <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-core-yc.js"></script>
<script src="scripts/lib/mootools-1.2-more.js"></script> <script src="scripts/lib/mootools-1.2-more.js"></script>
<script src="scripts/misc.js"></script>
<script> <script>
var newCategoryKeyboardEvents = new Keyboard({ var newCategoryKeyboardEvents = new Keyboard({
defaultEventType: 'keydown', defaultEventType: 'keydown',
events: { events: {
'enter': function(event) { 'enter': function(event) {
$('newCategoryButton').click(); $('categoryNameButton').click();
event.preventDefault(); event.preventDefault();
} }
} }
@ -20,42 +21,99 @@
newCategoryKeyboardEvents.activate(); newCategoryKeyboardEvents.activate();
window.addEvent('domready', function() { window.addEvent('domready', function() {
$('newCategory').focus(); var uriAction = safeTrim(new URI().getData('action'));
$('newCategoryButton').addEvent('click', function(e) { var uriHashes = safeTrim(new URI().getData('hashes'));
new Event(e).stop(); var uriCategoryName = safeTrim(new URI().getData('categoryName'));
// check field var uriSavePath = safeTrim(new URI().getData('savePath'));
var categoryName = $('newCategory').value.trim();
if (categoryName == null || categoryName == "") if (uriAction === "create") {
return false; $('categoryName').focus();
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]"); else if (uriAction === "edit") {
if (!uriCategoryName)
return false; return false;
}
var hashesList = new URI().getData('hashes'); $('categoryName').set('disabled', true);
if (!hashesList) { $('categoryName').set('value', escapeHtml(uriCategoryName));
new Request({ $('savePath').set('value', escapeHtml(uriSavePath));
url: 'api/v2/torrents/createCategory', $('savePath').focus();
method: 'post', }
data: {
category: categoryName $('categoryNameButton').addEvent('click', function(e) {
}, new Event(e).stop();
onComplete: function() {
window.parent.closeWindows(); var savePath = $('savePath').value.trim();
} var categoryName = $('categoryName').value.trim();
}).send();
} var verifyCategoryName = function(name) {
else { if ((name === null) || (name === ""))
new Request({ return false;
url: 'api/v2/torrents/setCategory', if (name.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) {
method: 'post', alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR[CONTEXT=HttpServer]");
data: { return false;
hashes: hashesList, }
category: categoryName return true;
}, };
onComplete: function() {
window.parent.closeWindows(); switch (uriAction) {
} case "set":
}).send(); 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 @@
<body> <body>
<div style="padding: 10px 10px 0px 10px;"> <div style="padding: 10px 10px 0px 10px;">
<p style="font-weight: bold;">QBT_TR(Category)QBT_TR[CONTEXT=TransferListWidget]:</p> <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;"> <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>
</div> </div>
</body> </body>

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

@ -327,13 +327,21 @@ window.addEvent('load', function() {
syncMainDataLastResponseId = response['rid']; syncMainDataLastResponseId = response['rid'];
} }
if (response['categories']) { if (response['categories']) {
response['categories'].each(function(category) { for (var key in response['categories']) {
var categoryHash = genHash(category); var category = response['categories'][key];
category_list[categoryHash] = { var categoryHash = genHash(key);
name: category, if (category_list[categoryHash] !== undefined) {
torrents: [] // 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; update_categories = true;
} }
if (response['categories_removed']) { if (response['categories_removed']) {

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

@ -387,9 +387,13 @@ var CategoriesFilterContextMenu = new Class({
Extends: ContextMenu, Extends: ContextMenu,
updateMenuItems: function() { updateMenuItems: function() {
var id = this.options.element.id; 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'); this.showItem('DeleteCategory');
else }
else {
this.hideItem('EditCategory');
this.hideItem('DeleteCategory'); this.hideItem('DeleteCategory');
}
} }
}); });

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

@ -120,3 +120,14 @@ function escapeHtml(str) {
div.appendChild(document.createTextNode(str)); div.appendChild(document.createTextNode(str));
return div.innerHTML; 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() {
}; };
torrentNewCategoryFN = function() { torrentNewCategoryFN = function() {
var action = "set";
var hashes = torrentsTable.selectedRowsIds(); var hashes = torrentsTable.selectedRowsIds();
if (hashes.length) { if (hashes.length) {
new MochaUI.Window({ new MochaUI.Window({
id: 'newCategoryPage', id: 'newCategoryPage',
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]", title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
loadMethod: 'iframe', loadMethod: 'iframe',
contentURL: 'newcategory.html?hashes=' + hashes.join('|'), contentURL: 'newcategory.html?action=' + action + '&hashes=' + hashes.join('|'),
scrollbars: false, scrollbars: false,
resizable: false, resizable: false,
maximizable: false, maximizable: false,
paddingVertical: 0, paddingVertical: 0,
paddingHorizontal: 0, paddingHorizontal: 0,
width: 250, width: 250,
height: 100 height: 150
}); });
} }
}; };
@ -494,18 +495,39 @@ initializeWindows = function() {
}; };
createCategoryFN = function() { createCategoryFN = function() {
var action = "create";
new MochaUI.Window({ new MochaUI.Window({
id: 'newCategoryPage', id: 'newCategoryPage',
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]", title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
loadMethod: 'iframe', loadMethod: 'iframe',
contentURL: 'newcategory.html', contentURL: 'newcategory.html?action=' + action,
scrollbars: false, scrollbars: false,
resizable: false, resizable: false,
maximizable: false, maximizable: false,
paddingVertical: 0, paddingVertical: 0,
paddingHorizontal: 0, paddingHorizontal: 0,
width: 250, 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(); updateMainData();
}; };

Loading…
Cancel
Save