From 73e9116d21015542caeb9a3cfd56bfb256ebed9d Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Tue, 31 Dec 2024 22:16:48 +0800 Subject: [PATCH] WebUI: Remove unnecessary hashing Now the containers support using string as key so the intermediate hashing/mapping to number isn't needed now. --- src/webui/www/private/scripts/client.js | 296 +++++++++--------- src/webui/www/private/scripts/contextmenu.js | 53 ++-- src/webui/www/private/scripts/dynamicTable.js | 47 +-- src/webui/www/private/scripts/misc.js | 11 - src/webui/www/private/scripts/mocha-init.js | 133 ++++---- src/webui/www/private/views/filters.html | 8 +- 6 files changed, 252 insertions(+), 296 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 7d58ac4c8752..974f29945ca6 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -139,31 +139,33 @@ const useAutoHideZeroStatusFilters = LocalPreferences.get("hide_zero_status_filt const displayFullURLTrackerColumn = LocalPreferences.get("full_url_tracker_column", "false") === "true"; /* Categories filter */ -const CATEGORIES_ALL = 1; -const CATEGORIES_UNCATEGORIZED = 2; +const CATEGORIES_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655"; +const CATEGORIES_UNCATEGORIZED = "e24bd469-ea22-404c-8e2e-a17c82f37ea0"; -const category_list = new Map(); +// Map +const categoryMap = new Map(); -let selectedCategory = Number(LocalPreferences.get("selected_category", CATEGORIES_ALL)); +let selectedCategory = LocalPreferences.get("selected_category", CATEGORIES_ALL); let setCategoryFilter = () => {}; /* Tags filter */ -const TAGS_ALL = 1; -const TAGS_UNTAGGED = 2; +const TAGS_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655"; +const TAGS_UNTAGGED = "e24bd469-ea22-404c-8e2e-a17c82f37ea0"; -const tagList = new Map(); +// Map +const tagMap = new Map(); -let selectedTag = Number(LocalPreferences.get("selected_tag", TAGS_ALL)); +let selectedTag = LocalPreferences.get("selected_tag", TAGS_ALL); let setTagFilter = () => {}; /* Trackers filter */ -const TRACKERS_ALL = 1; -const TRACKERS_TRACKERLESS = 2; +const TRACKERS_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655"; +const TRACKERS_TRACKERLESS = "e24bd469-ea22-404c-8e2e-a17c82f37ea0"; -/** @type Map}> **/ -const trackerList = new Map(); +// Map> +const trackerMap = new Map(); -let selectedTracker = Number(LocalPreferences.get("selected_tracker", TRACKERS_ALL)); +let selectedTracker = LocalPreferences.get("selected_tracker", TRACKERS_ALL); let setTrackerFilter = () => {}; /* All filters */ @@ -272,11 +274,11 @@ window.addEventListener("DOMContentLoaded", () => { handleFilterSelectionChange(currentHash, newHash); }; - setCategoryFilter = (hash) => { + setCategoryFilter = (category) => { const currentHash = torrentsTable.getCurrentTorrentID(); - LocalPreferences.set("selected_category", hash); - selectedCategory = Number(hash); + LocalPreferences.set("selected_category", category); + selectedCategory = category; highlightSelectedCategory(); updateMainData(); @@ -284,11 +286,11 @@ window.addEventListener("DOMContentLoaded", () => { handleFilterSelectionChange(currentHash, newHash); }; - setTagFilter = (hash) => { + setTagFilter = (tag) => { const currentHash = torrentsTable.getCurrentTorrentID(); - LocalPreferences.set("selected_tag", hash); - selectedTag = Number(hash); + LocalPreferences.set("selected_tag", tag); + selectedTag = tag; highlightSelectedTag(); updateMainData(); @@ -296,11 +298,11 @@ window.addEventListener("DOMContentLoaded", () => { handleFilterSelectionChange(currentHash, newHash); }; - setTrackerFilter = (hash) => { + setTrackerFilter = (tracker) => { const currentHash = torrentsTable.getCurrentTorrentID(); - LocalPreferences.set("selected_tracker", hash); - selectedTracker = Number(hash); + LocalPreferences.set("selected_tracker", tracker); + selectedTracker = tracker; highlightSelectedTracker(); updateMainData(); @@ -374,21 +376,18 @@ window.addEventListener("DOMContentLoaded", () => { const serverState = {}; const removeTorrentFromCategoryList = (hash) => { - if (!hash) + if (hash === undefined) return false; let removed = false; - category_list.forEach((category) => { - const deleteResult = category.torrents.delete(hash); - removed ||= deleteResult; - }); - + for (const data of categoryMap.values()) + removed ||= data.torrents.delete(hash); return removed; }; const addTorrentToCategoryList = (torrent) => { const category = torrent["category"]; - if (typeof category === "undefined") + if (category === undefined) return false; const hash = torrent["hash"]; @@ -397,33 +396,29 @@ window.addEventListener("DOMContentLoaded", () => { return true; } - const categoryHash = window.qBittorrent.Misc.genHash(category); - if (!category_list.has(categoryHash)) { // This should not happen - category_list.set(categoryHash, { - name: category, + let categoryData = categoryMap.get(category); + if (categoryData === undefined) { // This should not happen + categoryData = { torrents: new Set() - }); + }; + categoryMap.set(category, categoryData); } - const torrents = category_list.get(categoryHash).torrents; - if (!torrents.has(hash)) { - removeTorrentFromCategoryList(hash); - torrents.add(hash); - return true; - } - return false; + if (categoryData.torrents.has(hash)) + return false; + + removeTorrentFromCategoryList(hash); + categoryData.torrents.add(hash); + return true; }; const removeTorrentFromTagList = (hash) => { - if (!hash) + if (hash === undefined) return false; let removed = false; - tagList.forEach((tag) => { - const deleteResult = tag.torrents.delete(hash); - removed ||= deleteResult; - }); - + for (const torrents of tagMap.values()) + removed ||= torrents.delete(hash); return removed; }; @@ -437,23 +432,21 @@ window.addEventListener("DOMContentLoaded", () => { if (torrent["tags"].length === 0) // No tags return true; - const tags = torrent["tags"].split(","); + const tags = torrent["tags"].split(", "); let added = false; - for (let i = 0; i < tags.length; ++i) { - const tagHash = window.qBittorrent.Misc.genHash(tags[i].trim()); - if (!tagList.has(tagHash)) { // This should not happen - tagList.set(tagHash, { - name: tags, - torrents: new Set() - }); + for (const tag of tags) { + let torrents = tagMap.get(tag); + if (torrents === undefined) { // This should not happen + torrents = new Set(); + tagMap.set(tag, torrents); } - const torrents = tagList.get(tagHash).torrents; if (!torrents.has(hash)) { torrents.add(hash); added = true; } } + return added; }; @@ -500,13 +493,13 @@ window.addEventListener("DOMContentLoaded", () => { const categoryItemTemplate = document.getElementById("categoryFilterItem"); - const createCategoryLink = (hash, name, count) => { + const createLink = (category, text, count) => { const categoryFilterItem = categoryItemTemplate.content.cloneNode(true).firstElementChild; - categoryFilterItem.id = hash; - categoryFilterItem.classList.toggle("selectedFilter", hash === selectedCategory); + categoryFilterItem.id = category; + categoryFilterItem.classList.toggle("selectedFilter", (category === selectedCategory)); const span = categoryFilterItem.firstElementChild; - span.lastElementChild.textContent = `${name} (${count})`; + span.lastElementChild.textContent = `${text} (${count})`; return categoryFilterItem; }; @@ -516,7 +509,7 @@ window.addEventListener("DOMContentLoaded", () => { while (stack.length > 0) { const { parent, category } = stack.pop(); 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`; parent.appendChild(listItem); @@ -528,7 +521,7 @@ window.addEventListener("DOMContentLoaded", () => { for (const subcategory of category.children.reverse()) 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"); LocalPreferences.set(categoryLocalPref, listItem.classList.toggle("collapsedCategory", isCollapsed).toString()); } @@ -541,17 +534,18 @@ window.addEventListener("DOMContentLoaded", () => { } const sortedCategories = []; - category_list.forEach((category, hash) => sortedCategories.push({ - categoryName: category.name, - categoryHash: hash, - categoryCount: category.torrents.size, - nameSegments: category.name.split("/"), - ...(useSubcategories && { - children: [], - parentID: null, - forceExpand: LocalPreferences.get(`category_${hash}_collapsed`) === null - }) - })); + for (const [category, categoryData] of categoryMap) { + sortedCategories.push({ + categoryName: category, + categoryCount: categoryData.torrents.size, + nameSegments: category.split("/"), + ...(useSubcategories && { + children: [], + isRoot: true, + forceExpand: LocalPreferences.get(`category_${category}_collapsed`) === null + }) + }); + } sortedCategories.sort((left, right) => { const leftSegments = left.nameSegments; const rightSegments = right.nameSegments; @@ -567,8 +561,8 @@ window.addEventListener("DOMContentLoaded", () => { }); const categoriesFragment = new DocumentFragment(); - categoriesFragment.appendChild(createCategoryLink(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_ALL, "QBT_TR(All)QBT_TR[CONTEXT=CategoryFilterModel]", torrentsTable.getRowSize())); + categoriesFragment.appendChild(createLink(CATEGORIES_UNCATEGORIZED, "QBT_TR(Uncategorized)QBT_TR[CONTEXT=CategoryFilterModel]", uncategorized)); if (useSubcategories) { categoryList.classList.add("subcategories"); @@ -582,20 +576,20 @@ window.addEventListener("DOMContentLoaded", () => { const isDirectSubcategory = (subcategory.nameSegments.length - category.nameSegments.length) === 1; if (isDirectSubcategory) { - subcategory.parentID = category.categoryHash; + subcategory.isRoot = false; category.children.push(subcategory); } } } for (const category of sortedCategories) { - if (category.parentID === null) + if (category.isRoot) createCategoryTree(category); } } else { categoryList.classList.remove("subcategories"); - for (const { categoryHash, categoryName, categoryCount } of sortedCategories) - categoriesFragment.appendChild(createCategoryLink(categoryHash, categoryName, categoryCount)); + for (const { categoryName, categoryCount } of sortedCategories) + categoriesFragment.appendChild(createLink(categoryName, categoryName, categoryCount)); } categoryList.appendChild(categoriesFragment); @@ -608,7 +602,7 @@ window.addEventListener("DOMContentLoaded", () => { return; for (const category of categoryList.getElementsByTagName("li")) - category.classList.toggle("selectedFilter", (Number(category.id) === selectedCategory)); + category.classList.toggle("selectedFilter", (category.id === selectedCategory)); }; const updateTagList = () => { @@ -620,10 +614,10 @@ window.addEventListener("DOMContentLoaded", () => { const tagItemTemplate = document.getElementById("tagFilterItem"); - const createLink = (hash, text, count) => { + const createLink = (tag, text, count) => { const tagFilterItem = tagItemTemplate.content.cloneNode(true).firstElementChild; - tagFilterItem.id = hash; - tagFilterItem.classList.toggle("selectedFilter", hash === selectedTag); + tagFilterItem.id = tag; + tagFilterItem.classList.toggle("selectedFilter", (tag === selectedTag)); const span = tagFilterItem.firstElementChild; 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)); const sortedTags = []; - tagList.forEach((tag, hash) => sortedTags.push({ - tagName: tag.name, - tagHash: hash, - tagSize: tag.torrents.size - })); + for (const [tag, torrents] of tagMap) { + sortedTags.push({ + tagName: tag, + tagSize: torrents.size + }); + } sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName)); - for (const { tagName, tagHash, tagSize } of sortedTags) - tagFilterList.appendChild(createLink(tagHash, tagName, tagSize)); + for (const { tagName, tagSize } of sortedTags) + tagFilterList.appendChild(createLink(tagName, tagName, tagSize)); window.qBittorrent.Filters.tagsFilterContextMenu.searchAndAddTargets(); }; @@ -660,7 +655,7 @@ window.addEventListener("DOMContentLoaded", () => { return; for (const tag of tagFilterList.children) - tag.classList.toggle("selectedFilter", (Number(tag.id) === selectedTag)); + tag.classList.toggle("selectedFilter", (tag.id === selectedTag)); }; const updateTrackerList = () => { @@ -672,13 +667,13 @@ window.addEventListener("DOMContentLoaded", () => { const trackerItemTemplate = document.getElementById("trackerFilterItem"); - const createLink = (hash, text, count) => { + const createLink = (host, text, count) => { const trackerFilterItem = trackerItemTemplate.content.cloneNode(true).firstElementChild; - trackerFilterItem.id = hash; - trackerFilterItem.classList.toggle("selectedFilter", hash === selectedTracker); + trackerFilterItem.id = host; + trackerFilterItem.classList.toggle("selectedFilter", (host === selectedTracker)); const span = trackerFilterItem.firstElementChild; - span.lastChild.textContent = text.replace("%1", count); + span.lastChild.textContent = `${text} (${count})`; return trackerFilterItem; }; @@ -689,12 +684,12 @@ window.addEventListener("DOMContentLoaded", () => { trackerlessTorrentsCount += 1; } - trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All (%1))QBT_TR[CONTEXT=TrackerFiltersList]", torrentsTable.getRowSize())); - trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless (%1))QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount)); + trackerFilterList.appendChild(createLink(TRACKERS_ALL, "QBT_TR(All)QBT_TR[CONTEXT=TrackerFiltersList]", torrentsTable.getRowSize())); + trackerFilterList.appendChild(createLink(TRACKERS_TRACKERLESS, "QBT_TR(Trackerless)QBT_TR[CONTEXT=TrackerFiltersList]", trackerlessTorrentsCount)); // Sort trackers by hostname const sortedList = []; - trackerList.forEach(({ host, trackerTorrentMap }, hash) => { + for (const [host, trackerTorrentMap] of trackerMap) { const uniqueTorrents = new Set(); for (const torrents of trackerTorrentMap.values()) { for (const torrent of torrents) @@ -703,13 +698,12 @@ window.addEventListener("DOMContentLoaded", () => { sortedList.push({ trackerHost: host, - trackerHash: hash, trackerCount: uniqueTorrents.size, }); - }); + } sortedList.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.trackerHost, right.trackerHost)); - for (const { trackerHost, trackerHash, trackerCount } of sortedList) - trackerFilterList.appendChild(createLink(trackerHash, (trackerHost + " (%1)"), trackerCount)); + for (const { trackerHost, trackerCount } of sortedList) + trackerFilterList.appendChild(createLink(trackerHost, trackerHost, trackerCount)); window.qBittorrent.Filters.trackersFilterContextMenu.searchAndAddTargets(); }; @@ -720,7 +714,7 @@ window.addEventListener("DOMContentLoaded", () => { return; 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({ @@ -768,7 +762,7 @@ window.addEventListener("DOMContentLoaded", () => { let torrentsTableSelectedRows; let updateStatuses = false; - let update_categories = false; + let updateCategories = false; let updateTags = false; let updateTrackers = false; let updateTorrents = false; @@ -776,76 +770,64 @@ window.addEventListener("DOMContentLoaded", () => { if (fullUpdate) { torrentsTableSelectedRows = torrentsTable.selectedRowsIds(); updateStatuses = true; - update_categories = true; + updateCategories = true; updateTags = true; updateTrackers = true; updateTorrents = true; torrentsTable.clear(); - category_list.clear(); - tagList.clear(); - trackerList.clear(); + categoryMap.clear(); + tagMap.clear(); + trackerMap.clear(); } if (responseJSON["rid"]) syncMainDataLastResponseId = responseJSON["rid"]; if (responseJSON["categories"]) { - for (const key in responseJSON["categories"]) { - if (!Object.hasOwn(responseJSON["categories"], key)) + for (const responseName in responseJSON["categories"]) { + if (!Object.hasOwn(responseJSON["categories"], responseName)) continue; - const responseCategory = responseJSON["categories"][key]; - const categoryHash = window.qBittorrent.Misc.genHash(key); - const category = category_list.get(categoryHash); - if (category !== undefined) { - // only the save path can change for existing categories - category.savePath = responseCategory.savePath; - } - else { - category_list.set(categoryHash, { - name: responseCategory.name, - savePath: responseCategory.savePath, + const responseData = responseJSON["categories"][responseName]; + const categoryData = categoryMap.get(responseName); + if (categoryData === undefined) { + categoryMap.set(responseName, { + savePath: responseData.savePath, 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"]) { - responseJSON["categories_removed"].each((category) => { - const categoryHash = window.qBittorrent.Misc.genHash(category); - category_list.delete(categoryHash); - }); - update_categories = true; + for (const category of responseJSON["categories_removed"]) + categoryMap.delete(category); + updateCategories = true; } if (responseJSON["tags"]) { for (const tag of responseJSON["tags"]) { - const tagHash = window.qBittorrent.Misc.genHash(tag); - if (!tagList.has(tagHash)) { - tagList.set(tagHash, { - name: tag, - torrents: new Set() - }); - } + if (!tagMap.has(tag)) + tagMap.set(tag, new Set()); } updateTags = true; } if (responseJSON["tags_removed"]) { - for (let i = 0; i < responseJSON["tags_removed"].length; ++i) { - const tagHash = window.qBittorrent.Misc.genHash(responseJSON["tags_removed"][i]); - tagList.delete(tagHash); - } + for (const tag of responseJSON["tags_removed"]) + tagMap.delete(tag); updateTags = true; } if (responseJSON["trackers"]) { for (const [tracker, torrents] of Object.entries(responseJSON["trackers"])) { 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) { - trackerListItem = { host: host, trackerTorrentMap: new Map() }; - trackerList.set(hash, trackerListItem); + trackerListItem = new Map(); + trackerMap.set(host, trackerListItem); } - trackerListItem.trackerTorrentMap.set(tracker, new Set(torrents)); + trackerListItem.set(tracker, new Set(torrents)); } updateTrackers = true; } @@ -853,16 +835,16 @@ window.addEventListener("DOMContentLoaded", () => { for (let i = 0; i < responseJSON["trackers_removed"].length; ++i) { const tracker = responseJSON["trackers_removed"][i]; const host = window.qBittorrent.Misc.getHost(tracker); - const hash = window.qBittorrent.Misc.genHash(host); - const trackerListEntry = trackerList.get(hash); - if (trackerListEntry) { - trackerListEntry.trackerTorrentMap.delete(tracker); + + const trackerTorrentMap = trackerMap.get(host); + if (trackerTorrentMap !== undefined) { + trackerTorrentMap.delete(tracker); // Remove unused trackers - if (trackerListEntry.trackerTorrentMap.size === 0) { - trackerList.delete(hash); - if (selectedTracker === hash) { + if (trackerTorrentMap.size === 0) { + trackerMap.delete(host); + if (selectedTracker === host) { 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]); if (addTorrentToCategoryList(responseJSON["torrents"][key])) - update_categories = true; + updateCategories = true; if (addTorrentToTagList(responseJSON["torrents"][key])) updateTags = true; + updateTrackers = true; updateTorrents = true; } } @@ -894,9 +877,10 @@ window.addEventListener("DOMContentLoaded", () => { responseJSON["torrents_removed"].each((hash) => { torrentsTable.removeRow(hash); removeTorrentFromCategoryList(hash); - update_categories = true; // Always to update All category + updateCategories = true; // Always to update All category removeTorrentFromTagList(hash); updateTags = true; // Always to update All tag + updateTrackers = true; }); updateTorrents = true; updateStatuses = true; @@ -919,13 +903,13 @@ window.addEventListener("DOMContentLoaded", () => { if (updateStatuses) updateFiltersList(); - if (update_categories) { + if (updateCategories) { updateCategoryList(); - window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(category_list); + window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(categoryMap); } if (updateTags) { updateTagList(); - window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagList); + window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagMap); } if (updateTrackers) updateTrackerList(); diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 5917afddd62c..1c9405c2cdf0 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -457,25 +457,25 @@ window.qBittorrent.ContextMenu ??= (() => { this.setEnabled("copyInfohash2", thereAreV2Hashes); const contextTagList = $("contextTagList"); - tagList.forEach((tag, tagHash) => { - const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag.name}"] input[type="checkbox"]`); - const count = tagCount.get(tag.name); + for (const tag of tagMap.keys()) { + const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag}"] input[type="checkbox"]`); + const count = tagCount.get(tag); const hasCount = (count !== undefined); const isLesser = (count < selectedRows.length); checkbox.indeterminate = (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); + 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(categoryList) { + updateCategoriesSubMenu(categories) { const contextCategoryList = $("contextCategoryList"); contextCategoryList.getChildren().each(c => c.destroy()); @@ -495,24 +495,19 @@ window.qBittorrent.ContextMenu ??= (() => { return item; }; 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 = []; - categoryList.forEach((category, hash) => sortedCategories.push({ - categoryName: category.name, - categoryHash: hash - })); - sortedCategories.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare( - left.categoryName, right.categoryName)); + const sortedCategories = [...categories.keys()]; + sortedCategories.sort(window.qBittorrent.Misc.naturalSortCollator.compare); let first = true; - for (const { categoryName, categoryHash } of sortedCategories) { + for (const categoryName of sortedCategories) { const anchor = document.createElement("a"); anchor.href = `#Category/${categoryName}`; anchor.textContent = categoryName; anchor.addEventListener("click", (event) => { event.preventDefault(); - torrentSetCategoryFN(categoryHash); + torrentSetCategoryFN(categoryName); }); const img = document.createElement("img"); @@ -530,7 +525,7 @@ window.qBittorrent.ContextMenu ??= (() => { } } - updateTagsSubMenu(tagList) { + updateTagsSubMenu(tags) { const contextTagList = $("contextTagList"); 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(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", torrentRemoveAllTagsFN)); - const sortedTags = []; - tagList.forEach((tag, hash) => sortedTags.push({ - tagName: tag.name, - tagHash: hash - })); - sortedTags.sort((left, right) => window.qBittorrent.Misc.naturalSortCollator.compare(left.tagName, right.tagName)); + const sortedTags = [...tags.keys()]; + sortedTags.sort(window.qBittorrent.Misc.naturalSortCollator.compare); for (let i = 0; i < sortedTags.length; ++i) { - const { tagName, tagHash } = sortedTags[i]; + const tagName = sortedTags[i]; const input = document.createElement("input"); input.type = "checkbox"; @@ -573,7 +564,7 @@ window.qBittorrent.ContextMenu ??= (() => { anchor.textContent = tagName; anchor.addEventListener("click", (event) => { event.preventDefault(); - torrentSetTagsFN(tagHash, !input.checked); + torrentSetTagsFN(tagName, !input.checked); }); anchor.prepend(input); @@ -595,7 +586,7 @@ window.qBittorrent.ContextMenu ??= (() => { class CategoriesFilterContextMenu extends FilterListContextMenu { updateMenuItems() { - const id = Number(this.options.element.id); + const id = this.options.element.id; if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) { this.showItem("editCategory"); this.showItem("deleteCategory"); @@ -616,7 +607,7 @@ window.qBittorrent.ContextMenu ??= (() => { class TagsFilterContextMenu extends FilterListContextMenu { updateMenuItems() { - const id = Number(this.options.element.id); + const id = this.options.element.id; if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED)) this.showItem("deleteTag"); else @@ -628,7 +619,7 @@ window.qBittorrent.ContextMenu ??= (() => { class TrackersFilterContextMenu extends FilterListContextMenu { updateMenuItems() { - const id = Number(this.options.element.id); + const id = this.options.element.id; if ((id !== TRACKERS_ALL) && (id !== TRACKERS_TRACKERLESS)) this.showItem("deleteTracker"); else diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 88edb34c1cd3..b9b36466b0e6 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -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; let inactive = false; @@ -1515,59 +1515,64 @@ window.qBittorrent.DynamicTable ??= (() => { break; } - switch (categoryHash) { + switch (category) { case CATEGORIES_ALL: break; // do nothing + case CATEGORIES_UNCATEGORIZED: - if (row["full_data"].category.length !== 0) + if (row["full_data"].category.length > 0) return false; break; // do nothing - default: + + default: { if (!useSubcategories) { - if (categoryHash !== window.qBittorrent.Misc.genHash(row["full_data"].category)) + if (category !== row["full_data"].category) return false; } else { - const selectedCategory = category_list.get(categoryHash); + const selectedCategory = categoryMap.get(category); if (selectedCategory !== undefined) { - const selectedCategoryName = selectedCategory.name + "/"; - const torrentCategoryName = row["full_data"].category + "/"; + const selectedCategoryName = `${category}/`; + const torrentCategoryName = `${row["full_data"].category}/`; if (!torrentCategoryName.startsWith(selectedCategoryName)) return false; } } break; + } } - switch (tagHash) { + switch (tag) { case TAGS_ALL: break; // do nothing case TAGS_UNTAGGED: - if (row["full_data"].tags.length !== 0) + if (row["full_data"].tags.length > 0) return false; break; // do nothing default: { - const tagHashes = row["full_data"].tags.split(", ").map(tag => window.qBittorrent.Misc.genHash(tag)); - if (!tagHashes.contains(tagHash)) + const tags = row["full_data"].tags.split(", "); + if (!tags.contains(tag)) return false; break; } } - switch (trackerHash) { + switch (tracker) { case TRACKERS_ALL: break; // do nothing + case TRACKERS_TRACKERLESS: - if (row["full_data"].trackers_count !== 0) + if (row["full_data"].trackers_count > 0) return false; break; + default: { - const tracker = trackerList.get(trackerHash); - if (tracker) { + const trackerTorrentMap = trackerMap.get(tracker); + if (trackerTorrentMap !== undefined) { let found = false; - for (const torrents of tracker.trackerTorrentMap.values()) { + for (const torrents of trackerTorrentMap.values()) { if (torrents.has(row["full_data"].rowId)) { found = true; break; @@ -1596,17 +1601,17 @@ window.qBittorrent.DynamicTable ??= (() => { return true; }, - getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash, trackerHash) { + getFilteredTorrentsNumber: function(filterName, category, tag, tracker) { let cnt = 0; 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; } return cnt; }, - getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash, trackerHash) { + getFilteredTorrentsHashes: function(filterName, category, tag, tracker) { const rowsHashes = []; const useRegex = document.getElementById("torrentsFilterRegexBox").checked; const filterText = document.getElementById("torrentsFilterInput").value.trim().toLowerCase(); @@ -1621,7 +1626,7 @@ window.qBittorrent.DynamicTable ??= (() => { } 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"]); } diff --git a/src/webui/www/private/scripts/misc.js b/src/webui/www/private/scripts/misc.js index 6313adfe2c77..493ea41e0709 100644 --- a/src/webui/www/private/scripts/misc.js +++ b/src/webui/www/private/scripts/misc.js @@ -32,7 +32,6 @@ window.qBittorrent ??= {}; window.qBittorrent.Misc ??= (() => { const exports = () => { return { - genHash: genHash, getHost: getHost, createDebounceHandler: createDebounceHandler, 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)` const getHost = (url) => { // We want the hostname. diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index 03368ecd464e..e0e6acebba84 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -780,39 +780,36 @@ const initializeWindows = () => { }; torrentNewCategoryFN = () => { - const action = "set"; const hashes = torrentsTable.selectedRowsIds(); - if (hashes.length) { - new MochaUI.Window({ - id: "newCategoryPage", - icon: "images/qbittorrent-tray.svg", - title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]", - loadMethod: "iframe", - contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(), - scrollbars: false, - resizable: true, - maximizable: false, - paddingVertical: 0, - paddingHorizontal: 0, - width: 400, - height: 150 - }); - } + if (hashes.length <= 0) + return; + + new MochaUI.Window({ + id: "newCategoryPage", + icon: "images/qbittorrent-tray.svg", + title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]", + loadMethod: "iframe", + contentURL: new URI("newcategory.html").setData("action", "set").setData("hashes", hashes.join("|")).toString(), + scrollbars: false, + resizable: true, + maximizable: false, + paddingVertical: 0, + paddingHorizontal: 0, + width: 400, + height: 150 + }); }; - torrentSetCategoryFN = (categoryHash) => { + torrentSetCategoryFN = (category) => { const hashes = torrentsTable.selectedRowsIds(); if (hashes.length <= 0) return; - const categoryName = category_list.has(categoryHash) - ? category_list.get(categoryHash).name - : ""; fetch("api/v2/torrents/setCategory", { method: "POST", body: new URLSearchParams({ hashes: hashes.join("|"), - category: categoryName + category: category }) }) .then((response) => { @@ -824,13 +821,12 @@ const initializeWindows = () => { }; createCategoryFN = () => { - const action = "create"; new MochaUI.Window({ id: "newCategoryPage", icon: "images/qbittorrent-tray.svg", title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]", loadMethod: "iframe", - contentURL: new URI("newcategory.html").setData("action", action).toString(), + contentURL: new URI("newcategory.html").setData("action", "create").toString(), scrollbars: false, resizable: true, maximizable: false, @@ -841,15 +837,13 @@ const initializeWindows = () => { }); }; - createSubcategoryFN = (categoryHash) => { - const action = "createSubcategory"; - const categoryName = category_list.get(categoryHash).name + "/"; + createSubcategoryFN = (category) => { new MochaUI.Window({ id: "newSubcategoryPage", icon: "images/qbittorrent-tray.svg", title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]", 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, resizable: true, maximizable: false, @@ -860,15 +854,13 @@ const initializeWindows = () => { }); }; - editCategoryFN = (categoryHash) => { - const action = "edit"; - const category = category_list.get(categoryHash); + editCategoryFN = (category) => { new MochaUI.Window({ id: "editCategoryPage", icon: "images/qbittorrent-tray.svg", title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]", 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, resizable: true, maximizable: false, @@ -879,11 +871,11 @@ const initializeWindows = () => { }); }; - removeCategoryFN = (categoryHash) => { + removeCategoryFN = (category) => { fetch("api/v2/torrents/removeCategories", { method: "POST", body: new URLSearchParams({ - categories: category_list.get(categoryHash).name + categories: category }) }) .then((response) => { @@ -897,10 +889,10 @@ const initializeWindows = () => { deleteUnusedCategoriesFN = () => { const categories = []; - category_list.forEach((category, hash) => { - if (torrentsTable.getFilteredTorrentsNumber("all", hash, TAGS_ALL, TRACKERS_ALL) === 0) - categories.push(category.name); - }); + for (const category of categoryMap.keys()) { + if (torrentsTable.getFilteredTorrentsNumber("all", category, TAGS_ALL, TRACKERS_ALL) === 0) + categories.push(category); + } fetch("api/v2/torrents/removeCategories", { method: "POST", body: new URLSearchParams({ @@ -917,27 +909,27 @@ const initializeWindows = () => { }; torrentAddTagsFN = () => { - const action = "set"; const hashes = torrentsTable.selectedRowsIds(); - if (hashes.length) { - new MochaUI.Window({ - id: "newTagPage", - icon: "images/qbittorrent-tray.svg", - title: "QBT_TR(Add tags)QBT_TR[CONTEXT=TransferListWidget]", - loadMethod: "iframe", - contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(), - scrollbars: false, - resizable: true, - maximizable: false, - paddingVertical: 0, - paddingHorizontal: 0, - width: 250, - height: 100 - }); - } + if (hashes.length <= 0) + return; + + new MochaUI.Window({ + id: "newTagPage", + icon: "images/qbittorrent-tray.svg", + title: "QBT_TR(Add tags)QBT_TR[CONTEXT=TransferListWidget]", + loadMethod: "iframe", + contentURL: new URI("newtag.html").setData("action", "set").setData("hashes", hashes.join("|")).toString(), + scrollbars: false, + resizable: true, + maximizable: false, + paddingVertical: 0, + paddingHorizontal: 0, + width: 250, + height: 100 + }); }; - torrentSetTagsFN = (tagHash, isSet) => { + torrentSetTagsFN = (tag, isSet) => { const hashes = torrentsTable.selectedRowsIds(); if (hashes.length <= 0) return; @@ -946,7 +938,7 @@ const initializeWindows = () => { method: "POST", body: new URLSearchParams({ hashes: hashes.join("|"), - tags: (tagList.get(tagHash)?.name || "") + tags: tag }) }); }; @@ -964,13 +956,12 @@ const initializeWindows = () => { }; createTagFN = () => { - const action = "create"; new MochaUI.Window({ id: "newTagPage", icon: "images/qbittorrent-tray.svg", title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]", loadMethod: "iframe", - contentURL: new URI("newtag.html").setData("action", action).toString(), + contentURL: new URI("newtag.html").setData("action", "create").toString(), scrollbars: false, resizable: true, maximizable: false, @@ -982,11 +973,11 @@ const initializeWindows = () => { updateMainData(); }; - removeTagFN = (tagHash) => { + removeTagFN = (tag) => { fetch("api/v2/torrents/deleteTags", { method: "POST", body: new URLSearchParams({ - tags: tagList.get(tagHash).name + tags: tag }) }); setTagFilter(TAGS_ALL); @@ -994,10 +985,10 @@ const initializeWindows = () => { deleteUnusedTagsFN = () => { const tags = []; - tagList.forEach((tag, hash) => { - if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, hash, TRACKERS_ALL) === 0) - tags.push(tag.name); - }); + for (const tag of tagMap.keys()) { + if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, tag, TRACKERS_ALL) === 0) + tags.push(tag); + } fetch("api/v2/torrents/deleteTags", { method: "POST", body: new URLSearchParams({ @@ -1007,20 +998,16 @@ const initializeWindows = () => { setTagFilter(TAGS_ALL); }; - deleteTrackerFN = (trackerHash) => { - const trackerHashInt = Number(trackerHash); - if ((trackerHashInt === TRACKERS_ALL) || (trackerHashInt === TRACKERS_TRACKERLESS)) + deleteTrackerFN = (trackerHost) => { + if ((trackerHost === TRACKERS_ALL) || (trackerHost === TRACKERS_TRACKERLESS)) return; - const tracker = trackerList.get(trackerHashInt); - const host = tracker.host; - const urls = [...tracker.trackerTorrentMap.keys()]; - + const trackerURLs = [...trackerMap.get(trackerHost).keys()].map(encodeURIComponent).join("|"); new MochaUI.Window({ id: "confirmDeletionPage", title: "QBT_TR(Remove tracker)QBT_TR[CONTEXT=confirmDeletionDlg]", 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, resizable: true, maximizable: false, diff --git a/src/webui/www/private/views/filters.html b/src/webui/www/private/views/filters.html index 4c6f24fddca2..60eeb5c88d46 100644 --- a/src/webui/www/private/views/filters.html +++ b/src/webui/www/private/views/filters.html @@ -118,13 +118,13 @@ createCategoryFN(); }, createSubcategory: (element, ref) => { - createSubcategoryFN(Number(element.id)); + createSubcategoryFN(element.id); }, editCategory: (element, ref) => { - editCategoryFN(Number(element.id)); + editCategoryFN(element.id); }, deleteCategory: (element, ref) => { - removeCategoryFN(Number(element.id)); + removeCategoryFN(element.id); }, deleteUnusedCategories: (element, ref) => { deleteUnusedCategoriesFN(); @@ -160,7 +160,7 @@ createTagFN(); }, deleteTag: (element, ref) => { - removeTagFN(Number(element.id)); + removeTagFN(element.id); }, deleteUnusedTags: (element, ref) => { deleteUnusedTagsFN();