From 1d8dd7381d8abad1badd8bbb092e277227ca2e12 Mon Sep 17 00:00:00 2001 From: glax Date: Wed, 19 Mar 2025 02:37:36 +0100 Subject: [PATCH] Add Loader-Spinner Style Settings, re-add api-url-field --- Website/App.tsx | 152 ++- Website/modules/Footer.tsx | 14 +- Website/modules/Header.tsx | 4 +- Website/modules/Loader.tsx | 6 + Website/modules/LocalLibraryFunctions.tsx | 1 - Website/modules/MangaFunctions.tsx | 2 +- Website/modules/MonitorJobsList.tsx | 31 +- Website/modules/QueuePopUp.tsx | 22 +- Website/modules/Search.tsx | 37 +- Website/modules/Settings.tsx | 20 +- Website/modules/interfaces/IAuthor.tsx | 11 +- Website/modules/interfaces/ILink.tsx | 13 +- Website/modules/interfaces/IManga.tsx | 73 +- Website/modules/interfaces/IMangaAltTitle.ts | 5 - Website/modules/interfaces/IMangaAltTitle.tsx | 25 + .../interfaces/INotificationConnector.tsx | 71 +- Website/styles/index.css | 5 +- Website/styles/loader.css | 80 ++ Website/styles/mangaCover.css | 124 ++- Website/styles/monitorMangaList.css | 6 +- Website/styles/notificationConnector.css | 69 ++ Website/styles/popup.css | 5 +- Website/styles/search.css | 7 +- Website/styles/settings.css | 10 + package-lock.json | 875 ++++++++++-------- package.json | 2 +- 26 files changed, 1041 insertions(+), 629 deletions(-) create mode 100644 Website/modules/Loader.tsx delete mode 100644 Website/modules/interfaces/IMangaAltTitle.ts create mode 100644 Website/modules/interfaces/IMangaAltTitle.tsx create mode 100644 Website/styles/loader.css create mode 100644 Website/styles/notificationConnector.css diff --git a/Website/App.tsx b/Website/App.tsx index 2c9daf9..a288d61 100644 --- a/Website/App.tsx +++ b/Website/App.tsx @@ -6,157 +6,114 @@ import MonitorJobsList from "./modules/MonitorJobsList"; import './styles/index.css' import IFrontendSettings, {LoadFrontendSettings} from "./modules/interfaces/IFrontendSettings"; import {useCookies} from "react-cookie"; +import Loader from "./modules/Loader"; export default function App(){ const [, setCookie] = useCookies(['apiUri', 'jobInterval']); const [connected, setConnected] = React.useState(false); const [showSearch, setShowSearch] = React.useState(false); const [frontendSettings, setFrontendSettings] = useState(LoadFrontendSettings()); - const [updateInterval, setUpdateInterval] = React.useState(); - const [updateMonitorList, setUpdateMonitorList] = React.useState(new Date()); - const checkConnectedInterval = 1000; + const [updateInterval, setUpdateInterval] = React.useState(undefined); + const checkConnectedInterval = 5000; const apiUri = frontendSettings.apiUri; useEffect(() => { - checkConnection(apiUri).then(res => setConnected(res)).catch(() => setConnected(false)); + setCookie('apiUri', frontendSettings.apiUri); + setCookie('jobInterval', frontendSettings.jobInterval); + updateConnected(apiUri, connected, setConnected); + }, [frontendSettings]); + + useEffect(() => { if(updateInterval === undefined){ setUpdateInterval(setInterval(() => { - checkConnection(apiUri).then(res => setConnected(res)).catch(() => setConnected(false)); + updateConnected(apiUri, connected, setConnected); }, checkConnectedInterval)); }else{ clearInterval(updateInterval); setUpdateInterval(undefined); } - }, [frontendSettings]); - - function ChangeSettings(settings: IFrontendSettings) { - setFrontendSettings(settings); - setCookie('apiUri', settings.apiUri); - setCookie('jobInterval', settings.jobInterval); - } - - const UpdateList = () => {setUpdateMonitorList(new Date())} + }, [connected]); return(
-
+
{connected ? <> {showSearch ? <> - setShowSearch(false)} /> + setShowSearch(false)} />
: <>} - setShowSearch(true)} onJobsChanged={UpdateList} connectedToBackend={connected} /> + setShowSearch(true)} connectedToBackend={connected} checkConnectedInterval={checkConnectedInterval} /> : <>

No connection to the Backend.

Check the Settings ApiUri.

