diff --git a/src/tribler/core/restapi/settings_endpoint.py b/src/tribler/core/restapi/settings_endpoint.py index e5df86097c..65d0849357 100644 --- a/src/tribler/core/restapi/settings_endpoint.py +++ b/src/tribler/core/restapi/settings_endpoint.py @@ -69,9 +69,16 @@ async def update_settings(self, request: web.Request) -> RESTResponse: return RESTResponse({"modified": True}) - def _recursive_merge_settings(self, existing: dict, updates: dict) -> None: + def _recursive_merge_settings(self, existing: dict, updates: dict, top: bool = True) -> None: for key in existing: + # Ignore top-level ui entry + if top and key == "ui": + continue value = updates.get(key, existing[key]) if isinstance(value, dict): - self._recursive_merge_settings(existing[key], value) + self._recursive_merge_settings(existing[key], value, False) existing[key] = value + + # Since the core doesn't need to be aware of the GUI settings, we just copy them. + if top and "ui" in updates: + existing["ui"].update(updates["ui"]) diff --git a/src/tribler/tribler_config.py b/src/tribler/tribler_config.py index 27b0e2b3e4..99b9fd9097 100644 --- a/src/tribler/tribler_config.py +++ b/src/tribler/tribler_config.py @@ -27,6 +27,8 @@ class ApiConfig(TypedDict): https_enabled: bool https_host: str https_port: int + http_port_running: int + https_port_running: int class ContentDiscoveryCommunityConfig(TypedDict): diff --git a/src/tribler/ui/src/components/add-torrent.tsx b/src/tribler/ui/src/components/add-torrent.tsx index 7609d83ddd..081a83546b 100644 --- a/src/tribler/ui/src/components/add-torrent.tsx +++ b/src/tribler/ui/src/components/add-torrent.tsx @@ -124,7 +124,7 @@ export function AddTorrent() { const files = Array.from(event.target.files as ArrayLike); event.target.value = ''; - if (files.length === 1 && triblerService.getGuiSettings().ask_download_settings !== false) { + if (files.length === 1 && triblerService.guiSettings.ask_download_settings !== false) { setSaveAsDialogOpen(true); setTorrent(files[0]); } diff --git a/src/tribler/ui/src/components/language-select.tsx b/src/tribler/ui/src/components/language-select.tsx index 37932c9f54..19fcef265b 100644 --- a/src/tribler/ui/src/components/language-select.tsx +++ b/src/tribler/ui/src/components/language-select.tsx @@ -3,18 +3,24 @@ import { Button } from "./ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu"; import { useTranslation } from "react-i18next"; import { triblerService } from "@/services/tribler.service"; +import { useEffect } from "react"; const LanguageSelect = () => { const { language, setLanguage } = useLanguage(); const { t, i18n } = useTranslation(); - const changeLanguage = (lng: string) => { + useEffect(() => { + const lng = triblerService.guiSettings.lang ?? 'en_US'; setLanguage(lng); i18n.changeLanguage(lng); - triblerService.setGuiSettings({ - ...triblerService.getGuiSettings(), - lang: lng + }, []); + + const changeLanguage = async (lng: string) => { + setLanguage(lng); + i18n.changeLanguage(lng); + await triblerService.setSettings({ + ui: { lang: lng } }); }; diff --git a/src/tribler/ui/src/config/menu.ts b/src/tribler/ui/src/config/menu.ts index 4b354337f5..75098d794a 100644 --- a/src/tribler/ui/src/config/menu.ts +++ b/src/tribler/ui/src/config/menu.ts @@ -89,7 +89,7 @@ export const sideMenu: NavItemWithChildren[] = [ { title: 'Debug', icon: ExclamationTriangleIcon, - hide: () => triblerService.getGuiSettings().dev_mode !== true, + hide: () => triblerService.guiSettings.dev_mode !== true, items: [ { title: 'General', diff --git a/src/tribler/ui/src/contexts/LanguageContext.tsx b/src/tribler/ui/src/contexts/LanguageContext.tsx index 5ccb065c9a..75b2136290 100644 --- a/src/tribler/ui/src/contexts/LanguageContext.tsx +++ b/src/tribler/ui/src/contexts/LanguageContext.tsx @@ -9,7 +9,7 @@ interface LanguageContextType { const LanguageContext = createContext(undefined); export const LanguageProvider: React.FC = ({ children }) => { - const [language, setLanguage] = useState(triblerService.getGuiSettings().lang ?? 'en'); + const [language, setLanguage] = useState(triblerService.guiSettings.lang ?? 'en_US'); return ( diff --git a/src/tribler/ui/src/dialogs/SaveAs.tsx b/src/tribler/ui/src/dialogs/SaveAs.tsx index f21f296430..ad7bba55d2 100644 --- a/src/tribler/ui/src/dialogs/SaveAs.tsx +++ b/src/tribler/ui/src/dialogs/SaveAs.tsx @@ -157,7 +157,7 @@ export default function SaveAs(props: SaveAsProps & JSX.IntrinsicAttributes & Di } } - if (props.open && props.onOpenChange && !triblerService.getGuiSettings().ask_download_settings) { + if (props.open && props.onOpenChange && triblerService.guiSettings.ask_download_settings === false) { OnDownloadClicked(); return <>; } diff --git a/src/tribler/ui/src/i18n/index.ts b/src/tribler/ui/src/i18n/index.ts index 77cfb3f0fa..37130117ac 100644 --- a/src/tribler/ui/src/i18n/index.ts +++ b/src/tribler/ui/src/i18n/index.ts @@ -8,7 +8,7 @@ i18n .use(initReactI18next) .init({ supportedLngs: ['en_US', 'es_ES', 'pt_BR', 'ru_RU', 'zh_CN'], - lng: triblerService.getGuiSettings().lang, + lng: triblerService.guiSettings.lang, fallbackLng: 'en_US', interpolation: { escapeValue: false, diff --git a/src/tribler/ui/src/lib/utils.ts b/src/tribler/ui/src/lib/utils.ts index f81ad0afc3..24bf59ebfe 100644 --- a/src/tribler/ui/src/lib/utils.ts +++ b/src/tribler/ui/src/lib/utils.ts @@ -70,7 +70,7 @@ export function categoryIcon(name: category): string { } export function formatTimeAgo(ts: number) { - let locale = triblerService.getGuiSettings().lang ?? 'en-US'; + let locale = triblerService.guiSettings.lang ?? 'en_US'; const timeAg = new TimeAgo(locale.slice(0, 2)); return timeAg.format(ts * 1000); } diff --git a/src/tribler/ui/src/models/settings.model.tsx b/src/tribler/ui/src/models/settings.model.tsx index d1b8b1b3ab..b3434a7321 100644 --- a/src/tribler/ui/src/models/settings.model.tsx +++ b/src/tribler/ui/src/models/settings.model.tsx @@ -96,6 +96,7 @@ export interface Settings { }, state_dir: string; memory_db: boolean; + ui: GuiSettings; } export interface GuiSettings { diff --git a/src/tribler/ui/src/pages/Settings/Anonymity.tsx b/src/tribler/ui/src/pages/Settings/Anonymity.tsx index 9ee6c648d3..5b00d7ff2d 100644 --- a/src/tribler/ui/src/pages/Settings/Anonymity.tsx +++ b/src/tribler/ui/src/pages/Settings/Anonymity.tsx @@ -10,7 +10,10 @@ export default function Anonimity() { const { t } = useTranslation(); const [settings, setSettings] = useState(); - if (!settings) (async () => { setSettings(await triblerService.getSettings()) })(); + if (!settings) { + (async () => { setSettings(await triblerService.getSettings()) })(); + return; + } return (
diff --git a/src/tribler/ui/src/pages/Settings/Bandwidth.tsx b/src/tribler/ui/src/pages/Settings/Bandwidth.tsx index 0f746ccf5e..17429426e5 100644 --- a/src/tribler/ui/src/pages/Settings/Bandwidth.tsx +++ b/src/tribler/ui/src/pages/Settings/Bandwidth.tsx @@ -11,7 +11,10 @@ export default function Bandwith() { const { t } = useTranslation(); const [settings, setSettings] = useState(); - if (!settings) (async () => { setSettings(await triblerService.getSettings()) })(); + if (!settings) { + (async () => { setSettings(await triblerService.getSettings()) })(); + return; + } return (
diff --git a/src/tribler/ui/src/pages/Settings/Connection.tsx b/src/tribler/ui/src/pages/Settings/Connection.tsx index e4a392b88c..edcf324b32 100644 --- a/src/tribler/ui/src/pages/Settings/Connection.tsx +++ b/src/tribler/ui/src/pages/Settings/Connection.tsx @@ -13,7 +13,10 @@ export default function Connection() { const { t } = useTranslation(); const [settings, setSettings] = useState(); - if (!settings) (async () => { setSettings(await triblerService.getSettings()) })(); + if (!settings) { + (async () => { setSettings(await triblerService.getSettings()) })(); + return; + } return (
diff --git a/src/tribler/ui/src/pages/Settings/Debugging.tsx b/src/tribler/ui/src/pages/Settings/Debugging.tsx index 5c094b111f..4e53fc0b40 100644 --- a/src/tribler/ui/src/pages/Settings/Debugging.tsx +++ b/src/tribler/ui/src/pages/Settings/Debugging.tsx @@ -1,5 +1,5 @@ import { Checkbox } from "@/components/ui/checkbox"; -import { GuiSettings, Settings } from "@/models/settings.model"; +import { Settings } from "@/models/settings.model"; import { triblerService } from "@/services/tribler.service"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -9,21 +9,25 @@ import SaveButton from "./SaveButton"; export default function Debugging() { const { t } = useTranslation(); const [settings, setSettings] = useState(); - const [guiSettings, setGuiSettings] = useState(); - if (!settings) (async () => { setSettings(await triblerService.getSettings()) })(); - if (!guiSettings) setGuiSettings(triblerService.getGuiSettings()); + if (!settings) { + (async () => { setSettings(await triblerService.getSettings()) })(); + return; + } return (
{ - if (guiSettings) { - setGuiSettings({ - ...guiSettings, - dev_mode: !!value + if (settings) { + setSettings({ + ...settings, + ui: { + ...settings.ui, + dev_mode: !!value + } }) } @@ -58,12 +62,8 @@ export default function Debugging() { { - const refresh = guiSettings?.dev_mode !== triblerService.getGuiSettings().dev_mode; - if (guiSettings) - triblerService.setGuiSettings(guiSettings); if (settings) await triblerService.setSettings(settings); - if (refresh) window.location.reload(); }} /> diff --git a/src/tribler/ui/src/pages/Settings/General.tsx b/src/tribler/ui/src/pages/Settings/General.tsx index 69797308e9..6758243411 100644 --- a/src/tribler/ui/src/pages/Settings/General.tsx +++ b/src/tribler/ui/src/pages/Settings/General.tsx @@ -1,7 +1,7 @@ import { PathInput } from "@/components/path-input"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; -import { GuiSettings, Settings } from "@/models/settings.model"; +import { Settings } from "@/models/settings.model"; import { triblerService } from "@/services/tribler.service"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -12,10 +12,11 @@ import { Input } from "@/components/ui/input"; export default function General() { const { t } = useTranslation(); const [settings, setSettings] = useState(); - const [guiSettings, setGuiSettings] = useState(); - if (!settings) (async () => { setSettings(await triblerService.getSettings()) })(); - if (!guiSettings) setGuiSettings(triblerService.getGuiSettings()); + if (!settings) { + (async () => { setSettings(await triblerService.getSettings()) })(); + return; + } return (
@@ -70,12 +71,15 @@ export default function General() {
{ if (settings) { - setGuiSettings({ + setSettings({ ...settings, - ask_download_settings: !!value + ui: { + ...settings?.ui, + ask_download_settings: !!value + } }); } }} @@ -141,12 +145,15 @@ export default function General() {
{t('FamilyFilter')}
{ if (settings) { - setGuiSettings({ + setSettings({ ...settings, - family_filter: !!value + ui: { + ...settings?.ui, + family_filter: !!value + } }); } }} @@ -163,12 +170,15 @@ export default function General() {
{t('Tags')}
{ if (settings) { - setGuiSettings({ + setSettings({ ...settings, - disable_tags: !!value + ui: { + ...settings?.ui, + disable_tags: !!value + } }); } }} @@ -183,8 +193,6 @@ export default function General() { { - if (guiSettings) - triblerService.setGuiSettings(guiSettings); if (settings) await triblerService.setSettings(settings); }} diff --git a/src/tribler/ui/src/pages/Settings/Seeding.tsx b/src/tribler/ui/src/pages/Settings/Seeding.tsx index c9697994bb..156104abc2 100644 --- a/src/tribler/ui/src/pages/Settings/Seeding.tsx +++ b/src/tribler/ui/src/pages/Settings/Seeding.tsx @@ -12,7 +12,10 @@ export default function Seeding() { const { t } = useTranslation(); const [settings, setSettings] = useState(); - if (!settings) (async () => { setSettings(await triblerService.getSettings()) })(); + if (!settings) { + (async () => { setSettings(await triblerService.getSettings()) })(); + return; + } return (
diff --git a/src/tribler/ui/src/services/tribler.service.ts b/src/tribler/ui/src/services/tribler.service.ts index 1c8feb0ebd..aed9a2f89e 100644 --- a/src/tribler/ui/src/services/tribler.service.ts +++ b/src/tribler/ui/src/services/tribler.service.ts @@ -11,6 +11,8 @@ export class TriblerService { private http: AxiosInstance; private baseURL = "/api"; private events: EventSource; + // Store a cached version of the GuiSettings to prevent from having to call the server every time we need them. + public guiSettings: GuiSettings = {}; constructor() { this.http = axios.create({ @@ -18,6 +20,8 @@ export class TriblerService { withCredentials: true, }); this.events = new EventSource(this.baseURL + '/events', { withCredentials: true }) + // Gets the GuiSettings + this.getSettings(); } isOnline() { @@ -26,11 +30,11 @@ export class TriblerService { // Events - addEventListener(topic:string, listener: (event: MessageEvent) => void): void { + addEventListener(topic: string, listener: (event: MessageEvent) => void): void { this.events.addEventListener(topic, listener); } - removeEventListener(topic:string, listener: (event: MessageEvent) => void): void { + removeEventListener(topic: string, listener: (event: MessageEvent) => void): void { this.events.removeEventListener(topic, listener); } @@ -127,7 +131,7 @@ export class TriblerService { return (await this.http.get(`/metadata/torrents/popular?metadata_type=300&metadata_type=220&include_total=1&first=1&last=50&hide_xxx=${+hide_xxx}`)).data.results; } - async getTorrentHealth(infohash: string): Promise<{infohash: string, num_seeders: number, num_leechers: number, last_tracker_check: number}> { + async getTorrentHealth(infohash: string): Promise<{ infohash: string, num_seeders: number, num_leechers: number, last_tracker_check: number }> { return (await this.http.get(`/metadata/torrents/${infohash}/health`)).data; } @@ -146,22 +150,14 @@ export class TriblerService { // Settings async getSettings(): Promise { - return (await this.http.get('/settings')).data.settings; - } - - async setSettings(settings: Settings): Promise { - return (await this.http.post('/settings', settings)).data.modified; - } - - getGuiSettings(): GuiSettings { - const settings_json = localStorage.getItem("gui_settings") ?? '{"ask_download_settings": true}'; - const settings: GuiSettings = JSON.parse(settings_json); + const settings = (await this.http.get('/settings')).data.settings; + this.guiSettings = {...settings?.ui, ...this.guiSettings}; return settings } - setGuiSettings(settings: GuiSettings) { - const settings_json = JSON.stringify(settings); - localStorage.setItem("gui_settings", settings_json); + async setSettings(settings: Partial): Promise { + this.guiSettings = {...settings?.ui, ...this.guiSettings}; + return (await this.http.post('/settings', settings)).data.modified; } async getLibtorrentSession(hops: number) {