Add Progressbar to jobs
Add Cancel-Buttons to running jobs Auto-update Queue
This commit is contained in:
parent
f3a091f09d
commit
7364a20d5d
@ -10,7 +10,7 @@ export default function Footer({connectedToBackend, apiUri} : {connectedToBacken
|
|||||||
const [AllJobsCount, setAllJobsCount] = React.useState(0);
|
const [AllJobsCount, setAllJobsCount] = React.useState(0);
|
||||||
const [RunningJobsCount, setRunningJobsCount] = React.useState(0);
|
const [RunningJobsCount, setRunningJobsCount] = React.useState(0);
|
||||||
const [StandbyJobsCount, setStandbyJobsCount] = React.useState(0);
|
const [StandbyJobsCount, setStandbyJobsCount] = React.useState(0);
|
||||||
const [countUpdateInterval, setcountUpdateInterval] = React.useState<number>();
|
const [countUpdateInterval, setCountUpdateInterval] = React.useState<number>();
|
||||||
|
|
||||||
function UpdateBackendState(){
|
function UpdateBackendState(){
|
||||||
Job.GetMonitoringJobs(apiUri).then((jobs) => setMonitoringJobsCount(jobs.length));
|
Job.GetMonitoringJobs(apiUri).then((jobs) => setMonitoringJobsCount(jobs.length));
|
||||||
@ -22,19 +22,19 @@ export default function Footer({connectedToBackend, apiUri} : {connectedToBacken
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(connectedToBackend){
|
if(connectedToBackend){
|
||||||
UpdateBackendState();
|
UpdateBackendState();
|
||||||
setcountUpdateInterval(setInterval(() => {
|
setCountUpdateInterval(setInterval(() => {
|
||||||
UpdateBackendState();
|
UpdateBackendState();
|
||||||
}, 2000));
|
}, 2000));
|
||||||
}else{
|
}else{
|
||||||
clearInterval(countUpdateInterval);
|
clearInterval(countUpdateInterval);
|
||||||
setcountUpdateInterval(undefined);
|
setCountUpdateInterval(undefined);
|
||||||
}
|
}
|
||||||
}, [connectedToBackend]);
|
}, [connectedToBackend]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer>
|
<footer>
|
||||||
<div className="statusBadge"><Icon path={mdiEyeCheck} size={1}/> <span>{MonitoringJobsCount}</span></div>
|
<div className="statusBadge"><Icon path={mdiEyeCheck} size={1}/> <span>{MonitoringJobsCount}</span></div>
|
||||||
<QueuePopUp apiUri={apiUri}>
|
<QueuePopUp connectedToBackend={connectedToBackend} apiUri={apiUri}>
|
||||||
<div className="statusBadge hoverHand"><Icon path={mdiTrayFull} size={1}/> <span>{StandbyJobsCount}</span></div>
|
<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>
|
<div className="statusBadge hoverHand"><Icon path={mdiRun} size={1}/> <span>{RunningJobsCount}</span></div>
|
||||||
</QueuePopUp>
|
</QueuePopUp>
|
||||||
|
@ -1,20 +1,39 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import IJob, {JobTypeFromNumber} from "./interfaces/IJob";
|
import IJob from "./interfaces/IJob";
|
||||||
import '../styles/queuePopUp.css';
|
import '../styles/queuePopUp.css';
|
||||||
import '../styles/popup.css';
|
import '../styles/popup.css';
|
||||||
import {Job} from "./Job";
|
import {Job} from "./Job";
|
||||||
import IManga from "./interfaces/IManga";
|
import IManga, {QueueItem} from "./interfaces/IManga";
|
||||||
import {Manga} from "./Manga";
|
import {Manga} from "./Manga";
|
||||||
|
|
||||||
export default function QueuePopUp({children, apiUri} : {children: JSX.Element[], apiUri: string}) {
|
export default function QueuePopUp({connectedToBackend, children, apiUri} : {connectedToBackend: boolean, children: JSX.Element[], apiUri: string}) {
|
||||||
|
|
||||||
const [StandbyJobs, setStandbyJobs] = React.useState<IJob[]>([]);
|
const [StandbyJobs, setStandbyJobs] = React.useState<IJob[]>([]);
|
||||||
const [StandbyJobsManga, setStandbyJobsManga] = React.useState<IManga[]>([]);
|
const [StandbyJobsManga, setStandbyJobsManga] = React.useState<IManga[]>([]);
|
||||||
const [RunningJobs, setRunningJobs] = React.useState<IJob[]>([]);
|
const [RunningJobs, setRunningJobs] = React.useState<IJob[]>([]);
|
||||||
const [RunningJobsManga, setRunningJobsManga] = React.useState<IManga[]>([]);
|
const [RunningJobsManga, setRunningJobsManga] = React.useState<IManga[]>([]);
|
||||||
const [showQueuePopup, setShowQueuePopup] = useState<boolean>(false);
|
const [showQueuePopup, setShowQueuePopup] = useState<boolean>(false);
|
||||||
|
const [queueListInterval, setQueueListInterval] = React.useState<number>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if(!showQueuePopup)
|
||||||
|
return;
|
||||||
|
UpdateMonitoringJobsList();
|
||||||
|
}, [showQueuePopup]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(connectedToBackend){
|
||||||
|
UpdateMonitoringJobsList();
|
||||||
|
setQueueListInterval(setInterval(() => {
|
||||||
|
UpdateMonitoringJobsList();
|
||||||
|
}, 2000));
|
||||||
|
}else{
|
||||||
|
clearInterval(queueListInterval);
|
||||||
|
setQueueListInterval(undefined);
|
||||||
|
}
|
||||||
|
}, [connectedToBackend]);
|
||||||
|
|
||||||
|
function UpdateMonitoringJobsList(){
|
||||||
Job.GetStandbyJobs(apiUri)
|
Job.GetStandbyJobs(apiUri)
|
||||||
.then((jobs) => {
|
.then((jobs) => {
|
||||||
if(jobs.length > 0)
|
if(jobs.length > 0)
|
||||||
@ -37,7 +56,7 @@ export default function QueuePopUp({children, apiUri} : {children: JSX.Element[]
|
|||||||
//console.debug("Removing Metadata Jobs");
|
//console.debug("Removing Metadata Jobs");
|
||||||
setRunningJobs(jobs.filter(job => job.jobType <= 1));
|
setRunningJobs(jobs.filter(job => job.jobType <= 1));
|
||||||
});
|
});
|
||||||
}, []);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(StandbyJobs.length < 1)
|
if(StandbyJobs.length < 1)
|
||||||
@ -65,7 +84,7 @@ export default function QueuePopUp({children, apiUri} : {children: JSX.Element[]
|
|||||||
{showQueuePopup
|
{showQueuePopup
|
||||||
? <div className="popup" id="QueuePopUp">
|
? <div className="popup" id="QueuePopUp">
|
||||||
<div className="popupHeader">
|
<div className="popupHeader">
|
||||||
<h1>Queue Status {showQueuePopup ? "true" : "false"}</h1>
|
<h1>Queue Status</h1>
|
||||||
<img alt="Close Search" className="close" src="../media/close-x.svg"
|
<img alt="Close Search" className="close" src="../media/close-x.svg"
|
||||||
onClick={() => setShowQueuePopup(false)}/>
|
onClick={() => setShowQueuePopup(false)}/>
|
||||||
</div>
|
</div>
|
||||||
@ -76,12 +95,8 @@ export default function QueuePopUp({children, apiUri} : {children: JSX.Element[]
|
|||||||
{RunningJobs.map((job: IJob) => {
|
{RunningJobs.map((job: IJob) => {
|
||||||
const manga = RunningJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
const manga = RunningJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
||||||
if (manga === undefined || manga === null)
|
if (manga === undefined || manga === null)
|
||||||
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga
|
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga for {job.id}</div>
|
||||||
for {job.id}</div>
|
return QueueItem(apiUri, manga, job, UpdateMonitoringJobsList);
|
||||||
return <div className="QueueJob" key={"QueueJob-" + job.id}>
|
|
||||||
<img src={Manga.GetMangaCoverUrl(apiUri, manga.internalId)} alt="Manga Cover" />
|
|
||||||
<p>{JobTypeFromNumber(job.jobType)}</p>
|
|
||||||
</div>;
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -91,14 +106,8 @@ export default function QueuePopUp({children, apiUri} : {children: JSX.Element[]
|
|||||||
{StandbyJobs.map((job: IJob) => {
|
{StandbyJobs.map((job: IJob) => {
|
||||||
const manga = StandbyJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
const manga = StandbyJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId);
|
||||||
if (manga === undefined || manga === null)
|
if (manga === undefined || manga === null)
|
||||||
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga
|
return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga for {job.id}</div>
|
||||||
for {job.id}</div>
|
return QueueItem(apiUri, manga, job, UpdateMonitoringJobsList);
|
||||||
return <div className="QueueJob" key={"QueueJob-" + job.id}>
|
|
||||||
<img src={Manga.GetMangaCoverUrl(apiUri, manga.internalId)} alt="Manga Cover" />
|
|
||||||
<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>
|
||||||
|
@ -5,6 +5,9 @@ import React, {ReactElement} from "react";
|
|||||||
import Icon from '@mdi/react';
|
import Icon from '@mdi/react';
|
||||||
import { mdiTagTextOutline, mdiAccountEdit } from '@mdi/js';
|
import { mdiTagTextOutline, mdiAccountEdit } from '@mdi/js';
|
||||||
import MarkdownPreview from '@uiw/react-markdown-preview';
|
import MarkdownPreview from '@uiw/react-markdown-preview';
|
||||||
|
import IJob, {JobTypeFromNumber} from "./IJob";
|
||||||
|
import {Job} from "../Job";
|
||||||
|
import ProgressBar from "@ramonak/react-progress-bar";
|
||||||
|
|
||||||
export default interface IManga{
|
export default interface IManga{
|
||||||
"sortName": string,
|
"sortName": string,
|
||||||
@ -69,3 +72,32 @@ export function SearchResult(apiUri: string, manga: IManga, createJob: (internal
|
|||||||
</button>
|
</button>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ProgressbarStr(job: IJob): string {
|
||||||
|
return job.progressToken.timeRemaining.substring(0,job.progressToken.timeRemaining.indexOf(".")).concat(" ", ToPercentString(job.progressToken.progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToPercentString(n: number): string {
|
||||||
|
return n.toString().substring(2,4).concat("%");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QueueItem(apiUri: string, manga: IManga, job: IJob, triggerUpdate: () => void){
|
||||||
|
return (
|
||||||
|
<div className="QueueJob" key={"QueueJob-" + job.id}>
|
||||||
|
<img src={Manga.GetMangaCoverUrl(apiUri, manga.internalId)} alt="Manga Cover"/>
|
||||||
|
<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>
|
||||||
|
{job.progressToken.state === 0
|
||||||
|
? <ProgressBar labelColor={"#000"} height={"10px"} labelAlignment={"outside"} className="QueueJob-Progressbar" completed={job.progressToken.progress} maxCompleted={1} customLabel={ProgressbarStr(job)}/>
|
||||||
|
: <div className="QueueJob-Progressbar"></div>}
|
||||||
|
<div className="QueueJob-actions">
|
||||||
|
<button className="QueueJob-Cancel" onClick={() => Job.CancelJob(apiUri, job.id).then(triggerUpdate)}>Cancel</button>
|
||||||
|
{job.parentJobId != null
|
||||||
|
? <button className="QueueJob-Cancel" onClick={() => Job.CancelJob(apiUri, job.parentJobId!).then(triggerUpdate)}>Cancel all related</button>
|
||||||
|
: <></>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -5,6 +5,17 @@ export default interface IProgressToken{
|
|||||||
progress: number;
|
progress: number;
|
||||||
lastUpdate: Date;
|
lastUpdate: Date;
|
||||||
executionStarted: Date;
|
executionStarted: Date;
|
||||||
timeRemaining: Date;
|
timeRemaining: string;
|
||||||
state: number;
|
state: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetProgressStateFromNumber(n: number): string {
|
||||||
|
switch (n){
|
||||||
|
case 0: return "Running";
|
||||||
|
case 1: return "Complete";
|
||||||
|
case 2: return "Standby";
|
||||||
|
case 3: return "Cancelled";
|
||||||
|
case 4: return "Waiting";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
#QueuePopUp #QueuePopUpBody h1 {
|
#QueuePopUp #QueuePopUpBody h1 {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 5px 0;
|
margin: 0 0 5px 0;
|
||||||
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#QueuePopUp #QueuePopUpBody > *:first-child {
|
#QueuePopUp #QueuePopUpBody > *:first-child {
|
||||||
@ -30,12 +31,18 @@
|
|||||||
height: 200px;
|
height: 200px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 150px auto;
|
grid-template-columns: 150px auto;
|
||||||
grid-template-rows: 30% 30% auto;
|
grid-template-rows: 25% 20% auto 15% 12%;
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"cover name"
|
"cover name"
|
||||||
"cover jobType"
|
"cover jobType"
|
||||||
"cover additional"
|
"cover additionalInfo"
|
||||||
|
"cover progress"
|
||||||
|
"cover actions"
|
||||||
|
}
|
||||||
|
|
||||||
|
.QueueJob p {
|
||||||
|
margin: 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.QueueJob img{
|
.QueueJob img{
|
||||||
@ -54,6 +61,21 @@
|
|||||||
grid-area: jobType;
|
grid-area: jobType;
|
||||||
}
|
}
|
||||||
|
|
||||||
.QueueJob .QueueJob-additional {
|
.QueueJob .QueueJob-additionalInfo {
|
||||||
grid-area: additional;
|
grid-area: additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
.QueueJob .QueueJob-actions {
|
||||||
|
grid-area: actions;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.QueueJob .QueueJob-Cancel {
|
||||||
|
grid-area: actions;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.QueueJob .QueueJob-Progressbar {
|
||||||
|
grid-area: progress;
|
||||||
}
|
}
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -5,6 +5,7 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ramonak/react-progress-bar": "^5.3.0",
|
||||||
"@uiw/react-markdown-preview": "^5.1.3"
|
"@uiw/react-markdown-preview": "^5.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -438,6 +439,16 @@
|
|||||||
"prop-types": "^15.7.2"
|
"prop-types": "^15.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ramonak/react-progress-bar": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ramonak/react-progress-bar/-/react-progress-bar-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-PjpOcSBAVSQNyx2cvYyBCI14Tg2eFM0psC9m2ic33PYBIdOzO9/DieWndq9BUQTSjIIarhSpa/lqJ33W/mFJMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17 || ^18",
|
||||||
|
"react-dom": "^16.0.0 || ^17 || ^18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ramonak/react-progress-bar": "^5.3.0",
|
||||||
"@uiw/react-markdown-preview": "^5.1.3"
|
"@uiw/react-markdown-preview": "^5.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user