WebUI: Store persistent settings in client data API

This PR moves persistent WebUI settings from browser local storage to the new client data API. This allows these settings to be shared across multiple WebUI instances. This does not include settings that are specific to the local client, like window sizes, selected filters, selected tabs, and sorted columns.

Depends on #23088.
Closes #12100.


PR #23191.
This commit is contained in:
Tom Piccirello
2026-01-03 00:50:54 -08:00
committed by GitHub
parent a614bd3e40
commit f16797f1cd
12 changed files with 258 additions and 81 deletions

View File

@@ -29,6 +29,7 @@
<script defer src="scripts/monkeypatch.js?v=${CACHEID}"></script>
<script defer src="scripts/cache.js?v=${CACHEID}"></script>
<script defer src="scripts/localpreferences.js?v=${CACHEID}"></script>
<script defer src="scripts/client-data.js?v=${CACHEID}"></script>
<script defer src="scripts/color-scheme.js?v=${CACHEID}"></script>
<script defer src="scripts/mocha-init.js?locale=${LANG}&v=${CACHEID}"></script>
<script defer src="scripts/lib/clipboard-copy.js"></script>

View File

@@ -45,10 +45,10 @@ window.qBittorrent.AddTorrent ??= (() => {
let source = "";
let downloader = "";
const localPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
const clientData = window.parent.qBittorrent.ClientData;
const getCategories = () => {
const defaultCategory = localPreferences.get("add_torrent_default_category", "");
const defaultCategory = clientData.get("add_torrent_default_category") ?? "";
const categorySelect = document.getElementById("categorySelect");
for (const name of window.parent.qBittorrent.Client.categoryMap.keys()) {
const option = document.createElement("option");
@@ -320,10 +320,7 @@ window.qBittorrent.AddTorrent ??= (() => {
if (document.getElementById("setDefaultCategory").checked) {
const category = document.getElementById("category").value.trim();
if (category.length === 0)
localPreferences.remove("add_torrent_default_category");
else
localPreferences.set("add_torrent_default_category", category);
clientData.set({ add_torrent_default_category: (category.length > 0) ? category : null }).catch(console.error);
}
};

View File

@@ -0,0 +1,126 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Thomas Piccirello <thomas@piccirello.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
"use strict";
window.qBittorrent ??= {};
window.qBittorrent.ClientData ??= (() => {
const exports = () => {
return new ClientData();
};
// this is exposed as a singleton
class ClientData {
/**
* @type Map<string, any>
*/
#cache = new Map();
#keyPrefix = "qbt_";
#addKeyPrefix(data) {
return Object.fromEntries(Object.entries(data).map(([key, value]) => ([`${this.#keyPrefix}${key}`, value])));
}
#removeKeyPrefix(data) {
return Object.fromEntries(Object.entries(data).map(([key, value]) => ([key.substring(this.#keyPrefix.length), value])));
}
/**
* @param {string[]} keys
* @returns {Record<string, any>}
*/
async #fetch(keys) {
keys = keys.map(key => `${this.#keyPrefix}${key}`);
return await fetch("api/v2/clientdata/load", {
method: "POST",
body: new URLSearchParams({
keys: JSON.stringify(keys)
})
})
.then(async (response) => {
if (!response.ok)
return;
const data = await response.json();
return this.#removeKeyPrefix(data);
});
}
/**
* @param {Record<string, any>} data
*/
async #set(data) {
data = this.#addKeyPrefix(data);
await fetch("api/v2/clientdata/store", {
method: "POST",
body: new URLSearchParams({
data: JSON.stringify(data)
})
})
.then((response) => {
if (!response.ok)
throw new Error("Failed to store client data");
});
}
/**
* @param {string} key
* @returns {any}
*/
get(key) {
return this.#cache.get(key);
}
/**
* @param {string[]} keys
*/
async fetch(keys = []) {
const keysToFetch = keys.filter((key) => !this.#cache.has(key));
if (keysToFetch.length > 0) {
const fetchedData = await this.#fetch(keysToFetch);
for (const [key, value] of Object.entries(fetchedData))
this.#cache.set(key, value);
}
}
/**
* @param {Record<string, any>} data
*/
async set(data) {
await this.#set(data);
// update cache
for (const [key, value] of Object.entries(data))
this.#cache.set(key, value);
}
}
return exports();
})();
Object.freeze(window.qBittorrent.ClientData);

View File

@@ -31,6 +31,7 @@ window.qBittorrent.Client ??= (() => {
return {
setup: setup,
initializeCaches: initializeCaches,
initializeClientData: initializeClientData,
closeWindow: closeWindow,
closeFrameWindow: closeFrameWindow,
getSyncMainDataInterval: getSyncMainDataInterval,
@@ -56,15 +57,48 @@ window.qBittorrent.Client ??= (() => {
const tagMap = new Map();
let cacheAllSettled;
let clientDataPromise;
const setup = () => {
// fetch various data and store it in memory
clientDataPromise = window.qBittorrent.ClientData.fetch([
"add_torrent_default_category",
"color_scheme",
"dblclick_complete",
"dblclick_download",
"dblclick_filter",
"full_url_tracker_column",
"hide_zero_status_filters",
"qbt_selected_log_levels",
"search_in_filter",
"show_filters_sidebar",
"show_log_viewer",
"show_rss_reader",
"show_search_engine",
"show_status_bar",
"show_top_toolbar",
"speed_in_browser_title_bar",
"torrent_creator",
"use_alt_row_colors",
"use_virtual_list",
]);
cacheAllSettled = Promise.allSettled([
window.qBittorrent.Cache.buildInfo.init(),
window.qBittorrent.Cache.preferences.init(),
window.qBittorrent.Cache.qbtVersion.init()
window.qBittorrent.Cache.qbtVersion.init(),
clientDataPromise,
]);
};
const initializeClientData = async () => {
try {
await clientDataPromise;
}
catch (error) {
console.error(`Failed to initialize client data. Reason: "${error}".`);
}
};
const initializeCaches = async () => {
const results = await cacheAllSettled;
for (const [idx, result] of results.entries()) {
@@ -222,8 +256,8 @@ let alternativeSpeedLimits = false;
let queueing_enabled = true;
let serverSyncMainDataInterval = 1500;
let customSyncMainDataInterval = null;
const useAutoHideZeroStatusFilters = localPreferences.get("hide_zero_status_filters", "false") === "true";
const displayFullURLTrackerColumn = localPreferences.get("full_url_tracker_column", "false") === "true";
let useAutoHideZeroStatusFilters = false;
let displayFullURLTrackerColumn = false;
/* Categories filter */
const CATEGORIES_ALL = "b4af0e4c-e76d-4bac-a392-46cbc18d9655";
@@ -249,6 +283,8 @@ const TRACKERS_WARNING = "82a702c5-210c-412b-829f-97632d7557e9";
// Map<trackerHost: String, Map<trackerURL: String, torrents: Set>>
const trackerMap = new Map();
const clientData = window.qBittorrent.ClientData;
let selectedTracker = localPreferences.get("selected_tracker", TRACKERS_ALL);
let setTrackerFilter = () => {};
@@ -257,9 +293,16 @@ let selectedStatus = localPreferences.get("selected_filter", "all");
let setStatusFilter = () => {};
let toggleFilterDisplay = () => {};
window.addEventListener("DOMContentLoaded", (event) => {
window.addEventListener("DOMContentLoaded", async (event) => {
// execute this first
window.qBittorrent.LocalPreferences.upgrade();
await window.qBittorrent.Client.initializeClientData();
window.qBittorrent.ColorScheme.update();
useAutoHideZeroStatusFilters = clientData.get("hide_zero_status_filters") === true;
displayFullURLTrackerColumn = clientData.get("full_url_tracker_column") === true;
let isSearchPanelLoaded = false;
let isLogPanelLoaded = false;
let isRssPanelLoaded = false;
@@ -404,6 +447,13 @@ window.addEventListener("DOMContentLoaded", (event) => {
localPreferences.set(`filter_${filterListID.replace("FilterList", "")}_collapsed`, filterList.classList.toggle("invisible").toString());
};
const highlightSelectedStatus = () => {
const statusFilter = document.getElementById("statusFilterList");
const filterID = `${selectedStatus}_filter`;
for (const status of statusFilter.children)
status.classList.toggle("selectedFilter", (status.id === filterID));
};
new MochaUI.Panel({
id: "Filters",
title: "Panel",
@@ -425,35 +475,35 @@ window.addEventListener("DOMContentLoaded", (event) => {
initializeWindows();
// Show Top Toolbar is enabled by default
let showTopToolbar = localPreferences.get("show_top_toolbar", "true") === "true";
let showTopToolbar = (clientData.get("show_top_toolbar") ?? true) === true;
if (!showTopToolbar) {
document.getElementById("showTopToolbarLink").firstElementChild.style.opacity = "0";
document.getElementById("mochaToolbar").classList.add("invisible");
}
// Show Status Bar is enabled by default
let showStatusBar = localPreferences.get("show_status_bar", "true") === "true";
let showStatusBar = (clientData.get("show_status_bar") ?? true) === true;
if (!showStatusBar) {
document.getElementById("showStatusBarLink").firstElementChild.style.opacity = "0";
document.getElementById("desktopFooterWrapper").classList.add("invisible");
}
// Show Filters Sidebar is enabled by default
let showFiltersSidebar = localPreferences.get("show_filters_sidebar", "true") === "true";
let showFiltersSidebar = (clientData.get("show_filters_sidebar") ?? true) === true;
if (!showFiltersSidebar) {
document.getElementById("showFiltersSidebarLink").firstElementChild.style.opacity = "0";
document.getElementById("filtersColumn").classList.add("invisible");
document.getElementById("filtersColumn_handle").classList.add("invisible");
}
let speedInTitle = localPreferences.get("speed_in_browser_title_bar") === "true";
let speedInTitle = clientData.get("speed_in_browser_title_bar") === true;
if (!speedInTitle)
document.getElementById("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "0";
// After showing/hiding the toolbar + status bar
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");
window.qBittorrent.Client.showSearchEngine((clientData.get("show_search_engine") ?? true) === true);
window.qBittorrent.Client.showRssReader((clientData.get("show_rss_reader") ?? true) === true);
window.qBittorrent.Client.showLogViewer(clientData.get("show_log_viewer") === true);
// After Show Top Toolbar
MochaUI.Desktop.setDesktopSize();
@@ -570,13 +620,6 @@ window.addEventListener("DOMContentLoaded", (event) => {
window.qBittorrent.Filters.clearStatusFilter();
};
const highlightSelectedStatus = () => {
const statusFilter = document.getElementById("statusFilterList");
const filterID = `${selectedStatus}_filter`;
for (const status of statusFilter.children)
status.classList.toggle("selectedFilter", (status.id === filterID));
};
const updateCategoryList = () => {
const categoryList = document.getElementById("categoryFilterList");
if (!categoryList)
@@ -1210,7 +1253,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("showTopToolbarLink").addEventListener("click", (e) => {
showTopToolbar = !showTopToolbar;
localPreferences.set("show_top_toolbar", showTopToolbar.toString());
clientData.set({ show_top_toolbar: showTopToolbar }).catch(console.error);
if (showTopToolbar) {
document.getElementById("showTopToolbarLink").firstElementChild.style.opacity = "1";
document.getElementById("mochaToolbar").classList.remove("invisible");
@@ -1224,7 +1267,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("showStatusBarLink").addEventListener("click", (e) => {
showStatusBar = !showStatusBar;
localPreferences.set("show_status_bar", showStatusBar.toString());
clientData.set({ show_status_bar: showStatusBar }).catch(console.error);
if (showStatusBar) {
document.getElementById("showStatusBarLink").firstElementChild.style.opacity = "1";
document.getElementById("desktopFooterWrapper").classList.remove("invisible");
@@ -1261,7 +1304,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("showFiltersSidebarLink").addEventListener("click", (e) => {
showFiltersSidebar = !showFiltersSidebar;
localPreferences.set("show_filters_sidebar", showFiltersSidebar.toString());
clientData.set({ show_filters_sidebar: showFiltersSidebar }).catch(console.error);
if (showFiltersSidebar) {
document.getElementById("showFiltersSidebarLink").firstElementChild.style.opacity = "1";
document.getElementById("filtersColumn").classList.remove("invisible");
@@ -1277,7 +1320,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("speedInBrowserTitleBarLink").addEventListener("click", (e) => {
speedInTitle = !speedInTitle;
localPreferences.set("speed_in_browser_title_bar", speedInTitle.toString());
clientData.set({ speed_in_browser_title_bar: speedInTitle }).catch(console.error);
if (speedInTitle)
document.getElementById("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "1";
else
@@ -1287,19 +1330,19 @@ window.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("showSearchEngineLink").addEventListener("click", (e) => {
window.qBittorrent.Client.showSearchEngine(!window.qBittorrent.Client.isShowSearchEngine());
localPreferences.set("show_search_engine", window.qBittorrent.Client.isShowSearchEngine().toString());
clientData.set({ show_search_engine: window.qBittorrent.Client.isShowSearchEngine() }).catch(console.error);
updateTabDisplay();
});
document.getElementById("showRssReaderLink").addEventListener("click", (e) => {
window.qBittorrent.Client.showRssReader(!window.qBittorrent.Client.isShowRssReader());
localPreferences.set("show_rss_reader", window.qBittorrent.Client.isShowRssReader().toString());
clientData.set({ show_rss_reader: window.qBittorrent.Client.isShowRssReader() }).catch(console.error);
updateTabDisplay();
});
document.getElementById("showLogViewerLink").addEventListener("click", (e) => {
window.qBittorrent.Client.showLogViewer(!window.qBittorrent.Client.isShowLogViewer());
localPreferences.set("show_log_viewer", window.qBittorrent.Client.isShowLogViewer().toString());
clientData.set({ show_log_viewer: window.qBittorrent.Client.isShowLogViewer() }).catch(console.error);
updateTabDisplay();
});
@@ -1356,7 +1399,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
// main window tabs
const showTransfersTab = () => {
const showFiltersSidebar = localPreferences.get("show_filters_sidebar", "true") === "true";
const showFiltersSidebar = (clientData.get("show_filters_sidebar") ?? true) === true;
if (showFiltersSidebar) {
document.getElementById("filtersColumn").classList.remove("invisible");
document.getElementById("filtersColumn_handle").classList.remove("invisible");

View File

@@ -36,12 +36,12 @@ window.qBittorrent.ColorScheme ??= (() => {
};
};
const localPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
const clientData = window.parent.qBittorrent.ClientData;
const update = () => {
const root = document.documentElement;
const colorScheme = localPreferences.get("color_scheme");
const colorScheme = clientData.get("color_scheme");
const validScheme = (colorScheme === "light") || (colorScheme === "dark");
const isDark = colorSchemeQuery.matches;
root.classList.toggle("dark", ((!validScheme && isDark) || (colorScheme === "dark")));
@@ -52,5 +52,3 @@ window.qBittorrent.ColorScheme ??= (() => {
return exports();
})();
Object.freeze(window.qBittorrent.ColorScheme);
window.qBittorrent.ColorScheme.update();

View File

@@ -66,6 +66,7 @@ window.qBittorrent.DynamicTable ??= (() => {
};
const localPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
const clientData = window.qBittorrent.ClientData ?? window.parent.qBittorrent.ClientData;
class DynamicTable {
#DynamicTableHeaderContextMenuClass = null;
@@ -74,7 +75,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.dynamicTableDivId = dynamicTableDivId;
this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId;
this.dynamicTableDiv = document.getElementById(dynamicTableDivId);
this.useVirtualList = useVirtualList && (localPreferences.get("use_virtual_list", "false") === "true");
this.useVirtualList = useVirtualList && (clientData.get("use_virtual_list") === true);
this.fixedTableHeader = document.querySelector(`#${dynamicTableFixedHeaderDivId} thead tr`);
this.hiddenTableHeader = this.dynamicTableDiv.querySelector("thead tr");
this.table = this.dynamicTableDiv.querySelector("table");
@@ -726,7 +727,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}
setupAltRow() {
const useAltRowColors = (localPreferences.get("use_alt_row_colors", "true") === "true");
const useAltRowColors = (clientData.get("use_alt_row_colors") ?? true) === true;
if (useAltRowColors)
document.getElementById(this.dynamicTableDivId).classList.add("altRowColors");
}
@@ -1792,7 +1793,7 @@ window.qBittorrent.DynamicTable ??= (() => {
? "dblclick_download"
: "dblclick_complete";
if (localPreferences.get(prefKey, "1") !== "1")
if (clientData.get(prefKey) !== "1")
return true;
if (state.includes("stopped"))

View File

@@ -109,8 +109,7 @@ window.qBittorrent.Search ??= (() => {
});
const init = () => {
// load "Search in" preference from local storage
document.getElementById("searchInTorrentName").value = (localPreferences.get("search_in_filter") === "names") ? "names" : "everywhere";
document.getElementById("searchInTorrentName").value = (window.qBittorrent.ClientData.get("search_in_filter") === "names") ? "names" : "everywhere";
const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#searchResultsTableDiv tbody tr",
menu: "searchResultsTableMenu",
@@ -802,7 +801,8 @@ window.qBittorrent.Search ??= (() => {
};
const searchInTorrentName = () => {
localPreferences.set("search_in_filter", getSearchInTorrentName());
// don't await this
window.qBittorrent.ClientData.set({ search_in_filter: getSearchInTorrentName() }).catch(console.error);
searchFilterChanged();
};

View File

@@ -127,8 +127,6 @@
};
};
const localPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
const formatUrls = (urls) => {
return urls.split("\n").map(encodeURIComponent).join("|");
};
@@ -209,11 +207,12 @@
comments: document.getElementById("comments").value,
source: document.getElementById("source").value,
};
localPreferences.set("torrent_creator", JSON.stringify(preference));
window.parent.qBittorrent.ClientData.set({ torrent_creator: preference }).catch(console.error);
};
const loadPreference = () => {
const preference = JSON.parse(localPreferences.get("torrent_creator") ?? "{}");
const preference = window.parent.qBittorrent.ClientData.get("torrent_creator") ?? {};
document.getElementById("sourcePath").value = preference.sourcePath ?? "";
document.getElementById("torrentFormat").value = preference.torrentFormat ?? "hybrid";
document.getElementById("pieceSize").value = preference.pieceSize ?? 0;

View File

@@ -263,7 +263,7 @@
});
document.getElementById("Filters_pad").addEventListener("dblclick", (event) => {
if (localPreferences.get("dblclick_filter", "1") !== "1")
if ((window.qBittorrent.ClientData.get("dblclick_filter") ?? "1") !== "1")
return;
const filterItem = event.target.closest("li");

View File

@@ -182,12 +182,10 @@
}
};
const localPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
let logFilterTimer = -1;
let inputtedFilterText = "";
let selectBox;
let selectedLogLevels = JSON.parse(localPreferences.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
let selectedLogLevels = window.qBittorrent.ClientData.get("qbt_selected_log_levels") ?? ["1", "2", "4", "8"];
const init = () => {
for (const option of document.getElementById("logLevelSelect").options)
@@ -277,7 +275,8 @@
if (selectedLogLevels !== value) {
tableInfo[currentSelectedTab].last_id = -1;
selectedLogLevels = value;
localPreferences.set("qbt_selected_log_levels", JSON.stringify(selectedLogLevels));
// don't await this
window.qBittorrent.ClientData.set({ qbt_selected_log_levels: selectedLogLevels }).catch(console.error);
logFilterChanged();
}
};

View File

@@ -1829,8 +1829,6 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
};
};
const localPreferences = new window.qBittorrent.LocalPreferences.LocalPreferences();
// Behavior tab
const numberInputLimiter = (input) => {
const inputNumber = Number(input.value);
@@ -2249,10 +2247,8 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
document.getElementById("locale_select").value = selected;
};
const updateColoSchemeSelect = () => {
const updateColoSchemeSelect = (colorScheme) => {
const combobox = document.getElementById("colorSchemeSelect");
const colorScheme = localPreferences.get("color_scheme");
if (colorScheme === "light")
combobox.options[1].selected = true;
else if (colorScheme === "dark")
@@ -2264,20 +2260,31 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
const loadPreferences = () => {
window.parent.qBittorrent.Cache.preferences.init()
.then((pref) => {
const clientData = window.parent.qBittorrent.ClientData;
const colorScheme = clientData.get("color_scheme");
const fullUrlTrackerColumn = clientData.get("full_url_tracker_column");
const useVirtualList = clientData.get("use_virtual_list");
const hideZeroStatusFilters = clientData.get("hide_zero_status_filters");
const dblclickDownload = clientData.get("dblclick_download") ?? "1";
const dblclickComplete = clientData.get("dblclick_complete") ?? "1";
const dblclickFilter = clientData.get("dblclick_filter") ?? "1";
const useAltRowColors = clientData.get("use_alt_row_colors");
// Behavior tab
// Language
updateWebuiLocaleSelect(pref.locale);
updateColoSchemeSelect();
updateColoSchemeSelect(colorScheme);
document.getElementById("statusBarExternalIP").checked = pref.status_bar_external_ip;
document.getElementById("performanceWarning").checked = pref.performance_warning;
document.getElementById("displayFullURLTrackerColumn").checked = (localPreferences.get("full_url_tracker_column", "false") === "true");
document.getElementById("useVirtualList").checked = (localPreferences.get("use_virtual_list", "false") === "true");
document.getElementById("hideZeroFiltersCheckbox").checked = (localPreferences.get("hide_zero_status_filters", "false") === "true");
document.getElementById("dblclickDownloadSelect").value = localPreferences.get("dblclick_download", "1");
document.getElementById("dblclickCompleteSelect").value = localPreferences.get("dblclick_complete", "1");
document.getElementById("dblclickFiltersSelect").value = localPreferences.get("dblclick_filter", "1");
document.getElementById("displayFullURLTrackerColumn").checked = fullUrlTrackerColumn === true;
document.getElementById("useVirtualList").checked = useVirtualList === true;
document.getElementById("hideZeroFiltersCheckbox").checked = hideZeroStatusFilters === true;
document.getElementById("dblclickDownloadSelect").value = dblclickDownload;
document.getElementById("dblclickCompleteSelect").value = dblclickComplete;
document.getElementById("dblclickFiltersSelect").value = dblclickFilter;
document.getElementById("confirmTorrentDeletion").checked = pref.confirm_torrent_deletion;
document.getElementById("useAltRowColorsInput").checked = (localPreferences.get("use_alt_row_colors", "true") === "true");
document.getElementById("useAltRowColorsInput").checked = useAltRowColors === true;
document.getElementById("filelog_checkbox").checked = pref.file_log_enabled;
document.getElementById("filelog_save_path_input").value = pref.file_log_path;
document.getElementById("filelog_backup_checkbox").checked = pref.file_log_backup_enabled;
@@ -2701,6 +2708,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
const applyPreferences = () => {
const settings = {};
const clientData = {};
// Validate form data
// Behavior tab
@@ -2708,21 +2716,21 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
settings["locale"] = document.getElementById("locale_select").value;
const colorScheme = Number(document.getElementById("colorSchemeSelect").value);
if (colorScheme === 0)
localPreferences.remove("color_scheme");
clientData.color_scheme = null;
else if (colorScheme === 1)
localPreferences.set("color_scheme", "light");
clientData.color_scheme = "light";
else
localPreferences.set("color_scheme", "dark");
clientData.color_scheme = "dark";
settings["status_bar_external_ip"] = document.getElementById("statusBarExternalIP").checked;
settings["performance_warning"] = document.getElementById("performanceWarning").checked;
localPreferences.set("full_url_tracker_column", document.getElementById("displayFullURLTrackerColumn").checked.toString());
localPreferences.set("use_virtual_list", document.getElementById("useVirtualList").checked.toString());
localPreferences.set("hide_zero_status_filters", document.getElementById("hideZeroFiltersCheckbox").checked.toString());
localPreferences.set("dblclick_download", document.getElementById("dblclickDownloadSelect").value);
localPreferences.set("dblclick_complete", document.getElementById("dblclickCompleteSelect").value);
localPreferences.set("dblclick_filter", document.getElementById("dblclickFiltersSelect").value);
clientData.full_url_tracker_column = document.getElementById("displayFullURLTrackerColumn").checked;
clientData.use_virtual_list = document.getElementById("useVirtualList").checked;
clientData.hide_zero_status_filters = document.getElementById("hideZeroFiltersCheckbox").checked;
clientData.dblclick_download = document.getElementById("dblclickDownloadSelect").value;
clientData.dblclick_complete = document.getElementById("dblclickCompleteSelect").value;
clientData.dblclick_filter = document.getElementById("dblclickFiltersSelect").value;
settings["confirm_torrent_deletion"] = document.getElementById("confirmTorrentDeletion").checked;
localPreferences.set("use_alt_row_colors", document.getElementById("useAltRowColorsInput").checked.toString());
clientData.use_alt_row_colors = document.getElementById("useAltRowColorsInput").checked;
settings["file_log_enabled"] = document.getElementById("filelog_checkbox").checked;
settings["file_log_path"] = document.getElementById("filelog_save_path_input").value;
settings["file_log_backup_enabled"] = document.getElementById("filelog_backup_checkbox").checked;
@@ -3212,15 +3220,19 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
settings["i2p_inbound_length"] = Number(document.getElementById("i2pInboundLength").value);
settings["i2p_outbound_length"] = Number(document.getElementById("i2pOutboundLength").value);
// Send it to qBT
// Send data to qBT
Promise.allSettled([
window.parent.qBittorrent.ClientData.set(clientData),
window.parent.qBittorrent.Cache.preferences.set(settings)
.then(() => {
// Close window
]).then((results) => {
if (results.every((result) => result.status === "fulfilled")) {
window.parent.location.reload();
window.parent.qBittorrent.Client.closeWindow(document.getElementById("preferencesPage"));
}).catch((error) => {
}
else {
// keep window open so user can reattempt saving
alert("QBT_TR(Unable to save program preferences, qBittorrent is probably unreachable.)QBT_TR[CONTEXT=HttpServer]");
alert("QBT_TR(Unable to save preferences, qBittorrent is probably unreachable.)QBT_TR[CONTEXT=HttpServer]");
}
});
};

View File

@@ -390,6 +390,7 @@
<file>private/scripts/addtorrent.js</file>
<file>private/scripts/cache.js</file>
<file>private/scripts/client.js</file>
<file>private/scripts/client-data.js</file>
<file>private/scripts/color-scheme.js</file>
<file>private/scripts/contextmenu.js</file>
<file>private/scripts/dynamicTable.js</file>