Browse Source

Add save path and editing to WebUI new category dialog

adaptive-webui-19844
Thomas Piccirello 6 years ago
parent
commit
63c53766b4
  1. 4
      src/base/bittorrent/torrenthandle.cpp
  2. 13
      src/webui/api/synccontroller.cpp
  3. 31
      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. 90
      src/webui/www/private/newcategory.html
  9. 15
      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

4
src/base/bittorrent/torrenthandle.cpp

@ -1213,10 +1213,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) || !m_session->categories().contains(category))
return false; return false;
}
QString oldCategory = m_category; QString oldCategory = m_category;
m_category = category; m_category = category;

13
src/webui/api/synccontroller.cpp

@ -399,16 +399,17 @@ void SyncController::maindataAction()
data["torrents"] = torrents; data["torrents"] = torrents;
QVariantList categoriesList; QVariantHash categories;
const auto categories = session->categories(); const auto categoriesList = session->categories();
for (auto key : categories.keys()) { for (auto it = categoriesList.cbegin(); it != categoriesList.cend(); ++it) {
categoriesList << QVariantMap { const auto key = it.key();
categories[key] = QVariantMap {
{"name", key}, {"name", key},
{"savePath", categories.value(key)}, {"savePath", it.value()}
}; };
} }
data["categories"] = categoriesList; data["categories"] = categories;
QVariantMap serverState = getTranserInfo(); QVariantMap serverState = getTranserInfo();
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled(); serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();

31
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,12 +809,9 @@ 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)
{ {
auto *session = BitTorrent::Session::instance();
const QStringList categories = session->categories().keys();
if (!categories.contains(category) && !session->addCategory(category))
throw APIError(APIErrorType::Conflict, tr("Unable to create category"));
if (!torrent->setCategory(category)) if (!torrent->setCategory(category))
throw APIError(APIErrorType::Conflict, tr("Incorrect category name")); throw APIError(APIErrorType::Conflict, tr("Incorrect category name"));
}); });
@ -825,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

@ -23,6 +23,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>

90
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'));
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;
$('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(); new Event(e).stop();
// check field
var categoryName = $('newCategory').value.trim(); var savePath = $('savePath').value.trim();
if (categoryName == null || categoryName == "") var categoryName = $('categoryName').value.trim();
var verifyCategoryName = function(name) {
if ((name === null) || (name === ""))
return false; return false;
if (categoryName.match("^([^\\\\\\/]|[^\\\\\\/]([^\\\\\\/]|\\/(?=[^\\/]))*[^\\\\\\/])$") === null) { 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]"); alert("QBT_TR(Invalid category name:\nPlease do not use any special characters in the category name.)QBT_TR[CONTEXT=HttpServer]");
return false; return false;
} }
var hashesList = new URI().getData('hashes'); return true;
if (!hashesList) { };
switch (uriAction) {
case "set":
if ((uriHashes === "") || !verifyCategoryName(categoryName))
return;
new Request({ new Request({
url: 'api/v2/torrents/createCategory', url: 'api/v2/torrents/createCategory',
method: 'post', method: 'post',
data: { data: {
category: categoryName,
savePath: savePath
},
onSuccess: function() {
new Request({
url: 'api/v2/torrents/setCategory',
method: 'post',
data: {
hashes: uriHashes,
category: categoryName category: categoryName
}, },
onComplete: function() { onComplete: function() {
window.parent.closeWindows(); window.parent.closeWindows();
} }
}).send(); }).send();
},
onError: function() {
alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + escapeHtml(categoryName));
} }
else { }).send();
break;
case "create":
if (!verifyCategoryName(categoryName))
return;
new Request({ new Request({
url: 'api/v2/torrents/setCategory', url: 'api/v2/torrents/createCategory',
method: 'post', method: 'post',
data: { data: {
hashes: hashesList, category: categoryName,
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() { onComplete: function() {
window.parent.closeWindows(); window.parent.closeWindows();
} }
}).send(); }).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>

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

@ -316,19 +316,26 @@ 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.name); 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] = { category_list[categoryHash] = {
name: category.name, name: category.name,
savePath: category.savePath, savePath: category.savePath,
torrents: [] torrents: []
}; };
}); }
}
update_categories = true; update_categories = true;
} }
if (response['categories_removed']) { if (response['categories_removed']) {
response['categories_removed'].each(function(category) { response['categories_removed'].each(function(category) {
var categoryHash = genHash(category.name); var categoryHash = genHash(category);
delete category_list[categoryHash]; delete category_list[categoryHash];
}); });
update_categories = true; update_categories = true;

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