Add auto-update when adding/removing Manga

Style SearchResults
This commit is contained in:
2024-10-19 19:52:28 +02:00
parent daa05a0b4d
commit 3f26d3bbd6
11 changed files with 318 additions and 77 deletions

View File

@ -9,7 +9,9 @@ export class Job
return getData("http://127.0.0.1:6531/v2/Jobs")
.then((json) => {
console.debug("Got all Jobs");
return (json as string[]);
const ret = json as string[];
console.debug(ret);
return (ret);
});
}
@ -18,7 +20,9 @@ export class Job
return getData("http://127.0.0.1:6531/v2/Jobs/Running")
.then((json) => {
console.debug("Got all running Jobs");
return (json as string[]);
const ret = json as string[];
console.debug(ret);
return (ret);
});
}
@ -27,7 +31,9 @@ export class Job
return getData("http://127.0.0.1:6531/v2/Jobs/Waiting")
.then((json) => {
console.debug("Got all waiting Jobs");
return (json as string[]);
const ret = json as string[];
console.debug(ret);
return (ret);
});
}
@ -36,7 +42,9 @@ export class Job
return getData("http://127.0.0.1:6531/v2/Jobs/Monitoring")
.then((json) => {
console.debug("Got all monitoring Jobs");
return (json as string[]);
const ret = json as string[];
console.debug(ret);
return (ret);
});
}
@ -49,7 +57,9 @@ export class Job
return getData(`http://127.0.0.1:6531/v2/Job/${jobId}`)
.then((json) => {
console.debug(`Got Job ${jobId}`);
return (json as IJob);
const ret = json as IJob;
console.debug(ret);
return (ret);
});
}
@ -63,7 +73,9 @@ export class Job
return getData(`http://127.0.0.1:6531/v2/Job?jobIds=${reqStr}`)
.then((json) => {
console.debug(`Got Jobs ${reqStr}`);
return (json as IJob[]);
const ret = json as IJob[];
console.debug(ret);
return (ret);
});
}
@ -72,11 +84,13 @@ export class Job
return getData(`http://127.0.0.1:6531/v2/Job/${jobId}/Progress`)
.then((json) => {
console.debug(`Got Job ${jobId} Progress`);
return (json as IProgressToken);
const ret = json as IProgressToken;
console.debug(ret);
return (ret);
});
}
static async CreateJob(internalId: string, jobType: string, interval: string): Promise<IJob> {
static async CreateJob(internalId: string, jobType: string, interval: string): Promise<null> {
console.debug(`Creating Job for Manga ${internalId} at ${interval} interval`);
let data = {
internalId: internalId,
@ -85,19 +99,19 @@ export class Job
return postData(`http://127.0.0.1:6531/v2/Job/Create/${jobType}`, data)
.then((json) => {
console.debug(`Created Job for Manga ${internalId} at ${interval} interval`);
return (json as IJob);
return null;
});
}
static DeleteJob(jobId: string) {
deleteData(`http://127.0.0.1:6531/v2/Job/${jobId}`);
static DeleteJob(jobId: string) : Promise<void> {
return deleteData(`http://127.0.0.1:6531/v2/Job/${jobId}`);
}
static StartJob(jobId: string) {
postData(`http://127.0.0.1:6531/v2/Job/${jobId}/StartNow`, {});
static StartJob(jobId: string) : Promise<object> {
return postData(`http://127.0.0.1:6531/v2/Job/${jobId}/StartNow`, {});
}
static CancelJob(jobId: string) {
postData(`http://127.0.0.1:6531/v2/Job/${jobId}/Cancel`, {});
static CancelJob(jobId: string) : Promise<object> {
return postData(`http://127.0.0.1:6531/v2/Job/${jobId}/Cancel`, {});
}
}

View File

@ -8,7 +8,9 @@ export class Manga
return getData("http://127.0.0.1:6531/v2/Mangas")
.then((json) => {
console.debug("Got all Manga");
return (json as IManga[]);
const ret = json as IManga[];
console.debug(ret);
return (ret);
});
}
@ -17,7 +19,9 @@ export class Manga
return await getData(`http://127.0.0.1:6531/v2/Manga/Search?title=${name}`)
.then((json) => {
console.debug(`Got Manga ${name}`);
return (json as IManga[]);
const ret = json as IManga[];
console.debug(ret);
return (ret);
});
}
@ -26,7 +30,9 @@ export class Manga
return await getData(`http://127.0.0.1:6531/v2/Manga/${internalId}`)
.then((json) => {
console.debug(`Got Manga ${internalId}`);
return (json as IManga);
const ret = json as IManga;
console.debug(ret);
return (ret);
});
}
@ -35,7 +41,9 @@ export class Manga
return await getData(`http://127.0.0.1:6531/v2/Manga?internalIds=${internalIds.join(",")}`)
.then((json) => {
console.debug(`Got Manga ${internalIds.join(",")}`);
return (json as IManga[]);
const ret = json as IManga[];
console.debug(ret);
return (ret);
});
}

