diff --git a/Website/modules/Chapter.tsx b/Website/modules/Chapter.tsx new file mode 100644 index 0000000..6f80ce9 --- /dev/null +++ b/Website/modules/Chapter.tsx @@ -0,0 +1,19 @@ +import {getData} from "../App"; +import IChapter from "./interfaces/IChapter"; + +export default class Chapter{ + + static async GetChapterFromId(apiUri: string, chapterId: string): Promise { + if(chapterId === undefined || chapterId === null) { + console.error(`chapterId was not provided`); + return Promise.reject(); + } + return getData(`${apiUri}/v2/Query/Chapter/${chapterId}`) + .then((json) => { + //console.info("Got all Manga"); + const ret = json as IChapter; + //console.debug(ret); + return (ret); + }); + } +} \ No newline at end of file diff --git a/Website/modules/Footer.tsx b/Website/modules/Footer.tsx index 13e451c..e565910 100644 --- a/Website/modules/Footer.tsx +++ b/Website/modules/Footer.tsx @@ -4,7 +4,7 @@ import Job from './Job'; import Icon from '@mdi/react'; import {mdiCounter, mdiEyeCheck, mdiRun, mdiTrayFull} from '@mdi/js'; import QueuePopUp from "./QueuePopUp"; -import {JobState, JobType} from "./interfaces/IJob"; +import {JobState, JobType} from "./interfaces/Jobs/IJob"; export default function Footer({connectedToBackend, apiUri} : {connectedToBackend: boolean, apiUri: string}) { const [MonitoringJobsCount, setMonitoringJobsCount] = React.useState(0); diff --git a/Website/modules/Job.tsx b/Website/modules/Job.tsx index 9aa9da0..c62736b 100644 --- a/Website/modules/Job.tsx +++ b/Website/modules/Job.tsx @@ -1,5 +1,5 @@ import {deleteData, getData, patchData, postData, putData} from '../App'; -import IJob, {JobState, JobType} from "./interfaces/IJob"; +import IJob, {JobState, JobType} from "./interfaces/Jobs/IJob"; import IModifyJobRecord from "./interfaces/records/IModifyJobRecord"; export default class Job diff --git a/Website/modules/Manga.tsx b/Website/modules/Manga.tsx index bde6c16..1700d27 100644 --- a/Website/modules/Manga.tsx +++ b/Website/modules/Manga.tsx @@ -1,6 +1,5 @@ import IManga from './interfaces/IManga'; import {deleteData, getData, patchData, postData} from '../App'; -import {RefObject} from "react"; import IChapter from "./interfaces/IChapter"; export default class Manga diff --git a/Website/modules/MonitorJobsList.tsx b/Website/modules/MonitorJobsList.tsx index 1237ff2..9b2f92d 100644 --- a/Website/modules/MonitorJobsList.tsx +++ b/Website/modules/MonitorJobsList.tsx @@ -1,19 +1,15 @@ -import React, {EventHandler, MouseEventHandler, ReactElement, useEffect, useState} from 'react'; +import React, {EventHandler, ReactElement, useEffect, useState} from 'react'; import Job from './Job'; import '../styles/monitorMangaList.css'; -import IJob, {JobType} from "./interfaces/IJob"; -import IManga from "./interfaces/IManga"; +import {JobType} from "./interfaces/Jobs/IJob"; import '../styles/MangaCoverCard.css' +import DownloadAvailableChaptersJob from "./interfaces/Jobs/DownloadAvailableChaptersJob"; +import {CoverCard} from "./interfaces/IManga"; export default function MonitorJobsList({onStartSearch, onJobsChanged, connectedToBackend, apiUri, updateList} : {onStartSearch() : void, onJobsChanged: EventHandler, connectedToBackend: boolean, apiUri: string, updateList: Date}) { - const [MonitoringJobs, setMonitoringJobs] = useState([]); - const [AllManga, setAllManga] = useState([]); + const [MonitoringJobs, setMonitoringJobs] = useState([]); const [joblistUpdateInterval, setJoblistUpdateInterval] = React.useState(); - useEffect(() => { - - }, [MonitoringJobs]); - useEffect(() => { if(connectedToBackend){ UpdateMonitoringJobsList(); @@ -35,35 +31,24 @@ export default function MonitorJobsList({onStartSearch, onJobsChanged, connected return; //console.debug("Updating MonitoringJobsList"); Job.GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob) - .then((jobs) => setMonitoringJobs(jobs)); + .then((jobs) => setMonitoringJobs(jobs as DownloadAvailableChaptersJob[])); } function StartSearchMangaEntry() : ReactElement { - return (
-
- Blahaj -
-

Add new Manga

-

+

-
+ return (
+ Blahaj +
+

Add new Manga

+

+

); } - const DeleteJob : MouseEventHandler = (e) => { - const jobId = e.currentTarget.id.slice(e.currentTarget.id.indexOf("-")+1); - //console.info(`Pressed ${e.currentTarget.id} => ${jobId}`); - Job.DeleteJob(apiUri, jobId).then(() => onJobsChanged(jobId)); - } - - const StartJob : MouseEventHandler = (e) => { - const jobId = e.currentTarget.id.slice(e.currentTarget.id.indexOf("-")+1); - //console.info(`Pressed ${e.currentTarget.id} => ${jobId}`); - Job.StartJob(apiUri, jobId); - } - return (
- {StartSearchMangaEntry()} -
) + + {MonitoringJobs.map((MonitoringJob) => + + )} +
); } \ No newline at end of file diff --git a/Website/modules/QueuePopUp.tsx b/Website/modules/QueuePopUp.tsx index d2ee1d6..9663d93 100644 --- a/Website/modules/QueuePopUp.tsx +++ b/Website/modules/QueuePopUp.tsx @@ -1,10 +1,10 @@ import React, {useEffect, useState} from 'react'; -import IJob, {JobState} from "./interfaces/IJob"; +import IJob, {JobState, JobType} from "./interfaces/Jobs/IJob"; import '../styles/queuePopUp.css'; import '../styles/popup.css'; import Job from "./Job"; -import IManga, {QueueItem} from "./interfaces/IManga"; -import Manga from "./Manga"; +import DownloadSingleChapterJob from "./interfaces/Jobs/DownloadSingleChapterJob"; +import { ItemDownloadSingleChapterJob } from "./interfaces/IManga"; export default function QueuePopUp({connectedToBackend, children, apiUri} : {connectedToBackend: boolean, children: JSX.Element[], apiUri: string}) { @@ -33,17 +33,17 @@ export default function QueuePopUp({connectedToBackend, children, apiUri} : {con function UpdateMonitoringJobsList(){ Job.GetJobsInState(apiUri, JobState.Waiting) - .then((jobs:IJob[]) => { - //console.log(StandbyJobs) - setWaitingJobs(jobs); - //console.log(StandbyJobs) - }); + .then((jobs: IJob[]) => { + //console.log(jobs); + return jobs; + }) + .then(setWaitingJobs); Job.GetJobsInState(apiUri, JobState.Running) - .then((jobs:IJob[]) =>{ - //console.log(StandbyJobs) - setRunningJobs(jobs); - //console.log(StandbyJobs) - }); + .then((jobs: IJob[]) => { + //console.log(jobs); + return jobs; + }) + .then(setRunningJobs); } return (<> @@ -58,6 +58,12 @@ export default function QueuePopUp({connectedToBackend, children, apiUri} : {con onClick={() => setShowQueuePopup(false)}/>
+
+ {RunningJobs.filter(j => j.jobType == JobType.DownloadSingleChapterJob).map(j => )} +
+
+ {WaitingJobs.filter(j => j.jobType == JobType.DownloadSingleChapterJob).map(j => )} +
: <> diff --git a/Website/modules/Search.tsx b/Website/modules/Search.tsx index 23dfdd2..82d3baf 100644 --- a/Website/modules/Search.tsx +++ b/Website/modules/Search.tsx @@ -2,10 +2,11 @@ import React, {ChangeEventHandler, EventHandler, useEffect, useState} from 'reac import {MangaConnector} from "./MangaConnector"; import IMangaConnector from "./interfaces/IMangaConnector"; import {isValidUri} from "../App"; -import IManga, {SearchResult} from "./interfaces/IManga"; +import IManga, {ExtendedInfo} from "./interfaces/IManga"; import '../styles/search.css'; -import '../styles/MangaSearchResult.css' +import '../styles/ExtendedInfo.css' import SearchFunctions from "./SearchFunctions"; +import Job from "./Job"; export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch} : {apiUri: string, jobInterval: Date, onJobsChanged: (internalId: string) => void, closeSearch(): void}) { const [mangaConnectors, setConnectors] = useState(); @@ -90,14 +91,15 @@ export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch} ); } \ No newline at end of file diff --git a/Website/modules/interfaces/IFrontendSettings.tsx b/Website/modules/interfaces/IFrontendSettings.tsx index 1b56ab4..f0c67d1 100644 --- a/Website/modules/interfaces/IFrontendSettings.tsx +++ b/Website/modules/interfaces/IFrontendSettings.tsx @@ -9,7 +9,7 @@ export function LoadFrontendSettings(): IFrontendSettings { const cookies = new Cookies(); return { jobInterval: cookies.get('jobInterval') === undefined - ? new Date(0,0,0,3) + ? new Date(Date.parse("1970-01-01T03:00:00.000Z")) : cookies.get('jobInterval'), apiUri: cookies.get('apiUri') === undefined ? `${window.location.protocol}//${window.location.host}/api` diff --git a/Website/modules/interfaces/IManga.tsx b/Website/modules/interfaces/IManga.tsx index 0af58a4..162e33d 100644 --- a/Website/modules/interfaces/IManga.tsx +++ b/Website/modules/interfaces/IManga.tsx @@ -1,12 +1,13 @@ import Manga from "../Manga"; -import React, {ReactElement, ReactEventHandler} from "react"; +import React, {ReactElement, ReactEventHandler, useEffect} from "react"; import Icon from '@mdi/react'; import { mdiTagTextOutline, mdiAccountEdit, mdiLinkVariant } from '@mdi/js'; import MarkdownPreview from '@uiw/react-markdown-preview'; -import IJob from "./IJob"; import {AuthorElement} from "./IAuthor"; -import Job from "../Job"; import {LinkElement} from "./ILink"; +import DownloadSingleChapterJob from "./Jobs/DownloadSingleChapterJob"; +import IChapter from "./IChapter"; +import Chapter from "../Chapter"; export default interface IManga{ mangaId: string; @@ -34,28 +35,66 @@ export enum MangaReleaseStatus { Unreleased = "Unreleased", } -export function CoverCard(apiUri: string, manga: IManga) : ReactElement { - return( -
- Manga Cover -
+export const defaultManga: IManga = { + altTitleIds: [], + authorIds: [], + connectorId: "", + description: "", + folderName: "", + ignoreChapterBefore: 0, + linkIds: [], + mangaConnectorId: "", + name: "", + originalLanguage: "", + releaseStatus: MangaReleaseStatus.Unreleased, + tags: [], + websiteUrl: "", + year: 0, + mangaId: "" +} + +export function CoverCard({apiUri, mangaId} : {apiUri: string, mangaId: string}) : ReactElement { + let [manga, setContent] = React.useState(defaultManga); + let [extendedInfo, setExtendedInfo] = React.useState(false); + + useEffect(() => { + Manga.GetMangaById(apiUri, mangaId).then(setContent); + }, []); + + const MangaCover : ReactEventHandler = (e) => { + if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget)) + e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget); + } + + return ( +
{ + setExtendedInfo(!extendedInfo); + }}> + Manga Cover +

{manga.mangaConnectorId}

{manga.name}

+ {extendedInfo ?
+ { + Manga.DeleteManga(apiUri, manga.mangaId); + }}>Delete + ]} /> +
: null}
); } -export function SearchResult(apiUri: string, manga: IManga, interval: Date, onJobsChanged: (internalId: string) => void) : ReactElement { +export function ExtendedInfo({apiUri, manga, actions} : {apiUri: string, manga: IManga, actions: ReactElement[]}) : ReactElement { const MangaCover : ReactEventHandler = (e) => { - console.log(manga.mangaId); if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget)) e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget); } return( ); } -export function QueueItem(apiUri: string, manga: IManga, job: IJob, triggerUpdate: () => void){ +export function ItemDownloadSingleChapterJob({apiUri, job} : {apiUri: string, job: DownloadSingleChapterJob}){ + const MangaCover : ReactEventHandler = (e) => { + if(manga === null) + return; + if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget)) + e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget); + } + + let [chapter, setChapter] = React.useState(null); + let [manga, setManga] = React.useState(null); + + useEffect(() => { + Chapter.GetChapterFromId(apiUri, job.chapterId).then(setChapter); + }, []); + + useEffect(() => { + if(chapter === null){ + setManga(null); + return; + } + Manga.GetMangaById(apiUri, chapter.parentMangaId).then(setManga); + }, [chapter]); + return ( -
- Manga Cover -

{manga.name}

-

{job.jobType}

-
- - {job.parentJobId != null - ? - : <> - } -
+
); } \ No newline at end of file diff --git a/Website/modules/interfaces/Jobs/DownloadAvailableChaptersJob.ts b/Website/modules/interfaces/Jobs/DownloadAvailableChaptersJob.ts new file mode 100644 index 0000000..99dd3e3 --- /dev/null +++ b/Website/modules/interfaces/Jobs/DownloadAvailableChaptersJob.ts @@ -0,0 +1,5 @@ +import IJob from "./IJob"; + +export default interface DownloadAvailableChaptersJob extends IJob { + mangaId: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/Jobs/DownloadMangaCoverJob.ts b/Website/modules/interfaces/Jobs/DownloadMangaCoverJob.ts new file mode 100644 index 0000000..612dc15 --- /dev/null +++ b/Website/modules/interfaces/Jobs/DownloadMangaCoverJob.ts @@ -0,0 +1,5 @@ +import IJob from "./IJob"; + +export default interface DownloadMangaCoverJob extends IJob { + mangaId: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/Jobs/DownloadSingleChapterJob.ts b/Website/modules/interfaces/Jobs/DownloadSingleChapterJob.ts new file mode 100644 index 0000000..bfecfc6 --- /dev/null +++ b/Website/modules/interfaces/Jobs/DownloadSingleChapterJob.ts @@ -0,0 +1,5 @@ +import IJob from "./IJob"; + +export default interface DownloadSingleChapterJob extends IJob { + chapterId: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/IJob.ts b/Website/modules/interfaces/Jobs/IJob.ts similarity index 100% rename from Website/modules/interfaces/IJob.ts rename to Website/modules/interfaces/Jobs/IJob.ts diff --git a/Website/modules/interfaces/Jobs/MoveFileOrFolderJob.ts b/Website/modules/interfaces/Jobs/MoveFileOrFolderJob.ts new file mode 100644 index 0000000..fab7b05 --- /dev/null +++ b/Website/modules/interfaces/Jobs/MoveFileOrFolderJob.ts @@ -0,0 +1,6 @@ +import IJob from "./IJob"; + +export default interface MoveFileOrFolderJob extends IJob { + fromLocation: string; + toLocation: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/Jobs/RetrieveChaptersJob.ts b/Website/modules/interfaces/Jobs/RetrieveChaptersJob.ts new file mode 100644 index 0000000..bd371bd --- /dev/null +++ b/Website/modules/interfaces/Jobs/RetrieveChaptersJob.ts @@ -0,0 +1,5 @@ +import IJob from "./IJob"; + +export default interface RetrieveChaptersJob extends IJob { + mangaId: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/Jobs/UpdateFilesDownloadedJob.ts b/Website/modules/interfaces/Jobs/UpdateFilesDownloadedJob.ts new file mode 100644 index 0000000..f5a7041 --- /dev/null +++ b/Website/modules/interfaces/Jobs/UpdateFilesDownloadedJob.ts @@ -0,0 +1,5 @@ +import IJob from "./IJob"; + +export default interface UpdateFilesDownloadedJob extends IJob { + mangaId: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/Jobs/UpdateMetadataJob.ts b/Website/modules/interfaces/Jobs/UpdateMetadataJob.ts new file mode 100644 index 0000000..36bd2b2 --- /dev/null +++ b/Website/modules/interfaces/Jobs/UpdateMetadataJob.ts @@ -0,0 +1,5 @@ +import IJob from "./IJob"; + +export default interface UpdateMetadataJob extends IJob { + mangaId: string; +} \ No newline at end of file diff --git a/Website/styles/MangaSearchResult.css b/Website/styles/ExtendedInfo.css similarity index 92% rename from Website/styles/MangaSearchResult.css rename to Website/styles/ExtendedInfo.css index 727b642..be04a44 100644 --- a/Website/styles/MangaSearchResult.css +++ b/Website/styles/ExtendedInfo.css @@ -66,10 +66,12 @@ padding: 0; margin: 0; white-space: nowrap; + max-height: 100%; + overflow-y: scroll; } .SearchResult > .Manga-tags p { - margin: 0 2px; + margin: 2px; padding: 5px; font-size: 10pt; height: fit-content; @@ -102,8 +104,7 @@ overflow-y: scroll; } -.SearchResult > .Manga-AddButton { - grid-area: button; +.SearchResult > .Manga-actions button { background-color: white; border: 1px solid var(--primary-color); border-radius: 4px; @@ -112,6 +113,12 @@ padding: 5px 10px; } +.SearchResult > .Manga-actions { + grid-area: button; + padding: 0; + margin: 5px 0 0 0; +} + .SearchResult > .Manga-AddButton:hover { background-color: #eee; } @@ -126,6 +133,6 @@ bottom: 7px; } -.monitorMangaEntry { +.startSearchEntry { position: relative; } \ No newline at end of file diff --git a/Website/styles/MangaCoverCard.css b/Website/styles/MangaCoverCard.css index bdbe35e..daa3cb2 100644 --- a/Website/styles/MangaCoverCard.css +++ b/Website/styles/MangaCoverCard.css @@ -32,6 +32,10 @@ z-index: 0; } +.startSearchEntry::after{ + background: initial !important; +} + .Manga-name{ width: fit-content; font-size: 16pt; @@ -102,7 +106,7 @@ background-color: gray; } -.Manga img { +.Manga > img { position: absolute; top: 0; left: 0; @@ -117,11 +121,23 @@ margin: 2px 0; } -.Manga > div { +.Manga > .SimpleCover { position: relative; z-index: 1; width: 100%; height: 100%; left: 0; top: 0; +} + +div[extended-info="no"]{ + display: none; +} + +div[extended-info="yes"]{ + display: block; + position: absolute; + left: 0; + top: 0; + z-index: 2; } \ No newline at end of file diff --git a/Website/styles/monitorMangaList.css b/Website/styles/monitorMangaList.css index 9343c51..906cef9 100644 --- a/Website/styles/monitorMangaList.css +++ b/Website/styles/monitorMangaList.css @@ -4,7 +4,7 @@ flex-flow: row; flex-wrap: nowrap; flex-grow: 1; - height: 100%; + height: calc(100vh - 100px); overflow-y: scroll; scrollbar-color: var(--accent-color) var(--primary-color); scrollbar-width: thin; diff --git a/Website/styles/queuePopUp.css b/Website/styles/queuePopUp.css index f2abf91..561c02d 100644 --- a/Website/styles/queuePopUp.css +++ b/Website/styles/queuePopUp.css @@ -1,5 +1,6 @@ #QueuePopUp #QueuePopUpBody { display: flex; + color: black; } #QueuePopUp #QueuePopUpBody > * {