Add auto-update when adding/removing Manga
Style SearchResults
This commit is contained in:
parent
daa05a0b4d
commit
3f26d3bbd6
@ -1,42 +1,50 @@
|
|||||||
import React, {ReactElement, useEffect} from 'react';
|
import React, {EventHandler, ReactElement, useEffect, useState} from 'react';
|
||||||
import Footer from "./modules/Footer";
|
import Footer from "./modules/Footer";
|
||||||
import Search from "./modules/Search";
|
import Search from "./modules/Search";
|
||||||
import Header from "./modules/Header";
|
import Header from "./modules/Header";
|
||||||
import MonitorJobsList from "./modules/MonitorJobsList";
|
import MonitorJobsList from "./modules/MonitorJobsList";
|
||||||
import './styles/Manga.css'
|
import './styles/index.css'
|
||||||
|
|
||||||
export default function App(){
|
export default function App(){
|
||||||
const [content, setContent] = React.useState<ReactElement>();
|
const [connected, setConnected] = React.useState(false);
|
||||||
|
const [showSearch, setShowSearch] = React.useState(false);
|
||||||
function ShowSearch() {
|
const [lastMangaListUpdate, setLastMangaListUpdate] = React.useState<Date>(new Date());
|
||||||
setContent(<>
|
|
||||||
<Search />
|
|
||||||
<MonitorJobsList onStartSearch={ShowSearch} />
|
|
||||||
</>);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setContent(<h1>Testing connection to backend...</h1>)
|
|
||||||
getData('http://127.0.0.1:6531/v2/Ping').then((result) => {
|
getData('http://127.0.0.1:6531/v2/Ping').then((result) => {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
if(result === null){
|
if(result === null){
|
||||||
setContent(<h1>No connection to backend</h1>);
|
setConnected(false);
|
||||||
}else{
|
}else{
|
||||||
setContent(<>
|
setConnected(true);
|
||||||
<MonitorJobsList onStartSearch={ShowSearch} />
|
|
||||||
</>)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const JobsChanged : EventHandler<any> = () => {
|
||||||
|
console.log("Updating Mangalist");
|
||||||
|
setLastMangaListUpdate(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
return(<div>
|
return(<div>
|
||||||
<Header />
|
<Header/>
|
||||||
{content}
|
{connected
|
||||||
<Footer />
|
? <>
|
||||||
|
{showSearch
|
||||||
|
? <>
|
||||||
|
<Search onJobsChanged={JobsChanged}/>
|
||||||
|
<hr/>
|
||||||
|
</>
|
||||||
|
: <></>}
|
||||||
|
<MonitorJobsList onStartSearch={() => setShowSearch(true)} onJobsChanged={JobsChanged}
|
||||||
|
key={lastMangaListUpdate.getTime()}/>
|
||||||
|
</>
|
||||||
|
: <h1>No connection to backend</h1>}
|
||||||
|
<Footer/>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getData (uri: string) : Promise<object> {
|
export function getData(uri: string) : Promise<object> {
|
||||||
return fetch(uri,
|
return fetch(uri,
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -66,8 +74,10 @@ export function postData(uri: string, content: object) : Promise<object> {
|
|||||||
body: JSON.stringify(content)
|
body: JSON.stringify(content)
|
||||||
})
|
})
|
||||||
.then(function(response){
|
.then(function(response){
|
||||||
if(!response.ok) throw new Error("Could not fetch data");
|
if(!response.ok)
|
||||||
return response.json();
|
throw new Error("Could not fetch data");
|
||||||
|
let json = response.json();
|
||||||
|
return json.then((json) => json).catch(() => null);
|
||||||
})
|
})
|
||||||
.catch(function(err){
|
.catch(function(err){
|
||||||
console.error(`Error POSTing Data ${uri}\n${err}`);
|
console.error(`Error POSTing Data ${uri}\n${err}`);
|
||||||
@ -75,8 +85,8 @@ export function postData(uri: string, content: object) : Promise<object> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteData(uri: string) {
|
export function deleteData(uri: string) : Promise<void> {
|
||||||
fetch(uri,
|
return fetch(uri,
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers : {
|
headers : {
|
||||||
@ -84,6 +94,9 @@ export function deleteData(uri: string) {
|
|||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(() =>{
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
.catch(function(err){
|
.catch(function(err){
|
||||||
console.error(`Error DELETEing Data ${uri}\n${err}`);
|
console.error(`Error DELETEing Data ${uri}\n${err}`);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
|
@ -9,7 +9,9 @@ export class Job
|
|||||||
return getData("http://127.0.0.1:6531/v2/Jobs")
|
return getData("http://127.0.0.1:6531/v2/Jobs")
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug("Got all Jobs");
|
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")
|
return getData("http://127.0.0.1:6531/v2/Jobs/Running")
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug("Got all running Jobs");
|
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")
|
return getData("http://127.0.0.1:6531/v2/Jobs/Waiting")
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug("Got all waiting Jobs");
|
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")
|
return getData("http://127.0.0.1:6531/v2/Jobs/Monitoring")
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug("Got all monitoring Jobs");
|
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}`)
|
return getData(`http://127.0.0.1:6531/v2/Job/${jobId}`)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Got Job ${jobId}`);
|
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}`)
|
return getData(`http://127.0.0.1:6531/v2/Job?jobIds=${reqStr}`)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Got Jobs ${reqStr}`);
|
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`)
|
return getData(`http://127.0.0.1:6531/v2/Job/${jobId}/Progress`)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Got Job ${jobId} Progress`);
|
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`);
|
console.debug(`Creating Job for Manga ${internalId} at ${interval} interval`);
|
||||||
let data = {
|
let data = {
|
||||||
internalId: internalId,
|
internalId: internalId,
|
||||||
@ -85,19 +99,19 @@ export class Job
|
|||||||
return postData(`http://127.0.0.1:6531/v2/Job/Create/${jobType}`, data)
|
return postData(`http://127.0.0.1:6531/v2/Job/Create/${jobType}`, data)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Created Job for Manga ${internalId} at ${interval} interval`);
|
console.debug(`Created Job for Manga ${internalId} at ${interval} interval`);
|
||||||
return (json as IJob);
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static DeleteJob(jobId: string) {
|
static DeleteJob(jobId: string) : Promise<void> {
|
||||||
deleteData(`http://127.0.0.1:6531/v2/Job/${jobId}`);
|
return deleteData(`http://127.0.0.1:6531/v2/Job/${jobId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static StartJob(jobId: string) {
|
static StartJob(jobId: string) : Promise<object> {
|
||||||
postData(`http://127.0.0.1:6531/v2/Job/${jobId}/StartNow`, {});
|
return postData(`http://127.0.0.1:6531/v2/Job/${jobId}/StartNow`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static CancelJob(jobId: string) {
|
static CancelJob(jobId: string) : Promise<object> {
|
||||||
postData(`http://127.0.0.1:6531/v2/Job/${jobId}/Cancel`, {});
|
return postData(`http://127.0.0.1:6531/v2/Job/${jobId}/Cancel`, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,9 @@ export class Manga
|
|||||||
return getData("http://127.0.0.1:6531/v2/Mangas")
|
return getData("http://127.0.0.1:6531/v2/Mangas")
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug("Got all Manga");
|
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}`)
|
return await getData(`http://127.0.0.1:6531/v2/Manga/Search?title=${name}`)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Got Manga ${name}`);
|
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}`)
|
return await getData(`http://127.0.0.1:6531/v2/Manga/${internalId}`)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Got Manga ${internalId}`);
|
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(",")}`)
|
return await getData(`http://127.0.0.1:6531/v2/Manga?internalIds=${internalIds.join(",")}`)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
console.debug(`Got Manga ${internalIds.join(",")}`);
|
console.debug(`Got Manga ${internalIds.join(",")}`);
|
||||||
return (json as IManga[]);
|
const ret = json as IManga[];
|
||||||
|
console.debug(ret);
|
||||||
|
return (ret);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {Job} from './Job';
|
||||||
import '../styles/monitorMangaList.css';
|
import '../styles/monitorMangaList.css';
|
||||||
import IJob from "./interfaces/IJob";
|
import IJob from "./interfaces/IJob";
|
||||||
import IManga, {HTMLFromIManga} from "./interfaces/IManga";
|
import IManga, {CoverCard} from "./interfaces/IManga";
|
||||||
import {Manga} from './Manga';
|
import {Manga} from './Manga';
|
||||||
|
import '../styles/MangaCoverCard.css'
|
||||||
|
|
||||||
export default function MonitorJobsList({onStartSearch} : {onStartSearch() : void}){
|
export default function MonitorJobsList({onStartSearch, onJobsChanged} : {onStartSearch() : void, onJobsChanged: EventHandler<any>}) {
|
||||||
const [MonitoringJobs, setMonitoringJobs] = React.useState<IJob[]>([]);
|
const [MonitoringJobs, setMonitoringJobs] = useState<IJob[]>([]);
|
||||||
const [AllManga, setAllManga] = React.useState<IManga[]>([]);
|
const [AllManga, setAllManga] = useState<IManga[]>([]);
|
||||||
|
|
||||||
function UpdateMonitoringJobsList(){
|
|
||||||
Job.GetMonitoringJobs()
|
|
||||||
.then((jobs) => {
|
|
||||||
if(jobs.length > 0)
|
|
||||||
return Job.GetJobs(jobs)
|
|
||||||
return [];
|
|
||||||
})
|
|
||||||
.then((jobs) => setMonitoringJobs(jobs));
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.debug("Updating display list.");
|
||||||
//Remove all Manga that are not associated with a Job
|
//Remove all Manga that are not associated with a Job
|
||||||
setAllManga(AllManga.filter(manga => MonitoringJobs.find(job => job.mangaInternalId == manga.internalId)));
|
setAllManga(AllManga.filter(manga => MonitoringJobs.find(job => job.mangaInternalId == manga.internalId)));
|
||||||
//Fetch Manga that are missing (from Jobs)
|
//Fetch Manga that are missing (from Jobs)
|
||||||
@ -36,9 +28,20 @@ export default function MonitorJobsList({onStartSearch} : {onStartSearch() : voi
|
|||||||
UpdateMonitoringJobsList();
|
UpdateMonitoringJobsList();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const DeleteJob:MouseEventHandler = (e) => {
|
const DeleteJob : MouseEventHandler = (e) => {
|
||||||
const jobId = e.currentTarget.id;
|
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 {
|
function StartSearchMangaEntry() : ReactElement {
|
||||||
@ -61,7 +64,7 @@ export default function MonitorJobsList({onStartSearch} : {onStartSearch() : voi
|
|||||||
if (job === undefined || job == null)
|
if (job === undefined || job == null)
|
||||||
return <div>Error. Could not find matching job for {manga.internalId}</div>
|
return <div>Error. Could not find matching job for {manga.internalId}</div>
|
||||||
return <div key={"monitorMangaEntry." + manga.internalId} className="monitorMangaEntry">
|
return <div key={"monitorMangaEntry." + manga.internalId} className="monitorMangaEntry">
|
||||||
{HTMLFromIManga(manga)}
|
{CoverCard(manga)}
|
||||||
{job.id}
|
{job.id}
|
||||||
<button id={job.id} onClick={DeleteJob}>Delete</button>
|
<button id={job.id} onClick={DeleteJob}>Delete</button>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -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 {MangaConnector} from "./MangaConnector";
|
||||||
import {Job} from "./Job";
|
import {Job} from "./Job";
|
||||||
import IMangaConnector from "./interfaces/IMangaConnector";
|
import IMangaConnector from "./interfaces/IMangaConnector";
|
||||||
import {isValidUri} from "../App";
|
import {isValidUri} from "../App";
|
||||||
import IManga, {HTMLFromIManga} from "./interfaces/IManga";
|
import IManga, {SearchResult} from "./interfaces/IManga";
|
||||||
import '../styles/search.css';
|
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 [mangaConnectors, setConnectors] = useState<IMangaConnector[]>();
|
||||||
const [selectedConnector, setSelectedConnector] = useState<IMangaConnector>();
|
const [selectedConnector, setSelectedConnector] = useState<IMangaConnector>();
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState<string>();
|
const [selectedLanguage, setSelectedLanguage] = useState<string>();
|
||||||
@ -88,28 +89,25 @@ export default function Search(){
|
|||||||
|
|
||||||
return (<div>
|
return (<div>
|
||||||
<div id="SearchBox">
|
<div id="SearchBox">
|
||||||
<input type="text" placeholder="Manganame" onChange={searchBoxValueChanged}></input>
|
<input type="text" placeholder="Manganame" id="Searchbox-Manganame" onChange={searchBoxValueChanged}></input>
|
||||||
<select value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}>
|
<select id="Searchbox-Connector" value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}>
|
||||||
<option value="" disabled hidden>Select</option>
|
<option value="" disabled hidden>Select</option>
|
||||||
{mangaConnectors === undefined
|
{mangaConnectors === undefined
|
||||||
? <option value="Loading">Loading</option>
|
? <option value="Loading">Loading</option>
|
||||||
: mangaConnectors.map(con => <option value={con.name} key={con.name}>{con.name}</option>)}
|
: mangaConnectors.map(con => <option value={con.name} key={con.name}>{con.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<select onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}>
|
<select id="Searchbox-language" onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}>
|
||||||
{selectedConnector === undefined
|
{selectedConnector === undefined
|
||||||
? <option value="" disabled hidden>Select Connector</option>
|
? <option value="" disabled hidden>Select Connector</option>
|
||||||
: selectedConnector.SupportedLanguages.map(language => <option value={language}
|
: selectedConnector.SupportedLanguages.map(language => <option value={language}
|
||||||
key={language}>{language}</option>)}
|
key={language}>{language}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<button type="submit" onClick={ExecuteSearch}>Search</button>
|
<button id="Searchbox-button" type="submit" onClick={ExecuteSearch}>Search</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div id="SearchResults">
|
||||||
{searchResults === undefined
|
{searchResults === undefined
|
||||||
? <p>No Results yet</p>
|
? <p></p>
|
||||||
: searchResults.map(result => <div key={"searchResult."+result.internalId} className="searchResult">
|
: searchResults.map(result => SearchResult(result, onJobsChanged))}
|
||||||
{HTMLFromIManga(result)}
|
|
||||||
<button onClick={(e) => {Job.CreateJob(result.internalId, "MonitorManga", "03:00:00")}}>Monitor</button>
|
|
||||||
</div>)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
@ -1,7 +1,10 @@
|
|||||||
import IMangaConnector from "./IMangaConnector";
|
import IMangaConnector from "./IMangaConnector";
|
||||||
import KeyValuePair from "./KeyValuePair";
|
import KeyValuePair from "./KeyValuePair";
|
||||||
import {Manga} from "../Manga";
|
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{
|
export default interface IManga{
|
||||||
"sortName": string,
|
"sortName": string,
|
||||||
@ -36,7 +39,7 @@ function ReleaseStatusFromNumber(n: number): string {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HTMLFromIManga(manga: IManga) : ReactElement {
|
export function CoverCard(manga: IManga) : ReactElement {
|
||||||
return(
|
return(
|
||||||
<div className="Manga" key={manga.internalId}>
|
<div className="Manga" key={manga.internalId}>
|
||||||
<img src={Manga.GetMangaCoverUrl(manga.internalId)}></img>
|
<img src={Manga.GetMangaCoverUrl(manga.internalId)}></img>
|
||||||
@ -47,3 +50,22 @@ export function HTMLFromIManga(manga: IManga) : ReactElement {
|
|||||||
</div>
|
</div>
|
||||||
</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>);
|
||||||
|
}
|
88
Website/styles/MangaSearchResult.css
Normal file
88
Website/styles/MangaSearchResult.css
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
.SearchResult {
|
||||||
|
background-color: var(--second-background-color);
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 5px 5px 9px 5px;
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
width: fit-content;
|
||||||
|
height: 328px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 220px 600px 50px;
|
||||||
|
grid-template-rows: 40px 40px 200px auto;
|
||||||
|
column-gap: 10px;
|
||||||
|
grid-template-areas:
|
||||||
|
"cover header header"
|
||||||
|
"cover alltags alltags"
|
||||||
|
"cover description description"
|
||||||
|
"cover footer button";
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult p {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > img {
|
||||||
|
grid-area: cover;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
border: 2px solid var(--primary-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > .connector-name {
|
||||||
|
grid-area: cover;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
left: 2px;
|
||||||
|
top: 2px;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > .Manga-status {
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > .Manga-name {
|
||||||
|
grid-area: header;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > .Manga-tags {
|
||||||
|
grid-area: alltags;
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > ul > li {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult .Manga-author {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult .Manga-tag {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > .Manga-description {
|
||||||
|
grid-area: description;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchResult > .Manga-AddButton {
|
||||||
|
grid-area: button;
|
||||||
|
}
|
@ -1,3 +1,48 @@
|
|||||||
.searchResult{
|
#SearchBox{
|
||||||
background-color: var(--second-background-color);
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#SearchResults {
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#SearchBox select, #SearchBox button, #SearchBox input {
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
border-top-width: 2px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Searchbox-Manganame {
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-left-width: 2px;
|
||||||
|
border-right-width: 0;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Searchbox-connector {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Searchbox-language {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Searchbox-button {
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 2px;
|
||||||
|
width: 90px;
|
||||||
}
|
}
|
48
package-lock.json
generated
48
package-lock.json
generated
@ -5,6 +5,8 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"@mdi/react": "^1.6.1",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^18.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -403,6 +405,23 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mdi/js": {
|
||||||
|
"version": "7.4.47",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
|
||||||
|
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@mdi/react": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"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",
|
||||||
@ -760,6 +779,16 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@ -796,6 +825,18 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prop-types": {
|
||||||
|
"version": "15.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
@ -823,6 +864,13 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.24.0",
|
"version": "4.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"@mdi/react": "^1.6.1",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^18.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
Loading…
Reference in New Issue
Block a user