From 187dd22027ee750bd49ddb040e043418a06aa987 Mon Sep 17 00:00:00 2001 From: glax Date: Thu, 20 Mar 2025 00:07:20 +0100 Subject: [PATCH] Backend Settings --- Website/App.tsx | 12 +- Website/modules/BackendSettingsFunctions.tsx | 61 +++++++++ Website/modules/Settings.tsx | 120 +++++++++++++++++- .../modules/interfaces/IBackendSettings.ts | 15 --- .../modules/interfaces/IBackendSettings.tsx | 17 +++ Website/modules/interfaces/IRequestLimits.ts | 17 +++ Website/styles/popup.css | 5 +- Website/styles/settings.css | 11 +- 8 files changed, 229 insertions(+), 29 deletions(-) create mode 100644 Website/modules/BackendSettingsFunctions.tsx delete mode 100644 Website/modules/interfaces/IBackendSettings.ts create mode 100644 Website/modules/interfaces/IBackendSettings.tsx create mode 100644 Website/modules/interfaces/IRequestLimits.ts diff --git a/Website/App.tsx b/Website/App.tsx index 86d65ea..4b9f0e0 100644 --- a/Website/App.tsx +++ b/Website/App.tsx @@ -58,7 +58,7 @@ export function getData(uri: string) : Promise { return makeRequest("GET", uri, null) as Promise; } -export function postData(uri: string, content: object | string | number) : Promise { +export function postData(uri: string, content: object | string | number | boolean) : Promise { return makeRequest("POST", uri, content) as Promise; } @@ -66,15 +66,15 @@ export function deleteData(uri: string) : Promise { return makeRequest("DELETE", uri, null) as Promise; } -export function patchData(uri: string, content: object | string | number) : Promise { +export function patchData(uri: string, content: object | string | number | boolean) : Promise { return makeRequest("patch", uri, content) as Promise; } -export function putData(uri: string, content: object | string | number) : Promise { +export function putData(uri: string, content: object | string | number | boolean) : Promise { return makeRequest("PUT", uri, content) as Promise; } -function makeRequest(method: string, uri: string, content: object | string | number | null) : Promise { +function makeRequest(method: string, uri: string, content: object | string | number | null | boolean) : Promise { return fetch(uri, { method: method, @@ -139,8 +139,8 @@ export const checkConnection = async (apiUri: string): Promise =>{ { method: 'GET', }) - .then((response) =>{ - return response.type != "error"; + .then((response) => { + return response.ok; }) .catch(() => { return Promise.reject(); diff --git a/Website/modules/BackendSettingsFunctions.tsx b/Website/modules/BackendSettingsFunctions.tsx new file mode 100644 index 0000000..1c57e37 --- /dev/null +++ b/Website/modules/BackendSettingsFunctions.tsx @@ -0,0 +1,61 @@ +import {deleteData, getData, patchData} from "../App"; +import IRequestLimits, {RequestType} from "./interfaces/IRequestLimits"; +import IBackendSettings from "./interfaces/IBackendSettings"; + +export default class BackendSettings { + static async GetSettings(apiUri: string) : Promise { + return getData(`${apiUri}/v2/Settings`).then((s) => s as IBackendSettings); + } + + static async GetUserAgent(apiUri: string) : Promise { + return getData(`${apiUri}/v2/Settings/UserAgent`).then((text) => text as unknown as string); + } + + static async UpdateUserAgent(apiUri: string, userAgent: string) { + return patchData(`${apiUri}/v2/Settings/UserAgent`, userAgent); + } + + static async ResetUserAgent(apiUri: string) { + return deleteData(`${apiUri}/v2/Settings/UserAgent`); + } + + static async GetRequestLimits(apiUri: string) : Promise { + return getData(`${apiUri}/v2/Settings/RequestLimits`).then((limits) => limits as IRequestLimits); + } + + static async ResetRequestLimits(apiUri: string) { + return deleteData(`${apiUri}/v2/Settings/RequestLimits`); + } + + static async UpdateRequestLimit(apiUri: string, requestType: RequestType, value: number) { + return patchData(`${apiUri}/v2/Settings/RequestLimits/${requestType}`, value); + } + + static async ResetRequestLimit(apiUri: string, requestType: RequestType) { + return deleteData(`${apiUri}/v2/Settings/RequestLimits/${requestType}`); + } + + static async GetImageCompressionValue(apiUri: string) : Promise { + return getData(`${apiUri}/v2/Settings/ImageCompression`).then((n) => n as unknown as number); + } + + static async UpdateImageCompressionValue(apiUri: string, value: number) { + return patchData(`${apiUri}/v2/Settings/ImageCompression`, value); + } + + static async GetBWImageToggle(apiUri: string) : Promise { + return getData(`${apiUri}/v2/Settings/BWImages`).then((state) => state as unknown as boolean); + } + + static async UpdateBWImageToggle(apiUri: string, value: boolean) { + return patchData(`${apiUri}/v2/Settings/BWImages`, value); + } + + static async GetAprilFoolsToggle(apiUri: string) : Promise { + return getData(`${apiUri}/v2/Settings/AprilFoolsMode`).then((state) => state as unknown as boolean); + } + + static async UpdateAprilFoolsToggle(apiUri: string, value: boolean) { + return patchData(`${apiUri}/v2/Settings/AprilFoolsMode`, value); + } +} \ No newline at end of file diff --git a/Website/modules/Settings.tsx b/Website/modules/Settings.tsx index c011558..7a3f492 100644 --- a/Website/modules/Settings.tsx +++ b/Website/modules/Settings.tsx @@ -1,22 +1,35 @@ import IFrontendSettings from "./interfaces/IFrontendSettings"; import '../styles/settings.css'; import '../styles/react-toggle.css'; -import React, {useEffect, useState} from "react"; +import React, {LegacyRef, MutableRefObject, Ref, RefObject, useEffect, useRef, useState} from "react"; import INotificationConnector, {NotificationConnectorItem} from "./interfaces/INotificationConnector"; import NotificationConnectorFunctions from "./NotificationConnectorFunctions"; import ILocalLibrary, {LocalLibraryItem} from "./interfaces/ILocalLibrary"; import LocalLibraryFunctions from "./LocalLibraryFunctions"; +import IBackendSettings from "./interfaces/IBackendSettings"; +import BackendSettings from "./BackendSettingsFunctions"; +import Toggle from "react-toggle"; +import Loader from "./Loader"; +import {RequestType} from "./interfaces/IRequestLimits"; -export default function Settings({backendConnected, apiUri, frontendSettings, setFrontendSettings} : {backendConnected: boolean, apiUri: string, frontendSettings: IFrontendSettings, setFrontendSettings: (settings: IFrontendSettings) => void}) { - let [showSettings, setShowSettings] = useState(false); - let [notificationConnectors, setNotificationConnectors] = useState([]); - let [localLibraries, setLocalLibraries] = useState([]); +export default function Settings({ backendConnected, apiUri, frontendSettings, setFrontendSettings } : { + backendConnected: boolean, + apiUri: string, + frontendSettings: IFrontendSettings, + setFrontendSettings: (settings: IFrontendSettings) => void +}) { + const [showSettings, setShowSettings] = useState(false); + const [loadingBackend, setLoadingBackend] = useState(false); + const [backendSettings, setBackendSettings] = useState(null); + const [notificationConnectors, setNotificationConnectors] = useState([]); + const [localLibraries, setLocalLibraries] = useState([]); useEffect(() => { if(!backendConnected) return; NotificationConnectorFunctions.GetNotificationConnectors(apiUri).then(setNotificationConnectors); LocalLibraryFunctions.GetLibraries(apiUri).then(setLocalLibraries); + BackendSettings.GetSettings(apiUri).then(setBackendSettings); }, [backendConnected, showSettings]); const dateToStr = (x: Date) => { @@ -26,6 +39,16 @@ export default function Settings({backendConnected, apiUri, frontendSettings, se return ret; } + const ChangeRequestLimit = (requestType: RequestType, limit: number) => { + if(backendSettings === null) + return; + setLoadingBackend(true); + BackendSettings.UpdateRequestLimit(apiUri, requestType, limit) + .then(() => setBackendSettings({...backendSettings, [requestType]: requestType})) + .finally(() => setLoadingBackend(false)); + } + const ref : React.LegacyRef | undefined = useRef(null); + return (
setShowSettings(true)}> @@ -38,14 +61,99 @@ export default function Settings({backendConnected, apiUri, frontendSettings, se Close Settings setShowSettings(false)}/>
+
setFrontendSettings({...frontendSettings, apiUri:e.currentTarget.value})} id="ApiUri" />
-
+
setFrontendSettings({...frontendSettings, jobInterval: new Date(e.currentTarget.valueAsNumber-60*60*1000) ?? frontendSettings.jobInterval})}/>
+
+

B/W Images

+ { + if(backendSettings === null) + return; + setLoadingBackend(true); + BackendSettings.UpdateBWImageToggle(apiUri, e.target.checked) + .then(() => setBackendSettings({...backendSettings, bwImages: e.target.checked})) + .finally(() => setLoadingBackend(false)); + }} /> +
+
+

April Fools Mode

+ { + if(backendSettings === null) + return; + setLoadingBackend(true); + BackendSettings.UpdateAprilFoolsToggle(apiUri, e.target.checked) + .then(() => setBackendSettings({...backendSettings, aprilFoolsMode: e.target.checked})) + .finally(() => setLoadingBackend(false)); + }} /> +
+
+

Image Compression

+ { + if(backendSettings === null) + return; + setLoadingBackend(true); + BackendSettings.UpdateImageCompressionValue(apiUri, e.target.checked ? 40 : 100) + .then(() => setBackendSettings({...backendSettings, compression: e.target.checked ? 40 : 100})) + .then(() => { + if(ref.current != null){ + ref.current.value = e.target.checked ? "40" : "100"; + ref.current.disabled = !e.target.checked; + } + }) + .finally(() => setLoadingBackend(false)); + }} /> + { + if(backendSettings === null) + return; + setLoadingBackend(true); + BackendSettings.UpdateImageCompressionValue(apiUri, e.currentTarget.valueAsNumber) + .then(() => setBackendSettings({...backendSettings, compression: e.currentTarget.valueAsNumber})) + .finally(() => setLoadingBackend(false)); + }} /> +
+
+

Request Limits:

+ + ChangeRequestLimit(RequestType.Default, e.currentTarget.valueAsNumber)} /> + + ChangeRequestLimit(RequestType.MangaInfo, e.currentTarget.valueAsNumber)} /> + + ChangeRequestLimit(RequestType.MangaDexFeed, e.currentTarget.valueAsNumber)} /> + + ChangeRequestLimit(RequestType.MangaDexImage, e.currentTarget.valueAsNumber)} /> + + ChangeRequestLimit(RequestType.MangaImage, e.currentTarget.valueAsNumber)} /> + + ChangeRequestLimit(RequestType.MangaCover, e.currentTarget.valueAsNumber)} /> +
+
+ + { + if(backendSettings === null) + return; + setLoadingBackend(true); + BackendSettings.UpdateUserAgent(apiUri, e.currentTarget.value) + .then(() => setBackendSettings({...backendSettings, userAgent: e.currentTarget.value})) + .finally(() => setLoadingBackend(false)); + }} /> +

Notification Connectors:

{notificationConnectors.map(c => )} diff --git a/Website/modules/interfaces/IBackendSettings.ts b/Website/modules/interfaces/IBackendSettings.ts deleted file mode 100644 index 5661cff..0000000 --- a/Website/modules/interfaces/IBackendSettings.ts +++ /dev/null @@ -1,15 +0,0 @@ -export default interface IBackendSettings { - "downloadLocation": string; - "userAgent": string; - "aprilFoolsMode": boolean; - "compression": number; - "bwImages": boolean; - "requestLimits": { - "MangaInfo": number; - "MangaDexFeed": number; - "MangaDexImage": number; - "MangaImage": number; - "MangaCover": number; - "Default": number - } -} \ No newline at end of file diff --git a/Website/modules/interfaces/IBackendSettings.tsx b/Website/modules/interfaces/IBackendSettings.tsx new file mode 100644 index 0000000..8783e1b --- /dev/null +++ b/Website/modules/interfaces/IBackendSettings.tsx @@ -0,0 +1,17 @@ +export default interface IBackendSettings { + downloadLocation: string; + workingDirectory: string; + userAgent: string; + aprilFoolsMode: boolean; + requestLimits: { + Default: number, + MangaInfo: number, + MangaDexFeed: number, + MangaDexImage: number, + MangaImage: number, + MangaCover: number, + }; + compression: number; + bwImages: boolean; + startNewJobTimeoutMs: number; +} diff --git a/Website/modules/interfaces/IRequestLimits.ts b/Website/modules/interfaces/IRequestLimits.ts new file mode 100644 index 0000000..30d0d41 --- /dev/null +++ b/Website/modules/interfaces/IRequestLimits.ts @@ -0,0 +1,17 @@ +export default interface IRequestLimits { + Default: number; + MangaDexFeed: number; + MangaImage: number; + MangaCover: number; + MangaDexImage: number; + MangaInfo: number; +} + +export enum RequestType { + Default = "Default", + MangaDexFeed = "MangaDexFeed", + MangaImage = "MangaImage", + MangaCover = "MangaCover", + MangaDexImage = "MangaDexImage", + MangaInfo = "MangaInfo" +} \ No newline at end of file diff --git a/Website/styles/popup.css b/Website/styles/popup.css index 087f27e..a09e5ff 100644 --- a/Website/styles/popup.css +++ b/Website/styles/popup.css @@ -40,5 +40,8 @@ left: 0; width: calc(100% - 30px); height: calc(100% - 50px); - margin: 5px 15px; + padding: 5px 15px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; } \ No newline at end of file diff --git a/Website/styles/settings.css b/Website/styles/settings.css index 5bcf686..9c72663 100644 --- a/Website/styles/settings.css +++ b/Website/styles/settings.css @@ -43,6 +43,15 @@ #SettingsPopUpBody h1, #SettingsPopUpBody h2, #SettingsPopUpBody h3 { border: 0; - margin: 5px 0 0 0; + margin: 5px 0 2px 0; padding: 0; +} + +.settings-requestLimits { + display: flex; + flex-direction: column; +} + +.settings-requestLimits input { + width: min-content; } \ No newline at end of file