From 5a0914e333c10d31a64f8f442bac9369ba7b35f1 Mon Sep 17 00:00:00 2001 From: skomerko <168652295+skomerko@users.noreply.github.com> Date: Sat, 19 Oct 2024 10:32:20 +0200 Subject: [PATCH] WebUI: Use modern class syntax in context menu classes Context menu classes are now created using vanilla JS syntax (minimal changes to reduce MooTools bits). PR #21598. --- src/webui/www/private/scripts/contextmenu.js | 213 ++++++++---------- src/webui/www/private/scripts/dynamicTable.js | 25 +- 2 files changed, 107 insertions(+), 131 deletions(-) diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index aa7f8d0c1..ecc187f59 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -46,31 +46,24 @@ window.qBittorrent.ContextMenu ??= (() => { }; let lastShownContextMenu = null; - const ContextMenu = new Class({ - // implements - Implements: [Options, Events], - - // options - options: { - actions: {}, - menu: "menu_id", - stopEvent: true, - targets: "body", - offsets: { - x: 0, - y: 0 - }, - onShow: () => {}, - onHide: () => {}, - onClick: () => {}, - fadeSpeed: 200, - touchTimer: 600 - }, - - // initialization - initialize: function(options) { - // set options - this.setOptions(options); + class ContextMenu { + constructor(options) { + this.options = { + actions: {}, + menu: "menu_id", + stopEvent: true, + targets: "body", + offsets: { + x: 0, + y: 0 + }, + onShow: () => {}, + onHide: () => {}, + onClick: () => {}, + fadeSpeed: 200, + touchTimer: 600, + ...options + }; // option diffs menu this.menu = $(this.options.menu); @@ -92,9 +85,9 @@ window.qBittorrent.ContextMenu ??= (() => { this.menu.style.position = "absolute"; this.menu.style.top = "-900000px"; this.menu.style.display = "block"; - }, + } - adjustMenuPosition: function(e) { + adjustMenuPosition(e) { this.updateMenuItems(); const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75; @@ -144,9 +137,9 @@ window.qBittorrent.ContextMenu ??= (() => { ul.style.marginLeft = `${xPos - xPosOrigin}px`; ul.style.marginTop = `${yPos - yPosOrigin}px`; } - }, + } - setupEventListeners: function(elem) { + setupEventListeners(elem) { elem.addEventListener("contextmenu", (e) => { this.triggerMenu(e, elem); }); @@ -171,21 +164,21 @@ window.qBittorrent.ContextMenu ??= (() => { if (((now - touchStartAt) >= this.options.touchTimer) && isTargetUnchanged) this.triggerMenu(touchStartEvent, elem); }, { passive: true }); - }, + } - addTarget: function(t) { + addTarget(t) { // prevent long press from selecting this text t.style.userSelect = "none"; this.targets[this.targets.length] = t; this.setupEventListeners(t); - }, + } - searchAndAddTargets: function() { + searchAndAddTargets() { document.querySelectorAll(this.options.targets).forEach((target) => { this.addTarget(target); }); - }, + } - triggerMenu: function(e, el) { + triggerMenu(e, el) { if (this.options.disabled) return; @@ -199,10 +192,10 @@ window.qBittorrent.ContextMenu ??= (() => { this.adjustMenuPosition(e); // show the menu this.show(); - }, + } // get things started - startListener: function() { + startListener() { /* all elements */ this.targets.each((el) => { this.setupEventListeners(el); @@ -218,7 +211,7 @@ window.qBittorrent.ContextMenu ??= (() => { if (!menuItem.classList.contains("disabled")) { const anchor = menuItem.firstElementChild; this.execute(anchor.href.split("#")[1], this.options.element); - this.fireEvent("click", [anchor, e]); + this.options.onClick.call(this, anchor, e); } else { e.stopPropagation(); @@ -229,107 +222,103 @@ window.qBittorrent.ContextMenu ??= (() => { $(document.body).addEventListener("click", () => { this.hide(); }); - }, + } - updateMenuItems: function() {}, + updateMenuItems() {} // show menu - show: function(trigger) { + show(trigger) { if (lastShownContextMenu && (lastShownContextMenu !== this)) lastShownContextMenu.hide(); this.fx.start(1); - this.fireEvent("show"); + this.options.onShow.call(this); lastShownContextMenu = this; return this; - }, + } // hide the menu - hide: function(trigger) { + hide(trigger) { if (lastShownContextMenu && (lastShownContextMenu.menu.style.visibility !== "hidden")) { this.fx.start(0); - // this.menu.fade('out'); - this.fireEvent("hide"); + this.options.onHide.call(this); } return this; - }, + } - setItemChecked: function(item, checked) { + setItemChecked(item, checked) { this.menu.getElement("a[href$=" + item + "]").firstChild.style.opacity = checked ? "1" : "0"; return this; - }, + } - getItemChecked: function(item) { + getItemChecked(item) { return this.menu.getElement("a[href$=" + item + "]").firstChild.style.opacity !== "0"; - }, + } // hide an item - hideItem: function(item) { + hideItem(item) { this.menu.getElement("a[href$=" + item + "]").parentNode.addClass("invisible"); return this; - }, + } // show an item - showItem: function(item) { + showItem(item) { this.menu.getElement("a[href$=" + item + "]").parentNode.removeClass("invisible"); return this; - }, + } // enable/disable an item - setEnabled: function(item, enabled) { + setEnabled(item, enabled) { this.menu.querySelector(`:scope a[href$="${item}"]`).parentElement.classList.toggle("disabled", !enabled); return this; - }, + } // disable the entire menu - disable: function() { + disable() { this.options.disabled = true; return this; - }, + } // enable the entire menu - enable: function() { + enable() { this.options.disabled = false; return this; - }, + } // execute an action - execute: function(action, element) { + execute(action, element) { if (this.options.actions[action]) this.options.actions[action](element, this, action); return this; } - }); + }; - const FilterListContextMenu = new Class({ - Extends: ContextMenu, - initialize: function(options) { - this.parent(options); + class FilterListContextMenu extends ContextMenu { + constructor(options) { + super(options); this.torrentObserver = new MutationObserver((records, observer) => { this.updateTorrentActions(); }); - }, + } - startTorrentObserver: function() { + startTorrentObserver() { this.torrentObserver.observe(torrentsTable.tableBody, { childList: true }); - }, + } - stopTorrentObserver: function() { + stopTorrentObserver() { this.torrentObserver.disconnect(); - }, + } - updateTorrentActions: function() { + updateTorrentActions() { const torrentsVisible = torrentsTable.tableBody.children.length > 0; this.setEnabled("startTorrents", torrentsVisible) .setEnabled("stopTorrents", torrentsVisible) .setEnabled("deleteTorrents", torrentsVisible); } - }); + }; - const TorrentsTableContextMenu = new Class({ - Extends: ContextMenu, - - updateMenuItems: function() { + class TorrentsTableContextMenu extends ContextMenu { + updateMenuItems() { let all_are_seq_dl = true; let there_are_seq_dl = false; let all_are_f_l_piece_prio = true; @@ -485,9 +474,9 @@ window.qBittorrent.ContextMenu ??= (() => { const isEqual = ((count !== undefined) && (count === selectedRows.length)); categoryIcon.classList.toggle("highlightedCategoryIcon", isEqual); }); - }, + } - updateCategoriesSubMenu: function(categoryList) { + updateCategoriesSubMenu(categoryList) { const contextCategoryList = $("contextCategoryList"); contextCategoryList.getChildren().each(c => c.destroy()); @@ -540,9 +529,9 @@ window.qBittorrent.ContextMenu ??= (() => { contextCategoryList.appendChild(setCategoryItem); } - }, + } - updateTagsSubMenu: function(tagList) { + updateTagsSubMenu(tagList) { const contextTagList = $("contextTagList"); while (contextTagList.firstChild !== null) contextTagList.removeChild(contextTagList.firstChild); @@ -598,18 +587,16 @@ window.qBittorrent.ContextMenu ??= (() => { contextTagList.appendChild(setTagItem); } } - }); + }; - const StatusesFilterContextMenu = new Class({ - Extends: FilterListContextMenu, - updateMenuItems: function() { + class StatusesFilterContextMenu extends FilterListContextMenu { + updateMenuItems() { this.updateTorrentActions(); } - }); + }; - const CategoriesFilterContextMenu = new Class({ - Extends: FilterListContextMenu, - updateMenuItems: function() { + class CategoriesFilterContextMenu extends FilterListContextMenu { + updateMenuItems() { const id = Number(this.options.element.id); if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) { this.showItem("editCategory"); @@ -627,11 +614,10 @@ window.qBittorrent.ContextMenu ??= (() => { this.updateTorrentActions(); } - }); + }; - const TagsFilterContextMenu = new Class({ - Extends: FilterListContextMenu, - updateMenuItems: function() { + class TagsFilterContextMenu extends FilterListContextMenu { + updateMenuItems() { const id = Number(this.options.element.id); if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED)) this.showItem("deleteTag"); @@ -640,11 +626,10 @@ window.qBittorrent.ContextMenu ??= (() => { this.updateTorrentActions(); } - }); + }; - const TrackersFilterContextMenu = new Class({ - Extends: FilterListContextMenu, - updateMenuItems: function() { + class TrackersFilterContextMenu extends FilterListContextMenu { + updateMenuItems() { const id = Number(this.options.element.id); if ((id !== TRACKERS_ALL) && (id !== TRACKERS_TRACKERLESS)) this.showItem("deleteTracker"); @@ -653,12 +638,10 @@ window.qBittorrent.ContextMenu ??= (() => { this.updateTorrentActions(); } - }); + }; - const SearchPluginsTableContextMenu = new Class({ - Extends: ContextMenu, - - updateMenuItems: function() { + class SearchPluginsTableContextMenu extends ContextMenu { + updateMenuItems() { const enabledColumnIndex = function(text) { const columns = $("searchPluginsTableFixedHeaderRow").getChildren("th"); for (let i = 0; i < columns.length; ++i) { @@ -672,11 +655,10 @@ window.qBittorrent.ContextMenu ??= (() => { this.showItem("Uninstall"); } - }); + }; - const RssFeedContextMenu = new Class({ - Extends: ContextMenu, - updateMenuItems: function() { + class RssFeedContextMenu extends ContextMenu { + updateMenuItems() { const selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds(); this.menu.getElement("a[href$=newSubscription]").parentNode.addClass("separator"); switch (selectedRows.length) { @@ -746,15 +728,12 @@ window.qBittorrent.ContextMenu ??= (() => { break; } } - }); + }; - const RssArticleContextMenu = new Class({ - Extends: ContextMenu - }); + class RssArticleContextMenu extends ContextMenu {}; - const RssDownloaderRuleContextMenu = new Class({ - Extends: ContextMenu, - adjustMenuPosition: function(e) { + class RssDownloaderRuleContextMenu extends ContextMenu { + adjustMenuPosition(e) { this.updateMenuItems(); // draw the menu off-screen to know the menu dimensions @@ -774,8 +753,8 @@ window.qBittorrent.ContextMenu ??= (() => { this.menu.style.top = `${yPosMenu}px`; this.menu.style.position = "absolute"; this.menu.style.zIndex = "2000"; - }, - updateMenuItems: function() { + } + updateMenuItems() { const selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds(); this.showItem("addRule"); switch (selectedRows.length) { @@ -799,7 +778,7 @@ window.qBittorrent.ContextMenu ??= (() => { break; } } - }); + }; return exports(); })(); diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index e34611eec..59a1fa39b 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -291,21 +291,18 @@ window.qBittorrent.DynamicTable ??= (() => { }, setupDynamicTableHeaderContextMenuClass: function() { - if (!DynamicTableHeaderContextMenuClass) { - DynamicTableHeaderContextMenuClass = new Class({ - Extends: window.qBittorrent.ContextMenu.ContextMenu, - updateMenuItems: function() { - for (let i = 0; i < this.dynamicTable.columns.length; ++i) { - if (this.dynamicTable.columns[i].caption === "") - continue; - if (this.dynamicTable.columns[i].visible !== "0") - this.setItemChecked(this.dynamicTable.columns[i].name, true); - else - this.setItemChecked(this.dynamicTable.columns[i].name, false); - } + DynamicTableHeaderContextMenuClass ??= class extends window.qBittorrent.ContextMenu.ContextMenu { + updateMenuItems() { + for (let i = 0; i < this.dynamicTable.columns.length; ++i) { + if (this.dynamicTable.columns[i].caption === "") + continue; + if (this.dynamicTable.columns[i].visible !== "0") + this.setItemChecked(this.dynamicTable.columns[i].name, true); + else + this.setItemChecked(this.dynamicTable.columns[i].name, false); } - }); - } + } + }; }, showColumn: function(columnName, show) {