Add Header, Footer, Basic Search

This commit is contained in:
2024-10-18 02:10:58 +02:00
parent d5115809ca
commit cf09bc50fb
17 changed files with 1391 additions and 0 deletions

View File

@ -0,0 +1,8 @@
import React from 'react';
export default function Footer(){
return (
<footer>
<p id="madeWith">Made with Blåhaj 🦈</p>
</footer>)
}

View File

@ -0,0 +1,11 @@
import React from 'react';
export default function Header(){
return (
<header>
<div id="titlebox">
<img alt="website image is Blahaj" src="media/blahaj.png"/>
<span>Tranga</span>
</div>
</header>)
}

51
Website/modules/Manga.tsx Normal file
View File

@ -0,0 +1,51 @@
import IManga from './interfaces/IManga';
import { getData } from '../App';
export class Manga
{
static async GetAllManga(): Promise<IManga[]> {
let manga: IManga[] = [];
console.debug("Getting all Manga");
return getData("http://127.0.0.1:6531/v2/Mangas")
.then((json) => {
console.debug("Got all Manga");
return (json as IManga[]);
});
}
static async SearchManga(name: string): Promise<IManga[]> {
console.debug(`Getting Manga ${name} from all Connectors`);
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[]);
});
}
static async GetMangaById(internalId: string): Promise<IManga> {
console.debug(`Getting Manga ${internalId}`);
return await getData(`http://127.0.0.1:6531/v2/Manga/${internalId}`)
.then((json) => {
console.debug(`Got Manga ${internalId}`);
return (json as IManga);
});
}
static async GetMangaByIds(internalIds: string[]): Promise<IManga[]> {
console.debug(`Getting Mangas ${internalIds.join(",")}`);
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[]);
});
}
static async GetMangaCoverUrl(internalId: string): Promise<string> {
console.debug(`Getting Manga Cover-Url ${internalId}`);
return await getData(`http://127.0.0.1:6531/v2/Manga/${internalId}/Cover`)
.then((json) => {
console.debug(`Got Cover-Url of Manga ${internalId}`);
return (json.toString());
});
}
}

View File

@ -0,0 +1,33 @@
import IMangaConnector from './interfaces/IMangaConnector';
import IManga from './interfaces/IManga';
import { getData } from '../App';
export class MangaConnector
{
static async GetAllConnectors(): Promise<IMangaConnector[]> {
console.debug("Getting all MangaConnectors");
return getData("http://127.0.0.1:6531/v2/Connector/Types")
.then((json) => {
console.debug("Got all MangaConnectors");
return (json as IMangaConnector[]);
});
}
static async GetMangaFromConnectorByTitle(connector: IMangaConnector, name: string): Promise<IManga[]> {
console.debug(`Getting Manga ${name}`);
return await getData(`http://127.0.0.1:6531/v2/Connector/${connector.name}/GetManga?title=${name}`)
.then((json) => {
console.debug(`Got Manga ${name}`);
return (json as IManga[]);
});
}
static async GetMangaFromConnectorByUrl(connector: IMangaConnector, url: string): Promise<IManga> {
console.debug(`Getting Manga ${url}`);
return await getData(`http://127.0.0.1:6531/v2/Connector/${connector.name}/GetManga?url=${url}`)
.then((json) => {
console.debug(`Got Manga ${url}`);
return (json as IManga);
});
}
}

110
Website/modules/Search.tsx Normal file
View File

