Job Drawer add "Re-Run" button,

Move Chapter-Drawer to the bottom of the screen (inline with Manga-Drawer)
Format JobState and JobType (with spaces)
This commit is contained in:
2025-06-18 17:51:29 +02:00
parent b4b4479e9b
commit 4c81c571a4
3 changed files with 41 additions and 28 deletions

View File

@ -1,14 +1,15 @@
import React, {ReactElement, useContext, useState} from "react"; import React, {ReactElement, useContext, useState} from "react";
import IChapter from "../api/types/IChapter.ts"; import IChapter from "../api/types/IChapter.ts";
import {Box, Chip, Link, Stack, Typography} from "@mui/joy"; import {Box, Chip, Link, Stack, Tooltip, Typography} from "@mui/joy";
import {MangaFromId} from "./Manga.tsx"; import {MangaFromId} from "./Manga.tsx";
import {ChapterContext} from "../api/Contexts/ChapterContext.tsx"; import {ChapterContext} from "../api/Contexts/ChapterContext.tsx";
import Drawer from "@mui/joy/Drawer"; import Drawer from "@mui/joy/Drawer";
import ModalClose from "@mui/joy/ModalClose"; import ModalClose from "@mui/joy/ModalClose";
import {Archive} from "@mui/icons-material";
export function ChapterPopupFromId({chapterId, open, setOpen, children}: { chapterId: string | null, open: boolean, setOpen: React.Dispatch<React.SetStateAction<boolean>>, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }) { export function ChapterPopupFromId({chapterId, open, setOpen, children}: { chapterId: string | null, open: boolean, setOpen: React.Dispatch<React.SetStateAction<boolean>>, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }) {
return ( return (
<Drawer open={open} onClose={() => setOpen(false)}> <Drawer anchor={"bottom"} open={open} onClose={() => setOpen(false)}>
<ModalClose /> <ModalClose />
{ {
chapterId !== null ? chapterId !== null ?
@ -35,13 +36,13 @@ export function ChapterFromId({chapterId, children} : { chapterId: string, child
export function Chapter({chapter, children} : { chapter: IChapter, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }){ export function Chapter({chapter, children} : { chapter: IChapter, children?: ReactElement<any, any> | ReactElement<any, any>[] | undefined }){
return ( return (
<Stack direction={"row"}> <Stack direction={"row"} spacing={5} sx={{paddingTop: "10px"}}>
<MangaFromId mangaId={chapter.parentMangaId} /> <MangaFromId mangaId={chapter.parentMangaId} />
<Box> <Box>
<Link target={"_blank"} level={"title-lg"} href={chapter.url}>{chapter.title}</Link> <Link target={"_blank"} level={"title-lg"} href={chapter.url}>{chapter.title}</Link>
<Typography>Volume <Chip>{chapter.volumeNumber}</Chip></Typography> <Typography>Volume <Chip>{chapter.volumeNumber}</Chip></Typography>
<Typography>Chapter <Chip>{chapter.chapterNumber}</Chip></Typography> <Typography>Chapter <Chip>{chapter.chapterNumber}</Chip></Typography>
<Typography>Title <Chip>{chapter.title}</Chip></Typography> <Tooltip title={chapter.fullArchiveFilePath} placement={"bottom-start"}><Archive /></Tooltip>
</Box> </Box>
{children} {children}
</Stack> </Stack>

View File

@ -9,11 +9,11 @@ import {
Table, Table,
Typography Typography
} from "@mui/joy"; } from "@mui/joy";
import {GetJobsInState, GetJobsOfTypeAndWithState, GetJobsWithType} from "../api/Job.tsx"; import {GetJobsInState, GetJobsOfTypeAndWithState, GetJobsWithType, StartJob} from "../api/Job.tsx";
import * as React from "react"; import * as React from "react";
import {useCallback, useContext, useEffect, useState} from "react"; import {useCallback, useContext, useEffect, useState} from "react";
import {ApiUriContext} from "../api/fetchApi.tsx"; import {ApiUriContext} from "../api/fetchApi.tsx";
import IJob, {JobState, JobType} from "../api/types/Jobs/IJob.ts"; import IJob, {JobState, JobStateToString, JobType, JobTypeToString} from "../api/types/Jobs/IJob.ts";
import ModalClose from "@mui/joy/ModalClose"; import ModalClose from "@mui/joy/ModalClose";
import {MangaPopupFromId} from "./MangaPopup.tsx"; import {MangaPopupFromId} from "./MangaPopup.tsx";
import IJobWithMangaId from "../api/types/Jobs/IJobWithMangaId.ts"; import IJobWithMangaId from "../api/types/Jobs/IJobWithMangaId.ts";
@ -85,6 +85,10 @@ export default function JobsDrawer({open, connected, setOpen} : {open: boolean,
setSelectedChapterId(chapterId); setSelectedChapterId(chapterId);
setChapterPopupOpen(true); setChapterPopupOpen(true);
} }
const ReRunJob = useCallback((jobId: string) => {
StartJob(apiUri, jobId, false);
}, [apiUri]);
return ( return (
<Drawer size={"lg"} anchor={"left"} open={open} onClose={() => setOpen(false)}> <Drawer size={"lg"} anchor={"left"} open={open} onClose={() => setOpen(false)}>
@ -95,13 +99,13 @@ export default function JobsDrawer({open, connected, setOpen} : {open: boolean,
<Typography>State</Typography> <Typography>State</Typography>
}> }>
<Option value={null}>None</Option> <Option value={null}>None</Option>
{Object.keys(JobState).map((state) => <Option value={state}>{state}</Option>)} {Object.keys(JobState).map((state) => <Option value={state}>{JobStateToString(state)}</Option>)}
</Select> </Select>
<Select placeholder={"Type"} value={filterType} onChange={handleChangeType} startDecorator={ <Select placeholder={"Type"} value={filterType} onChange={handleChangeType} startDecorator={
<Typography>Type</Typography> <Typography>Type</Typography>
}> }>
<Option value={null}>None</Option> <Option value={null}>None</Option>
{Object.keys(JobType).map((type) => <Option value={type}>{type}</Option>)} {Object.keys(JobType).map((type) => <Option value={type}>{JobTypeToString(type)}</Option>)}
</Select> </Select>
<Input type={"number"} <Input type={"number"}
value={page} value={page}
@ -111,26 +115,28 @@ export default function JobsDrawer({open, connected, setOpen} : {open: boolean,
endDecorator={<Typography>/{Math.ceil(allJobs.length / pageSize)}</Typography>}/> endDecorator={<Typography>/{Math.ceil(allJobs.length / pageSize)}</Typography>}/>
</Stack> </Stack>
<DialogContent> <DialogContent>
<Table borderAxis={"xBetween"} stickyHeader> <Table borderAxis={"bothBetween"} stickyHeader sx={{tableLayout: "auto", width: "100%"}}>
<thead> <thead>
<tr> <tr>
<th>Type</th> <th>Type</th>
<th>State</th> <th>State</th>
<th>Last Execution</th> <th>Last Execution</th>
<th>NextExecution</th> <th>Next Execution</th>
<th>Extra</th> <th></th>
</tr> <th>Extra</th>
</tr>
</thead> </thead>
<tbody> <tbody>
{allJobs.slice((page-1)*pageSize, page*pageSize).map((job) => ( {allJobs.slice((page-1)*pageSize, page*pageSize).map((job) => (
<tr key={job.jobId}> <tr key={job.jobId}>
<td>{job.jobType}</td> <td>{JobTypeToString(job.jobType)}</td>
<td>{job.state}</td> <td>{JobStateToString(job.state)}</td>
<td>{new Date(job.lastExecution).toLocaleString()}</td> <td>{new Date(job.lastExecution).toLocaleString()}</td>
<td>{new Date(job.nextExecution).toLocaleString()}</td> <td>{new Date(job.nextExecution).toLocaleString()}</td>
<td>{ExtraContent(job, OpenMangaPopupDrawer, OpenChapterPopupDrawer)}</td> <td style={{whiteSpace: "nowrap"}}><Button onClick={() => ReRunJob(job.jobId)}>Re-Run</Button></td>
</tr> <td>{ExtraContent(job, OpenMangaPopupDrawer, OpenChapterPopupDrawer)}</td>
))} </tr>
))}
</tbody> </tbody>
</Table> </Table>
</DialogContent> </DialogContent>
@ -150,8 +156,7 @@ function ExtraContent(job: IJob, OpenMangaPopupDrawer: (mangaId: string) => void
case JobType.MoveMangaLibraryJob: case JobType.MoveMangaLibraryJob:
return <Button onClick={() => OpenMangaPopupDrawer((job as IJobWithMangaId).mangaId)}>Open Manga</Button> return <Button onClick={() => OpenMangaPopupDrawer((job as IJobWithMangaId).mangaId)}>Open Manga</Button>
case JobType.DownloadSingleChapterJob: case JobType.DownloadSingleChapterJob:
case JobType.UpdateSingleChapterDownloadedJob: return <Button onClick={() => OpenChapterPopupDrawer((job as IJobWithChapterId).chapterId)}>Show Chapter</Button>
return <Button onClick={() => OpenChapterPopupDrawer((job as IJobWithChapterId).chapterId)}>ShowChapter</Button>
default: default:
return null; return null;
} }

View File

@ -16,14 +16,21 @@ export enum JobType {
RetrieveChaptersJob = "RetrieveChaptersJob", RetrieveChaptersJob = "RetrieveChaptersJob",
UpdateChaptersDownloadedJob = "UpdateChaptersDownloadedJob", UpdateChaptersDownloadedJob = "UpdateChaptersDownloadedJob",
MoveMangaLibraryJob = "MoveMangaLibraryJob", MoveMangaLibraryJob = "MoveMangaLibraryJob",
UpdateSingleChapterDownloadedJob = "UpdateSingleChapterDownloadedJob",
UpdateCoverJob = "UpdateCoverJob" UpdateCoverJob = "UpdateCoverJob"
} }
export function JobTypeToString(job: JobType | string): string {
return job.replace(/([A-Z])/g, ' $1').replace("Job", "").trim();
}
export enum JobState { export enum JobState {
FirstExecution = "FirstExecution", FirstExecution = "FirstExecution",
Running = "Running", Running = "Running",
Completed = "Completed", Completed = "Completed",
CompletedWaiting = "CompletedWaiting", CompletedWaiting = "CompletedWaiting",
Failed = "Failed" Failed = "Failed"
}
export function JobStateToString(state: JobState | string): string {
return state.replace(/([A-Z])/g, ' $1').trim();
} }