diff --git a/webUI/react/src/components/CategorySelect.tsx b/webUI/react/src/components/CategorySelect.tsx index 8b51ddf..2e31893 100644 --- a/webUI/react/src/components/CategorySelect.tsx +++ b/webUI/react/src/components/CategorySelect.tsx @@ -85,6 +85,14 @@ export default function CategorySelect(props: IProps) { Set categories + {categoryInfos.length === 0 + && ( + + No categories found! +
+ You should make some from settings. +
+ )} {categoryInfos.map((categoryInfo) => ( { const { diff --git a/webUI/react/src/components/MangaDetails.tsx b/webUI/react/src/components/MangaDetails.tsx index 6efe101..723761e 100644 --- a/webUI/react/src/components/MangaDetails.tsx +++ b/webUI/react/src/components/MangaDetails.tsx @@ -2,55 +2,179 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Button, createStyles, makeStyles } from '@material-ui/core'; -import React, { useState } from 'react'; +import { makeStyles } from '@material-ui/core'; +import IconButton from '@material-ui/core/IconButton'; +import { Theme } from '@material-ui/core/styles'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import PublicIcon from '@material-ui/icons/Public'; +import React, { useContext, useEffect, useState } from 'react'; +import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; +import useLocalStorage from '../util/useLocalStorage'; import CategorySelect from './CategorySelect'; -const useStyles = makeStyles(() => createStyles({ +const useStyles = (inLibrary: string) => makeStyles((theme: Theme) => ({ root: { + width: '100%', + [theme.breakpoints.up('md')]: { + display: 'flex', + }, + }, + top: { + padding: '10px', + [theme.breakpoints.up('md')]: { + minWidth: '50%', + }, + }, + leftRight: { display: 'flex', - flexDirection: 'row-reverse', + }, + leftSide: { + '& img': { + borderRadius: 4, + maxWidth: '100%', + minWidth: '100%', + height: 'auto', + }, + maxWidth: '50%', + [theme.breakpoints.up('md')]: { + minWidth: '100px', + }, + }, + rightSide: { + marginLeft: 15, + maxWidth: '100%', + '& span': { + fontWeight: '400', + }, + [theme.breakpoints.up('lg')]: { + fontSize: '1.3em', + }, + }, + buttons: { + display: 'flex', + justifyContent: 'space-around', '& button': { - marginLeft: 10, + color: inLibrary === 'In Library' ? '#2196f3' : 'inherit', + }, + '& span': { + display: 'block', + fontSize: '0.85em', + }, + '& a': { + textDecoration: 'none', + color: '#858585', + '& button': { + color: 'inherit', + }, + }, + }, + bottom: { + paddingLeft: '10px', + paddingRight: '10px', + [theme.breakpoints.up('md')]: { + fontSize: '1.2em', + maxWidth: '50%', + }, + [theme.breakpoints.up('lg')]: { + fontSize: '1.3em', + }, + }, + description: { + '& h4': { + marginTop: '1em', + marginBottom: 0, + }, + '& p': { + textAlign: 'justify', + textJustify: 'inter-word', + }, + }, + genre: { + display: 'flex', + flexWrap: 'wrap', + '& h5': { + border: '2px solid #2196f3', + borderRadius: '1.13em', + marginRight: '1em', + marginTop: 0, + marginBottom: '10px', + padding: '0.3em', + color: '#2196f3', }, }, })); interface IProps{ manga: IManga - source: ISource } function getSourceName(source: ISource) { - if (source.name !== null) { return source.name; } + if (source.name !== null) { + return `${source.name} (${source.lang.toLocaleUpperCase()})`; + } return source.id; } +function getValueOrUnknown(val: string) { + return val || 'UNKNOWN'; +} + export default function MangaDetails(props: IProps) { - const classes = useStyles(); - const { manga, source } = props; + const { setAction } = useContext(NavbarContext); + + const { manga } = props; const [inLibrary, setInLibrary] = useState( - manga.inLibrary ? 'In Library' : 'Not In Library', + manga.inLibrary ? 'In Library' : 'Add to Library', ); + const [categoryDialogOpen, setCategoryDialogOpen] = useState(false); + useEffect(() => { + if (inLibrary === 'In Library') { + setAction( + <> + setCategoryDialogOpen(true)} + aria-label="display more actions" + edge="end" + color="inherit" + > + + + + , + + ); + } else { setAction(<>); } + }, [inLibrary, categoryDialogOpen]); + + const [serverAddress] = useLocalStorage('serverBaseURL', ''); + + const classes = useStyles(inLibrary)(); + function addToLibrary() { - setInLibrary('adding'); + // setInLibrary('adding'); client.get(`/api/v1/manga/${manga.id}/library/`).then(() => { setInLibrary('In Library'); }); } function removeFromLibrary() { - setInLibrary('removing'); + // setInLibrary('removing'); client.delete(`/api/v1/manga/${manga.id}/library/`).then(() => { - setInLibrary('Not In Library'); + setInLibrary('Add To Library'); }); } function handleButtonClick() { - if (inLibrary === 'Not In Library') { + if (inLibrary === 'Add To Library') { addToLibrary(); } else { removeFromLibrary(); @@ -58,26 +182,64 @@ export default function MangaDetails(props: IProps) { } return ( -
-

- {manga.title} -

-

- Source: - {' '} - {getSourceName(source)} -

-
- - {inLibrary === 'In Library' - && } - +
+
+
+
+ Manga Thumbnail +
+
+

+ {manga.title} +

+

+ Author: + {' '} + {getValueOrUnknown(manga.author)} +

+

+ Artist: + {' '} + {getValueOrUnknown(manga.artist)} +

+

+ Status: + {' '} + {manga.status} +

+

+ Source: + {' '} + {getSourceName(manga.source)} +

+
+
+
+
+ handleButtonClick()}> + {inLibrary === 'In Library' && } + {inLibrary !== 'In Library' && } + {inLibrary} + +
+ { /* eslint-disable-next-line react/jsx-no-target-blank */ } + + + + Open Site + + +
+
+
+
+

About

+

{manga.description}

+
+
+ {manga.genre.split(', ').map((g) =>
{g}
)} +
-
); } diff --git a/webUI/react/src/components/MangaGrid.tsx b/webUI/react/src/components/MangaGrid.tsx index 15a3ae4..a08e6e3 100644 --- a/webUI/react/src/components/MangaGrid.tsx +++ b/webUI/react/src/components/MangaGrid.tsx @@ -7,7 +7,7 @@ import Grid from '@material-ui/core/Grid'; import MangaCard from './MangaCard'; interface IProps{ - mangas: IManga[] + mangas: IMangaCard[] message?: string hasNextPage: boolean lastPageNum: number @@ -48,7 +48,7 @@ export default function MangaGrid(props: IProps) { } return ( - + {mapped} ); diff --git a/webUI/react/src/screens/Manga.tsx b/webUI/react/src/screens/Manga.tsx index 867945f..a212ab5 100644 --- a/webUI/react/src/screens/Manga.tsx +++ b/webUI/react/src/screens/Manga.tsx @@ -10,13 +10,12 @@ import NavbarContext from '../context/NavbarContext'; import client from '../util/client'; export default function Manga() { - const { setTitle, setAction } = useContext(NavbarContext); - useEffect(() => { setTitle('Manga'); setAction(<>); }, []); + const { setTitle } = useContext(NavbarContext); + useEffect(() => { setTitle('Manga'); }, []); // delegate setting topbar action to MangaDetails const { id } = useParams<{id: string}>(); const [manga, setManga] = useState(); - const [source, setSource] = useState(); const [chapters, setChapters] = useState([]); useEffect(() => { @@ -28,16 +27,6 @@ export default function Manga() { }); }, []); - useEffect(() => { - if (manga !== undefined) { - client.get(`/api/v1/source/${manga.sourceId}`) - .then((response) => response.data) - .then((data: ISource) => { - setSource(data); - }); - } - }, [manga]); - useEffect(() => { client.get(`/api/v1/manga/${id}/chapters`) .then((response) => response.data) @@ -52,7 +41,7 @@ export default function Manga() { return ( <> - {(manga && source) && } + {manga && } {chapterCards} ); diff --git a/webUI/react/src/screens/SearchSingle.tsx b/webUI/react/src/screens/SearchSingle.tsx index d13d346..b56544b 100644 --- a/webUI/react/src/screens/SearchSingle.tsx +++ b/webUI/react/src/screens/SearchSingle.tsx @@ -27,7 +27,7 @@ export default function SearchSingle() { const { sourceId } = useParams<{sourceId: string}>(); const classes = useStyles(); const [error, setError] = useState(false); - const [mangas, setMangas] = useState([]); + const [mangas, setMangas] = useState([]); const [message, setMessage] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [hasNextPage, setHasNextPage] = useState(false); diff --git a/webUI/react/src/screens/SourceMangas.tsx b/webUI/react/src/screens/SourceMangas.tsx index fdfea4c..15868e1 100644 --- a/webUI/react/src/screens/SourceMangas.tsx +++ b/webUI/react/src/screens/SourceMangas.tsx @@ -13,7 +13,7 @@ export default function SourceMangas(props: { popular: boolean }) { useEffect(() => { setTitle('Source'); setAction(<>); }, []); const { sourceId } = useParams<{sourceId: string}>(); - const [mangas, setMangas] = useState([]); + const [mangas, setMangas] = useState([]); const [hasNextPage, setHasNextPage] = useState(false); const [lastPageNum, setLastPageNum] = useState(1); diff --git a/webUI/react/src/typings.d.ts b/webUI/react/src/typings.d.ts index d4466e8..89e9dad 100644 --- a/webUI/react/src/typings.d.ts +++ b/webUI/react/src/typings.d.ts @@ -21,12 +21,28 @@ interface ISource { history: any } -interface IManga { +interface IMangaCard { id: number - sourceId?: string title: string thumbnailUrl: string - inLibrary?: boolean +} + +interface IManga { + id: number + sourceId: string + + url: string + title: string + thumbnailUrl: string + + artist: string + author: string + description: string + genre: string + status: string + + inLibrary: boolean + source: ISource } interface IChapter {