mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-06-15 16:27:54 +02:00
Adjust all endpoints and methods to tranga/postgres-Server-V2.
Search working.
This commit is contained in:
21
Website/modules/interfaces/IAuthor.tsx
Normal file
21
Website/modules/interfaces/IAuthor.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React, {ReactElement, useEffect} from "react";
|
||||
import {getData} from "../../App";
|
||||
|
||||
export default interface IAuthor {
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
}
|
||||
|
||||
export function AuthorElement({apiUri, authorId} : {apiUri: string, authorId: string}) : ReactElement{
|
||||
let [name, setName] = React.useState<string>(authorId);
|
||||
|
||||
useEffect(()=> {
|
||||
getData(`${apiUri}/v2/Query/Author/${authorId}`)
|
||||
.then((json) => {
|
||||
let ret = json as IAuthor;
|
||||
setName(ret.authorName);
|
||||
});
|
||||
}, [])
|
||||
|
||||
return (<span>{name}</span>);
|
||||
}
|
@ -1,11 +1,6 @@
|
||||
export default interface IBackendSettings {
|
||||
"downloadLocation": string;
|
||||
"workingDirectory": string;
|
||||
"apiPortNumber": number;
|
||||
"userAgent": string;
|
||||
"bufferLibraryUpdates": boolean;
|
||||
"bufferNotifications": boolean;
|
||||
"version": number;
|
||||
"aprilFoolsMode": boolean;
|
||||
"compression": number;
|
||||
"bwImages": boolean;
|
10
Website/modules/interfaces/IChapter.ts
Normal file
10
Website/modules/interfaces/IChapter.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export default interface IChapter{
|
||||
chapterId: string;
|
||||
volumeNumber: number;
|
||||
chapterNumber: string;
|
||||
url: string;
|
||||
title: string | undefined;
|
||||
archiveFileName: string;
|
||||
downloaded: boolean;
|
||||
parentMangaId: string;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import IManga from "./IManga";
|
||||
|
||||
export default interface IChapter{
|
||||
parentManga: IManga;
|
||||
name: string | undefined;
|
||||
volumeNumber: string;
|
||||
chapterNumber: string;
|
||||
url: string;
|
||||
fileName: string;
|
||||
}
|
28
Website/modules/interfaces/IJob.ts
Normal file
28
Website/modules/interfaces/IJob.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export default interface IJob{
|
||||
jobId: string;
|
||||
parentJobId: string;
|
||||
dependsOnJobIds: string[];
|
||||
jobType: JobType;
|
||||
recurrenceMs: number;
|
||||
lastExecution: Date;
|
||||
nextExecution: Date;
|
||||
state: JobState;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export enum JobType {
|
||||
DownloadSingleChapterJob = "DownloadSingleChapterJob",
|
||||
DownloadAvailableChaptersJob = "DownloadAvailableChaptersJob",
|
||||
UpdateMetaDataJob = "UpdateMetaDataJob",
|
||||
MoveFileOrFolderJob = "MoveFileOrFolderJob",
|
||||
DownloadMangaCoverJob = "DownloadMangaCoverJob",
|
||||
RetrieveChaptersJob = "RetrieveChaptersJob",
|
||||
UpdateFilesDownloadedJob = "UpdateFilesDownloadedJob"
|
||||
}
|
||||
|
||||
export enum JobState {
|
||||
Waiting = "Waiting",
|
||||
Running = "Running",
|
||||
Completed = "Completed",
|
||||
Failed = "Failed"
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import IMangaConnector from "./IMangaConnector";
|
||||
import IProgressToken from "./IProgressToken";
|
||||
import IChapter from "./IChapter";
|
||||
|
||||
export default interface IJob{
|
||||
progressToken: IProgressToken;
|
||||
recurring: boolean;
|
||||
recurrenceTime: string;
|
||||
lastExecution: Date;
|
||||
nextExecution: Date;
|
||||
id: string;
|
||||
jobType: number;
|
||||
parentJobId: string | null;
|
||||
mangaConnector: IMangaConnector;
|
||||
mangaInternalId: string | undefined; //only on DownloadNewChapters
|
||||
translatedLanguage: string | undefined; //only on DownloadNewChapters
|
||||
chapter: IChapter | undefined; //only on DownloadChapter
|
||||
}
|
||||
|
||||
export function JobTypeFromNumber(n: number): string {
|
||||
switch(n) {
|
||||
case 0: return "Download Chapter";
|
||||
case 1: return "Download New Chapters";
|
||||
case 2: return "Update Metadata";
|
||||
case 3: return "Monitor";
|
||||
}
|
||||
return "";
|
||||
}
|
11
Website/modules/interfaces/ILibraryConnector.ts
Normal file
11
Website/modules/interfaces/ILibraryConnector.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export default interface ILibraryConnector {
|
||||
libraryConnectorId: string;
|
||||
libraryType: LibraryType;
|
||||
baseUrl: string;
|
||||
auth: string;
|
||||
}
|
||||
|
||||
export enum LibraryType {
|
||||
Komga = "Komga",
|
||||
Kavita = "Kavita"
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
export default interface ILibraryConnector {
|
||||
libraryType: number;
|
||||
baseUrl: string;
|
||||
auth: string;
|
||||
}
|
||||
|
||||
export function GetLibraryConnectorNameFromNumber(n: number): string {
|
||||
switch(n){
|
||||
case 0: return "Komga";
|
||||
case 1: return "Kavita";
|
||||
}
|
||||
return "";
|
||||
}
|
25
Website/modules/interfaces/ILink.tsx
Normal file
25
Website/modules/interfaces/ILink.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, {ReactElement, useEffect} from "react";
|
||||
import {getData} from "../../App";
|
||||
import IAuthor from "./IAuthor";
|
||||
|
||||
export default interface ILink {
|
||||
linkId: string;
|
||||
linkProvider: string;
|
||||
linkUrl: string;
|
||||
}
|
||||
|
||||
export function LinkElement({apiUri, linkId} : {apiUri: string, linkId: string}) : ReactElement{
|
||||
let [provider, setProvider] = React.useState<string>(linkId);
|
||||
let [linkUrl, setLinkUrl] = React.useState<string>("");
|
||||
|
||||
useEffect(()=> {
|
||||
getData(`${apiUri}/v2/Query/Link/${linkId}`)
|
||||
.then((json) => {
|
||||
let ret = json as ILink;
|
||||
setProvider(ret.linkProvider);
|
||||
setLinkUrl(ret.linkUrl);
|
||||
});
|
||||
}, [])
|
||||
|
||||
return (<a href={linkUrl}>{provider}</a>);
|
||||
}
|
@ -1,126 +1,104 @@
|
||||
import IMangaConnector from "./IMangaConnector";
|
||||
import KeyValuePair from "./KeyValuePair";
|
||||
import Manga from "../Manga";
|
||||
import React, {EventHandler, ReactElement, ReactEventHandler} from "react";
|
||||
import React, {ReactElement, ReactEventHandler} from "react";
|
||||
import Icon from '@mdi/react';
|
||||
import { mdiTagTextOutline, mdiAccountEdit } from '@mdi/js';
|
||||
import { mdiTagTextOutline, mdiAccountEdit, mdiLinkVariant } from '@mdi/js';
|
||||
import MarkdownPreview from '@uiw/react-markdown-preview';
|
||||
import IJob, {JobTypeFromNumber} from "./IJob";
|
||||
import IJob from "./IJob";
|
||||
import {AuthorElement} from "./IAuthor";
|
||||
import Job from "../Job";
|
||||
import ProgressBar from "@ramonak/react-progress-bar";
|
||||
import {LinkElement} from "./ILink";
|
||||
|
||||
export default interface IManga{
|
||||
"sortName": string,
|
||||
"authors": string[],
|
||||
"altTitles": KeyValuePair[],
|
||||
"description": string,
|
||||
"tags": string[],
|
||||
"coverUrl": string,
|
||||
"coverFileNameInCache": string,
|
||||
"links": KeyValuePair[],
|
||||
"year": number,
|
||||
"originalLanguage": string,
|
||||
"releaseStatus": number,
|
||||
"folderName": string,
|
||||
"publicationId": string,
|
||||
"internalId": string,
|
||||
"ignoreChaptersBelow": number,
|
||||
"latestChapterDownloaded": number,
|
||||
"latestChapterAvailable": number,
|
||||
"websiteUrl": string,
|
||||
"mangaConnector": IMangaConnector
|
||||
mangaId: string;
|
||||
connectorId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
year: number;
|
||||
originalLanguage: string;
|
||||
releaseStatus: MangaReleaseStatus;
|
||||
folderName: string;
|
||||
ignoreChapterBefore: number;
|
||||
mangaConnectorId: string;
|
||||
authorIds: string[];
|
||||
tags: string[];
|
||||
linkIds: string[];
|
||||
altTitleIds: string[];
|
||||
}
|
||||
|
||||
export function ReleaseStatusFromNumber(n: number): string {
|
||||
switch(n) {
|
||||
case 0: return "Ongoing";
|
||||
case 1: return "Completed";
|
||||
case 2: return "OnHiatus";
|
||||
case 3: return "Cancelled";
|
||||
case 4: return "Unreleased";
|
||||
}
|
||||
return "";
|
||||
export enum MangaReleaseStatus {
|
||||
Continuing = "Continuing",
|
||||
Completed = "Completed",
|
||||
OnHiatus = "OnHiatus",
|
||||
Cancelled = "Cancelled",
|
||||
Unreleased = "Unreleased",
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function CoverCard(apiUri: string, manga: IManga) : ReactElement {
|
||||
const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => {
|
||||
if(e.currentTarget.src != Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget))
|
||||
e.currentTarget.src = Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget);
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="Manga" key={manga.internalId}>
|
||||
<img src="../../media/blahaj.png" onLoad={MangaCover} alt="Manga Cover"></img>
|
||||
<div className="Manga" key={manga.mangaId}>
|
||||
<img src="../../media/blahaj.png" alt="Manga Cover"></img>
|
||||
<div>
|
||||
<p className="pill connector-name">{manga.mangaConnector.name}</p>
|
||||
<div className="Manga-status" release-status={ReleaseStatusFromNumber(manga.releaseStatus)}></div>
|
||||
<p className="Manga-name">{manga.sortName}</p>
|
||||
<p className="pill connector-name">{manga.mangaConnectorId}</p>
|
||||
<div className="Manga-status" release-status={manga.releaseStatus}></div>
|
||||
<p className="Manga-name">{manga.name}</p>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function SearchResult(apiUri: string, manga: IManga, interval: Date, onJobsChanged: (internalId: string) => void) : ReactElement {
|
||||
const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => {
|
||||
if(e.currentTarget.src != Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget))
|
||||
e.currentTarget.src = Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget);
|
||||
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(
|
||||
<div className="SearchResult" key={manga.internalId}>
|
||||
<img src="../../media/blahaj.png" onLoad={MangaCover} alt="Manga Cover"></img>
|
||||
<p className="connector-name">{manga.mangaConnector.name}</p>
|
||||
<div className="Manga-status" release-status={ReleaseStatusFromNumber(manga.releaseStatus)}></div>
|
||||
<p className="Manga-name"><a href={manga.websiteUrl}>{manga.sortName}<img src="../../media/link.svg"
|
||||
<div className="SearchResult" key={manga.mangaId}>
|
||||
<img src={Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, undefined)} alt="Manga Cover" onLoad={MangaCover}></img>
|
||||
<p className="connector-name">{manga.mangaConnectorId}</p>
|
||||
<div className="Manga-status" release-status={manga.releaseStatus}></div>
|
||||
<p className="Manga-name"><a href={manga.websiteUrl}>{manga.name}<img src="../../media/link.svg"
|
||||
alt=""/></a></p>
|
||||
<div className="Manga-tags">
|
||||
{manga.authors.map(author => <p className="Manga-author" key={manga.internalId + "-author-" + author}>
|
||||
<Icon path={mdiAccountEdit} size={0.5}/> {author}</p>)}
|
||||
{manga.tags.map(tag => <p className="Manga-tag" key={manga.internalId + "-tag-" + tag}><Icon
|
||||
path={mdiTagTextOutline} size={0.5}/> {tag}</p>)}
|
||||
{manga.authorIds.map(authorId =>
|
||||
<p className="Manga-author" key={manga.mangaId + "-author-" + authorId} >
|
||||
<Icon path={mdiAccountEdit} size={0.5} />
|
||||
<AuthorElement apiUri={apiUri} authorId={authorId}></AuthorElement>
|
||||
</p>)}
|
||||
{manga.tags.map(tag =>
|
||||
<p className="Manga-tag" key={manga.mangaId + "-tag-" + tag}>
|
||||
<Icon path={mdiTagTextOutline} size={0.5}/>
|
||||
{tag}
|
||||
</p>)}
|
||||
{manga.linkIds.map(linkId =>
|
||||
<p className="Manga-link" key={manga.mangaId + "-link-" + linkId}>
|
||||
<Icon path={mdiLinkVariant} size={0.5}/>
|
||||
<LinkElement apiUri={apiUri} linkId={linkId}></LinkElement>
|
||||
</p>)}
|
||||
</div>
|
||||
<MarkdownPreview className="Manga-description" source={manga.description}
|
||||
style={{backgroundColor: "transparent", color: "black"}}/>
|
||||
<button className="Manga-AddButton" onClick={() => {
|
||||
Job.CreateJobDateInterval(apiUri, manga.internalId, "MonitorManga", interval).then(() => onJobsChanged(manga.internalId));
|
||||
Job.CreateDownloadAvailableChaptersJob(apiUri, manga.mangaId, interval.getMilliseconds()).then(() => onJobsChanged(manga.mangaId));
|
||||
}}>Monitor
|
||||
</button>
|
||||
</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){
|
||||
const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => {
|
||||
if(e.currentTarget.src != Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget))
|
||||
e.currentTarget.src = Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="QueueJob" key={"QueueJob-" + job.id}>
|
||||
<img src="../../media/blahaj.png" onLoad={MangaCover} alt="Manga Cover"></img>
|
||||
<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" key={"QueueJob-" + job.jobId}>
|
||||
<img src="../../media/blahaj.png" alt="Manga Cover"></img>
|
||||
<p className="QueueJob-Name">{manga.name}</p>
|
||||
<p className="QueueJob-JobType">{job.jobType}</p>
|
||||
<div className="QueueJob-actions">
|
||||
<button className="QueueJob-Cancel"
|
||||
onClick={() => Job.CancelJob(apiUri, job.id).then(triggerUpdate)}>Cancel
|
||||
onClick={() => Job.StopJob(apiUri, job.jobId).then(triggerUpdate)}>Cancel
|
||||
</button>
|
||||
{job.parentJobId != null
|
||||
? <button className="QueueJob-Cancel"
|
||||
onClick={() => Job.CancelJob(apiUri, job.parentJobId!).then(triggerUpdate)}>Cancel all
|
||||
onClick={() => Job.StopJob(apiUri, job.parentJobId!).then(triggerUpdate)}>Cancel all
|
||||
related</button>
|
||||
: <></>
|
||||
}
|
||||
|
5
Website/modules/interfaces/IMangaAltTitle.ts
Normal file
5
Website/modules/interfaces/IMangaAltTitle.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export default interface IMangaAltTitle {
|
||||
altTitleId: string;
|
||||
language: string;
|
||||
title: string;
|
||||
}
|
7
Website/modules/interfaces/IMangaConnector.ts
Normal file
7
Website/modules/interfaces/IMangaConnector.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default interface IMangaConnector {
|
||||
name: string;
|
||||
supportedLanguages: string[];
|
||||
iconUrl: string;
|
||||
baseUris: string[];
|
||||
enabled: boolean;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export default interface IMangaConnector {
|
||||
SupportedLanguages: string[];
|
||||
name: string;
|
||||
BaseUris: string[];
|
||||
}
|
7
Website/modules/interfaces/INotificationConnector.ts
Normal file
7
Website/modules/interfaces/INotificationConnector.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default interface INotificationConnector {
|
||||
name: string;
|
||||
url: string;
|
||||
headers: Record<string, string>[];
|
||||
httpMethod: string;
|
||||
body: string;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
export default interface INotificationConnector {
|
||||
"notificationConnectorType": number; //see NotificationConnectorType
|
||||
"endpoint": string | undefined;//only on Ntfy, Gotify
|
||||
"appToken": string | undefined;//only on Gotify
|
||||
"auth": string | undefined;//only on Ntfy
|
||||
"topic": string | undefined;//only on Ntfy
|
||||
"id": string | undefined;//only on LunaSea
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
export default interface IProgressToken{
|
||||
cancellationRequested: boolean;
|
||||
increments: number;
|
||||
incrementsCompleted: number;
|
||||
progress: number;
|
||||
lastUpdate: Date;
|
||||
executionStarted: Date;
|
||||
timeRemaining: string;
|
||||
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 "";
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export default interface KeyValuePair {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
export default interface ICoverFormatRequestRecord {
|
||||
size: Size;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
isEmpty: boolean;
|
||||
}
|
5
Website/modules/interfaces/records/IGotifyRecord.ts
Normal file
5
Website/modules/interfaces/records/IGotifyRecord.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export default interface IGotifyRecord {
|
||||
endpoint: string;
|
||||
appToken: string;
|
||||
priority: number;
|
||||
}
|
4
Website/modules/interfaces/records/IModifyJobRecord.ts
Normal file
4
Website/modules/interfaces/records/IModifyJobRecord.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default interface IModifyJobRecord {
|
||||
recurrenceMs: number;
|
||||
enabled: boolean;
|
||||
}
|
7
Website/modules/interfaces/records/INtfyRecord.ts
Normal file
7
Website/modules/interfaces/records/INtfyRecord.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default interface INtfyRecord {
|
||||
endpoint: string;
|
||||
username: string;
|
||||
password: string;
|
||||
topic: string;
|
||||
priority: number;
|
||||
}
|
3
Website/modules/interfaces/records/IlunaseaRecord.ts
Normal file
3
Website/modules/interfaces/records/IlunaseaRecord.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default interface IlunaseaRecord {
|
||||
id: string;
|
||||
}
|
Reference in New Issue
Block a user