Created standardized Popup-Window
Moved Update-functions for Queue-Status and Monitoring-list to their respective modules
This commit is contained in:
parent
1304bc750a
commit
fcc1ff392c
@ -1,10 +1,11 @@
|
||||
import React, {EventHandler, useEffect} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import Footer from "./modules/Footer";
|
||||
import Search from "./modules/Search";
|
||||
import Header from "./modules/Header";
|
||||
import MonitorJobsList from "./modules/MonitorJobsList";
|
||||
import './styles/index.css'
|
||||
import QueuePopUp from "./modules/QueuePopUp";
|
||||
import {Job} from "./modules/Job";
|
||||
import IFrontendSettings from "./modules/interfaces/IFrontendSettings";
|
||||
|
||||
export default function App(){
|
||||
const [connected, setConnected] = React.useState(false);
|
||||
@ -15,35 +16,14 @@ export default function App(){
|
||||
const [joblistUpdateInterval, setJoblistUpdateInterval] = React.useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
checkConnection();
|
||||
checkConnection().then(res => setConnected(res)).catch(() => setConnected(false));
|
||||
setInterval(() => {
|
||||
checkConnection();
|
||||
checkConnection().then(res => setConnected(res)).catch(() => setConnected(false));
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
const checkConnection = () =>{
|
||||
getData('http://127.0.0.1:6531/v2/Ping').then((result) => {
|
||||
setConnected(result != null);
|
||||
}).catch(() => setConnected(false));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(connected){
|
||||
setLastJobListUpdate(new Date());
|
||||
setJoblistUpdateInterval(setInterval(() => {
|
||||
setLastJobListUpdate(new Date());
|
||||
}, 5000));
|
||||
}else{
|
||||
clearInterval(joblistUpdateInterval);
|
||||
setJoblistUpdateInterval(undefined);
|
||||
}
|
||||
}, [connected]);
|
||||
|
||||
|
||||
|
||||
const JobsChanged : EventHandler<any> = () => {
|
||||
setLastMangaListUpdate(new Date());
|
||||
setLastJobListUpdate(new Date());
|
||||
function CreateJob(internalId: string, jobType: string){
|
||||
Job.CreateJobDateInterval(internalId, jobType, frontendSettings.jobInterval);
|
||||
}
|
||||
|
||||
return(<div>
|
||||
@ -52,18 +32,14 @@ export default function App(){
|
||||
? <>
|
||||
{showSearch
|
||||
? <>
|
||||
<Search onJobsChanged={JobsChanged} closeSearch={() => setShowSearch(false)} />
|
||||
<Search createJob={CreateJob} closeSearch={() => setShowSearch(false)} />
|
||||
<hr/>
|
||||
</>
|
||||
: <></>}
|
||||
{showQueue
|
||||
? <QueuePopUp closeQueue={() => setShowQueue(false)} />
|
||||
: <></>
|
||||
}
|
||||
<MonitorJobsList onStartSearch={() => setShowSearch(true)} onJobsChanged={JobsChanged} key={lastMangaListUpdate.getTime()}/>
|
||||
<MonitorJobsList onStartSearch={() => setShowSearch(true)} onJobsChanged={() => console.info("jobsChanged")} connectedToBackend={connected} />
|
||||
</>
|
||||
: <h1>No connection to backend</h1>}
|
||||
<Footer key={lastJobListUpdate.getTime()} showQueue={() => setShowQueue(true)}/>
|
||||
<Footer connectedToBackend={connected} />
|
||||
</div>)
|
||||
}
|
||||
|
||||
@ -133,4 +109,10 @@ export function isValidUri(uri: string) : boolean{
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const checkConnection = async (): Promise<boolean> =>{
|
||||
return getData('http://127.0.0.1:6531/v2/Ping').then((result) => {
|
||||
return result != null;
|
||||
}).catch(() => Promise.reject());
|
||||
}
|
@ -3,12 +3,14 @@ import '../styles/footer.css';
|
||||
import {Job} from './Job';
|
||||
import Icon from '@mdi/react';
|
||||
import { mdiRun, mdiCounter, mdiEyeCheck, mdiTrayFull } from '@mdi/js';
|
||||
import QueuePopUp from "./QueuePopUp";
|
||||
|
||||
export default function Footer({showQueue} : {showQueue(): void}){
|
||||
export default function Footer({connectedToBackend} : {connectedToBackend: boolean}) {
|
||||
const [MonitoringJobsCount, setMonitoringJobsCount] = React.useState(0);
|
||||
const [AllJobsCount, setAllJobsCount] = React.useState(0);
|
||||
const [RunningJobsCount, setRunningJobsCount] = React.useState(0);
|
||||
const [StandbyJobsCount, setStandbyJobsCount] = React.useState(0);
|
||||
const [countUpdateInterval, setcountUpdateInterval] = React.useState<number>();
|
||||
|
||||
function UpdateBackendState(){
|
||||
Job.GetMonitoringJobs().then((jobs) => setMonitoringJobsCount(jobs.length));
|
||||
@ -18,15 +20,25 @@ export default function Footer({showQueue} : {showQueue(): void}){
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
UpdateBackendState();
|
||||
}, []);
|
||||
if(connectedToBackend){
|
||||
UpdateBackendState();
|
||||
setcountUpdateInterval(setInterval(() => {
|
||||
UpdateBackendState();
|
||||
}, 2000));
|
||||
}else{
|
||||
clearInterval(countUpdateInterval);
|
||||
setcountUpdateInterval(undefined);
|
||||
}
|
||||
}, [connectedToBackend]);
|
||||
|
||||
return (
|
||||
<footer>
|
||||
<div><Icon path={mdiEyeCheck} size={1}/> <span>{MonitoringJobsCount}</span></div>
|
||||
<div onClick={showQueue} className="hoverHand"><Icon path={mdiTrayFull} size={1}/> <span>{StandbyJobsCount}</span></div>
|
||||
<div onClick={showQueue} className="hoverHand"><Icon path={mdiRun} size={1}/> <span>{RunningJobsCount}</span></div>
|
||||
<div><Icon path={mdiCounter} size={1}/> <span>{AllJobsCount}</span></div>
|
||||
<div className="statusBadge"><Icon path={mdiEyeCheck} size={1}/> <span>{MonitoringJobsCount}</span></div>
|
||||
<QueuePopUp>
|
||||
<div className="statusBadge hoverHand"><Icon path={mdiTrayFull} size={1}/> <span>{StandbyJobsCount}</span></div>
|
||||
<div className="statusBadge hoverHand"><Icon path={mdiRun} size={1}/> <span>{RunningJobsCount}</span></div>
|
||||
</QueuePopUp>
|
||||
<div className="statusBadge"><Icon path={mdiCounter} size={1}/> <span>{AllJobsCount}</span></div>
|
||||
<p id="madeWith">Made with Blåhaj 🦈</p>
|
||||
</footer>)
|
||||
}
|
@ -4,6 +4,10 @@ import IProgressToken from "./interfaces/IProgressToken";
|
||||
|
||||
export class Job
|
||||
{
|
||||
static IntervalStringFromDate(date: Date) : string {
|
||||
return `${date.getDay()}.${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||
}
|
||||
|
||||
static async GetAllJobs(): Promise<string[]> {
|
||||
console.info("Getting all Jobs");
|
||||
return getData("http://127.0.0.1:6531/v2/Jobs")
|
||||
@ -101,9 +105,18 @@ export class Job
|
||||
});
|
||||
}
|
||||
|
||||
static async CreateJobDateInterval(internalId: string, jobType: string, interval: Date) : Promise<null> {
|
||||
return this.CreateJob(internalId, jobType, this.IntervalStringFromDate(interval));
|
||||
}
|
||||
|
||||
static async CreateJob(internalId: string, jobType: string, interval: string): Promise<null> {
|
||||
const validate = /(?:[0-9]{1,2}\.)?[0-9]{1,2}:[0-9]{1,2}(?::[0-9]{1,2})?/
|
||||
console.info(`Creating Job for Manga ${internalId} at ${interval} interval`);
|
||||
let data = {
|
||||
if(!validate.test(interval)){
|
||||
console.error("Interval was in incorrect format.");
|
||||
return Promise.reject();
|
||||
}
|
||||
const data = {
|
||||
internalId: internalId,
|
||||
interval: interval
|
||||
};
|
||||
|
@ -8,9 +8,10 @@ import '../styles/MangaCoverCard.css'
|
||||
import Icon from '@mdi/react';
|
||||
import { mdiTrashCanOutline, mdiPlayBoxOutline } from '@mdi/js';
|
||||
|
||||
export default function MonitorJobsList({onStartSearch, onJobsChanged} : {onStartSearch() : void, onJobsChanged: EventHandler<any>}) {
|
||||
export default function MonitorJobsList({onStartSearch, onJobsChanged, connectedToBackend} : {onStartSearch() : void, onJobsChanged: EventHandler<any>, connectedToBackend: boolean}) {
|
||||
const [MonitoringJobs, setMonitoringJobs] = useState<IJob[]>([]);
|
||||
const [AllManga, setAllManga] = useState<IManga[]>([]);
|
||||
const [joblistUpdateInterval, setJoblistUpdateInterval] = React.useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
console.debug("Updating display list.");
|
||||
@ -28,8 +29,16 @@ export default function MonitorJobsList({onStartSearch, onJobsChanged} : {onStar
|
||||
}, [MonitoringJobs]);
|
||||
|
||||
useEffect(() => {
|
||||
UpdateMonitoringJobsList();
|
||||
}, []);
|
||||
if(connectedToBackend){
|
||||
UpdateMonitoringJobsList();
|
||||
setJoblistUpdateInterval(setInterval(() => {
|
||||
UpdateMonitoringJobsList();
|
||||
}, 1000));
|
||||
}else{
|
||||
clearInterval(joblistUpdateInterval);
|
||||
setJoblistUpdateInterval(undefined);
|
||||
}
|
||||
}, [connectedToBackend]);
|
||||
|
||||
function UpdateMonitoringJobsList(){
|
||||
console.debug("Updating MonitoringJobsList");
|
||||
|
@ -1,16 +1,18 @@
|
||||
import React, {ReactElement, useEffect} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import IJob, {JobTypeFromNumber} from "./interfaces/IJob";
|
||||
import '../styles/queuePopUp.css';
|
||||
import '../styles/popup.css';
|
||||
import {Job} from "./Job";
|
||||
import IManga from "./interfaces/IManga";
|
||||
import {Manga} from "./Manga";
|
||||
|
||||
export default function QueuePopUp({closeQueue} : {closeQueue(): void}){
|
||||
export default function QueuePopUp({children} : {children: JSX.Element[]}) {
|
||||
|
||||
const [StandbyJobs, setStandbyJobs] = React.useState<IJob[]>([]);
|
||||
const [StandbyJobsManga, setStandbyJobsManga] = React.useState<IManga[]>([]);
|
||||
const [RunningJobs, setRunningJobs] = React.useState<IJob[]>([]);
|
||||
const [RunningJobsManga, setRunningJobsManga] = React.useState<IManga[]>([]);
|
||||
const [showQueuePopup, setShowQueuePopup] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
Job.GetStandbyJobs()
|
||||
@ -56,45 +58,54 @@ export default function QueuePopUp({closeQueue} : {closeQueue(): void}){
|
||||
.then(setRunningJobsManga);
|
||||
}, [RunningJobs]);
|
||||
|
||||
return (
|
||||
<div id="QueuePopUp">
|
||||
<div id="QueuePopUpHeader">
|
||||
<h1>Queue Status</h1>
|
||||
<img alt="Close Search" id="closeSearch" src="../media/close-x.svg" onClick={closeQueue}/>
|
||||
return (<>
|
||||
<div onClick={() => setShowQueuePopup(true)}>
|
||||
{children}
|
||||
</div>
|
||||
<div id="QueuePopUpBody">
|
||||
<div id="RunningJobQueue">
|
||||
<h1>Running</h1>
|
||||
<div className="JobQueue">
|
||||
{RunningJobs.map((job: IJob) => {
|
||||
const manga = RunningJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
||||
if (manga === undefined || manga === null)
|
||||
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga for {job.id}</div>
|
||||
return <div className="QueueJob" key={"QueueJob-" + job.id}>
|
||||
<img src={Manga.GetMangaCoverUrl(manga.internalId)} />
|
||||
<p>{JobTypeFromNumber(job.jobType)}</p>
|
||||
</div>;
|
||||
})}
|
||||
{showQueuePopup
|
||||
? <div className="popup" id="QueuePopUp">
|
||||
<div className="popupHeader">
|
||||
<h1>Queue Status {showQueuePopup ? "true" : "false"}</h1>
|
||||
<img alt="Close Search" className="close" src="../media/close-x.svg"
|
||||
onClick={() => setShowQueuePopup(false)}/>
|
||||
</div>
|
||||
<div id="QueuePopUpBody" className="popupBody">
|
||||
<div id="RunningJobQueue">
|
||||
<h1>Running</h1>
|
||||
<div className="JobQueue">
|
||||
{RunningJobs.map((job: IJob) => {
|
||||
const manga = RunningJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
||||
if (manga === undefined || manga === null)
|
||||
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga
|
||||
for {job.id}</div>
|
||||
return <div className="QueueJob" key={"QueueJob-" + job.id}>
|
||||
<img src={Manga.GetMangaCoverUrl(manga.internalId)}/>
|
||||
<p>{JobTypeFromNumber(job.jobType)}</p>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div id="WaitingJobQueue">
|
||||
<h1>Standby</h1>
|
||||
<div className="JobQueue">
|
||||
{StandbyJobs.map((job: IJob) => {
|
||||
const manga = StandbyJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
||||
if (manga === undefined || manga === null)
|
||||
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga
|
||||
for {job.id}</div>
|
||||
return <div className="QueueJob" key={"QueueJob-" + job.id}>
|
||||
<img src={Manga.GetMangaCoverUrl(manga.internalId)}/>
|
||||
<p className="QueueJob-Name">{manga.sortName}</p>
|
||||
<p className="QueueJob-JobType">{JobTypeFromNumber(job.jobType)}</p>
|
||||
<p className="QueueJob-additional">{job.jobType == 0 ? `Vol.${job.chapter?.volumeNumber} Ch.${job.chapter?.chapterNumber}` : ""}</p>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="WaitingJobQueue">
|
||||
<h1>Standby</h1>
|
||||
<div className="JobQueue">
|
||||
{StandbyJobs.map((job: IJob) => {
|
||||
const manga = StandbyJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
||||
if (manga === undefined || manga === null)
|
||||
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga
|
||||
for {job.id}</div>
|
||||
return <div className="QueueJob" key={"QueueJob-" + job.id}>
|
||||
<img src={Manga.GetMangaCoverUrl(manga.internalId)}/>
|
||||
<p className="QueueJob-Name">{manga.sortName}</p>
|
||||
<p className="QueueJob-JobType">{JobTypeFromNumber(job.jobType)}</p>
|
||||
<p className="QueueJob-additional">{job.jobType == 0 ? `Vol.${job.chapter?.volumeNumber} Ch.${job.chapter?.chapterNumber}` : ""}</p>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
: <></>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
@ -6,7 +6,7 @@ import IManga, {SearchResult} from "./interfaces/IManga";
|
||||
import '../styles/search.css';
|
||||
import '../styles/MangaSearchResult.css'
|
||||
|
||||
export default function Search({onJobsChanged, closeSearch} : {onJobsChanged: EventHandler<any>, closeSearch(): void}) {
|
||||
export default function Search({createJob, closeSearch} : {createJob: (internalId: string, type: string) => void, closeSearch(): void}) {
|
||||
const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>();
|
||||
const [selectedConnector, setSelectedConnector] = useState<IMangaConnector>();
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<string>();
|
||||
@ -98,8 +98,7 @@ export default function Search({onJobsChanged, closeSearch} : {onJobsChanged: Ev
|
||||
<select id="Searchbox-language" onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}>
|
||||
{selectedConnector === undefined
|
||||
? <option value="" disabled hidden>Select Connector</option>
|
||||
: selectedConnector.SupportedLanguages.map(language => <option value={language}
|
||||
key={language}>{language}</option>)}
|
||||
: selectedConnector.SupportedLanguages.map(language => <option value={language} key={language}>{language}</option>)}
|
||||
</select>
|
||||
<button id="Searchbox-button" type="submit" onClick={ExecuteSearch}>Search</button>
|
||||
</div>
|
||||
@ -107,7 +106,7 @@ export default function Search({onJobsChanged, closeSearch} : {onJobsChanged: Ev
|
||||
<div id="SearchResults">
|
||||
{searchResults === undefined
|
||||
? <p></p>
|
||||
: searchResults.map(result => SearchResult(result, onJobsChanged))}
|
||||
: searchResults.map(result => SearchResult(result, createJob))}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
@ -52,7 +52,7 @@ export function CoverCard(manga: IManga) : ReactElement {
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function SearchResult(manga: IManga, jobsChanged: EventHandler<any>) : ReactElement {
|
||||
export function SearchResult(manga: IManga, createJob: (internalId: string, type: string) => void) : ReactElement {
|
||||
return(
|
||||
<div className="SearchResult" key={manga.internalId}>
|
||||
<img src={Manga.GetMangaCoverUrl(manga.internalId)}></img>
|
||||
@ -65,7 +65,7 @@ export function SearchResult(manga: IManga, jobsChanged: EventHandler<any>) : Re
|
||||
</div>
|
||||
<MarkdownPreview className="Manga-description" source={manga.description} style={{ backgroundColor: "transparent", color: "black" }} />
|
||||
<button className="Manga-AddButton" onClick={(e) => {
|
||||
Job.CreateJob(manga.internalId, "MonitorManga", "03:00:00").then(() => jobsChanged(manga.internalId));
|
||||
createJob(manga.internalId, "MonitorManga")
|
||||
}}>Monitor
|
||||
</button>
|
||||
</div>);
|
||||
|
@ -11,6 +11,7 @@ footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#madeWith {
|
||||
@ -20,19 +21,20 @@ footer {
|
||||
cursor: url("Website/media/blahaj.png"), grab;
|
||||
}
|
||||
|
||||
footer div {
|
||||
footer .statusBadge {
|
||||
margin: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
background-color: rgba(255,255,255, 0.3);
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
footer div > * {
|
||||
margin: 0 2px;
|
||||
padding: 3px 0;
|
||||
footer > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
footer .hoverHand {
|
||||
|
43
Website/styles/popup.css
Normal file
43
Website/styles/popup.css
Normal file
@ -0,0 +1,43 @@
|
||||
.popup {
|
||||
position: fixed;
|
||||
left: 10%;
|
||||
top: 7.5%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: auto;
|
||||
z-index: 100;
|
||||
background-color: var(--second-background-color);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.popup .popupHeader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.popup .popupHeader h1 {
|
||||
margin: 4px 10px;
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
.popup .close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popup .popupBody {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 40px);
|
||||
}
|
@ -1,44 +1,4 @@
|
||||
#QueuePopUp {
|
||||
position: absolute;
|
||||
left: 10%;
|
||||
top: 7.5%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: auto;
|
||||
z-index: 100;
|
||||
background-color: var(--second-background-color);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#QueuePopUp #QueuePopUpHeader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
#QueuePopUp #QueuePopUpHeader h1 {
|
||||
margin: 4px 10px;
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
#QueuePopUp #closeSearch {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#QueuePopUp #QueuePopUpBody {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 40px);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user