View File

@ -1,25 +1,17 @@
import React, {MouseEventHandler, ReactElement, useEffect} from 'react';
import React, {EventHandler, MouseEventHandler, ReactElement, useEffect, useState} from 'react';
import {Job} from './Job';
import '../styles/monitorMangaList.css';
import IJob from "./interfaces/IJob";
import IManga, {HTMLFromIManga} from "./interfaces/IManga";
import IManga, {CoverCard} from "./interfaces/IManga";
import {Manga} from './Manga';
import '../styles/MangaCoverCard.css'
export default function MonitorJobsList({onStartSearch} : {onStartSearch() : void}){
const [MonitoringJobs, setMonitoringJobs] = React.useState<IJob[]>([]);
const [AllManga, setAllManga] = React.useState<IManga[]>([]);
function UpdateMonitoringJobsList(){
Job.GetMonitoringJobs()
.then((jobs) => {
if(jobs.length > 0)
return Job.GetJobs(jobs)
return [];
})
.then((jobs) => setMonitoringJobs(jobs));
}
export default function MonitorJobsList({onStartSearch, onJobsChanged} : {onStartSearch() : void, onJobsChanged: EventHandler<any>}) {
const [MonitoringJobs, setMonitoringJobs] = useState<IJob[]>([]);
const [AllManga, setAllManga] = useState<IManga[]>([]);
useEffect(() => {
console.debug("Updating display list.");
//Remove all Manga that are not associated with a Job
setAllManga(AllManga.filter(manga => MonitoringJobs.find(job => job.mangaInternalId == manga.internalId)));
//Fetch Manga that are missing (from Jobs)
@ -36,9 +28,20 @@ export default function MonitorJobsList({onStartSearch} : {onStartSearch() : voi
UpdateMonitoringJobsList();
}, []);
const DeleteJob:MouseEventHandler = (e) => {
const DeleteJob : MouseEventHandler = (e) => {
const jobId = e.currentTarget.id;
Job.DeleteJob(jobId);
Job.DeleteJob(jobId).then(() => onJobsChanged(jobId));
}
function UpdateMonitoringJobsList(){
console.debug("Updating MonitoringJobsList");
Job.GetMonitoringJobs()
.then((jobs) => {
if(jobs.length > 0)
return Job.GetJobs(jobs)
return [];
})
.then((jobs) => setMonitoringJobs(jobs));
}
function StartSearchMangaEntry() : ReactElement {
@ -61,7 +64,7 @@ export default function MonitorJobsList({onStartSearch} : {onStartSearch() : voi
if (job === undefined || job == null)
return <div>Error. Could not find matching job for {manga.internalId}</div>
return <div key={"monitorMangaEntry." + manga.internalId} className="monitorMangaEntry">
{HTMLFromIManga(manga)}
{CoverCard(manga)}
{job.id}
<button id={job.id} onClick={DeleteJob}>Delete</button>
</div>;

View File

@ -1,12 +1,13 @@
import React, { ChangeEventHandler, MouseEventHandler, useEffect, useState} from 'react';
import React, {ChangeEventHandler, EventHandler, MouseEventHandler, useEffect, useState} from 'react';
import {MangaConnector} from "./MangaConnector";
import {Job} from "./Job";
import IMangaConnector from "./interfaces/IMangaConnector";
import {isValidUri} from "../App";
import IManga, {HTMLFromIManga} from "./interfaces/IManga";
import IManga, {SearchResult} from "./interfaces/IManga";
import '../styles/search.css';
import '../styles/MangaSearchResult.css'
export default function Search(){
export default function Search({onJobsChanged} : {onJobsChanged: EventHandler<any>}) {
const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>();
const [selectedConnector, setSelectedConnector] = useState<IMangaConnector>();
const [selectedLanguage, setSelectedLanguage] = useState<string>();
@ -88,28 +89,25 @@ export default function Search(){
return (<div>
<div id="SearchBox">
<input type="text" placeholder="Manganame" onChange={searchBoxValueChanged}></input>
<select value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}>
<input type="text" placeholder="Manganame" id="Searchbox-Manganame" onChange={searchBoxValueChanged}></input>
<select id="Searchbox-Connector" value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}>
<option value="" disabled hidden>Select</option>
{mangaConnectors === undefined
? <option value="Loading">Loading</option>
: mangaConnectors.map(con => <option value={con.name} key={con.name}>{con.name}</option>)}
</select>
<select onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}>
<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>)}
</select>
<button type="submit" onClick={ExecuteSearch}>Search</button>
<button id="Searchbox-button" type="submit" onClick={ExecuteSearch}>Search</button>
</div>
<div>
<div id="SearchResults">
{searchResults === undefined
? <p>No Results yet</p>
: searchResults.map(result => <div key={"searchResult."+result.internalId} className="searchResult">
{HTMLFromIManga(result)}
<button onClick={(e) => {Job.CreateJob(result.internalId, "MonitorManga", "03:00:00")}}>Monitor</button>
</div>)}
? <p></p>
: searchResults.map(result => SearchResult(result, onJobsChanged))}
</div>
</div>)
}

View File

@ -1,7 +1,10 @@
import IMangaConnector from "./IMangaConnector";
import KeyValuePair from "./KeyValuePair";
import {Manga} from "../Manga";
import {ReactElement} from "react";
import React, {ChangeEventHandler, EventHandler, ReactElement} from "react";
import {Job} from "../Job";
import Icon from '@mdi/react';
import { mdiTagTextOutline, mdiAccountEdit } from '@mdi/js';
export default interface IManga{
"sortName": string,
@ -36,7 +39,7 @@ function ReleaseStatusFromNumber(n: number): string {
return "";
}
export function HTMLFromIManga(manga: IManga) : ReactElement {
export function CoverCard(manga: IManga) : ReactElement {
return(
<div className="Manga" key={manga.internalId}>
<img src={Manga.GetMangaCoverUrl(manga.internalId)}></img>
@ -46,4 +49,23 @@ export function HTMLFromIManga(manga: IManga) : ReactElement {
<p className="Manga-name">{manga.sortName}</p>
</div>
</div>);
}
export function SearchResult(manga: IManga, jobsChanged: EventHandler<any>) : ReactElement {
return(
<div className="SearchResult" key={manga.internalId}>
<img src={Manga.GetMangaCoverUrl(manga.internalId)}></img>
<p className="connector-name">{manga.mangaConnector.name}</p>
<div className="Manga-status" release-status={ReleaseStatusFromNumber(manga.releaseStatus)}></div>
<p className="Manga-name">{manga.sortName}</p>
<ul className="Manga-tags">
{manga.authors.map(author => <li className="Manga-author" key={manga.internalId + "-author-" + author}> <Icon path={mdiAccountEdit} size={0.5} /> {author}</li>)}
{manga.tags.map(tag => <li className="Manga-tag" key={manga.internalId + "-tag-" + tag}><Icon path={mdiTagTextOutline} size={0.5} /> {tag}</li>)}
</ul>
<p className="Manga-description">{manga.description}</p>
<button className="Manga-AddButton" onClick={(e) => {
Job.CreateJob(manga.internalId, "MonitorManga", "03:00:00").then(() => jobsChanged(manga.internalId));
}}>Monitor
</button>
</div>);
}