+ } -
+
) } export function getData(uri: string) : Promise { - return fetch(uri, - { - method: 'GET', - headers : { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - }) - .then(function(response){ - if(!response.ok) throw new Error("Could not fetch data"); - return response.json(); - }) - .catch(function(err){ - console.error(`Error GETting Data ${uri}\n${err}`); - return Promise.reject(); - }); + return makeRequest("GET", uri, null) as Promise; } export function postData(uri: string, content: object | string | number) : Promise { - return fetch(uri, - { - method: 'POST', - headers : { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify(content) - }) - .then(function(response){ - if(!response.ok) - throw new Error("Could not fetch data"); - let json = response.json(); - return json.then((json) => json).catch(() => null); - }) - .catch(function(err){ - console.error(`Error POSTing Data ${uri}\n${err}`); - return Promise.reject(); - }); + return makeRequest("POST", uri, content) as Promise; } export function deleteData(uri: string) : Promise { - return fetch(uri, - { - method: 'DELETE', - headers : { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - }) - .then(() =>{ - return Promise.resolve(); - }) - .catch(function(err){ - console.error(`Error DELETEing Data ${uri}\n${err}`); - return Promise.reject(); - }); + return makeRequest("PUT", uri, null) as Promise; } export function patchData(uri: string, content: object | string | number) : Promise { - return fetch(uri, - { - method: 'PATCH', - headers : { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: JSON.stringify(content) - }) - .then(function(response){ - if(!response.ok) - throw new Error("Could not fetch data"); - let json = response.json(); - return json.then((json) => json).catch(() => null); - }) - .catch(function(err){ - console.error(`Error PATCHing Data ${uri}\n${err}`); - return Promise.reject(); - }); + return makeRequest("patch", uri, content) as Promise; } export function putData(uri: string, content: object | string | number) : Promise { + return makeRequest("PUT", uri, content) as Promise; +} + +function makeRequest(method: string, uri: string, content: object | string | number | null) : Promise { return fetch(uri, { - method: 'PUT', + method: method, headers : { 'Content-Type': 'application/json', 'Accept': 'application/json' }, - body: JSON.stringify(content) + body: content ? JSON.stringify(content) : null }) .then(function(response){ - if(!response.ok) - throw new Error("Could not fetch data"); + if(!response.ok){ + if(response.status === 503){ + let retryHeaderVal = response.headers.get("Retry-After"); + let seconds = 10; + if(!retryHeaderVal){ + return response.text().then(text => { + seconds = parseInt(text); + return new Promise(resolve => setTimeout(resolve, seconds * 1000)) + .then(() => { + return makeRequest(method, uri, content); + }); + }); + }else { + seconds = parseInt(retryHeaderVal); + return new Promise(resolve => setTimeout(resolve, seconds * 1000)) + .then(() => { + return makeRequest(method, uri, content); + }); + } + }else + throw new Error(response.statusText); + } let json = response.json(); return json.then((json) => json).catch(() => null); }) - .catch(function(err){ - console.error(`Error PUTting Data ${uri}\n${err}`); + .catch(function(err : Error){ + console.error(`Error ${method}ing Data ${uri}\n${err}`); return Promise.reject(); }); } @@ -170,6 +127,15 @@ export function isValidUri(uri: string) : boolean{ } } +const updateConnected = (apiUri: string, connected: boolean, setConnected: (c: boolean) => void) => { + checkConnection(apiUri) + .then(res => { + if(connected != res) + setConnected(res); + }) + .catch(() => setConnected(false)); +} + export const checkConnection = async (apiUri: string): Promise =>{ return fetch(`${apiUri}/swagger`, { diff --git a/Website/modules/Footer.tsx b/Website/modules/Footer.tsx index 74955dc..6cd5a3c 100644 --- a/Website/modules/Footer.tsx +++ b/Website/modules/Footer.tsx @@ -6,12 +6,12 @@ import {mdiCounter, mdiEyeCheck, mdiRun, mdiTrayFull} from '@mdi/js'; import QueuePopUp from "./QueuePopUp"; import {JobState, JobType} from "./interfaces/Jobs/IJob"; -export default function Footer({connectedToBackend, apiUri} : {connectedToBackend: boolean, apiUri: string}) { +export default function Footer({connectedToBackend, apiUri, checkConnectedInterval} : {connectedToBackend: boolean, apiUri: string, checkConnectedInterval: number}) { const [MonitoringJobsCount, setMonitoringJobsCount] = React.useState(0); const [AllJobsCount, setAllJobsCount] = React.useState(0); const [RunningJobsCount, setRunningJobsCount] = React.useState(0); const [WaitingJobsCount, setWaitingJobs] = React.useState(0); - const [countUpdateInterval, setCountUpdateInterval] = React.useState(); + const [countUpdateInterval, setCountUpdateInterval] = React.useState(undefined); function UpdateBackendState(){ JobFunctions.GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob).then((jobs) => setMonitoringJobsCount(jobs.length)); @@ -23,9 +23,11 @@ export default function Footer({connectedToBackend, apiUri} : {connectedToBacken useEffect(() => { if(connectedToBackend){ UpdateBackendState(); - setCountUpdateInterval(setInterval(() => { - UpdateBackendState(); - }, 2000)); + if(countUpdateInterval === undefined){ + setCountUpdateInterval(setInterval(() => { + UpdateBackendState(); + }, checkConnectedInterval)); + } }else{ clearInterval(countUpdateInterval); setCountUpdateInterval(undefined); @@ -36,7 +38,7 @@ export default function Footer({connectedToBackend, apiUri} : {connectedToBacken