WebUI: Remove unnecessary hashing

Now the containers support using string as key so the intermediate hashing/mapping to number
isn't needed now.
This commit is contained in:
Chocobo1 2024-12-31 22:16:48 +08:00
commit 73e9116d21
No known key found for this signature in database
GPG key ID: 210D9C873253A68C
6 changed files with 255 additions and 299 deletions

View file

@ -139,31 +139,33 @@ const useAutoHideZeroStatusFilters = LocalPreferences.get("hide_zero_status_filt
const displayFullURLTrackerColumn = LocalPreferences.get("full_url_tracker_column", "false") === "true"; const displayFullURLTrackerColumn = LocalPreferences.get("full_url_tracker_column", "false") === "true";
/* Categories filter */ /* Categories filter */
const CATEGORIES_ALL = 1; const CATEGORIES_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655";
const CATEGORIES_UNCATEGORIZED = 2; const CATEGORIES_UNCATEGORIZED = "e24bd469-ea22-404c-8e2e-a17c82f37ea0";
const category_list = new Map(); // Map<category: String, {savePath: String, torrents: Set}>
const categoryMap = new Map();
let selectedCategory = Number(LocalPreferences.get("selected_category", CATEGORIES_ALL)); let selectedCategory = LocalPreferences.get("selected_category", CATEGORIES_ALL);
let setCategoryFilter = () => {}; let setCategoryFilter = () => {};
/* Tags filter */ /* Tags filter */
const TAGS_ALL = 1; const TAGS_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655";
const TAGS_UNTAGGED = 2; const TAGS_UNTAGGED = "e24bd469-ea22-404c-8e2e-a17c82f37ea0";
const tagList = new Map(); // Map<tag: String, torrents: Set>
const tagMap = new Map();
let selectedTag = Number(LocalPreferences.get("selected_tag", TAGS_ALL)); let selectedTag = LocalPreferences.get("selected_tag", TAGS_ALL);
let setTagFilter = () => {}; let setTagFilter = () => {};
/* Trackers filter */ /* Trackers filter */
const TRACKERS_ALL = 1; const TRACKERS_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655";
const TRACKERS_TRACKERLESS = 2; const TRACKERS_TRACKERLESS = "e24bd469-ea22-404c-8e2e-a17c82f37ea0";
/** @type Map<number, {host: string, trackerTorrentMap: Map<string, string[]>}> **/ // Map<trackerHost: String, Map<trackerURL: String, torrents: Set>>
const trackerList = new Map(); const trackerMap = new Map();
let selectedTracker = Number(LocalPreferences.get("selected_tracker", TRACKERS_ALL)); let selectedTracker = LocalPreferences.get("selected_tracker", TRACKERS_ALL);
let setTrackerFilter = () => {}; let setTrackerFilter = () => {};
/* All filters */ /* All filters */
@ -272,11 +274,11 @@ window.addEventListener("DOMContentLoaded", () => {
handleFilterSelectionChange(currentHash, newHash); handleFilterSelectionChange(currentHash, newHash);
}; };
setCategoryFilter = (hash) => { setCategoryFilter = (category) => {
const currentHash = torrentsTable.getCurrentTorrentID(); const currentHash = torrentsTable.getCurrentTorrentID();
LocalPreferences.set("selected_category", hash); LocalPreferences.set("selected_category", category);
selectedCategory = Number(hash); selectedCategory = category;
highlightSelectedCategory(); highlightSelectedCategory();
updateMainData(); updateMainData();
@ -284,11 +286,11 @@ window.addEventListener("DOMContentLoaded", () => {
handleFilterSelectionChange(currentHash, newHash); handleFilterSelectionChange(currentHash, newHash);
}; };
setTagFilter = (hash) => { setTagFilter = (tag) => {
const currentHash = torrentsTable.getCurrentTorrentID(); const currentHash = torrentsTable.getCurrentTorrentID();
LocalPreferences.set("selected_tag", hash); LocalPreferences.set("selected_tag", tag);
selectedTag = Number(hash); selectedTag = tag;
highlightSelectedTag(); highlightSelectedTag();
updateMainData(); updateMainData();
@ -296,11 +298,11 @@ window.addEventListener("DOMContentLoaded", () => {
handleFilterSelectionChange(currentHash, newHash); handleFilterSelectionChange(currentHash, newHash);
}; };
setTrackerFilter = (hash) => { setTrackerFilter = (tracker) => {
const currentHash = torrentsTable.getCurrentTorrentID(); const currentHash = torrentsTable.getCurrentTorrentID();
LocalPreferences.set("selected_tracker", hash); LocalPreferences.set("selected_tracker", tracker);
selectedTracker = Number(hash); selectedTracker = tracker;
highlightSelectedTracker(); highlightSelectedTracker();
updateMainData(); updateMainData();
@ -374,21 +376,18 @@ window.addEventListener("DOMContentLoaded", () => {
const serverState = {}; const serverState = {};
const removeTorrentFromCategoryList = (hash) => { const removeTorrentFromCategoryList = (hash) => {
if (!hash) if (hash === undefined)
return false; return false;
let removed = false; let removed = false;
category_list.forEach((category) => { for (const data of categoryMap.values())
const deleteResult = category.torrents.delete(hash); removed ||= data.torrents.delete(hash);
removed ||= deleteResult;
});
return removed; return removed;
}; };
const addTorrentToCategoryList = (torrent) => { const addTorrentToCategoryList = (torrent) => {
const category = torrent["category"]; const category = torrent["category"];
if (typeof category === "undefined") if (category === undefined)
return false; return false;
const hash = torrent["hash"]; const hash = torrent["hash"];
@ -397,33 +396,29 @@ window.addEventListener("DOMContentLoaded", () => {
return true; return true;
} }
const categoryHash = window.qBittorrent.Misc.genHash(category); let categoryData = categoryMap.get(category);
if (!category_list.has(categoryHash)) { // This should not happen if (categoryData === undefined) { // This should not happen
category_list.set(categoryHash, { categoryData = {
name: category,
torrents: new Set() torrents: new Set()
}); };
categoryMap.set(category, categoryData);
} }
const torrents = category_list.get(categoryHash).torrents; if (categoryData.torrents.has(hash))
if (!torrents.has(hash)) {
removeTorrentFromCategoryList(hash);
torrents.add(hash);
return true;
}
return false; return false;
removeTorrentFromCategoryList(hash);
categoryData.torrents.add(hash);
return true;
}; };
const removeTorrentFromTagList = (hash) => { const removeTorrentFromTagList = (hash) => {
if (!hash) if (hash === undefined)
return false; return false;
let removed = false; let removed = false;
tagList.forEach((tag) => { for (const torrents of tagMap.values())
const deleteResult = tag.torrents.delete(hash); removed ||= torrents.delete(hash);
removed ||= deleteResult;
});
return removed; return removed;
}; };
@ -439,21 +434,19 @@ window.addEventListener("DOMContentLoaded", () => {
const tags = torrent["tags"].split(", "); const tags = torrent["tags"].split(", ");
let added = false; let added = false;
for (let i = 0; i < tags.length; ++i) { for (const tag of tags) {
const tagHash = window.qBittorrent.Misc.genHash(tags[i].trim()); let torrents = tagMap.get(tag);
if (!tagList.has(tagHash)) { // This should not happen if (torrents === undefined) { // This should not happen
tagList.set(tagHash, { torrents = new Set();
name: tags, tagMap.set(tag, torrents);
torrents: new Set()
});
} }
const torrents = tagList.get(tagHash).torrents;
if (!torrents.has(hash)) { if (!torrents.has(hash)) {
torrents.add(hash); torrents.add(hash);
added = true; added = true;
} }
} }
return added; return added;
}; };
@ -500,13 +493,13 @@ window.addEventListener("DOMContentLoaded", () => {
const categoryItemTemplate = document.getElementById("categoryFilterItem"); const categoryItemTemplate = document.getElementById("categoryFilterItem");
const createCategoryLink = (hash, name, count) => { const createLink = (category, text, count) => {
const categoryFilterItem = categoryItemTemplate.content.cloneNode(true).firstElementChild; const categoryFilterItem = categoryItemTemplate.content.cloneNode(true).firstElementChild;
categoryFilterItem.id = hash; categoryFilterItem.id = category;
categoryFilterItem.classList.toggle("selectedFilter", hash === selectedCategory); categoryFilterItem.classList.toggle("selectedFilter", (category === selectedCategory));
const span = categoryFilterItem.firstElementChild; const span = categoryFilterItem.firstElementChild;
span.lastElementChild.textContent = `${name} (${count})`; span.lastElementChild.textContent = `${text} (${count})`;
return categoryFilterItem; return categoryFilterItem;
}; };
@ -516,7 +509,7 @@ window.addEventListener("DOMContentLoaded", () => {
while (stack.length > 0) { while (stack.length > 0) {
const { parent, category } = stack.pop(); const { parent, category } = stack.pop();
const displayName = category.nameSegments.at(-1); const displayName = category.nameSegments.at(-1);
const listItem = createCategoryLink(category.categoryHash, displayName, category.categoryCount); const listItem = createLink(category.categoryName, displayName, category.categoryCount);
listItem.firstElementChild.style.paddingLeft = `${(category.nameSegments.length - 1) * 20 + 6}px`; listItem.firstElementChild.style.paddingLeft = `${(category.nameSegments.length - 1) * 20 + 6}px`;
parent.appendChild(listItem); parent.appendChild(listItem);
@ -528,7 +521,7 @@ window.addEventListener("DOMContentLoaded", () => {
for (const subcategory of category.children.reverse()) for (const subcategory of category.children.reverse())
stack.push({ parent: unorderedList, category: subcategory }); stack.push({ parent: unorderedList, category: subcategory });
} }
const categoryLocalPref = `category_${category.categoryHash}_collapsed`; const categoryLocalPref = `category_${category.categoryName}_collapsed`;
const isCollapsed = !category.forceExpand && (LocalPreferences.get(categoryLocalPref, "false") === "true"); const isCollapsed = !category.forceExpand && (LocalPreferences.get(categoryLocalPref, "false") === "true");
LocalPreferences.set(categoryLocalPref, listItem.classList.toggle("collapsedCategory", isCollapsed).toString()); LocalPreferences.set(categoryLocalPref, listItem.classList.toggle("collapsedCategory", isCollapsed).toString());
} }
@ -541,17 +534,18 @@ window.addEventListener("DOMContentLoaded", () => {
} }
const sortedCategories = []; const sortedCategories = [];
category_list.forEach((category, hash) => sortedCategories.push({ for (const [category, categoryData] of categoryMap) {
categoryName: category.name, sortedCategories.push({
categoryHash: hash, categoryName: category,
categoryCount: category.torrents.size, categoryCount: categoryData.torrents.size,
nameSegments: category.name.split("/"), nameSegments: category.split("/"),
...(useSubcategories && { ...(useSubcategories && {
children: [], children: [],
parentID: null, isRoot: true,
forceExpand: LocalPreferences.get(`category_${hash}_collapsed`) === null forceExpand: LocalPreferences.get(`category_${category}_collapsed`) === null
}) })
})); });
}
sortedCategories.sort((left, right) => { sortedCategories.sort((left, right) => {
const leftSegments = left.nameSegments; const leftSegments = left.nameSegments;
const rightSegments = right.nameSegments; const rightSegments = right.nameSegments;
@ -567,8 +561,8 @@ window.addEventListener("DOMContentLoaded", () => {
}); });
const categoriesFragment = new DocumentFragment(); const categoriesFragment = new DocumentFragment();
categoriesFragment.appendChild(createCategoryLink(CATEGORIES_ALL, "QBT_TR(All)QBT_TR[CONTEXT=CategoryFilterModel]", torrentsTable.getRowSize())); categoriesFragment.appendChild(createLink(CATEGORIES_ALL, "QBT_TR(All)QBT_TR[CONTEXT=CategoryFilterModel]", torrentsTable.getRowSize()));
categoriesFragment.appendChild(createCategoryLink(CATEGORIES_UNCATEGORIZED, "QBT_TR(Uncategorized)QBT_TR[CONTEXT=CategoryFilterModel]", uncategorized)); categoriesFragment.appendChild(createLink(CATEGORIES_UNCATEGORIZED, "QBT_TR(Uncategorized)QBT_TR[CONTEXT=CategoryFilterModel]", uncategorized));
if (useSubcategories) { if (useSubcategories) {
categoryList.classList.add("subcategories"); categoryList.classList.add("subcategories");
@ -582,20 +576,20 @@ window.addEventListener("DOMContentLoaded", () => {
const isDirectSubcategory = (subcategory.nameSegments.length - category.nameSegments.length) === 1; const isDirectSubcategory = (subcategory.nameSegments.length - category.nameSegments.length) === 1;
if (isDirectSubcategory) { if (isDirectSubcategory) {
subcategory.parentID = category.categoryHash; subcategory.isRoot = false;
category.children.push(subcategory); category.children.push(subcategory);
} }
} }
} }
for (const category of sortedCategories) { for (const category of sortedCategories) {
if (category.parentID === null) if (category.isRoot)
createCategoryTree(category); createCategoryTree(category);
} }
} }
else { else {
categoryList.classList.remove("subcategories"); categoryList.classList.remove("subcategories");
for (const { categoryHash, categoryName, categoryCount } of sortedCategories) for (const { categoryName, categoryCount } of sortedCategories)
categoriesFragment.appendChild(createCategoryLink(categoryHash, categoryName, categoryCount)); categoriesFragment.appendChild(createLink(categoryName, categoryName, categoryCount));
} }
categoryList.appendChild(categoriesFragment); categoryList.appendChild(categoriesFragment);
@ -608,7 +602,7 @@ window.addEventListener("DOMContentLoaded", () => {
return; return;
for (const category of categoryList.getElementsByTagName("li")) for (const category of categoryList.getElementsByTagName("li"))
category.classList.toggle("selectedFilter", (Number(category.id) === selectedCategory)); category.classList.toggle("selectedFilter", (category.id === selectedCategory));
}; };
const updateTagList = () => { const updateTagList = () => {
@ -620,10 +614,10 @@ window.addEventListener("DOMContentLoaded", () => {
const tagItemTemplate = document.getElementById("tagFilterItem"); const tagItemTemplate = document.getElementById("tagFilterItem");
const createLink = (hash, text, count) => { const createLink = (tag, text, count) => {
const tagFilterItem = tagItemTemplate.content.cloneNode(true).firstElementChild; const tagFilterItem = tagItemTemplate.content.cloneNode(true).firstElementChild;
tagFilterItem.id = hash; tagFilterItem.id = tag;
tagFilterItem.classList.toggle("selectedFilter", hash === selectedTag); tagFilterItem.classList.toggle("selectedFilter", (tag === selectedTag));
const span = tagFilterItem.firstElementChild; const span = tagFilterItem.firstElementChild;
span.lastChild.textContent = `${text} (${count})`; span.lastChild.textContent = `${text} (${count})`;
@ -641,15 +635,16 @@ window.addEventListener("DOMContentLoaded", () => {
tagFilterList.appendChild(createLink(TAGS_UNTAGGED, "QBT_TR(Untagged)QBT_TR[CONTEXT=TagFilterModel]", untagged)); tagFilterList.appendChild(createLink(TAGS_UNTAGGED, "QBT_TR(Untagged)QBT_TR[CONTEXT=TagFilterModel]", untagged));
const sortedTags = []; const sortedTags = [];
tagList.forEach((tag, hash) => sortedTags.push({ for (const [tag, torrents] of tagMap) {
tagName: tag.name, sortedTags.push({
tagHash: hash, tagName: tag,
tagSize: tag.torrents.size tagSize: torrents.size
})); });
}
sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName)); sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName));
for (const { tagName, tagHash, tagSize } of sortedTags) for (const { tagName, tagSize } of sortedTags)
tagFilterList.appendChild(createLink(tagHash, tagName, tagSize)); tagFilterList.appendChild(createLink(tagName, tagName, tagSize));
window.qBittorrent.Filters.tagsFilterContextMenu.searchAndAddTargets(); window.qBittorrent.Filters.tagsFilterContextMenu.searchAndAddTargets();
}; };
@ -660,7 +655,7 @@ window.addEventListener("DOMContentLoaded", () => {
return; return;
for (const tag of tagFilterList.children) for (const tag of tagFilterList.children)
tag.classList.toggle("selectedFilter", (Number(tag.id) === selectedTag)); tag.classList.toggle("selectedFilter", (tag.id === selectedTag));
}; };
const updateTrackerList = () => { const updateTrackerList = () => {
@ -672,13 +667,13 @@ window.addEventListener("DOMContentLoaded", () => {
const trackerItemTemplate = document.getElementById("trackerFilterItem"); const trackerItemTemplate = document.getElementById("trackerFilterItem");
const createLink = (hash, text, count) => { const createLink = (host, text, count) => {
const trackerFilterItem = trackerItemTemplate.content.cloneNode(true).firstElementChild; const trackerFilterItem = trackerItemTemplate.content.cloneNode(true).firstElementChild;
trackerFilterItem.id = hash; trackerFilterItem.id = host;
trackerFilterItem.classList.toggle("selectedFilter", hash === selectedTracker); trackerFilterItem.classList.toggle("selectedFilter", (host === selectedTracker));
const span = trackerFilterItem.firstElementChild; const span = trackerFilterItem.firstElementChild;
span.lastChild.textContent = text.replace("%1", count); span.lastChild.textContent = `${text} (${count})`;
return trackerFilterItem; return trackerFilterItem;
}; };
@ -689,12 +684,12 @@ window.addEventListener("DOMContentLoaded", () => {
trackerlessTorrentsCount += 1; trackerlessTorrentsCount += 1;
} }
trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All (%1))QBT_TR[CONTEXT=TrackerFiltersList]", torrentsTable.getRowSize())); trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All)QBT_TR[CONTEXT=TrackerFiltersList]", torrentsTable.getRowSize()));
trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless (%1))QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount)); trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless)QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount));
// Sort trackers by hostname // Sort trackers by hostname
const sortedList = []; const sortedList = [];
trackerList.forEach(({ host, trackerTorrentMap }, hash) => { for (const [host, trackerTorrentMap] of trackerMap) {
const uniqueTorrents = new Set(); const uniqueTorrents = new Set();
for (const torrents of trackerTorrentMap.values()) { for (const torrents of trackerTorrentMap.values()) {
for (const torrent of torrents) for (const torrent of torrents)
@ -703,13 +698,12 @@ window.addEventListener("DOMContentLoaded", () => {
sortedList.push({ sortedList.push({
trackerHost: host, trackerHost: host,
trackerHash: hash,
trackerCount: uniqueTorrents.size, trackerCount: uniqueTorrents.size,
}); });
}); }
sortedList.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.trackerHost, right.trackerHost)); sortedList.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.trackerHost, right.trackerHost));
for (const { trackerHost, trackerHash, trackerCount } of sortedList) for (const { trackerHost, trackerCount } of sortedList)
trackerFilterList.appendChild(createLink(trackerHash, (trackerHost + " (%1)"), trackerCount)); trackerFilterList.appendChild(createLink(trackerHost, trackerHost, trackerCount));
window.qBittorrent.Filters.trackersFilterContextMenu.searchAndAddTargets(); window.qBittorrent.Filters.trackersFilterContextMenu.searchAndAddTargets();
}; };
@ -720,7 +714,7 @@ window.addEventListener("DOMContentLoaded", () => {
return; return;
for (const tracker of trackerFilterList.children) for (const tracker of trackerFilterList.children)
tracker.classList.toggle("selectedFilter", (Number(tracker.id) === selectedTracker)); tracker.classList.toggle("selectedFilter", (tracker.id === selectedTracker));
}; };
const statusSortOrder = Object.freeze({ const statusSortOrder = Object.freeze({
@ -768,7 +762,7 @@ window.addEventListener("DOMContentLoaded", () => {
let torrentsTableSelectedRows; let torrentsTableSelectedRows;
let updateStatuses = false; let updateStatuses = false;
let update_categories = false; let updateCategories = false;
let updateTags = false; let updateTags = false;
let updateTrackers = false; let updateTrackers = false;
let updateTorrents = false; let updateTorrents = false;
@ -776,76 +770,64 @@ window.addEventListener("DOMContentLoaded", () => {
if (fullUpdate) { if (fullUpdate) {
torrentsTableSelectedRows = torrentsTable.selectedRowsIds(); torrentsTableSelectedRows = torrentsTable.selectedRowsIds();
updateStatuses = true; updateStatuses = true;
update_categories = true; updateCategories = true;
updateTags = true; updateTags = true;
updateTrackers = true; updateTrackers = true;
updateTorrents = true; updateTorrents = true;
torrentsTable.clear(); torrentsTable.clear();
category_list.clear(); categoryMap.clear();
tagList.clear(); tagMap.clear();
trackerList.clear(); trackerMap.clear();
} }
if (responseJSON["rid"]) if (responseJSON["rid"])
syncMainDataLastResponseId = responseJSON["rid"]; syncMainDataLastResponseId = responseJSON["rid"];
if (responseJSON["categories"]) { if (responseJSON["categories"]) {
for (const key in responseJSON["categories"]) { for (const responseName in responseJSON["categories"]) {
if (!Object.hasOwn(responseJSON["categories"], key)) if (!Object.hasOwn(responseJSON["categories"], responseName))
continue; continue;
const responseCategory = responseJSON["categories"][key]; const responseData = responseJSON["categories"][responseName];
const categoryHash = window.qBittorrent.Misc.genHash(key); const categoryData = categoryMap.get(responseName);
const category = category_list.get(categoryHash); if (categoryData === undefined) {
if (category !== undefined) { categoryMap.set(responseName, {
// only the save path can change for existing categories savePath: responseData.savePath,
category.savePath = responseCategory.savePath;
}
else {
category_list.set(categoryHash, {
name: responseCategory.name,
savePath: responseCategory.savePath,
torrents: new Set() torrents: new Set()
}); });
} }
else {
// only the save path can change for existing categories
categoryData.savePath = responseData.savePath;
} }
update_categories = true; }
updateCategories = true;
} }
if (responseJSON["categories_removed"]) { if (responseJSON["categories_removed"]) {
responseJSON["categories_removed"].each((category) => { for (const category of responseJSON["categories_removed"])
const categoryHash = window.qBittorrent.Misc.genHash(category); categoryMap.delete(category);
category_list.delete(categoryHash); updateCategories = true;
});
update_categories = true;
} }
if (responseJSON["tags"]) { if (responseJSON["tags"]) {
for (const tag of responseJSON["tags"]) { for (const tag of responseJSON["tags"]) {
const tagHash = window.qBittorrent.Misc.genHash(tag); if (!tagMap.has(tag))
if (!tagList.has(tagHash)) { tagMap.set(tag, new Set());
tagList.set(tagHash, {
name: tag,
torrents: new Set()
});
}
} }
updateTags = true; updateTags = true;
} }
if (responseJSON["tags_removed"]) { if (responseJSON["tags_removed"]) {
for (let i = 0; i < responseJSON["tags_removed"].length; ++i) { for (const tag of responseJSON["tags_removed"])
const tagHash = window.qBittorrent.Misc.genHash(responseJSON["tags_removed"][i]); tagMap.delete(tag);
tagList.delete(tagHash);
}
updateTags = true; updateTags = true;
} }
if (responseJSON["trackers"]) { if (responseJSON["trackers"]) {
for (const [tracker, torrents] of Object.entries(responseJSON["trackers"])) { for (const [tracker, torrents] of Object.entries(responseJSON["trackers"])) {
const host = window.qBittorrent.Misc.getHost(tracker); const host = window.qBittorrent.Misc.getHost(tracker);
const hash = window.qBittorrent.Misc.genHash(host);
let trackerListItem = trackerList.get(hash); let trackerListItem = trackerMap.get(host);
if (trackerListItem === undefined) { if (trackerListItem === undefined) {
trackerListItem = { host: host, trackerTorrentMap: new Map() }; trackerListItem = new Map();
trackerList.set(hash, trackerListItem); trackerMap.set(host, trackerListItem);
} }
trackerListItem.trackerTorrentMap.set(tracker, new Set(torrents)); trackerListItem.set(tracker, new Set(torrents));
} }
updateTrackers = true; updateTrackers = true;
} }
@ -853,16 +835,16 @@ window.addEventListener("DOMContentLoaded", () => {
for (let i = 0; i < responseJSON["trackers_removed"].length; ++i) { for (let i = 0; i < responseJSON["trackers_removed"].length; ++i) {
const tracker = responseJSON["trackers_removed"][i]; const tracker = responseJSON["trackers_removed"][i];
const host = window.qBittorrent.Misc.getHost(tracker); const host = window.qBittorrent.Misc.getHost(tracker);
const hash = window.qBittorrent.Misc.genHash(host);
const trackerListEntry = trackerList.get(hash); const trackerTorrentMap = trackerMap.get(host);
if (trackerListEntry) { if (trackerTorrentMap !== undefined) {
trackerListEntry.trackerTorrentMap.delete(tracker); trackerTorrentMap.delete(tracker);
// Remove unused trackers // Remove unused trackers
if (trackerListEntry.trackerTorrentMap.size === 0) { if (trackerTorrentMap.size === 0) {
trackerList.delete(hash); trackerMap.delete(host);
if (selectedTracker === hash) { if (selectedTracker === host) {
selectedTracker = TRACKERS_ALL; selectedTracker = TRACKERS_ALL;
LocalPreferences.set("selected_tracker", selectedTracker.toString()); LocalPreferences.set("selected_tracker", selectedTracker);
} }
} }
} }
@ -884,9 +866,10 @@ window.addEventListener("DOMContentLoaded", () => {
} }
torrentsTable.updateRowData(responseJSON["torrents"][key]); torrentsTable.updateRowData(responseJSON["torrents"][key]);
if (addTorrentToCategoryList(responseJSON["torrents"][key])) if (addTorrentToCategoryList(responseJSON["torrents"][key]))
update_categories = true; updateCategories = true;
if (addTorrentToTagList(responseJSON["torrents"][key])) if (addTorrentToTagList(responseJSON["torrents"][key]))
updateTags = true; updateTags = true;
updateTrackers = true;
updateTorrents = true; updateTorrents = true;
} }
} }
@ -894,9 +877,10 @@ window.addEventListener("DOMContentLoaded", () => {
responseJSON["torrents_removed"].each((hash) => { responseJSON["torrents_removed"].each((hash) => {
torrentsTable.removeRow(hash); torrentsTable.removeRow(hash);
removeTorrentFromCategoryList(hash); removeTorrentFromCategoryList(hash);
update_categories = true; // Always to update All category updateCategories = true; // Always to update All category
removeTorrentFromTagList(hash); removeTorrentFromTagList(hash);
updateTags = true; // Always to update All tag updateTags = true; // Always to update All tag
updateTrackers = true;
}); });
updateTorrents = true; updateTorrents = true;
updateStatuses = true; updateStatuses = true;
@ -919,13 +903,13 @@ window.addEventListener("DOMContentLoaded", () => {
if (updateStatuses) if (updateStatuses)
updateFiltersList(); updateFiltersList();
if (update_categories) { if (updateCategories) {
updateCategoryList(); updateCategoryList();
window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(category_list); window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(categoryMap);
} }
if (updateTags) { if (updateTags) {
updateTagList(); updateTagList();
window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagList); window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagMap);
} }
if (updateTrackers) if (updateTrackers)
updateTrackerList(); updateTrackerList();

View file

@ -457,25 +457,25 @@ window.qBittorrent.ContextMenu ??= (() => {
this.setEnabled("copyInfohash2", thereAreV2Hashes); this.setEnabled("copyInfohash2", thereAreV2Hashes);
const contextTagList = $("contextTagList"); const contextTagList = $("contextTagList");
tagList.forEach((tag, tagHash) => { for (const tag of tagMap.keys()) {
const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag.name}"] input[type="checkbox"]`); const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag}"] input[type="checkbox"]`);
const count = tagCount.get(tag.name); const count = tagCount.get(tag);
const hasCount = (count !== undefined); const hasCount = (count !== undefined);
const isLesser = (count < selectedRows.length); const isLesser = (count < selectedRows.length);
checkbox.indeterminate = (hasCount ? isLesser : false); checkbox.indeterminate = (hasCount ? isLesser : false);
checkbox.checked = (hasCount ? !isLesser : false); checkbox.checked = (hasCount ? !isLesser : false);
});
const contextCategoryList = document.getElementById("contextCategoryList");
category_list.forEach((category, categoryHash) => {
const categoryIcon = contextCategoryList.querySelector(`a[href$="#Category/${category.name}"] img`);
const count = categoryCount.get(category.name);
const isEqual = ((count !== undefined) && (count === selectedRows.length));
categoryIcon.classList.toggle("highlightedCategoryIcon", isEqual);
});
} }
updateCategoriesSubMenu(categoryList) { const contextCategoryList = document.getElementById("contextCategoryList");
for (const category of categoryMap.keys()) {
const categoryIcon = contextCategoryList.querySelector(`a[href$="#Category/${category}"] img`);
const count = categoryCount.get(category);
const isEqual = ((count !== undefined) && (count === selectedRows.length));
categoryIcon.classList.toggle("highlightedCategoryIcon", isEqual);
}
}
updateCategoriesSubMenu(categories) {
const contextCategoryList = $("contextCategoryList"); const contextCategoryList = $("contextCategoryList");
contextCategoryList.getChildren().each(c => c.destroy()); contextCategoryList.getChildren().each(c => c.destroy());
@ -495,24 +495,19 @@ window.qBittorrent.ContextMenu ??= (() => {
return item; return item;
}; };
contextCategoryList.appendChild(createMenuItem("QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentNewCategoryFN)); contextCategoryList.appendChild(createMenuItem("QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentNewCategoryFN));
contextCategoryList.appendChild(createMenuItem("QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", () => { torrentSetCategoryFN(0); })); contextCategoryList.appendChild(createMenuItem("QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", () => { torrentSetCategoryFN(""); }));
const sortedCategories = []; const sortedCategories = [...categories.keys()];
categoryList.forEach((category, hash) => sortedCategories.push({ sortedCategories.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
categoryName: category.name,
categoryHash: hash
}));
sortedCategories.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(
left.categoryName, right.categoryName));
let first = true; let first = true;
for (const { categoryName, categoryHash } of sortedCategories) { for (const categoryName of sortedCategories) {
const anchor = document.createElement("a"); const anchor = document.createElement("a");
anchor.href = `#Category/${categoryName}`; anchor.href = `#Category/${categoryName}`;
anchor.textContent = categoryName; anchor.textContent = categoryName;
anchor.addEventListener("click", (event) => { anchor.addEventListener("click", (event) => {
event.preventDefault(); event.preventDefault();
torrentSetCategoryFN(categoryHash); torrentSetCategoryFN(categoryName);
}); });
const img = document.createElement("img"); const img = document.createElement("img");
@ -530,7 +525,7 @@ window.qBittorrent.ContextMenu ??= (() => {
} }
} }
updateTagsSubMenu(tagList) { updateTagsSubMenu(tags) {
const contextTagList = $("contextTagList"); const contextTagList = $("contextTagList");
contextTagList.replaceChildren(); contextTagList.replaceChildren();
@ -552,15 +547,11 @@ window.qBittorrent.ContextMenu ??= (() => {
contextTagList.appendChild(createMenuItem("QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentAddTagsFN)); contextTagList.appendChild(createMenuItem("QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentAddTagsFN));
contextTagList.appendChild(createMenuItem("QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", torrentRemoveAllTagsFN)); contextTagList.appendChild(createMenuItem("QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", torrentRemoveAllTagsFN));
const sortedTags = []; const sortedTags = [...tags.keys()];
tagList.forEach((tag, hash) => sortedTags.push({ sortedTags.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
tagName: tag.name,
tagHash: hash
}));
sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName));
for (let i = 0; i < sortedTags.length; ++i) { for (let i = 0; i < sortedTags.length; ++i) {
const { tagName, tagHash } = sortedTags[i]; const tagName = sortedTags[i];
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "checkbox"; input.type = "checkbox";
@ -573,7 +564,7 @@ window.qBittorrent.ContextMenu ??= (() => {
anchor.textContent = tagName; anchor.textContent = tagName;
anchor.addEventListener("click", (event) => { anchor.addEventListener("click", (event) => {
event.preventDefault(); event.preventDefault();
torrentSetTagsFN(tagHash, !input.checked); torrentSetTagsFN(tagName, !input.checked);
}); });
anchor.prepend(input); anchor.prepend(input);
@ -595,7 +586,7 @@ window.qBittorrent.ContextMenu ??= (() => {
class CategoriesFilterContextMenu extends FilterListContextMenu { class CategoriesFilterContextMenu extends FilterListContextMenu {
updateMenuItems() { updateMenuItems() {
const id = Number(this.options.element.id); const id = this.options.element.id;
if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) { if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
this.showItem("editCategory"); this.showItem("editCategory");
this.showItem("deleteCategory"); this.showItem("deleteCategory");
@ -616,7 +607,7 @@ window.qBittorrent.ContextMenu ??= (() => {
class TagsFilterContextMenu extends FilterListContextMenu { class TagsFilterContextMenu extends FilterListContextMenu {
updateMenuItems() { updateMenuItems() {
const id = Number(this.options.element.id); const id = this.options.element.id;
if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED)) if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
this.showItem("deleteTag"); this.showItem("deleteTag");
else else
@ -628,7 +619,7 @@ window.qBittorrent.ContextMenu ??= (() => {
class TrackersFilterContextMenu extends FilterListContextMenu { class TrackersFilterContextMenu extends FilterListContextMenu {
updateMenuItems() { updateMenuItems() {
const id = Number(this.options.element.id); const id = this.options.element.id;
if ((id !== TRACKERS_ALL) && (id !== TRACKERS_TRACKERLESS)) if ((id !== TRACKERS_ALL) && (id !== TRACKERS_TRACKERLESS))
this.showItem("deleteTracker"); this.showItem("deleteTracker");
else else

View file

@ -1451,7 +1451,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}; };
}, },
applyFilter: (row, filterName, categoryHash, tagHash, trackerHash, filterTerms) => { applyFilter: (row, filterName, category, tag, tracker, filterTerms) => {
const state = row["full_data"].state; const state = row["full_data"].state;
let inactive = false; let inactive = false;
@ -1515,59 +1515,64 @@ window.qBittorrent.DynamicTable ??= (() => {
break; break;
} }
switch (categoryHash) { switch (category) {
case CATEGORIES_ALL: case CATEGORIES_ALL:
break; // do nothing break; // do nothing
case CATEGORIES_UNCATEGORIZED: case CATEGORIES_UNCATEGORIZED:
if (row["full_data"].category.length !== 0) if (row["full_data"].category.length > 0)
return false; return false;
break; // do nothing break; // do nothing
default:
default: {
if (!useSubcategories) { if (!useSubcategories) {
if (categoryHash !== window.qBittorrent.Misc.genHash(row["full_data"].category)) if (category !== row["full_data"].category)
return false; return false;
} }
else { else {
const selectedCategory = category_list.get(categoryHash); const selectedCategory = categoryMap.get(category);
if (selectedCategory !== undefined) { if (selectedCategory !== undefined) {
const selectedCategoryName = selectedCategory.name + "/"; const selectedCategoryName = `${category}/`;
const torrentCategoryName = row["full_data"].category + "/"; const torrentCategoryName = `${row["full_data"].category}/`;
if (!torrentCategoryName.startsWith(selectedCategoryName)) if (!torrentCategoryName.startsWith(selectedCategoryName))
return false; return false;
} }
} }
break; break;
} }
}
switch (tagHash) { switch (tag) {
case TAGS_ALL: case TAGS_ALL:
break; // do nothing break; // do nothing
case TAGS_UNTAGGED: case TAGS_UNTAGGED:
if (row["full_data"].tags.length !== 0) if (row["full_data"].tags.length > 0)
return false; return false;
break; // do nothing break; // do nothing
default: { default: {
const tagHashes = row["full_data"].tags.split(", ").map(tag => window.qBittorrent.Misc.genHash(tag)); const tags = row["full_data"].tags.split(", ");
if (!tagHashes.contains(tagHash)) if (!tags.contains(tag))
return false; return false;
break; break;
} }
} }
switch (trackerHash) { switch (tracker) {
case TRACKERS_ALL: case TRACKERS_ALL:
break; // do nothing break; // do nothing
case TRACKERS_TRACKERLESS: case TRACKERS_TRACKERLESS:
if (row["full_data"].trackers_count !== 0) if (row["full_data"].trackers_count > 0)
return false; return false;
break; break;
default: { default: {
const tracker = trackerList.get(trackerHash); const trackerTorrentMap = trackerMap.get(tracker);
if (tracker) { if (trackerTorrentMap !== undefined) {
let found = false; let found = false;
for (const torrents of tracker.trackerTorrentMap.values()) { for (const torrents of trackerTorrentMap.values()) {
if (torrents.has(row["full_data"].rowId)) { if (torrents.has(row["full_data"].rowId)) {
found = true; found = true;
break; break;
@ -1596,17 +1601,17 @@ window.qBittorrent.DynamicTable ??= (() => {
return true; return true;
}, },
getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash, trackerHash) { getFilteredTorrentsNumber: function(filterName, category, tag, tracker) {
let cnt = 0; let cnt = 0;
for (const row of this.rows.values()) { for (const row of this.rows.values()) {
if (this.applyFilter(row, filterName, categoryHash, tagHash, trackerHash, null)) if (this.applyFilter(row, filterName, category, tag, tracker, null))
++cnt; ++cnt;
} }
return cnt; return cnt;
}, },
getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash, trackerHash) { getFilteredTorrentsHashes: function(filterName, category, tag, tracker) {
const rowsHashes = []; const rowsHashes = [];
const useRegex = document.getElementById("torrentsFilterRegexBox").checked; const useRegex = document.getElementById("torrentsFilterRegexBox").checked;
const filterText = document.getElementById("torrentsFilterInput").value.trim().toLowerCase(); const filterText = document.getElementById("torrentsFilterInput").value.trim().toLowerCase();
@ -1621,7 +1626,7 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
for (const row of this.rows.values()) { for (const row of this.rows.values()) {
if (this.applyFilter(row, filterName, categoryHash, tagHash, trackerHash, filterTerms)) if (this.applyFilter(row, filterName, category, tag, tracker, filterTerms))
rowsHashes.push(row["rowId"]); rowsHashes.push(row["rowId"]);
} }

View file

@ -32,7 +32,6 @@ window.qBittorrent ??= {};
window.qBittorrent.Misc ??= (() => { window.qBittorrent.Misc ??= (() => {
const exports = () => { const exports = () => {
return { return {
genHash: genHash,
getHost: getHost, getHost: getHost,
createDebounceHandler: createDebounceHandler, createDebounceHandler: createDebounceHandler,
friendlyUnit: friendlyUnit, friendlyUnit: friendlyUnit,
@ -54,16 +53,6 @@ window.qBittorrent.Misc ??= (() => {
}; };
}; };
const genHash = (string) => {
// origins:
// https://stackoverflow.com/a/8831937
// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
let hash = 0;
for (let i = 0; i < string.length; ++i)
hash = ((Math.imul(hash, 31) + string.charCodeAt(i)) | 0);
return hash;
};
// getHost emulate the GUI version `QString getHost(const QString &url)` // getHost emulate the GUI version `QString getHost(const QString &url)`
const getHost = (url) => { const getHost = (url) => {
// We want the hostname. // We want the hostname.

View file

@ -780,15 +780,16 @@ const initializeWindows = () => {
}; };
torrentNewCategoryFN = () => { torrentNewCategoryFN = () => {
const action = "set";
const hashes = torrentsTable.selectedRowsIds(); const hashes = torrentsTable.selectedRowsIds();
if (hashes.length) { if (hashes.length <= 0)
return;
new MochaUI.Window({ new MochaUI.Window({
id: "newCategoryPage", id: "newCategoryPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]", title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(), contentURL: new URI("newcategory.html").setData("action", "set").setData("hashes", hashes.join("|")).toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,
@ -797,22 +798,18 @@ const initializeWindows = () => {
width: 400, width: 400,
height: 150 height: 150
}); });
}
}; };
torrentSetCategoryFN = (categoryHash) => { torrentSetCategoryFN = (category) => {
const hashes = torrentsTable.selectedRowsIds(); const hashes = torrentsTable.selectedRowsIds();
if (hashes.length <= 0) if (hashes.length <= 0)
return; return;
const categoryName = category_list.has(categoryHash)
? category_list.get(categoryHash).name
: "";
fetch("api/v2/torrents/setCategory", { fetch("api/v2/torrents/setCategory", {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
hashes: hashes.join("|"), hashes: hashes.join("|"),
category: categoryName category: category
}) })
}) })
.then((response) => { .then((response) => {
@ -824,13 +821,12 @@ const initializeWindows = () => {
}; };
createCategoryFN = () => { createCategoryFN = () => {
const action = "create";
new MochaUI.Window({ new MochaUI.Window({
id: "newCategoryPage", id: "newCategoryPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]", title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("newcategory.html").setData("action", action).toString(), contentURL: new URI("newcategory.html").setData("action", "create").toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,
@ -841,15 +837,13 @@ const initializeWindows = () => {
}); });
}; };
createSubcategoryFN = (categoryHash) => { createSubcategoryFN = (category) => {
const action = "createSubcategory";
const categoryName = category_list.get(categoryHash).name + "/";
new MochaUI.Window({ new MochaUI.Window({
id: "newSubcategoryPage", id: "newSubcategoryPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]", title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", categoryName).toString(), contentURL: new URI("newcategory.html").setData("action", "createSubcategory").setData("categoryName", `${category}/`).toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,
@ -860,15 +854,13 @@ const initializeWindows = () => {
}); });
}; };
editCategoryFN = (categoryHash) => { editCategoryFN = (category) => {
const action = "edit";
const category = category_list.get(categoryHash);
new MochaUI.Window({ new MochaUI.Window({
id: "editCategoryPage", id: "editCategoryPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]", title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(), contentURL: new URI("newcategory.html").setData("action", "edit").setData("categoryName", category).setData("savePath", categoryMap.get(category).savePath).toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,
@ -879,11 +871,11 @@ const initializeWindows = () => {
}); });
}; };
removeCategoryFN = (categoryHash) => { removeCategoryFN = (category) => {
fetch("api/v2/torrents/removeCategories", { fetch("api/v2/torrents/removeCategories", {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
categories: category_list.get(categoryHash).name categories: category
}) })
}) })
.then((response) => { .then((response) => {
@ -897,10 +889,10 @@ const initializeWindows = () => {
deleteUnusedCategoriesFN = () => { deleteUnusedCategoriesFN = () => {
const categories = []; const categories = [];
category_list.forEach((category, hash) => { for (const category of categoryMap.keys()) {
if (torrentsTable.getFilteredTorrentsNumber("all", hash, TAGS_ALL, TRACKERS_ALL) === 0) if (torrentsTable.getFilteredTorrentsNumber("all", category, TAGS_ALL, TRACKERS_ALL) === 0)
categories.push(category.name); categories.push(category);
}); }
fetch("api/v2/torrents/removeCategories", { fetch("api/v2/torrents/removeCategories", {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
@ -917,15 +909,16 @@ const initializeWindows = () => {
}; };
torrentAddTagsFN = () => { torrentAddTagsFN = () => {
const action = "set";
const hashes = torrentsTable.selectedRowsIds(); const hashes = torrentsTable.selectedRowsIds();
if (hashes.length) { if (hashes.length <= 0)
return;
new MochaUI.Window({ new MochaUI.Window({
id: "newTagPage", id: "newTagPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(Add tags)QBT_TR[CONTEXT=TransferListWidget]", title: "QBT_TR(Add tags)QBT_TR[CONTEXT=TransferListWidget]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(), contentURL: new URI("newtag.html").setData("action", "set").setData("hashes", hashes.join("|")).toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,
@ -934,10 +927,9 @@ const initializeWindows = () => {
width: 250, width: 250,
height: 100 height: 100
}); });
}
}; };
torrentSetTagsFN = (tagHash, isSet) => { torrentSetTagsFN = (tag, isSet) => {
const hashes = torrentsTable.selectedRowsIds(); const hashes = torrentsTable.selectedRowsIds();
if (hashes.length <= 0) if (hashes.length <= 0)
return; return;
@ -946,7 +938,7 @@ const initializeWindows = () => {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
hashes: hashes.join("|"), hashes: hashes.join("|"),
tags: (tagList.get(tagHash)?.name || "") tags: tag
}) })
}); });
}; };
@ -964,13 +956,12 @@ const initializeWindows = () => {
}; };
createTagFN = () => { createTagFN = () => {
const action = "create";
new MochaUI.Window({ new MochaUI.Window({
id: "newTagPage", id: "newTagPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]", title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("newtag.html").setData("action", action).toString(), contentURL: new URI("newtag.html").setData("action", "create").toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,
@ -982,11 +973,11 @@ const initializeWindows = () => {
updateMainData(); updateMainData();
}; };
removeTagFN = (tagHash) => { removeTagFN = (tag) => {
fetch("api/v2/torrents/deleteTags", { fetch("api/v2/torrents/deleteTags", {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
tags: tagList.get(tagHash).name tags: tag
}) })
}); });
setTagFilter(TAGS_ALL); setTagFilter(TAGS_ALL);
@ -994,10 +985,10 @@ const initializeWindows = () => {
deleteUnusedTagsFN = () => { deleteUnusedTagsFN = () => {
const tags = []; const tags = [];
tagList.forEach((tag, hash) => { for (const tag of tagMap.keys()) {
if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, hash, TRACKERS_ALL) === 0) if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, tag, TRACKERS_ALL) === 0)
tags.push(tag.name); tags.push(tag);
}); }
fetch("api/v2/torrents/deleteTags", { fetch("api/v2/torrents/deleteTags", {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
@ -1007,20 +998,16 @@ const initializeWindows = () => {
setTagFilter(TAGS_ALL); setTagFilter(TAGS_ALL);
}; };
deleteTrackerFN = (trackerHash) => { deleteTrackerFN = (trackerHost) => {
const trackerHashInt = Number(trackerHash); if ((trackerHost === TRACKERS_ALL) || (trackerHost === TRACKERS_TRACKERLESS))
if ((trackerHashInt === TRACKERS_ALL) || (trackerHashInt === TRACKERS_TRACKERLESS))
return; return;
const tracker = trackerList.get(trackerHashInt); const trackerURLs = [...trackerMap.get(trackerHost).keys()].map(encodeURIComponent).join("|");
const host = tracker.host;
const urls = [...tracker.trackerTorrentMap.keys()];
new MochaUI.Window({ new MochaUI.Window({
id: "confirmDeletionPage", id: "confirmDeletionPage",
title: "QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg]", title: "QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg]",
loadMethod: "iframe", loadMethod: "iframe",
contentURL: new URI("confirmtrackerdeletion.html").setData("host", host).setData("urls", urls.map(encodeURIComponent).join("|")).toString(), contentURL: new URI("confirmtrackerdeletion.html").setData("host", trackerHost).setData("urls", trackerURLs).toString(),
scrollbars: false, scrollbars: false,
resizable: true, resizable: true,
maximizable: false, maximizable: false,

View file

@ -118,13 +118,13 @@
createCategoryFN(); createCategoryFN();
}, },
createSubcategory: (element, ref) => { createSubcategory: (element, ref) => {
createSubcategoryFN(Number(element.id)); createSubcategoryFN(element.id);
}, },
editCategory: (element, ref) => { editCategory: (element, ref) => {
editCategoryFN(Number(element.id)); editCategoryFN(element.id);
}, },
deleteCategory: (element, ref) => { deleteCategory: (element, ref) => {
removeCategoryFN(Number(element.id)); removeCategoryFN(element.id);
}, },
deleteUnusedCategories: (element, ref) => { deleteUnusedCategories: (element, ref) => {
deleteUnusedCategoriesFN(); deleteUnusedCategoriesFN();
@ -160,7 +160,7 @@
createTagFN(); createTagFN();
}, },
deleteTag: (element, ref) => { deleteTag: (element, ref) => {
removeTagFN(Number(element.id)); removeTagFN(element.id);
}, },
deleteUnusedTags: (element, ref) => { deleteUnusedTags: (element, ref) => {
deleteUnusedTagsFN(); deleteUnusedTagsFN();