WebUI: Restore previously used tab on load

This PR restores the users previously used tab (Transfer, Search, RSS, etc.) when the WebUI is reloaded.

PR #20705.
This commit is contained in:
Thomas Piccirello 2024-05-27 05:51:02 -07:00 committed by sledgehammer999
parent 8a15ea8026
commit d2e5163861
No known key found for this signature in database
GPG key ID: 6E4A2D025B7CC9A2
4 changed files with 979 additions and 866 deletions

View file

@ -37,7 +37,13 @@ window.qBittorrent.Client = (() => {
getSyncMainDataInterval: getSyncMainDataInterval,
isStopped: isStopped,
stop: stop,
mainTitle: mainTitle
mainTitle: mainTitle,
showSearchEngine: showSearchEngine,
showRssReader: showRssReader,
showLogViewer: showLogViewer,
isShowSearchEngine: isShowSearchEngine,
isShowRssReader: isShowRssReader,
isShowLogViewer: isShowLogViewer
};
};
@ -77,6 +83,29 @@ window.qBittorrent.Client = (() => {
return title;
};
let showingSearchEngine = false;
let showingRssReader = false;
let showingLogViewer = false;
const showSearchEngine = function(bool) {
showingSearchEngine = bool;
};
const showRssReader = function(bool) {
showingRssReader = bool;
};
const showLogViewer = function(bool) {
showingLogViewer = bool;
};
const isShowSearchEngine = function() {
return showingSearchEngine;
};
const isShowRssReader = function() {
return showingRssReader;
};
const isShowLogViewer = function() {
return showingLogViewer;
};
return exports();
})();
Object.freeze(window.qBittorrent.Client);
@ -128,6 +157,9 @@ let setFilter = function() {};
let toggleFilterDisplay = function() {};
window.addEventListener("DOMContentLoaded", function() {
let isSearchPanelLoaded = false;
let isLogPanelLoaded = false;
const saveColumnSizes = function() {
const filters_width = $('Filters').getSize().x;
LocalPreferences.set('filters_width', filters_width);
@ -307,9 +339,9 @@ window.addEventListener("DOMContentLoaded", function() {
$('speedInBrowserTitleBarLink').firstChild.style.opacity = '0';
// After showing/hiding the toolbar + status bar
let showSearchEngine = LocalPreferences.get('show_search_engine') !== "false";
let showRssReader = LocalPreferences.get('show_rss_reader') !== "false";
let showLogViewer = LocalPreferences.get('show_log_viewer') === 'true';
window.qBittorrent.Client.showSearchEngine(LocalPreferences.get('show_search_engine') !== "false");
window.qBittorrent.Client.showRssReader(LocalPreferences.get('show_rss_reader') !== "false");
window.qBittorrent.Client.showLogViewer(LocalPreferences.get('show_log_viewer') === 'true');
// After Show Top Toolbar
MochaUI.Desktop.setDesktopSize();
@ -1070,25 +1102,25 @@ window.addEventListener("DOMContentLoaded", function() {
});
$('showSearchEngineLink').addEvent('click', function(e) {
showSearchEngine = !showSearchEngine;
LocalPreferences.set('show_search_engine', showSearchEngine.toString());
window.qBittorrent.Client.showSearchEngine(!window.qBittorrent.Client.isShowSearchEngine());
LocalPreferences.set('show_search_engine', window.qBittorrent.Client.isShowSearchEngine().toString());
updateTabDisplay();
});
$('showRssReaderLink').addEvent('click', function(e) {
showRssReader = !showRssReader;
LocalPreferences.set('show_rss_reader', showRssReader.toString());
window.qBittorrent.Client.showRssReader(!window.qBittorrent.Client.isShowRssReader());
LocalPreferences.set('show_rss_reader', window.qBittorrent.Client.isShowRssReader().toString());
updateTabDisplay();
});
$('showLogViewerLink').addEvent('click', function(e) {
showLogViewer = !showLogViewer;
LocalPreferences.set('show_log_viewer', showLogViewer.toString());
window.qBittorrent.Client.showLogViewer(!window.qBittorrent.Client.isShowLogViewer());
LocalPreferences.set('show_log_viewer', window.qBittorrent.Client.isShowLogViewer().toString());
updateTabDisplay();
});
const updateTabDisplay = function() {
if (showRssReader) {
if (window.qBittorrent.Client.isShowRssReader()) {
$('showRssReaderLink').firstChild.style.opacity = '1';
$('mainWindowTabs').removeClass('invisible');
$('rssTabLink').removeClass('invisible');
@ -1102,7 +1134,7 @@ window.addEventListener("DOMContentLoaded", function() {
$("transfersTabLink").click();
}
if (showSearchEngine) {
if (window.qBittorrent.Client.isShowSearchEngine()) {
$('showSearchEngineLink').firstChild.style.opacity = '1';
$('mainWindowTabs').removeClass('invisible');
$('searchTabLink').removeClass('invisible');
@ -1116,7 +1148,7 @@ window.addEventListener("DOMContentLoaded", function() {
$("transfersTabLink").click();
}
if (showLogViewer) {
if (window.qBittorrent.Client.isShowLogViewer()) {
$('showLogViewerLink').firstChild.style.opacity = '1';
$('mainWindowTabs').removeClass('invisible');
$('logTabLink').removeClass('invisible');
@ -1131,7 +1163,7 @@ window.addEventListener("DOMContentLoaded", function() {
}
// display no tabs
if (!showRssReader && !showSearchEngine && !showLogViewer)
if (!window.qBittorrent.Client.isShowRssReader() && !window.qBittorrent.Client.isShowSearchEngine() && !window.qBittorrent.Client.isShowLogViewer())
$('mainWindowTabs').addClass('invisible');
};
@ -1154,6 +1186,8 @@ window.addEventListener("DOMContentLoaded", function() {
hideSearchTab();
hideRssTab();
hideLogTab();
LocalPreferences.set('selected_tab', 'transfers');
};
const hideTransfersTab = function() {
@ -1168,6 +1202,16 @@ window.addEventListener("DOMContentLoaded", function() {
let searchTabInitialized = false;
return () => {
// we must wait until the panel is fully loaded before proceeding.
// this include's the panel's custom js, which is loaded via MochaUI.Panel's 'require' field.
// MochaUI loads these files asynchronously and thus all required libs may not be available immediately
if (!isSearchPanelLoaded) {
setTimeout(() => {
showSearchTab();
}, 100);
return;
}
if (!searchTabInitialized) {
window.qBittorrent.Search.init();
searchTabInitialized = true;
@ -1178,6 +1222,8 @@ window.addEventListener("DOMContentLoaded", function() {
hideTransfersTab();
hideRssTab();
hideLogTab();
LocalPreferences.set('selected_tab', 'search');
};
})();
@ -1203,6 +1249,8 @@ window.addEventListener("DOMContentLoaded", function() {
hideTransfersTab();
hideSearchTab();
hideLogTab();
LocalPreferences.set('selected_tab', 'rss');
};
})();
@ -1216,6 +1264,16 @@ window.addEventListener("DOMContentLoaded", function() {
let logTabInitialized = false;
return () => {
// we must wait until the panel is fully loaded before proceeding.
// this include's the panel's custom js, which is loaded via MochaUI.Panel's 'require' field.
// MochaUI loads these files asynchronously and thus all required libs may not be available immediately
if (!isLogPanelLoaded) {
setTimeout(() => {
showLogTab();
}, 100);
return;
}
if (!logTabInitialized) {
window.qBittorrent.Log.init();
logTabInitialized = true;
@ -1229,6 +1287,8 @@ window.addEventListener("DOMContentLoaded", function() {
hideTransfersTab();
hideSearchTab();
hideRssTab();
LocalPreferences.set('selected_tab', 'log');
};
})();
@ -1251,6 +1311,12 @@ window.addEventListener("DOMContentLoaded", function() {
},
loadMethod: 'xhr',
contentURL: 'views/search.html',
require: {
js: ['scripts/search.js'],
onload: () => {
isSearchPanelLoaded = true;
},
},
content: '',
column: 'searchTabColumn',
height: null
@ -1292,6 +1358,9 @@ window.addEventListener("DOMContentLoaded", function() {
require: {
css: ['css/vanillaSelectBox.css'],
js: ['scripts/lib/vanillaSelectBox.js'],
onload: () => {
isLogPanelLoaded = true;
},
},
tabsURL: 'views/logTabs.html',
tabsOnload: function() {
@ -1594,4 +1663,27 @@ window.addEventListener("load", () => {
window.qBittorrent.Cache.buildInfo.init();
window.qBittorrent.Cache.preferences.init();
window.qBittorrent.Cache.qbtVersion.init();
// switch to previously used tab
const previouslyUsedTab = LocalPreferences.get('selected_tab', 'transfers');
switch (previouslyUsedTab) {
case 'search':
if (window.qBittorrent.Client.isShowSearchEngine())
$('searchTabLink').click();
break;
case 'rss':
if (window.qBittorrent.Client.isShowRssReader())
$('rssTabLink').click();
break;
case 'log':
if (window.qBittorrent.Client.isShowLogViewer())
$('logTabLink').click();
break;
case 'transfers':
$('transfersTabLink').click();
break;
default:
console.error(`Unexpected 'selected_tab' value: ${previouslyUsedTab}`);
$('transfersTabLink').click();
};
});

View file

@ -0,0 +1,872 @@
/*
* MIT License
* Copyright (C) 2024 Thomas Piccirello
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
'use strict';
if (window.qBittorrent === undefined) {
window.qBittorrent = {};
}
window.qBittorrent.Search = (function() {
const exports = function() {
return {
startStopSearch: startStopSearch,
manageSearchPlugins: manageSearchPlugins,
searchPlugins: searchPlugins,
searchText: searchText,
searchSeedsFilter: searchSeedsFilter,
searchSizeFilter: searchSizeFilter,
init: init,
getPlugin: getPlugin,
searchInTorrentName: searchInTorrentName,
onSearchPatternChanged: onSearchPatternChanged,
categorySelected: categorySelected,
pluginSelected: pluginSelected,
searchSeedsFilterChanged: searchSeedsFilterChanged,
searchSizeFilterChanged: searchSizeFilterChanged,
searchSizeFilterPrefixChanged: searchSizeFilterPrefixChanged,
closeSearchTab: closeSearchTab,
};
};
const searchTabIdPrefix = "Search-";
let loadSearchPluginsTimer;
const searchPlugins = [];
let prevSearchPluginsResponse;
let selectedCategory = "QBT_TR(All categories)QBT_TR[CONTEXT=SearchEngineWidget]";
let selectedPlugin = "all";
let prevSelectedPlugin;
// whether the current search pattern differs from the pattern that the active search was performed with
let searchPatternChanged = false;
let searchResultsTable;
/** @type Map<number, {
* searchPattern: string,
* filterPattern: string,
* seedsFilter: {min: number, max: number},
* sizeFilter: {min: number, minUnit: number, max: number, maxUnit: number},
* searchIn: string,
* rows: [],
* rowId: number,
* selectedRowIds: number[],
* running: boolean,
* loadResultsTimer: Timer,
* sort: {column: string, reverse: string},
* }> **/
const searchState = new Map();
const searchText = {
pattern: "",
filterPattern: ""
};
const searchSeedsFilter = {
min: 0,
max: 0
};
const searchSizeFilter = {
min: 0.00,
minUnit: 2, // B = 0, KiB = 1, MiB = 2, GiB = 3, TiB = 4, PiB = 5, EiB = 6
max: 0.00,
maxUnit: 3
};
const init = function() {
// load "Search in" preference from local storage
$('searchInTorrentName').set('value', (LocalPreferences.get('search_in_filter') === "names") ? "names" : "everywhere");
const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: '.searchTableRow',
menu: 'searchResultsTableMenu',
actions: {
Download: downloadSearchTorrent,
OpenDescriptionUrl: openSearchTorrentDescriptionUrl
},
offsets: {
x: -15,
y: -53
}
});
searchResultsTable = new window.qBittorrent.DynamicTable.SearchResultsTable();
searchResultsTable.setup('searchResultsTableDiv', 'searchResultsTableFixedHeaderDiv', searchResultsTableContextMenu);
getPlugins();
// listen for changes to searchInNameFilter
let searchInNameFilterTimer = -1;
$('searchInNameFilter').addEvent('input', () => {
clearTimeout(searchInNameFilterTimer);
searchInNameFilterTimer = setTimeout(() => {
searchInNameFilterTimer = -1;
const value = $('searchInNameFilter').get("value");
searchText.filterPattern = value;
searchFilterChanged();
}, window.qBittorrent.Misc.FILTER_INPUT_DELAY);
});
new Keyboard({
defaultEventType: 'keydown',
events: {
'Enter': function(e) {
// accept enter key as a click
new Event(e).stop();
const elem = e.event.srcElement;
if (elem.className.contains("searchInputField")) {
$('startSearchButton').click();
return;
}
switch (elem.id) {
case "manageSearchPlugins":
manageSearchPlugins();
break;
}
}
}
}).activate();
// restore search tabs
const searchJobs = JSON.parse(LocalPreferences.get('search_jobs', '[]'));
for (const { id, pattern } of searchJobs) {
createSearchTab(id, pattern);
}
};
const numSearchTabs = function() {
return $('searchTabs').getElements('li').length;
};
const getSearchIdFromTab = function(tab) {
return Number(tab.id.substring(searchTabIdPrefix.length));
};
const createSearchTab = function(searchId, pattern) {
const newTabId = `${searchTabIdPrefix}${searchId}`;
const tabElem = new Element('a', {
text: pattern,
});
const closeTabElem = new Element('img', {
alt: 'QBT_TR(Close tab)QBT_TR[CONTEXT=SearchWidget]',
title: 'QBT_TR(Close tab)QBT_TR[CONTEXT=SearchWidget]',
src: 'images/application-exit.svg',
width: '8',
height: '8',
style: 'padding-right: 7px; margin-bottom: -1px; margin-left: -5px',
onclick: 'qBittorrent.Search.closeSearchTab(this)',
});
closeTabElem.inject(tabElem, 'top');
tabElem.appendChild(getStatusIconElement('QBT_TR(Searching...)QBT_TR[CONTEXT=SearchJobWidget]', 'images/queued.svg'));
$('searchTabs').appendChild(new Element('li', {
id: newTabId,
class: 'selected',
html: tabElem.outerHTML,
}));
// unhide the results elements
if (numSearchTabs() >= 1) {
$('searchResultsNoSearches').style.display = "none";
$('searchResultsFilters').style.display = "block";
$('searchResultsTableContainer').style.display = "block";
$('searchTabsToolbar').style.display = "block";
}
// reinitialize tabs
$('searchTabs').getElements('li').removeEvents('click');
$('searchTabs').getElements('li').addEvent('click', function(e) {
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
setActiveTab(this);
});
// select new tab
setActiveTab($(newTabId));
searchResultsTable.clear();
resetFilters();
searchState.set(searchId, {
searchPattern: pattern,
filterPattern: searchText.filterPattern,
seedsFilter: { min: searchSeedsFilter.min, max: searchSeedsFilter.max },
sizeFilter: { min: searchSizeFilter.min, minUnit: searchSizeFilter.minUnit, max: searchSizeFilter.max, maxUnit: searchSizeFilter.maxUnit },
searchIn: getSearchInTorrentName(),
rows: [],
rowId: 0,
selectedRowIds: [],
running: true,
loadResultsTimer: null,
sort: { column: searchResultsTable.sortedColumn, reverse: searchResultsTable.reverseSort },
});
updateSearchResultsData(searchId);
};
const closeSearchTab = function(el) {
const tab = el.parentElement.parentElement;
const searchId = getSearchIdFromTab(tab);
const isTabSelected = tab.hasClass('selected');
const newTabToSelect = isTabSelected ? tab.nextSibling || tab.previousSibling : null;
const currentSearchId = getSelectedSearchId();
const state = searchState.get(currentSearchId);
// don't bother sending a stop request if already stopped
if (state && state.running) {
stopSearch(searchId);
}
tab.destroy();
new Request({
url: new URI('api/v2/search/delete'),
method: 'post',
data: {
id: searchId
},
}).send();
const searchJobs = JSON.parse(LocalPreferences.get('search_jobs', '[]'));
const jobIndex = searchJobs.findIndex((job) => job.id === searchId);
if (jobIndex >= 0) {
searchJobs.splice(jobIndex, 1);
LocalPreferences.set('search_jobs', JSON.stringify(searchJobs));
}
if (numSearchTabs() === 0) {
resetSearchState();
resetFilters();
$('numSearchResultsVisible').set('html', 0);
$('numSearchResultsTotal').set('html', 0);
$('searchResultsNoSearches').style.display = "block";
$('searchResultsFilters').style.display = "none";
$('searchResultsTableContainer').style.display = "none";
$('searchTabsToolbar').style.display = "none";
}
else if (isTabSelected && newTabToSelect) {
setActiveTab(newTabToSelect);
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
}
};
const saveCurrentTabState = function() {
const currentSearchId = getSelectedSearchId();
if (!currentSearchId)
return;
const state = searchState.get(currentSearchId);
if (!state)
return;
state.filterPattern = searchText.filterPattern;
state.seedsFilter = {
min: searchSeedsFilter.min,
max: searchSeedsFilter.max,
};
state.sizeFilter = {
min: searchSizeFilter.min,
minUnit: searchSizeFilter.minUnit,
max: searchSizeFilter.max,
maxUnit: searchSizeFilter.maxUnit,
};
state.searchIn = getSearchInTorrentName();
state.sort = {
column: searchResultsTable.sortedColumn,
reverse: searchResultsTable.reverseSort,
};
// we must copy the array to avoid taking a reference to it
state.selectedRowIds = [...searchResultsTable.selectedRows];
};
const setActiveTab = function(tab) {
const searchId = getSearchIdFromTab(tab);
if (searchId === getSelectedSearchId())
return;
saveCurrentTabState();
MochaUI.selected(tab, 'searchTabs');
const state = searchState.get(searchId);
let rowsToSelect = [];
// restore table rows
searchResultsTable.clear();
if (state) {
for (const row of state.rows) {
searchResultsTable.updateRowData(row);
}
rowsToSelect = state.selectedRowIds;
// restore filters
searchText.pattern = state.searchPattern;
searchText.filterPattern = state.filterPattern;
$('searchInNameFilter').set("value", state.filterPattern);
searchSeedsFilter.min = state.seedsFilter.min;
searchSeedsFilter.max = state.seedsFilter.max;
$('searchMinSeedsFilter').set('value', state.seedsFilter.min);
$('searchMaxSeedsFilter').set('value', state.seedsFilter.max);
searchSizeFilter.min = state.sizeFilter.min;
searchSizeFilter.minUnit = state.sizeFilter.minUnit;
searchSizeFilter.max = state.sizeFilter.max;
searchSizeFilter.maxUnit = state.sizeFilter.maxUnit;
$('searchMinSizeFilter').set('value', state.sizeFilter.min);
$('searchMinSizePrefix').set('value', state.sizeFilter.minUnit);
$('searchMaxSizeFilter').set('value', state.sizeFilter.max);
$('searchMaxSizePrefix').set('value', state.sizeFilter.maxUnit);
const currentSearchPattern = $('searchPattern').getProperty('value').trim();
if (state.running && (state.searchPattern === currentSearchPattern)) {
// allow search to be stopped
$('startSearchButton').set('text', 'QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]');
searchPatternChanged = false;
}
searchResultsTable.setSortedColumn(state.sort.column, state.sort.reverse);
$('searchInTorrentName').set('value', state.searchIn);
}
// must restore all filters before calling updateTable
searchResultsTable.updateTable();
searchResultsTable.altRow();
// must reselect rows after calling updateTable
if (rowsToSelect.length > 0) {
searchResultsTable.reselectRows(rowsToSelect);
}
$('numSearchResultsVisible').set('html', searchResultsTable.getFilteredAndSortedRows().length);
$('numSearchResultsTotal').set('html', searchResultsTable.getRowIds().length);
setupSearchTableEvents(true);
};
const getStatusIconElement = function(text, image) {
return new Element('img', {
alt: text,
title: text,
src: image,
class: 'statusIcon',
width: '10',
height: '10',
style: 'margin-bottom: -2px; margin-left: 7px',
});
};
const updateStatusIconElement = function(searchId, text, image) {
const searchTab = $(`${searchTabIdPrefix}${searchId}`);
if (searchTab) {
const statusIcon = searchTab.getElement('.statusIcon');
statusIcon.set('alt', text);
statusIcon.set('title', text);
statusIcon.set('src', image);
}
};
const startSearch = function(pattern, category, plugins) {
searchPatternChanged = false;
const url = new URI('api/v2/search/start');
new Request.JSON({
url: url,
method: 'post',
data: {
pattern: pattern,
category: category,
plugins: plugins
},
onSuccess: function(response) {
$('startSearchButton').set('text', 'QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]');
const searchId = response.id;
createSearchTab(searchId, pattern);
const searchJobs = JSON.parse(LocalPreferences.get('search_jobs', '[]'));
searchJobs.push({ id: searchId, pattern: pattern });
LocalPreferences.set('search_jobs', JSON.stringify(searchJobs));
}
}).send();
};
const stopSearch = function(searchId) {
const url = new URI('api/v2/search/stop');
new Request({
url: url,
method: 'post',
data: {
id: searchId
},
onSuccess: function(response) {
resetSearchState(searchId);
// not strictly necessary to do this when the tab is being closed, but there's no harm in it
updateStatusIconElement(searchId, 'QBT_TR(Search aborted)QBT_TR[CONTEXT=SearchJobWidget]', 'images/task-reject.svg');
}
}).send();
};
const getSelectedSearchId = function() {
const selectedTab = $('searchTabs').getElement('li.selected');
return selectedTab ? getSearchIdFromTab(selectedTab) : null;
};
const startStopSearch = function() {
const currentSearchId = getSelectedSearchId();
const state = searchState.get(currentSearchId);
const isSearchRunning = state && state.running;
if (!isSearchRunning || searchPatternChanged) {
const pattern = $('searchPattern').getProperty('value').trim();
let category = $('categorySelect').getProperty('value');
const plugins = $('pluginsSelect').getProperty('value');
if (!pattern || !category || !plugins)
return;
searchText.pattern = pattern;
startSearch(pattern, category, plugins);
}
else {
stopSearch(currentSearchId);
}
};
const openSearchTorrentDescriptionUrl = function() {
searchResultsTable.selectedRowsIds().each(function(rowId) {
window.open(searchResultsTable.rows.get(rowId).full_data.descrLink, "_blank");
});
};
const copySearchTorrentName = function() {
const names = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
names.push(searchResultsTable.rows.get(rowId).full_data.fileName);
});
return names.join("\n");
};
const copySearchTorrentDownloadLink = function() {
const urls = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
urls.push(searchResultsTable.rows.get(rowId).full_data.fileUrl);
});
return urls.join("\n");
};
const copySearchTorrentDescriptionUrl = function() {
const urls = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
urls.push(searchResultsTable.rows.get(rowId).full_data.descrLink);
});
return urls.join("\n");
};
const downloadSearchTorrent = function() {
const urls = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
urls.push(searchResultsTable.rows.get(rowId).full_data.fileUrl);
});
// only proceed if at least 1 row was selected
if (!urls.length)
return;
showDownloadPage(urls);
};
const manageSearchPlugins = function() {
const id = 'searchPlugins';
if (!$(id))
new MochaUI.Window({
id: id,
title: "QBT_TR(Search plugins)QBT_TR[CONTEXT=PluginSelectDlg]",
loadMethod: 'xhr',
contentURL: 'views/searchplugins.html',
scrollbars: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(id, 600),
height: loadWindowHeight(id, 360),
onResize: function() {
saveWindowSize(id);
},
onBeforeBuild: function() {
loadSearchPlugins();
},
onClose: function() {
clearTimeout(loadSearchPluginsTimer);
}
});
};
const loadSearchPlugins = function() {
getPlugins();
loadSearchPluginsTimer = loadSearchPlugins.delay(2000);
};
const onSearchPatternChanged = function() {
const currentSearchId = getSelectedSearchId();
const state = searchState.get(currentSearchId);
const currentSearchPattern = $('searchPattern').getProperty('value').trim();
// start a new search if pattern has changed, otherwise allow the search to be stopped
if (state && (state.searchPattern === currentSearchPattern)) {
searchPatternChanged = false;
$('startSearchButton').set('text', 'QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]');
}
else {
searchPatternChanged = true;
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
}
};
const categorySelected = function() {
selectedCategory = $("categorySelect").get("value");
};
const pluginSelected = function() {
selectedPlugin = $("pluginsSelect").get("value");
if (selectedPlugin !== prevSelectedPlugin) {
prevSelectedPlugin = selectedPlugin;
getSearchCategories();
}
};
const reselectCategory = function() {
for (let i = 0; i < $("categorySelect").options.length; ++i)
if ($("categorySelect").options[i].get("value") === selectedCategory)
$("categorySelect").options[i].selected = true;
categorySelected();
};
const reselectPlugin = function() {
for (let i = 0; i < $("pluginsSelect").options.length; ++i)
if ($("pluginsSelect").options[i].get("value") === selectedPlugin)
$("pluginsSelect").options[i].selected = true;
pluginSelected();
};
const resetSearchState = function(searchId) {
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
const state = searchState.get(searchId);
if (state) {
state.running = false;
clearTimeout(state.loadResultsTimer);
}
};
const getSearchCategories = function() {
const populateCategorySelect = function(categories) {
const categoryHtml = [];
categories.each(function(category) {
const option = new Element("option");
option.set("value", category.id);
option.set("html", category.name);
categoryHtml.push(option.outerHTML);
});
// first category is "All Categories"
if (categoryHtml.length > 1) {
// add separator
const option = new Element("option");
option.set("disabled", true);
option.set("html", "──────────");
categoryHtml.splice(1, 0, option.outerHTML);
}
$('categorySelect').set('html', categoryHtml.join(""));
};
const selectedPlugin = $('pluginsSelect').get("value");
if ((selectedPlugin === "all") || (selectedPlugin === "enabled")) {
const uniqueCategories = {};
for (const plugin of searchPlugins) {
if ((selectedPlugin === "enabled") && !plugin.enabled)
continue;
for (const category of plugin.supportedCategories) {
if (uniqueCategories[category.id] === undefined) {
uniqueCategories[category.id] = category;
}
}
}
// we must sort the ids to maintain consistent order.
const categories = Object.keys(uniqueCategories).sort().map(id => uniqueCategories[id]);
populateCategorySelect(categories);
}
else {
const plugin = getPlugin(selectedPlugin);
const plugins = (plugin === null) ? [] : plugin.supportedCategories;
populateCategorySelect(plugins);
}
reselectCategory();
};
const getPlugins = function() {
new Request.JSON({
url: new URI('api/v2/search/plugins'),
method: 'get',
noCache: true,
onSuccess: function(response) {
if (response !== prevSearchPluginsResponse) {
prevSearchPluginsResponse = response;
searchPlugins.length = 0;
response.forEach(function(plugin) {
searchPlugins.push(plugin);
});
const pluginsHtml = [];
pluginsHtml.push('<option value="enabled">QBT_TR(Only enabled)QBT_TR[CONTEXT=SearchEngineWidget]</option>');
pluginsHtml.push('<option value="all">QBT_TR(All plugins)QBT_TR[CONTEXT=SearchEngineWidget]</option>');
const searchPluginsEmpty = (searchPlugins.length === 0);
if (!searchPluginsEmpty) {
$('searchResultsNoPlugins').style.display = "none";
if (numSearchTabs() === 0) {
$('searchResultsNoSearches').style.display = "block";
}
// sort plugins alphabetically
const allPlugins = searchPlugins.sort((left, right) => {
const leftName = left.fullName;
const rightName = right.fullName;
return window.qBittorrent.Misc.naturalSortCollator.compare(leftName, rightName);
});
allPlugins.each(function(plugin) {
if (plugin.enabled === true)
pluginsHtml.push("<option value='" + window.qBittorrent.Misc.escapeHtml(plugin.name) + "'>" + window.qBittorrent.Misc.escapeHtml(plugin.fullName) + "</option>");
});
if (pluginsHtml.length > 2)
pluginsHtml.splice(2, 0, "<option disabled>──────────</option>");
}
$('pluginsSelect').set('html', pluginsHtml.join(""));
$('searchPattern').setProperty('disabled', searchPluginsEmpty);
$('categorySelect').setProperty('disabled', searchPluginsEmpty);
$('pluginsSelect').setProperty('disabled', searchPluginsEmpty);
$('startSearchButton').setProperty('disabled', searchPluginsEmpty);
if (window.qBittorrent.SearchPlugins !== undefined)
window.qBittorrent.SearchPlugins.updateTable();
reselectPlugin();
}
}
}).send();
};
const getPlugin = function(name) {
for (let i = 0; i < searchPlugins.length; ++i)
if (searchPlugins[i].name === name)
return searchPlugins[i];
return null;
};
const resetFilters = function() {
searchText.filterPattern = '';
$('searchInNameFilter').set('value', '');
searchSeedsFilter.min = 0;
searchSeedsFilter.max = 0;
$('searchMinSeedsFilter').set('value', searchSeedsFilter.min);
$('searchMaxSeedsFilter').set('value', searchSeedsFilter.max);
searchSizeFilter.min = 0.00;
searchSizeFilter.minUnit = 2; // B = 0, KiB = 1, MiB = 2, GiB = 3, TiB = 4, PiB = 5, EiB = 6
searchSizeFilter.max = 0.00;
searchSizeFilter.maxUnit = 3;
$('searchMinSizeFilter').set('value', searchSizeFilter.min);
$('searchMinSizePrefix').set('value', searchSizeFilter.minUnit);
$('searchMaxSizeFilter').set('value', searchSizeFilter.max);
$('searchMaxSizePrefix').set('value', searchSizeFilter.maxUnit);
};
const getSearchInTorrentName = function() {
return $('searchInTorrentName').get('value') === "names" ? "names" : "everywhere";
};
const searchInTorrentName = function() {
LocalPreferences.set('search_in_filter', getSearchInTorrentName());
searchFilterChanged();
};
const searchSeedsFilterChanged = function() {
searchSeedsFilter.min = $('searchMinSeedsFilter').get('value');
searchSeedsFilter.max = $('searchMaxSeedsFilter').get('value');
searchFilterChanged();
};
const searchSizeFilterChanged = function() {
searchSizeFilter.min = $('searchMinSizeFilter').get('value');
searchSizeFilter.minUnit = $('searchMinSizePrefix').get('value');
searchSizeFilter.max = $('searchMaxSizeFilter').get('value');
searchSizeFilter.maxUnit = $('searchMaxSizePrefix').get('value');
searchFilterChanged();
};
const searchSizeFilterPrefixChanged = function() {
if ((Number($('searchMinSizeFilter').get('value')) !== 0) || (Number($('searchMaxSizeFilter').get('value')) !== 0))
searchSizeFilterChanged();
};
const searchFilterChanged = function() {
searchResultsTable.updateTable();
$('numSearchResultsVisible').set('html', searchResultsTable.getFilteredAndSortedRows().length);
};
const setupSearchTableEvents = function(enable) {
if (enable)
$$(".searchTableRow").each(function(target) {
target.addEventListener('dblclick', downloadSearchTorrent, false);
});
else
$$(".searchTableRow").each(function(target) {
target.removeEventListener('dblclick', downloadSearchTorrent, false);
});
};
const loadSearchResultsData = function(searchId) {
const state = searchState.get(searchId);
const maxResults = 500;
const url = new URI('api/v2/search/results');
new Request.JSON({
url: url,
method: 'get',
noCache: true,
data: {
id: searchId,
limit: maxResults,
offset: state.rowId
},
onFailure: function(response) {
if ((response.status === 400) || (response.status === 404)) {
// bad params. search id is invalid
resetSearchState(searchId);
updateStatusIconElement(searchId, 'QBT_TR(An error occurred during search...)QBT_TR[CONTEXT=SearchJobWidget]', 'images/error.svg');
}
else {
clearTimeout(state.loadResultsTimer);
state.loadResultsTimer = loadSearchResultsData.delay(3000, this, searchId);
}
},
onSuccess: function(response) {
$('error_div').set('html', '');
const state = searchState.get(searchId);
// check if user stopped the search prior to receiving the response
if (!state.running) {
clearTimeout(state.loadResultsTimer);
updateStatusIconElement(searchId, 'QBT_TR(Search aborted)QBT_TR[CONTEXT=SearchJobWidget]', 'images/task-reject.svg');
return;
}
if (response) {
setupSearchTableEvents(false);
const state = searchState.get(searchId);
const newRows = [];
if (response.results) {
const results = response.results;
for (let i = 0; i < results.length; ++i) {
const result = results[i];
const row = {
rowId: state.rowId,
descrLink: result.descrLink,
fileName: result.fileName,
fileSize: result.fileSize,
fileUrl: result.fileUrl,
nbLeechers: result.nbLeechers,
nbSeeders: result.nbSeeders,
siteUrl: result.siteUrl,
pubDate: result.pubDate,
};
newRows.push(row);
state.rows.push(row);
state.rowId += 1;
}
}
// only update table if this search is currently being displayed
if (searchId === getSelectedSearchId()) {
for (const row of newRows) {
searchResultsTable.updateRowData(row);
}
$('numSearchResultsVisible').set('html', searchResultsTable.getFilteredAndSortedRows().length);
$('numSearchResultsTotal').set('html', searchResultsTable.getRowIds().length);
searchResultsTable.updateTable();
searchResultsTable.altRow();
}
setupSearchTableEvents(true);
if ((response.status === "Stopped") && (state.rowId >= response.total)) {
resetSearchState(searchId);
updateStatusIconElement(searchId, 'QBT_TR(Search has finished)QBT_TR[CONTEXT=SearchJobWidget]', 'images/task-complete.svg');
return;
}
}
clearTimeout(state.loadResultsTimer);
state.loadResultsTimer = loadSearchResultsData.delay(2000, this, searchId);
}
}).send();
};
const updateSearchResultsData = function(searchId) {
const state = searchState.get(searchId);
clearTimeout(state.loadResultsTimer);
state.loadResultsTimer = loadSearchResultsData.delay(500, this, searchId);
};
new ClipboardJS('.copySearchDataToClipboard', {
text: function(trigger) {
switch (trigger.id) {
case "copySearchTorrentName":
return copySearchTorrentName();
case "copySearchTorrentDownloadLink":
return copySearchTorrentDownloadLink();
case "copySearchTorrentDescriptionUrl":
return copySearchTorrentDescriptionUrl();
default:
return "";
}
}
});
return exports();
})();
Object.freeze(window.qBittorrent.Search);

View file

@ -199,855 +199,3 @@
</ul>
</li>
</ul>
<script>
'use strict';
if (window.qBittorrent === undefined) {
window.qBittorrent = {};
}
window.qBittorrent.Search = (function() {
const exports = function() {
return {
startStopSearch: startStopSearch,
manageSearchPlugins: manageSearchPlugins,
searchPlugins: searchPlugins,
searchText: searchText,
searchSeedsFilter: searchSeedsFilter,
searchSizeFilter: searchSizeFilter,
init: init,
getPlugin: getPlugin,
searchInTorrentName: searchInTorrentName,
onSearchPatternChanged: onSearchPatternChanged,
categorySelected: categorySelected,
pluginSelected: pluginSelected,
searchSeedsFilterChanged: searchSeedsFilterChanged,
searchSizeFilterChanged: searchSizeFilterChanged,
searchSizeFilterPrefixChanged: searchSizeFilterPrefixChanged,
closeSearchTab: closeSearchTab,
};
};
const searchTabIdPrefix = "Search-";
let loadSearchPluginsTimer;
const searchPlugins = [];
let prevSearchPluginsResponse;
let selectedCategory = "QBT_TR(All categories)QBT_TR[CONTEXT=SearchEngineWidget]";
let selectedPlugin = "all";
let prevSelectedPlugin;
// whether the current search pattern differs from the pattern that the active search was performed with
let searchPatternChanged = false;
let searchResultsTable;
/** @type Map<number, {
* searchPattern: string,
* filterPattern: string,
* seedsFilter: {min: number, max: number},
* sizeFilter: {min: number, minUnit: number, max: number, maxUnit: number},
* searchIn: string,
* rows: [],
* rowId: number,
* selectedRowIds: number[],
* running: boolean,
* loadResultsTimer: Timer,
* sort: {column: string, reverse: string},
* }> **/
const searchState = new Map();
const searchText = {
pattern: "",
filterPattern: ""
};
const searchSeedsFilter = {
min: 0,
max: 0
};
const searchSizeFilter = {
min: 0.00,
minUnit: 2, // B = 0, KiB = 1, MiB = 2, GiB = 3, TiB = 4, PiB = 5, EiB = 6
max: 0.00,
maxUnit: 3
};
const init = function() {
// load "Search in" preference from local storage
$('searchInTorrentName').set('value', (LocalPreferences.get('search_in_filter') === "names") ? "names" : "everywhere");
const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: '.searchTableRow',
menu: 'searchResultsTableMenu',
actions: {
Download: downloadSearchTorrent,
OpenDescriptionUrl: openSearchTorrentDescriptionUrl
},
offsets: {
x: -15,
y: -53
}
});
searchResultsTable = new window.qBittorrent.DynamicTable.SearchResultsTable();
searchResultsTable.setup('searchResultsTableDiv', 'searchResultsTableFixedHeaderDiv', searchResultsTableContextMenu);
getPlugins();
// listen for changes to searchInNameFilter
let searchInNameFilterTimer = -1;
$('searchInNameFilter').addEvent('input', () => {
clearTimeout(searchInNameFilterTimer);
searchInNameFilterTimer = setTimeout(() => {
searchInNameFilterTimer = -1;
const value = $('searchInNameFilter').get("value");
searchText.filterPattern = value;
searchFilterChanged();
}, window.qBittorrent.Misc.FILTER_INPUT_DELAY);
});
new Keyboard({
defaultEventType: 'keydown',
events: {
'Enter': function(e) {
// accept enter key as a click
new Event(e).stop();
const elem = e.event.srcElement;
if (elem.className.contains("searchInputField")) {
$('startSearchButton').click();
return;
}
switch (elem.id) {
case "manageSearchPlugins":
manageSearchPlugins();
break;
}
}
}
}).activate();
// restore search tabs
const searchJobs = JSON.parse(LocalPreferences.get('search_jobs', '[]'));
for (const { id, pattern } of searchJobs) {
createSearchTab(id, pattern);
}
};
const numSearchTabs = function() {
return $('searchTabs').getElements('li').length;
};
const getSearchIdFromTab = function(tab) {
return Number(tab.id.substring(searchTabIdPrefix.length));
};
const createSearchTab = function(searchId, pattern) {
const newTabId = `${searchTabIdPrefix}${searchId}`;
const tabElem = new Element('a', {
text: pattern,
});
const closeTabElem = new Element('img', {
alt: 'QBT_TR(Close tab)QBT_TR[CONTEXT=SearchWidget]',
title: 'QBT_TR(Close tab)QBT_TR[CONTEXT=SearchWidget]',
src: 'images/application-exit.svg',
width: '8',
height: '8',
style: 'padding-right: 7px; margin-bottom: -1px; margin-left: -5px',
onclick: 'qBittorrent.Search.closeSearchTab(this)',
});
closeTabElem.inject(tabElem, 'top');
tabElem.appendChild(getStatusIconElement('QBT_TR(Searching...)QBT_TR[CONTEXT=SearchJobWidget]', 'images/queued.svg'));
$('searchTabs').appendChild(new Element('li', {
id: newTabId,
class: 'selected',
html: tabElem.outerHTML,
}));
// unhide the results elements
if (numSearchTabs() >= 1) {
$('searchResultsNoSearches').style.display = "none";
$('searchResultsFilters').style.display = "block";
$('searchResultsTableContainer').style.display = "block";
$('searchTabsToolbar').style.display = "block";
}
// reinitialize tabs
$('searchTabs').getElements('li').removeEvents('click');
$('searchTabs').getElements('li').addEvent('click', function(e) {
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
setActiveTab(this);
});
// select new tab
setActiveTab($(newTabId));
searchResultsTable.clear();
resetFilters();
searchState.set(searchId, {
searchPattern: pattern,
filterPattern: searchText.filterPattern,
seedsFilter: { min: searchSeedsFilter.min, max: searchSeedsFilter.max },
sizeFilter: { min: searchSizeFilter.min, minUnit: searchSizeFilter.minUnit, max: searchSizeFilter.max, maxUnit: searchSizeFilter.maxUnit },
searchIn: getSearchInTorrentName(),
rows: [],
rowId: 0,
selectedRowIds: [],
running: true,
loadResultsTimer: null,
sort: { column: searchResultsTable.sortedColumn, reverse: searchResultsTable.reverseSort },
});
updateSearchResultsData(searchId);
};
const closeSearchTab = function(el) {
const tab = el.parentElement.parentElement;
const searchId = getSearchIdFromTab(tab);
const isTabSelected = tab.hasClass('selected');
const newTabToSelect = isTabSelected ? tab.nextSibling || tab.previousSibling : null;
const currentSearchId = getSelectedSearchId();
const state = searchState.get(currentSearchId);
// don't bother sending a stop request if already stopped
if (state && state.running) {
stopSearch(searchId);
}
tab.destroy();
new Request({
url: new URI('api/v2/search/delete'),
method: 'post',
data: {
id: searchId
},
}).send();
const searchJobs = JSON.parse(LocalPreferences.get('search_jobs', '[]'));
const jobIndex = searchJobs.findIndex((job) => job.id === searchId);
if (jobIndex >= 0) {
searchJobs.splice(jobIndex, 1);
LocalPreferences.set('search_jobs', JSON.stringify(searchJobs));
}
if (numSearchTabs() === 0) {
resetSearchState();
resetFilters();
$('numSearchResultsVisible').set('html', 0);
$('numSearchResultsTotal').set('html', 0);
$('searchResultsNoSearches').style.display = "block";
$('searchResultsFilters').style.display = "none";
$('searchResultsTableContainer').style.display = "none";
$('searchTabsToolbar').style.display = "none";
}
else if (isTabSelected && newTabToSelect) {
setActiveTab(newTabToSelect);
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
}
};
const saveCurrentTabState = function() {
const currentSearchId = getSelectedSearchId();
if (!currentSearchId)
return;
const state = searchState.get(currentSearchId);
if (!state)
return;
state.filterPattern = searchText.filterPattern;
state.seedsFilter = {
min: searchSeedsFilter.min,
max: searchSeedsFilter.max,
};
state.sizeFilter = {
min: searchSizeFilter.min,
minUnit: searchSizeFilter.minUnit,
max: searchSizeFilter.max,
maxUnit: searchSizeFilter.maxUnit,
};
state.searchIn = getSearchInTorrentName();
state.sort = {
column: searchResultsTable.sortedColumn,
reverse: searchResultsTable.reverseSort,
};
// we must copy the array to avoid taking a reference to it
state.selectedRowIds = [...searchResultsTable.selectedRows];
};
const setActiveTab = function(tab) {
const searchId = getSearchIdFromTab(tab);
if (searchId === getSelectedSearchId())
return;
saveCurrentTabState();
MochaUI.selected(tab, 'searchTabs');
const state = searchState.get(searchId);
let rowsToSelect = [];
// restore table rows
searchResultsTable.clear();
if (state) {
for (const row of state.rows) {
searchResultsTable.updateRowData(row);
}
rowsToSelect = state.selectedRowIds;
// restore filters
searchText.pattern = state.searchPattern;
searchText.filterPattern = state.filterPattern;
$('searchInNameFilter').set("value", state.filterPattern);
searchSeedsFilter.min = state.seedsFilter.min;
searchSeedsFilter.max = state.seedsFilter.max;
$('searchMinSeedsFilter').set('value', state.seedsFilter.min);
$('searchMaxSeedsFilter').set('value', state.seedsFilter.max);
searchSizeFilter.min = state.sizeFilter.min;
searchSizeFilter.minUnit = state.sizeFilter.minUnit;
searchSizeFilter.max = state.sizeFilter.max;
searchSizeFilter.maxUnit = state.sizeFilter.maxUnit;
$('searchMinSizeFilter').set('value', state.sizeFilter.min);
$('searchMinSizePrefix').set('value', state.sizeFilter.minUnit);
$('searchMaxSizeFilter').set('value', state.sizeFilter.max);
$('searchMaxSizePrefix').set('value', state.sizeFilter.maxUnit);
const currentSearchPattern = $('searchPattern').getProperty('value').trim();
if (state.running && (state.searchPattern === currentSearchPattern)) {
// allow search to be stopped
$('startSearchButton').set('text', 'QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]');
searchPatternChanged = false;
}
searchResultsTable.setSortedColumn(state.sort.column, state.sort.reverse);
$('searchInTorrentName').set('value', state.searchIn);
}
// must restore all filters before calling updateTable
searchResultsTable.updateTable();
searchResultsTable.altRow();
// must reselect rows after calling updateTable
if (rowsToSelect.length > 0) {
searchResultsTable.reselectRows(rowsToSelect);
}
$('numSearchResultsVisible').set('html', searchResultsTable.getFilteredAndSortedRows().length);
$('numSearchResultsTotal').set('html', searchResultsTable.getRowIds().length);
setupSearchTableEvents(true);
};
const getStatusIconElement = function(text, image) {
return new Element('img', {
alt: text,
title: text,
src: image,
class: 'statusIcon',
width: '10',
height: '10',
style: 'margin-bottom: -2px; margin-left: 7px',
});
};
const updateStatusIconElement = function(searchId, text, image) {
const searchTab = $(`${searchTabIdPrefix}${searchId}`);
if (searchTab) {
const statusIcon = searchTab.getElement('.statusIcon');
statusIcon.set('alt', text);
statusIcon.set('title', text);
statusIcon.set('src', image);
}
};
const startSearch = function(pattern, category, plugins) {
searchPatternChanged = false;
const url = new URI('api/v2/search/start');
new Request.JSON({
url: url,
method: 'post',
data: {
pattern: pattern,
category: category,
plugins: plugins
},
onSuccess: function(response) {
$('startSearchButton').set('text', 'QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]');
const searchId = response.id;
createSearchTab(searchId, pattern);
const searchJobs = JSON.parse(LocalPreferences.get('search_jobs', '[]'));
searchJobs.push({ id: searchId, pattern: pattern });
LocalPreferences.set('search_jobs', JSON.stringify(searchJobs));
}
}).send();
};
const stopSearch = function(searchId) {
const url = new URI('api/v2/search/stop');
new Request({
url: url,
method: 'post',
data: {
id: searchId
},
onSuccess: function(response) {
resetSearchState(searchId);
// not strictly necessary to do this when the tab is being closed, but there's no harm in it
updateStatusIconElement(searchId, 'QBT_TR(Search aborted)QBT_TR[CONTEXT=SearchJobWidget]', 'images/task-reject.svg');
}
}).send();
};
const getSelectedSearchId = function() {
const selectedTab = $('searchTabs').getElement('li.selected');
return selectedTab ? getSearchIdFromTab(selectedTab) : null;
};
const startStopSearch = function() {
const currentSearchId = getSelectedSearchId();
const state = searchState.get(currentSearchId);
const isSearchRunning = state && state.running;
if (!isSearchRunning || searchPatternChanged) {
const pattern = $('searchPattern').getProperty('value').trim();
let category = $('categorySelect').getProperty('value');
const plugins = $('pluginsSelect').getProperty('value');
if (!pattern || !category || !plugins)
return;
searchText.pattern = pattern;
startSearch(pattern, category, plugins);
}
else {
stopSearch(currentSearchId);
}
};
const openSearchTorrentDescriptionUrl = function() {
searchResultsTable.selectedRowsIds().each(function(rowId) {
window.open(searchResultsTable.rows.get(rowId).full_data.descrLink, "_blank");
});
};
const copySearchTorrentName = function() {
const names = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
names.push(searchResultsTable.rows.get(rowId).full_data.fileName);
});
return names.join("\n");
};
const copySearchTorrentDownloadLink = function() {
const urls = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
urls.push(searchResultsTable.rows.get(rowId).full_data.fileUrl);
});
return urls.join("\n");
};
const copySearchTorrentDescriptionUrl = function() {
const urls = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
urls.push(searchResultsTable.rows.get(rowId).full_data.descrLink);
});
return urls.join("\n");
};
const downloadSearchTorrent = function() {
const urls = [];
searchResultsTable.selectedRowsIds().each(function(rowId) {
urls.push(searchResultsTable.rows.get(rowId).full_data.fileUrl);
});
// only proceed if at least 1 row was selected
if (!urls.length)
return;
showDownloadPage(urls);
};
const manageSearchPlugins = function() {
const id = 'searchPlugins';
if (!$(id))
new MochaUI.Window({
id: id,
title: "QBT_TR(Search plugins)QBT_TR[CONTEXT=PluginSelectDlg]",
loadMethod: 'xhr',
contentURL: 'views/searchplugins.html',
scrollbars: false,
maximizable: false,
paddingVertical: 0,
paddingHorizontal: 0,
width: loadWindowWidth(id, 600),
height: loadWindowHeight(id, 360),
onResize: function() {
saveWindowSize(id);
},
onBeforeBuild: function() {
loadSearchPlugins();
},
onClose: function() {
clearTimeout(loadSearchPluginsTimer);
}
});
};
const loadSearchPlugins = function() {
getPlugins();
loadSearchPluginsTimer = loadSearchPlugins.delay(2000);
};
const onSearchPatternChanged = function() {
const currentSearchId = getSelectedSearchId();
const state = searchState.get(currentSearchId);
const currentSearchPattern = $('searchPattern').getProperty('value').trim();
// start a new search if pattern has changed, otherwise allow the search to be stopped
if (state && (state.searchPattern === currentSearchPattern)) {
searchPatternChanged = false;
$('startSearchButton').set('text', 'QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]');
}
else {
searchPatternChanged = true;
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
}
};
const categorySelected = function() {
selectedCategory = $("categorySelect").get("value");
};
const pluginSelected = function() {
selectedPlugin = $("pluginsSelect").get("value");
if (selectedPlugin !== prevSelectedPlugin) {
prevSelectedPlugin = selectedPlugin;
getSearchCategories();
}
};
const reselectCategory = function() {
for (let i = 0; i < $("categorySelect").options.length; ++i)
if ($("categorySelect").options[i].get("value") === selectedCategory)
$("categorySelect").options[i].selected = true;
categorySelected();
};
const reselectPlugin = function() {
for (let i = 0; i < $("pluginsSelect").options.length; ++i)
if ($("pluginsSelect").options[i].get("value") === selectedPlugin)
$("pluginsSelect").options[i].selected = true;
pluginSelected();
};
const resetSearchState = function(searchId) {
$('startSearchButton').set('text', 'QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]');
const state = searchState.get(searchId);
if (state) {
state.running = false;
clearTimeout(state.loadResultsTimer);
}
};
const getSearchCategories = function() {
const populateCategorySelect = function(categories) {
const categoryHtml = [];
categories.each(function(category) {
const option = new Element("option");
option.set("value", category.id);
option.set("html", category.name);
categoryHtml.push(option.outerHTML);
});
// first category is "All Categories"
if (categoryHtml.length > 1) {
// add separator
const option = new Element("option");
option.set("disabled", true);
option.set("html", "──────────");
categoryHtml.splice(1, 0, option.outerHTML);
}
$('categorySelect').set('html', categoryHtml.join(""));
};
const selectedPlugin = $('pluginsSelect').get("value");
if ((selectedPlugin === "all") || (selectedPlugin === "enabled")) {
const uniqueCategories = {};
for (const plugin of searchPlugins) {
if ((selectedPlugin === "enabled") && !plugin.enabled)
continue;
for (const category of plugin.supportedCategories) {
if (uniqueCategories[category.id] === undefined) {
uniqueCategories[category.id] = category;
}
}
}
// we must sort the ids to maintain consistent order.
const categories = Object.keys(uniqueCategories).sort().map(id => uniqueCategories[id]);
populateCategorySelect(categories);
}
else {
const plugin = getPlugin(selectedPlugin);
const plugins = (plugin === null) ? [] : plugin.supportedCategories;
populateCategorySelect(plugins);
}
reselectCategory();
};
const getPlugins = function() {
new Request.JSON({
url: new URI('api/v2/search/plugins'),
method: 'get',
noCache: true,
onSuccess: function(response) {
if (response !== prevSearchPluginsResponse) {
prevSearchPluginsResponse = response;
searchPlugins.length = 0;
response.forEach(function(plugin) {
searchPlugins.push(plugin);
});
const pluginsHtml = [];
pluginsHtml.push('<option value="enabled">QBT_TR(Only enabled)QBT_TR[CONTEXT=SearchEngineWidget]</option>');
pluginsHtml.push('<option value="all">QBT_TR(All plugins)QBT_TR[CONTEXT=SearchEngineWidget]</option>');
const searchPluginsEmpty = (searchPlugins.length === 0);
if (!searchPluginsEmpty) {
$('searchResultsNoPlugins').style.display = "none";
if (numSearchTabs() === 0) {
$('searchResultsNoSearches').style.display = "block";
}
// sort plugins alphabetically
const allPlugins = searchPlugins.sort((left, right) => {
const leftName = left.fullName;
const rightName = right.fullName;
return window.qBittorrent.Misc.naturalSortCollator.compare(leftName, rightName);
});
allPlugins.each(function(plugin) {
if (plugin.enabled === true)
pluginsHtml.push("<option value='" + window.qBittorrent.Misc.escapeHtml(plugin.name) + "'>" + window.qBittorrent.Misc.escapeHtml(plugin.fullName) + "</option>");
});
if (pluginsHtml.length > 2)
pluginsHtml.splice(2, 0, "<option disabled>──────────</option>");
}
$('pluginsSelect').set('html', pluginsHtml.join(""));
$('searchPattern').setProperty('disabled', searchPluginsEmpty);
$('categorySelect').setProperty('disabled', searchPluginsEmpty);
$('pluginsSelect').setProperty('disabled', searchPluginsEmpty);
$('startSearchButton').setProperty('disabled', searchPluginsEmpty);
if (window.qBittorrent.SearchPlugins !== undefined)
window.qBittorrent.SearchPlugins.updateTable();
reselectPlugin();
}
}
}).send();
};
const getPlugin = function(name) {
for (let i = 0; i < searchPlugins.length; ++i)
if (searchPlugins[i].name === name)
return searchPlugins[i];
return null;
};
const resetFilters = function() {
searchText.filterPattern = '';
$('searchInNameFilter').set('value', '');
searchSeedsFilter.min = 0;
searchSeedsFilter.max = 0;
$('searchMinSeedsFilter').set('value', searchSeedsFilter.min);
$('searchMaxSeedsFilter').set('value', searchSeedsFilter.max);
searchSizeFilter.min = 0.00;
searchSizeFilter.minUnit = 2; // B = 0, KiB = 1, MiB = 2, GiB = 3, TiB = 4, PiB = 5, EiB = 6
searchSizeFilter.max = 0.00;
searchSizeFilter.maxUnit = 3;
$('searchMinSizeFilter').set('value', searchSizeFilter.min);
$('searchMinSizePrefix').set('value', searchSizeFilter.minUnit);
$('searchMaxSizeFilter').set('value', searchSizeFilter.max);
$('searchMaxSizePrefix').set('value', searchSizeFilter.maxUnit);
};
const getSearchInTorrentName = function() {
return $('searchInTorrentName').get('value') === "names" ? "names" : "everywhere";
};
const searchInTorrentName = function() {
LocalPreferences.set('search_in_filter', getSearchInTorrentName());
searchFilterChanged();
};
const searchSeedsFilterChanged = function() {
searchSeedsFilter.min = $('searchMinSeedsFilter').get('value');
searchSeedsFilter.max = $('searchMaxSeedsFilter').get('value');
searchFilterChanged();
};
const searchSizeFilterChanged = function() {
searchSizeFilter.min = $('searchMinSizeFilter').get('value');
searchSizeFilter.minUnit = $('searchMinSizePrefix').get('value');
searchSizeFilter.max = $('searchMaxSizeFilter').get('value');
searchSizeFilter.maxUnit = $('searchMaxSizePrefix').get('value');
searchFilterChanged();
};
const searchSizeFilterPrefixChanged = function() {
if ((Number($('searchMinSizeFilter').get('value')) !== 0) || (Number($('searchMaxSizeFilter').get('value')) !== 0))
searchSizeFilterChanged();
};
const searchFilterChanged = function() {
searchResultsTable.updateTable();
$('numSearchResultsVisible').set('html', searchResultsTable.getFilteredAndSortedRows().length);
};
const setupSearchTableEvents = function(enable) {
if (enable)
$$(".searchTableRow").each(function(target) {
target.addEventListener('dblclick', downloadSearchTorrent, false);
});
else
$$(".searchTableRow").each(function(target) {
target.removeEventListener('dblclick', downloadSearchTorrent, false);
});
};
const loadSearchResultsData = function(searchId) {
const state = searchState.get(searchId);
const maxResults = 500;
const url = new URI('api/v2/search/results');
new Request.JSON({
url: url,
method: 'get',
noCache: true,
data: {
id: searchId,
limit: maxResults,
offset: state.rowId
},
onFailure: function(response) {
if ((response.status === 400) || (response.status === 404)) {
// bad params. search id is invalid
resetSearchState(searchId);
updateStatusIconElement(searchId, 'QBT_TR(An error occurred during search...)QBT_TR[CONTEXT=SearchJobWidget]', 'images/error.svg');
}
else {
clearTimeout(state.loadResultsTimer);
state.loadResultsTimer = loadSearchResultsData.delay(3000, this, searchId);
}
},
onSuccess: function(response) {
$('error_div').set('html', '');
const state = searchState.get(searchId);
// check if user stopped the search prior to receiving the response
if (!state.running) {
clearTimeout(state.loadResultsTimer);
updateStatusIconElement(searchId, 'QBT_TR(Search aborted)QBT_TR[CONTEXT=SearchJobWidget]', 'images/task-reject.svg');
return;
}
if (response) {
setupSearchTableEvents(false);
const state = searchState.get(searchId);
const newRows = [];
if (response.results) {
const results = response.results;
for (let i = 0; i < results.length; ++i) {
const result = results[i];
const row = {
rowId: state.rowId,
descrLink: result.descrLink,
fileName: result.fileName,
fileSize: result.fileSize,
fileUrl: result.fileUrl,
nbLeechers: result.nbLeechers,
nbSeeders: result.nbSeeders,
siteUrl: result.siteUrl,
pubDate: result.pubDate,
};
newRows.push(row);
state.rows.push(row);
state.rowId += 1;
}
}
// only update table if this search is currently being displayed
if (searchId === getSelectedSearchId()) {
for (const row of newRows) {
searchResultsTable.updateRowData(row);
}
$('numSearchResultsVisible').set('html', searchResultsTable.getFilteredAndSortedRows().length);
$('numSearchResultsTotal').set('html', searchResultsTable.getRowIds().length);
searchResultsTable.updateTable();
searchResultsTable.altRow();
}
if ((response.status === "Stopped") && (state.rowId >= response.total)) {
resetSearchState(searchId);
updateStatusIconElement(searchId, 'QBT_TR(Search has finished)QBT_TR[CONTEXT=SearchJobWidget]', 'images/task-complete.svg');
return;
}
setupSearchTableEvents(true);
}
clearTimeout(state.loadResultsTimer);
state.loadResultsTimer = loadSearchResultsData.delay(2000, this, searchId);
}
}).send();
};
const updateSearchResultsData = function(searchId) {
const state = searchState.get(searchId);
clearTimeout(state.loadResultsTimer);
state.loadResultsTimer = loadSearchResultsData.delay(500, this, searchId);
};
new ClipboardJS('.copySearchDataToClipboard', {
text: function(trigger) {
switch (trigger.id) {
case "copySearchTorrentName":
return copySearchTorrentName();
case "copySearchTorrentDownloadLink":
return copySearchTorrentDownloadLink();
case "copySearchTorrentDescriptionUrl":
return copySearchTorrentDescriptionUrl();
default:
return "";
}
}
});
return exports();
})();
Object.freeze(window.qBittorrent.Search);
</script>

View file

@ -400,6 +400,7 @@
<file>private/scripts/prop-trackers.js</file>
<file>private/scripts/prop-webseeds.js</file>
<file>private/scripts/rename-files.js</file>
<file>private/scripts/search.js</file>
<file>private/scripts/speedslider.js</file>
<file>private/setlocation.html</file>
<file>private/shareratio.html</file>