mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-09-10 11:58:20 +02:00
Fix MangaCard/List
Style AddNotificationConnector
This commit is contained in:
@@ -4,12 +4,12 @@ import Settings from "./Components/Settings/Settings.tsx";
|
||||
import Header from "./Header.tsx";
|
||||
import {createContext, useEffect, useState} from "react";
|
||||
import {V2} from "./apiClient/V2.ts";
|
||||
import {GetManga, MangaContext } from './apiClient/MangaContext.tsx';
|
||||
import { ApiContext } from './apiClient/ApiContext.tsx';
|
||||
import MangaList from "./Components/Mangas/MangaList.tsx";
|
||||
import {MangaConnector} from "./apiClient/data-contracts.ts";
|
||||
import {Manga, MangaConnector} from "./apiClient/data-contracts.ts";
|
||||
|
||||
export const MangaConnectorContext = createContext<MangaConnector[]>([]);
|
||||
export const MangaContext = createContext<Manga[]>([]);
|
||||
|
||||
export default function App () {
|
||||
const apiUriStr = localStorage.getItem("apiUri") ?? window.location.href.substring(0, window.location.href.lastIndexOf("/")) + "/api";
|
||||
@@ -17,10 +17,24 @@ export default function App () {
|
||||
const [Api, setApi] = useState<V2>(new V2());
|
||||
|
||||
const [mangaConnectors, setMangaConnectors] = useState<MangaConnector[]>([]);
|
||||
const [manga, setManga] = useState<Manga[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
Api.mangaConnectorList().then(response => {
|
||||
if (response.ok)
|
||||
setMangaConnectors(response.data);
|
||||
});
|
||||
|
||||
Api.mangaList().then(response => {
|
||||
if (!response.ok)
|
||||
{
|
||||
setManga([]);
|
||||
return;
|
||||
}
|
||||
Api.mangaWithIDsCreate(response.data).then(response => {
|
||||
if (response.ok)
|
||||
setManga(response.data);
|
||||
})
|
||||
})
|
||||
}, [Api]);
|
||||
|
||||
@@ -35,7 +49,7 @@ export default function App () {
|
||||
return (
|
||||
<ApiContext.Provider value={Api}>
|
||||
<MangaConnectorContext.Provider value={mangaConnectors}>
|
||||
<MangaContext.Provider value={{GetManga: GetManga}}>
|
||||
<MangaContext.Provider value={manga}>
|
||||
<Sheet className={"app"}>
|
||||
<Header>
|
||||
<Settings setApiUri={setApiUri} />
|
||||
|
@@ -4,10 +4,10 @@
|
||||
}
|
||||
|
||||
.manga-cover-blur {
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.8), rgba(0,0,0,0.2) 75%);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(9px);
|
||||
-webkit-backdrop-filter: blur(9px);
|
||||
background: linear-gradient(135deg, rgba(245, 169, 184, 0.9) 20%, rgba(91, 206, 250, 0.6));
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);)
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.manga-card-badge-icon {
|
||||
|
@@ -13,18 +13,16 @@ import {
|
||||
} from "@mui/joy";
|
||||
import {Manga} from "../../apiClient/data-contracts.ts";
|
||||
import {Dispatch, SetStateAction, useContext, useState} from "react";
|
||||
import {MangaContext} from "../../apiClient/MangaContext.tsx";
|
||||
import "./MangaCard.css";
|
||||
import MangaConnectorBadge from "./MangaConnectorBadge.tsx";
|
||||
import ModalClose from "@mui/joy/ModalClose";
|
||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
||||
import MarkdownPreview from '@uiw/react-markdown-preview';
|
||||
import {MangaContext} from "../../App.tsx";
|
||||
|
||||
export function MangaCardFromId({mangaId} : {mangaId: string}) {
|
||||
const Mangas = useContext(MangaContext);
|
||||
const [manga, setManga] = useState<Manga | undefined>(undefined);
|
||||
|
||||
Mangas.GetManga(mangaId).then(setManga);
|
||||
const mangas = useContext(MangaContext);
|
||||
const manga = mangas.find(manga => manga.key === mangaId);
|
||||
|
||||
return <MangaCard manga={manga} />
|
||||
}
|
||||
|
@@ -1,23 +1,15 @@
|
||||
import {useContext, useState} from "react";
|
||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
||||
import {MangaCardFromId} from "./MangaCard.tsx";
|
||||
import {useContext} from "react";
|
||||
import {MangaCard} from "./MangaCard.tsx";
|
||||
import {Stack} from "@mui/joy";
|
||||
import "./MangaList.css";
|
||||
import {MangaContext} from "../../App.tsx";
|
||||
|
||||
export default function MangaList (){
|
||||
const Api = useContext(ApiContext);
|
||||
|
||||
const [mangaIds, setMangaIds] = useState<string[]>();
|
||||
|
||||
Api.mangaList().then((response) => {
|
||||
if (!response.ok)
|
||||
return;
|
||||
setMangaIds(response.data);
|
||||
});
|
||||
const mangas = useContext(MangaContext);
|
||||
|
||||
return (
|
||||
<Stack className={"manga-list"} direction={"row"} useFlexGap={true} spacing={2} flexWrap={"wrap"}>
|
||||
{mangaIds?.map(id => <MangaCardFromId key={id} mangaId={id} />)}
|
||||
{mangas?.map(manga => <MangaCard key={manga.key} manga={manga} />)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
|
@@ -1,8 +1,27 @@
|
||||
import {ReactNode, useContext, useState} from "react";
|
||||
import { ApiContext } from "../../apiClient/ApiContext";
|
||||
import {Button, Input, Modal, ModalDialog, Tab, TabList, TabPanel, Tabs} from "@mui/joy";
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Input,
|
||||
Modal,
|
||||
ModalDialog,
|
||||
Stack,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@mui/joy";
|
||||
import ModalClose from "@mui/joy/ModalClose";
|
||||
import {GotifyRecord, NtfyRecord, PushoverRecord} from "../../apiClient/data-contracts.ts";
|
||||
import {Close, Done} from "@mui/icons-material";
|
||||
|
||||
enum LoadingState {
|
||||
none,
|
||||
loading,
|
||||
success,
|
||||
failure
|
||||
}
|
||||
|
||||
export default function ({open, setOpen} : {open: boolean, setOpen: (open: boolean) => void}) {
|
||||
|
||||
@@ -25,11 +44,38 @@ export default function ({open, setOpen} : {open: boolean, setOpen: (open: boole
|
||||
);
|
||||
}
|
||||
|
||||
function NotificationConnectorTab({ value, children, add }: { value: string, children: ReactNode, add: (data: any) => void }) {
|
||||
function NotificationConnectorTab({ value, children, add, state }: { value: string, children: ReactNode, add: (data: any) => void, state: LoadingState }) {
|
||||
const StateIndicator = (state : LoadingState) : ReactNode => {
|
||||
switch (state) {
|
||||
case LoadingState.loading:
|
||||
return (<CircularProgress />);
|
||||
case LoadingState.failure:
|
||||
return (<Close />);
|
||||
case LoadingState.success:
|
||||
return (<Done />);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const StateColor = (state : LoadingState) => {
|
||||
switch (state) {
|
||||
case LoadingState.failure:
|
||||
return "danger";
|
||||
case LoadingState.success:
|
||||
return "success";
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const IsLoading = (state : LoadingState) : boolean => state === LoadingState.loading;
|
||||
|
||||
return (
|
||||
<TabPanel value={value}>
|
||||
{children}
|
||||
<Button onClick={add}>Add</Button>
|
||||
<Stack spacing={1}>
|
||||
{children}
|
||||
<Button onClick={add} endDecorator={StateIndicator(state)} loading={IsLoading(state)} disabled={IsLoading(state)} color={StateColor(state)}>Add</Button>
|
||||
</Stack>
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
@@ -37,9 +83,22 @@ function NotificationConnectorTab({ value, children, add }: { value: string, chi
|
||||
function Gotify() {
|
||||
const Api = useContext(ApiContext);
|
||||
const [gotifyData, setGotifyData] = useState<GotifyRecord>({});
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
||||
|
||||
const Add = () => {
|
||||
setLoadingState(LoadingState.loading);
|
||||
Api.notificationConnectorGotifyUpdate(gotifyData)
|
||||
.then((response) => {
|
||||
if (response.ok)
|
||||
setLoadingState(LoadingState.success);
|
||||
else
|
||||
setLoadingState(LoadingState.failure);
|
||||
})
|
||||
.catch(_ => setLoadingState(LoadingState.failure));
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationConnectorTab value={"gotify"} add={() => Api.notificationConnectorGotifyUpdate(gotifyData)}>
|
||||
<NotificationConnectorTab value={"gotify"} add={Add} state={loadingState}>
|
||||
<Input placeholder={"Name"} value={gotifyData.name as string} onChange={(e) => setGotifyData({...gotifyData, name: e.target.value})} />
|
||||
<Input placeholder={"https://[...]/message"} value={gotifyData.endpoint as string} onChange={(e) => setGotifyData({...gotifyData, endpoint: e.target.value})} />
|
||||
<Input placeholder={"Apptoken"} type={"password"} value={gotifyData.appToken as string} onChange={(e) => setGotifyData({...gotifyData, appToken: e.target.value})} />
|
||||
@@ -51,9 +110,22 @@ function Gotify() {
|
||||
function Ntfy() {
|
||||
const Api = useContext(ApiContext);
|
||||
const [ntfyData, setNtfyData] = useState<NtfyRecord>({});
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
||||
|
||||
const Add = () => {
|
||||
setLoadingState(LoadingState.loading);
|
||||
Api.notificationConnectorNtfyUpdate(ntfyData)
|
||||
.then((response) => {
|
||||
if (response.ok)
|
||||
setLoadingState(LoadingState.success);
|
||||
else
|
||||
setLoadingState(LoadingState.failure);
|
||||
})
|
||||
.catch(_ => setLoadingState(LoadingState.failure));
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationConnectorTab value={"ntfy"} add={() => Api.notificationConnectorNtfyUpdate(ntfyData)}>
|
||||
<NotificationConnectorTab value={"ntfy"} add={Add} state={loadingState}>
|
||||
<Input placeholder={"Name"} value={ntfyData.name as string} onChange={(e) => setNtfyData({...ntfyData, name: e.target.value})} />
|
||||
<Input placeholder={"Endpoint"} value={ntfyData.endpoint as string} onChange={(e) => setNtfyData({...ntfyData, endpoint: e.target.value})} />
|
||||
<Input placeholder={"Topic"} value={ntfyData.topic as string} onChange={(e) => setNtfyData({...ntfyData, topic: e.target.value})} />
|
||||
@@ -67,9 +139,22 @@ function Ntfy() {
|
||||
function Pushover() {
|
||||
const Api = useContext(ApiContext);
|
||||
const [pushoverData, setPushoverData] = useState<PushoverRecord>({});
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>(LoadingState.none);
|
||||
|
||||
const Add = () => {
|
||||
setLoadingState(LoadingState.loading);
|
||||
Api.notificationConnectorPushoverUpdate(pushoverData)
|
||||
.then((response) => {
|
||||
if (response.ok)
|
||||
setLoadingState(LoadingState.success);
|
||||
else
|
||||
setLoadingState(LoadingState.failure);
|
||||
})
|
||||
.catch(_ => setLoadingState(LoadingState.failure));
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationConnectorTab value={"pushover"} add={() => Api.notificationConnectorPushoverUpdate(pushoverData)}>
|
||||
<NotificationConnectorTab value={"pushover"} add={Add} state={loadingState}>
|
||||
<Input placeholder={"Name"} value={pushoverData.name as string} onChange={(e) => setPushoverData({...pushoverData, name: e.target.value})} />
|
||||
<Input placeholder={"User"} value={pushoverData.user as string} onChange={(e) => setPushoverData({...pushoverData, user: e.target.value})} />
|
||||
<Input placeholder={"AppToken"} type={"password"} value={pushoverData.appToken as string} onChange={(e) => setPushoverData({...pushoverData, appToken: e.target.value})} />
|
@@ -15,7 +15,7 @@ import {createContext, Dispatch, useContext, useEffect, useState} from "react";
|
||||
import {Article} from '@mui/icons-material';
|
||||
import {TrangaSettings} from "../../apiClient/data-contracts.ts";
|
||||
import {ApiContext} from "../../apiClient/ApiContext.tsx";
|
||||
import NotificationConnectors from "./NotificationConnectors.tsx";
|
||||
import NotificationConnectors from "./AddNotificationConnector.tsx";
|
||||
|
||||
export const SettingsContext = createContext<TrangaSettings>({});
|
||||
|
||||
|
@@ -1,34 +0,0 @@
|
||||
import {createContext, useContext} from "react";
|
||||
import {Manga} from "./data-contracts.ts";
|
||||
import {ApiContext} from "./ApiContext.tsx";
|
||||
|
||||
const mangaPromises = new Map<string, Promise<Manga | undefined>>();
|
||||
const mangas : Manga[] = [];
|
||||
|
||||
export const GetManga : (id: string) => Promise<Manga | undefined> = (id: string) => {
|
||||
const API = useContext(ApiContext);
|
||||
|
||||
const promise = mangaPromises.get(id);
|
||||
if(promise) return promise;
|
||||
const p = new Promise<Manga | undefined>((resolve, reject) => {
|
||||
let ret = mangas?.find(m => m.key == id);
|
||||
if (ret) resolve(ret);
|
||||
|
||||
console.log(`Fetching manga ${id}`);
|
||||
API.mangaDetail(id)
|
||||
.then(result => {
|
||||
if (!result.ok)
|
||||
throw new Error(`Error fetching manga detail ${id}`);
|
||||
mangas.push(result.data);
|
||||
resolve(result.data);
|
||||
}).catch(reject);
|
||||
});
|
||||
mangaPromises.set(id, p);
|
||||
return p;
|
||||
};
|
||||
|
||||
export const MangaContext = createContext<{ GetManga: (id: string) => Promise<Manga | undefined> }>(
|
||||
{
|
||||
GetManga: GetManga
|
||||
}
|
||||
);
|
Reference in New Issue
Block a user