mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-12-24 15:51:49 +01:00
MangaDetails component improved drastically
This commit is contained in:
parent
149107e749
commit
26cc2f2c96
@ -85,6 +85,14 @@ export default function CategorySelect(props: IProps) {
|
||||
<DialogTitle>Set categories</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<FormGroup>
|
||||
{categoryInfos.length === 0
|
||||
&& (
|
||||
<span>
|
||||
No categories found!
|
||||
<br />
|
||||
You should make some from settings.
|
||||
</span>
|
||||
)}
|
||||
{categoryInfos.map((categoryInfo) => (
|
||||
<FormControlLabel
|
||||
control={(
|
||||
|
@ -43,7 +43,7 @@ const useStyles = makeStyles({
|
||||
});
|
||||
|
||||
interface IProps {
|
||||
manga: IManga
|
||||
manga: IMangaCard
|
||||
}
|
||||
const MangaCard = React.forwardRef((props: IProps, ref) => {
|
||||
const {
|
||||
|
@ -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',
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
},
|
||||
top: {
|
||||
padding: '10px',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minWidth: '50%',
|
||||
},
|
||||
},
|
||||
leftRight: {
|
||||
display: 'flex',
|
||||
},
|
||||
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<string>(
|
||||
manga.inLibrary ? 'In Library' : 'Not In Library',
|
||||
manga.inLibrary ? 'In Library' : 'Add to Library',
|
||||
);
|
||||
|
||||
const [categoryDialogOpen, setCategoryDialogOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (inLibrary === 'In Library') {
|
||||
setAction(
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => setCategoryDialogOpen(true)}
|
||||
aria-label="display more actions"
|
||||
edge="end"
|
||||
color="inherit"
|
||||
>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
<CategorySelect
|
||||
open={categoryDialogOpen}
|
||||
setOpen={setCategoryDialogOpen}
|
||||
mangaId={manga.id}
|
||||
/>
|
||||
</>,
|
||||
|
||||
);
|
||||
} else { setAction(<></>); }
|
||||
}, [inLibrary, categoryDialogOpen]);
|
||||
|
||||
const [serverAddress] = useLocalStorage<String>('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 (
|
||||
<div>
|
||||
<div className={classes.root}>
|
||||
<div className={classes.top}>
|
||||
<div className={classes.leftRight}>
|
||||
<div className={classes.leftSide}>
|
||||
<img src={serverAddress + manga.thumbnailUrl} alt="Manga Thumbnail" />
|
||||
</div>
|
||||
<div className={classes.rightSide}>
|
||||
<h1>
|
||||
{manga.title}
|
||||
</h1>
|
||||
<h3>
|
||||
Author:
|
||||
{' '}
|
||||
<span>{getValueOrUnknown(manga.author)}</span>
|
||||
</h3>
|
||||
<h3>
|
||||
Artist:
|
||||
{' '}
|
||||
<span>{getValueOrUnknown(manga.artist)}</span>
|
||||
</h3>
|
||||
<h3>
|
||||
Status:
|
||||
{' '}
|
||||
{manga.status}
|
||||
</h3>
|
||||
<h3>
|
||||
Source:
|
||||
{' '}
|
||||
{getSourceName(source)}
|
||||
{getSourceName(manga.source)}
|
||||
</h3>
|
||||
<div className={classes.root}>
|
||||
<Button variant="outlined" onClick={() => handleButtonClick()}>{inLibrary}</Button>
|
||||
{inLibrary === 'In Library'
|
||||
&& <Button variant="outlined" onClick={() => setCategoryDialogOpen(true)}>Edit Categories</Button>}
|
||||
|
||||
</div>
|
||||
<CategorySelect
|
||||
open={categoryDialogOpen}
|
||||
setOpen={setCategoryDialogOpen}
|
||||
mangaId={manga.id}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.buttons}>
|
||||
<div>
|
||||
<IconButton onClick={() => handleButtonClick()}>
|
||||
{inLibrary === 'In Library' && <FavoriteIcon />}
|
||||
{inLibrary !== 'In Library' && <FavoriteBorderIcon />}
|
||||
<span>{inLibrary}</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
{ /* eslint-disable-next-line react/jsx-no-target-blank */ }
|
||||
<a href={manga.url} target="_blank">
|
||||
<IconButton>
|
||||
<PublicIcon />
|
||||
<span>Open Site</span>
|
||||
</IconButton>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.bottom}>
|
||||
<div className={classes.description}>
|
||||
<h4>About</h4>
|
||||
<p>{manga.description}</p>
|
||||
</div>
|
||||
<div className={classes.genre}>
|
||||
{manga.genre.split(', ').map((g) => <h5 key={g}>{g}</h5>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<Grid container spacing={1} style={{ margin: 0, padding: '5px' }}>
|
||||
<Grid container spacing={1} style={{ margin: 0, width: '100%', padding: '5px' }}>
|
||||
{mapped}
|
||||
</Grid>
|
||||
);
|
||||
|
@ -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<IManga>();
|
||||
const [source, setSource] = useState<ISource>();
|
||||
const [chapters, setChapters] = useState<IChapter[]>([]);
|
||||
|
||||
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) && <MangaDetails manga={manga} source={source} />}
|
||||
{manga && <MangaDetails manga={manga} />}
|
||||
{chapterCards}
|
||||
</>
|
||||
);
|
||||
|
@ -27,7 +27,7 @@ export default function SearchSingle() {
|
||||
const { sourceId } = useParams<{sourceId: string}>();
|
||||
const classes = useStyles();
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
const [mangas, setMangas] = useState<IManga[]>([]);
|
||||
const [mangas, setMangas] = useState<IMangaCard[]>([]);
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false);
|
||||
|
@ -13,7 +13,7 @@ export default function SourceMangas(props: { popular: boolean }) {
|
||||
useEffect(() => { setTitle('Source'); setAction(<></>); }, []);
|
||||
|
||||
const { sourceId } = useParams<{sourceId: string}>();
|
||||
const [mangas, setMangas] = useState<IManga[]>([]);
|
||||
const [mangas, setMangas] = useState<IMangaCard[]>([]);
|
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false);
|
||||
const [lastPageNum, setLastPageNum] = useState<number>(1);
|
||||
|
||||
|
22
webUI/react/src/typings.d.ts
vendored
22
webUI/react/src/typings.d.ts
vendored
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user