@ -0,0 +1,110 @@
import React, { ChangeEventHandler, MouseEventHandler, useEffect, useState} from 'react';
import {MangaConnector} from "./MangaConnector";
import IMangaConnector from "./interfaces/IMangaConnector";
import {isValidUri} from "../App";
import IManga, {HTMLFromIManga} from "./interfaces/IManga";
export default function Search(){
const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>();
const [selectedConnector, setSelectedConnector] = useState<IMangaConnector>();
const [selectedLanguage, setSelectedLanguage] = useState<string>();
const [searchBoxValue, setSearchBoxValue] = useState("");
const [searchResults, setSearchResults] = useState<IManga[]>();
const pattern = /https:\/\/([a-z0-9.]+\.[a-z0-9]{2,})(?:\/.*)?/i
useEffect(() => {
if(mangaConnectors === undefined) {
MangaConnector.GetAllConnectors().then(setConnectors);
return;
}
}, [mangaConnectors]);
const selectedConnectorChanged : ChangeEventHandler<HTMLSelectElement> = (event) => {
event.preventDefault();
if(mangaConnectors === undefined)
return;
const selectedConnector = mangaConnectors.find((con: IMangaConnector) => con.name == event.target.value);
if(selectedConnector === undefined)
return;
setSelectedConnector(selectedConnector);
setSelectedLanguage(selectedConnector.SupportedLanguages[0]);
}
const searchBoxValueChanged : ChangeEventHandler<HTMLInputElement> = (event) => {
if(mangaConnectors === undefined)
return;
var str : string = event.target.value;
setSearchBoxValue(str);
if(isValidUri(str))
setSelectedConnector(undefined);
const match = str.match(pattern);
if(match === null)
return;
let baseUri = match[1];
const selectCon = mangaConnectors.find((con: IMangaConnector) => {
let found = con.BaseUris.find(uri => uri == baseUri);
return found;
});
if(selectCon != undefined){
setSelectedConnector(selectCon);
setSelectedLanguage(selectCon.SupportedLanguages[0]);
}
}
const ExecuteSearch : MouseEventHandler<HTMLButtonElement> = (event) => {
if(searchBoxValue.length < 1 || selectedConnector === undefined || selectedLanguage === ""){
console.error("Tried initiating search while not all fields where submitted.")
return;
}
console.debug(`Searching Name: ${searchBoxValue} Connector: ${selectedConnector.name} Language: ${selectedLanguage}`);
if(isValidUri(searchBoxValue) && !selectedConnector.BaseUris.find((uri: string) => {
const match = searchBoxValue.match(pattern);
if(match === null)
return false;
return match[1] == uri
}))
{
console.error("URL in Searchbox detected, but does not match selected connector.");
return;
}
if(!isValidUri(searchBoxValue)){
MangaConnector.GetMangaFromConnectorByTitle(selectedConnector, searchBoxValue)
.then((mangas: IManga[]) => {
setSearchResults(mangas);
});
}else{
MangaConnector.GetMangaFromConnectorByUrl(selectedConnector, searchBoxValue)
.then((manga: IManga) => {
setSearchResults([manga]);
});
}
}
const changeSelectedLanguage : ChangeEventHandler<HTMLSelectElement> = (event) => setSelectedLanguage(event.target.value);
return (<div>
<div id="SearchBox">
<input type="text" placeholder="Manganame" onChange={searchBoxValueChanged}></input>
<select 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}>
{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>
</div>
<div>
{searchResults === undefined
? <p>No Results yet</p>
: searchResults.map(result => HTMLFromIManga(result))}
</div>
</div>)
}

View File

@ -0,0 +1,34 @@
import IMangaConnector from "./IMangaConnector";
import KeyValuePair from "./KeyValuePair";
import {Manga} from "../Manga";
import {ReactElement} from "react";
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
}
export function HTMLFromIManga(manga: IManga) : ReactElement {
return (<div className="Manga" key={manga.internalId}>
<p>{manga.sortName}</p>
<p>Description: {manga.description}</p>
<p>MangaConnector: {manga.mangaConnector.name}</p>
</div>)
}

View File

@ -0,0 +1,5 @@
export default interface IMangaConnector {
SupportedLanguages: string[];
name: string;
BaseUris: string[];
}

View File

@ -0,0 +1,4 @@
export default interface KeyValuePair {
key: string;
value: string;
}