add prettier for auto formatting (#231)

* [Prettier] Add "prettier"

Makes the formatting of the code consistent.
By using the eslint-plugin-prettier formatting issues will be highlighted as a lint error.
These errors will be auto fixed by running "lint --fix" (which can be done automatically on file save)

* [Prettier] Add "prettier" - Fix formatting
This commit is contained in:
Daniel
2023-02-06 10:06:33 +01:00
committed by GitHub
parent 2eeea41a45
commit 4e0a860dfd
101 changed files with 2065 additions and 1886 deletions

View File

@@ -15,9 +15,7 @@ import DoneAll from '@mui/icons-material/DoneAll';
import Download from '@mui/icons-material/Download';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import RemoveDone from '@mui/icons-material/RemoveDone';
import {
CardActionArea, Checkbox, ListItemIcon, ListItemText, Stack,
} from '@mui/material';
import { CardActionArea, Checkbox, ListItemIcon, ListItemText, Stack } from '@mui/material';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import IconButton from '@mui/material/IconButton';
@@ -33,19 +31,24 @@ import { BACK } from 'util/useBackTo';
import { getUploadDateString } from 'util/date';
interface IProps {
chapter: IChapter
triggerChaptersUpdate: () => void
downloadChapter: IDownloadChapter | undefined
showChapterNumber: boolean
onSelect: (selected: boolean) => void
selected: boolean | null
chapter: IChapter;
triggerChaptersUpdate: () => void;
downloadChapter: IDownloadChapter | undefined;
showChapterNumber: boolean;
onSelect: (selected: boolean) => void;
selected: boolean | null;
}
const ChapterCard: React.FC<IProps> = (props: IProps) => {
const theme = useTheme();
const {
chapter, triggerChaptersUpdate, downloadChapter: dc, showChapterNumber, onSelect, selected,
chapter,
triggerChaptersUpdate,
downloadChapter: dc,
showChapterNumber,
onSelect,
selected,
} = props;
const isSelecting = selected !== null;
@@ -71,7 +74,8 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
if (key === 'read') {
formData.append('lastPageRead', '1');
}
client.patch(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`, formData)
client
.patch(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`, formData)
.then(() => triggerChaptersUpdate());
};
@@ -81,7 +85,8 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
};
const deleteChapter = () => {
client.delete(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`)
client
.delete(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`)
.then(() => triggerChaptersUpdate());
handleClose();
};
@@ -112,7 +117,10 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
>
<CardActionArea
component={Link}
to={{ pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`, state: { backLink: BACK } }}
to={{
pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`,
state: { backLink: BACK },
}}
style={{
color: theme.palette.text[chapter.read ? 'disabled' : 'primary'],
}}
@@ -130,13 +138,16 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
<Stack direction="column" flex={1}>
<Typography variant="h5" component="h2">
{chapter.bookmarked && (
<BookmarkIcon color="primary" sx={{ mr: 0.5, position: 'relative', top: '0.15em' }} />
<BookmarkIcon
color="primary"
sx={{ mr: 0.5, position: 'relative', top: '0.15em' }}
/>
)}
{showChapterNumber ? `Chapter ${chapter.chapterNumber}` : chapter.name}
</Typography>
<Typography variant="caption">
{chapter.scanlator}
{showChapterNumber
? `Chapter ${chapter.chapterNumber}`
: chapter.name}
</Typography>
<Typography variant="caption">{chapter.scanlator}</Typography>
<Typography variant="caption">
{getUploadDateString(chapter.uploadDate)}
{isDownloaded && ' • Downloaded'}
@@ -164,18 +175,14 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
<ListItemIcon>
<CheckBoxOutlineBlank fontSize="small" />
</ListItemIcon>
<ListItemText>
Select
</ListItemText>
<ListItemText>Select</ListItemText>
</MenuItem>
{isDownloaded && (
<MenuItem onClick={deleteChapter}>
<ListItemIcon>
<Delete fontSize="small" />
</ListItemIcon>
<ListItemText>
Delete
</ListItemText>
<ListItemText>Delete</ListItemText>
</MenuItem>
)}
{canBeDownloaded && (
@@ -183,9 +190,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
<ListItemIcon>
<Download fontSize="small" />
</ListItemIcon>
<ListItemText>
Download
</ListItemText>
<ListItemText>Download</ListItemText>
</MenuItem>
)}
<MenuItem onClick={() => sendChange('bookmarked', !chapter.bookmarked)}>
@@ -212,9 +217,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
<ListItemIcon>
<DoneAll fontSize="small" />
</ListItemIcon>
<ListItemText>
Mark previous as Read
</ListItemText>
<ListItemText>Mark previous as Read</ListItemText>
</MenuItem>
</Menu>
</Card>

View File

@@ -5,9 +5,7 @@
* 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, CircularProgress, Stack,
} from '@mui/material';
import { Button, CircularProgress, Stack } from '@mui/material';
import Typography from '@mui/material/Typography';
import { styled } from '@mui/system';
import useSubscription from 'components/library/useSubscription';
@@ -17,10 +15,7 @@ import { filterAndSortChapters, useChapterOptions } from 'components/manga/util'
import EmptyView from 'components/util/EmptyView';
import { interpolate } from 'components/util/helpers';
import makeToast from 'components/util/Toast';
import React, {
ComponentProps,
useEffect, useMemo, useRef, useState,
} from 'react';
import React, { ComponentProps, useEffect, useMemo, useRef, useState } from 'react';
import { Virtuoso } from 'react-virtuoso';
import client, { useQuery } from 'util/client';
import ChaptersToolbarMenu from 'components/manga/ChaptersToolbarMenu';
@@ -66,13 +61,13 @@ const actionsStrings = {
};
export interface IChapterWithMeta {
chapter: IChapter
downloadChapter: IDownloadChapter | undefined
selected: boolean | null
chapter: IChapter;
downloadChapter: IDownloadChapter | undefined;
selected: boolean | null;
}
interface IProps {
mangaId: string
mangaId: string;
}
const ChapterList: React.FC<IProps> = ({ mangaId }) => {
@@ -92,9 +87,9 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
if (prevQueueRef.current && queue) {
const prevQueue = prevQueueRef.current;
const changedDownloads = queue.filter((cd) => {
const prevChapterDownload = prevQueue
.find((pcd) => cd.chapterIndex === pcd.chapterIndex
&& cd.mangaId === pcd.mangaId);
const prevChapterDownload = prevQueue.find(
(pcd) => cd.chapterIndex === pcd.chapterIndex && cd.mangaId === pcd.mangaId,
);
if (!prevChapterDownload) return true;
return cd.state !== prevChapterDownload.state;
});
@@ -107,13 +102,19 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
prevQueueRef.current = queue;
}, [queue]);
const visibleChapters = useMemo(() => filterAndSortChapters(chapters, options), //
[chapters, options]);
const visibleChapters = useMemo(
() => filterAndSortChapters(chapters, options), //
[chapters, options],
);
const firstUnreadChapter = useMemo(() => visibleChapters.slice()
.reverse()
.find((c) => c.read === false),
[visibleChapters]);
const firstUnreadChapter = useMemo(
() =>
visibleChapters
.slice()
.reverse()
.find((c) => c.read === false),
[visibleChapters],
);
const handleSelection = (index: number) => {
const chapter = visibleChapters[index];
@@ -139,7 +140,10 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
setSelection(null);
};
const handleFabAction: ComponentProps<typeof SelectionFAB>['onAction'] = (action, actionChapters) => {
const handleFabAction: ComponentProps<typeof SelectionFAB>['onAction'] = (
action,
actionChapters,
) => {
if (actionChapters.length === 0) return;
const chapterIds = actionChapters.map(({ chapter }) => chapter.id);
@@ -160,18 +164,26 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
}
actionPromise
.then(() => makeToast(interpolate(chapterIds.length, actionsStrings[action].success), 'success'))
.then(() =>
makeToast(
interpolate(chapterIds.length, actionsStrings[action].success),
'success',
),
)
.then(() => mutate())
.catch(() => makeToast(interpolate(chapterIds.length, actionsStrings[action].error), 'error'));
.catch(() =>
makeToast(interpolate(chapterIds.length, actionsStrings[action].error), 'error'),
);
};
if (loading) {
return (
<div style={{
margin: '10px auto',
display: 'flex',
justifyContent: 'center',
}}
<div
style={{
margin: '10px auto',
display: 'flex',
justifyContent: 'center',
}}
>
<CircularProgress thickness={5} />
</div>
@@ -183,8 +195,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
const chaptersWithMeta: IChapterWithMeta[] = visibleChapters.map((chapter) => {
const downloadChapter = queue?.find(
(cd) => cd.chapterIndex === chapter.index
&& cd.mangaId === chapter.mangaId,
(cd) => cd.chapterIndex === chapter.index && cd.mangaId === chapter.mangaId,
);
const selected = selection?.includes(chapter.id) ?? null;
return {
@@ -194,9 +205,10 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
};
});
const selectedChapters = (selection === null)
? null
: chaptersWithMeta.filter(({ chapter }) => selection.includes(chapter.id));
const selectedChapters =
selection === null
? null
: chaptersWithMeta.filter(({ chapter }) => selection.includes(chapter.id));
return (
<>
@@ -206,38 +218,44 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
alignItems="center"
justifyContent="space-between"
sx={{
m: 1, mb: 0, mr: 2, minHeight: 40,
m: 1,
mb: 0,
mr: 2,
minHeight: 40,
}}
>
<Typography variant="h5">
{`${visibleChapters.length} Chapter${visibleChapters.length === 1 ? '' : 's'}`}
{`${visibleChapters.length} Chapter${
visibleChapters.length === 1 ? '' : 's'
}`}
</Typography>
{selection === null ? (
<ChaptersToolbarMenu options={options} optionsDispatch={dispatch} />
) : (
<Stack direction="row">
<Button size="small" onClick={handleSelectAll}>Select all</Button>
<Button size="small" onClick={handleClear}>Clear</Button>
<Button size="small" onClick={handleSelectAll}>
Select all
</Button>
<Button size="small" onClick={handleClear}>
Clear
</Button>
</Stack>
)}
</Stack>
{noChaptersFound && (
<EmptyView message="No chapters found" />
)}
{noChaptersMatchingFilter && (
<EmptyView message="No chapters matching filter" />
)}
{noChaptersFound && <EmptyView message="No chapters found" />}
{noChaptersMatchingFilter && <EmptyView message="No chapters matching filter" />}
<StyledVirtuoso
style={{ // override Virtuoso default values and set them with class
style={{
// override Virtuoso default values and set them with class
height: 'undefined',
// 900 is the md breakpoint in MUI
overflowY: window.innerWidth < 900 ? 'visible' : 'auto',
}}
totalCount={visibleChapters.length}
itemContent={(index:number) => (
itemContent={(index: number) => (
<ChapterCard
{...chaptersWithMeta[index]}
showChapterNumber={options.showChapterNumber}
@@ -250,10 +268,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
/>
</Stack>
{selectedChapters !== null ? (
<SelectionFAB
selectedChapters={selectedChapters}
onAction={handleFabAction}
/>
<SelectionFAB selectedChapters={selectedChapters} onAction={handleFabAction} />
) : (
firstUnreadChapter && <ResumeFab chapter={firstUnreadChapter} mangaId={mangaId} />
)}

View File

@@ -14,10 +14,10 @@ import React from 'react';
import { SORT_OPTIONS } from 'components/manga/util';
interface IProps {
open: boolean
onClose: () => void
options: ChapterListOptions
optionsDispatch: React.Dispatch<ChapterOptionsReducerAction>
open: boolean;
onClose: () => void;
options: ChapterListOptions;
optionsDispatch: React.Dispatch<ChapterOptionsReducerAction>;
}
const TITLES = {
@@ -26,9 +26,7 @@ const TITLES = {
display: 'Display',
};
const ChapterOptions: React.FC<IProps> = ({
open, onClose, options, optionsDispatch,
}) => (
const ChapterOptions: React.FC<IProps> = ({ open, onClose, options, optionsDispatch }) => (
<OptionsTabs<'filter' | 'sort' | 'display'>
open={open}
onClose={onClose}
@@ -39,9 +37,39 @@ const ChapterOptions: React.FC<IProps> = ({
if (key === 'filter') {
return (
<>
<ThreeStateCheckboxInput label="Unread" checked={options.unread} onChange={(c) => optionsDispatch({ type: 'filter', filterType: 'unread', filterValue: c })} />
<ThreeStateCheckboxInput label="Downloaded" checked={options.downloaded} onChange={(c) => optionsDispatch({ type: 'filter', filterType: 'downloaded', filterValue: c })} />
<ThreeStateCheckboxInput label="Bookmarked" checked={options.bookmarked} onChange={(c) => optionsDispatch({ type: 'filter', filterType: 'bookmarked', filterValue: c })} />
<ThreeStateCheckboxInput
label="Unread"
checked={options.unread}
onChange={(c) =>
optionsDispatch({
type: 'filter',
filterType: 'unread',
filterValue: c,
})
}
/>
<ThreeStateCheckboxInput
label="Downloaded"
checked={options.downloaded}
onChange={(c) =>
optionsDispatch({
type: 'filter',
filterType: 'downloaded',
filterValue: c,
})
}
/>
<ThreeStateCheckboxInput
label="Bookmarked"
checked={options.bookmarked}
onChange={(c) =>
optionsDispatch({
type: 'filter',
filterType: 'bookmarked',
filterValue: c,
})
}
/>
</>
);
}
@@ -52,15 +80,20 @@ const ChapterOptions: React.FC<IProps> = ({
label={label}
checked={options.sortBy === mode}
sortDescending={options.reverse}
onClick={() => (mode !== options.sortBy
? optionsDispatch({ type: 'sortBy', sortBy: mode })
: optionsDispatch({ type: 'sortReverse' }))}
onClick={() =>
mode !== options.sortBy
? optionsDispatch({ type: 'sortBy', sortBy: mode })
: optionsDispatch({ type: 'sortReverse' })
}
/>
));
}
if (key === 'display') {
return (
<RadioGroup onChange={() => optionsDispatch({ type: 'showChapterNumber' })} value={options.showChapterNumber}>
<RadioGroup
onChange={() => optionsDispatch({ type: 'showChapterNumber' })}
value={options.showChapterNumber}
>
<RadioInput label="Source Title" value={false} />
<RadioInput label="Chapter Number" value />
</RadioGroup>

View File

@@ -12,8 +12,8 @@ import ChapterOptions from 'components/manga/ChapterOptions';
import { isFilterActive } from 'components/manga/util';
interface IProps {
options: ChapterListOptions
optionsDispatch: React.Dispatch<ChapterOptionsReducerAction>
options: ChapterListOptions;
optionsDispatch: React.Dispatch<ChapterOptionsReducerAction>;
}
const ChaptersToolbarMenu = ({ options, optionsDispatch }: IProps) => {

View File

@@ -17,102 +17,103 @@ import { mutate } from 'swr';
import client from 'util/client';
import useLocalStorage from 'util/useLocalStorage';
const useStyles = (inLibrary: boolean) => makeStyles((theme: Theme) => ({
root: {
width: '100%',
[theme.breakpoints.up('md')]: {
position: 'sticky',
top: '64px',
left: '0px',
width: '50vw',
height: 'calc(100vh - 64px)',
alignSelf: 'flex-start',
overflowY: 'auto',
},
},
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': {
color: inLibrary ? '#2196f3' : 'inherit',
},
'& a': {
textDecoration: 'none',
color: '#858585',
'& button': {
color: 'inherit',
const useStyles = (inLibrary: boolean) =>
makeStyles((theme: Theme) => ({
root: {
width: '100%',
[theme.breakpoints.up('md')]: {
position: 'sticky',
top: '64px',
left: '0px',
width: '50vw',
height: 'calc(100vh - 64px)',
alignSelf: 'flex-start',
overflowY: 'auto',
},
},
},
bottom: {
paddingLeft: '10px',
paddingRight: '10px',
[theme.breakpoints.up('md')]: {
fontSize: '1.2em',
// maxWidth: '50%',
top: {
padding: '10px',
// [theme.breakpoints.up('md')]: {
// minWidth: '50%',
// },
},
[theme.breakpoints.up('lg')]: {
fontSize: '1.3em',
leftRight: {
display: 'flex',
},
},
description: {
'& h4': {
marginTop: '1em',
marginBottom: 0,
leftSide: {
'& img': {
borderRadius: 4,
maxWidth: '100%',
minWidth: '100%',
height: 'auto',
},
maxWidth: '50%',
// [theme.breakpoints.up('md')]: {
// minWidth: '100px',
// },
},
'& p': {
textAlign: 'justify',
textJustify: 'inter-word',
rightSide: {
marginLeft: 15,
maxWidth: '100%',
'& span': {
fontWeight: '400',
},
[theme.breakpoints.up('lg')]: {
fontSize: '1.3em',
},
},
},
genre: {
display: 'flex',
flexWrap: 'wrap',
'& h5': {
border: '2px solid #2196f3',
borderRadius: '1.13em',
marginRight: '1em',
marginTop: 0,
marginBottom: '10px',
padding: '0.3em',
color: '#2196f3',
buttons: {
display: 'flex',
justifyContent: 'space-around',
'& button': {
color: inLibrary ? '#2196f3' : 'inherit',
},
'& 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
interface IProps {
manga: IManga;
}
function getSourceName(source: ISource) {
@@ -133,14 +134,24 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
const classes = useStyles(manga.inLibrary)();
const addToLibrary = () => {
mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`, { ...manga, inLibrary: true }, { revalidate: false });
client.get(`/api/v1/manga/${manga.id}/library/`)
mutate(
`/api/v1/manga/${manga.id}/?onlineFetch=false`,
{ ...manga, inLibrary: true },
{ revalidate: false },
);
client
.get(`/api/v1/manga/${manga.id}/library/`)
.then(() => mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`));
};
const removeFromLibrary = () => {
mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`, { ...manga, inLibrary: false }, { revalidate: false });
client.delete(`/api/v1/manga/${manga.id}/library/`)
mutate(
`/api/v1/manga/${manga.id}/?onlineFetch=false`,
{ ...manga, inLibrary: false },
{ revalidate: false },
);
client
.delete(`/api/v1/manga/${manga.id}/library/`)
.then(() => mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`));
};
@@ -149,12 +160,13 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
<div className={classes.top}>
<div className={classes.leftRight}>
<div className={classes.leftSide}>
<img src={`${serverAddress}${manga.thumbnailUrl}?useCache=${useCache}`} alt="Manga Thumbnail" />
<img
src={`${serverAddress}${manga.thumbnailUrl}?useCache=${useCache}`}
alt="Manga Thumbnail"
/>
</div>
<div className={classes.rightSide}>
<h1>
{manga.title}
</h1>
<h1>{manga.title}</h1>
<h3>
{'Author: '}
<span>{getValueOrUnknown(manga.author)}</span>
@@ -163,20 +175,21 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
{'Artist: '}
<span>{getValueOrUnknown(manga.artist)}</span>
</h3>
<h3>
{`Status: ${manga.status}`}
</h3>
<h3>
{`Source: ${getSourceName(manga.source)}`}
</h3>
<h3>{`Status: ${manga.status}`}</h3>
<h3>{`Source: ${getSourceName(manga.source)}`}</h3>
</div>
</div>
<div className={classes.buttons}>
<div>
<IconButton onClick={manga.inLibrary ? removeFromLibrary : addToLibrary} size="large">
{manga.inLibrary
? <FavoriteIcon sx={{ mr: 1 }} />
: <FavoriteBorderIcon sx={{ mr: 1 }} />}
<IconButton
onClick={manga.inLibrary ? removeFromLibrary : addToLibrary}
size="large"
>
{manga.inLibrary ? (
<FavoriteIcon sx={{ mr: 1 }} />
) : (
<FavoriteBorderIcon sx={{ mr: 1 }} />
)}
<Typography sx={{ fontSize: { xs: '0.75em', sm: '0.85em' } }}>
{manga.inLibrary ? 'In Library' : 'Add To Library'}
</Typography>
@@ -198,7 +211,9 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
<p>{manga.description}</p>
</div>
<div className={classes.genre}>
{manga.genre.map((g) => <h5 key={g}>{g}</h5>)}
{manga.genre.map((g) => (
<h5 key={g}>{g}</h5>
))}
</div>
</div>
</div>

View File

@@ -9,7 +9,14 @@ import Label from '@mui/icons-material/Label';
import MoreHoriz from '@mui/icons-material/MoreHoriz';
import Refresh from '@mui/icons-material/Refresh';
import {
IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip, useMediaQuery, useTheme,
IconButton,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Tooltip,
useMediaQuery,
useTheme,
} from '@mui/material';
import CategorySelect from 'components/navbar/action/CategorySelect';
import React, { useState } from 'react';
@@ -37,13 +44,22 @@ const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
{isLargeScreen && (
<>
<Tooltip title="Reload data from source">
<IconButton onClick={() => { onRefresh(); }} disabled={refreshing}>
<IconButton
onClick={() => {
onRefresh();
}}
disabled={refreshing}
>
<Refresh />
</IconButton>
</Tooltip>
{manga.inLibrary && (
<Tooltip title="Edit manga categories">
<IconButton onClick={() => { setEditCategories(true); }}>
<IconButton
onClick={() => {
setEditCategories(true);
}}
>
<Label />
</IconButton>
</Tooltip>
@@ -71,37 +87,35 @@ const MangaToolbarMenu = ({ manga, onRefresh, refreshing }: IProps) => {
}}
>
<MenuItem
onClick={() => { onRefresh(); handleClose(); }}
onClick={() => {
onRefresh();
handleClose();
}}
disabled={refreshing}
>
<ListItemIcon>
<Refresh fontSize="small" />
</ListItemIcon>
<ListItemText>
Reload data from source
</ListItemText>
<ListItemText>Reload data from source</ListItemText>
</MenuItem>
{manga.inLibrary && (
<MenuItem
onClick={() => { setEditCategories(true); handleClose(); }}
onClick={() => {
setEditCategories(true);
handleClose();
}}
>
<ListItemIcon>
<Label fontSize="small" />
</ListItemIcon>
<ListItemText>
Edit manga categories
</ListItemText>
<ListItemText>Edit manga categories</ListItemText>
</MenuItem>
)}
</Menu>
</>
)}
<CategorySelect
open={editCategories}
setOpen={setEditCategories}
mangaId={manga.id}
/>
<CategorySelect open={editCategories} setOpen={setEditCategories} mangaId={manga.id} />
</>
);
};

View File

@@ -11,23 +11,29 @@ import { Link } from 'react-router-dom';
import { PlayArrow } from '@mui/icons-material';
import { BACK } from 'util/useBackTo';
interface ResumeFABProps{
chapter: IChapter
mangaId: string
interface ResumeFABProps {
chapter: IChapter;
mangaId: string;
}
export default function ResumeFab(props: ResumeFABProps) {
const { chapter: { index, lastPageRead }, mangaId } = props;
const {
chapter: { index, lastPageRead },
mangaId,
} = props;
return (
<Fab
sx={{ position: 'fixed', bottom: '2em', right: '3em' }}
component={Link}
variant="extended"
color="primary"
to={{ pathname: `/manga/${mangaId}/chapter/${index}/page/${lastPageRead}`, state: { backLink: BACK } }}
to={{
pathname: `/manga/${mangaId}/chapter/${index}/page/${lastPageRead}`,
state: { backLink: BACK },
}}
>
<PlayArrow />
{index === 1 ? 'Start' : 'Resume' }
{index === 1 ? 'Start' : 'Resume'}
</Fab>
);
}

View File

@@ -6,20 +6,24 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import MoreHoriz from '@mui/icons-material/MoreHoriz';
import {
Fab, Menu,
} from '@mui/material';
import { Fab, Menu } from '@mui/material';
import { Box } from '@mui/system';
import { pluralize } from 'components/util/helpers';
import React, { useRef, useState } from 'react';
import type { IChapterWithMeta } from 'components/manga/ChapterList';
import SelectionFABActionItem from 'components/manga/SelectionFABActionItem';
export type SelectionAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
export type SelectionAction =
| 'download'
| 'delete'
| 'bookmark'
| 'unbookmark'
| 'mark_as_read'
| 'mark_as_unread';
interface SelectionFABProps{
selectedChapters: IChapterWithMeta[]
onAction: (action: SelectionAction, chapters: IChapterWithMeta[]) => void
interface SelectionFABProps {
selectedChapters: IChapterWithMeta[];
onAction: (action: SelectionAction, chapters: IChapterWithMeta[]) => void;
}
const SelectionFAB: React.FC<SelectionFABProps> = (props) => {
@@ -38,7 +42,10 @@ const SelectionFAB: React.FC<SelectionFABProps> = (props) => {
return (
<Box
sx={{
position: 'fixed', bottom: '2em', right: '3em', pt: 1,
position: 'fixed',
bottom: '2em',
right: '3em',
pt: 1,
}}
ref={anchorEl}
>

View File

@@ -17,10 +17,10 @@ import type { IChapterWithMeta } from 'components/manga/ChapterList';
import type { SelectionAction } from 'components/manga/SelectionFAB';
interface IProps {
action: SelectionAction
matchingChapters: IChapterWithMeta[]
title: string
onClick: (action: SelectionAction, chapters: IChapterWithMeta[]) => void
action: SelectionAction;
matchingChapters: IChapterWithMeta[];
title: string;
onClick: (action: SelectionAction, chapters: IChapterWithMeta[]) => void;
}
const ICONS = {
@@ -32,16 +32,11 @@ const ICONS = {
mark_as_unread: RemoveDone,
};
const SelectionFABActionItem: React.FC<IProps> = ({
action, matchingChapters, onClick, title,
}) => {
const SelectionFABActionItem: React.FC<IProps> = ({ action, matchingChapters, onClick, title }) => {
const count = matchingChapters.length;
const Icon = ICONS[action];
return (
<MenuItem
onClick={() => onClick(action, matchingChapters)}
disabled={count === 0}
>
<MenuItem onClick={() => onClick(action, matchingChapters)} disabled={count === 0}>
<ListItemIcon>
<Icon fontSize="small" />
</ListItemIcon>

View File

@@ -16,10 +16,14 @@ export const useRefreshManga = (mangaId: string) => {
const handleRefresh = useCallback(async () => {
setFetchingOnline(true);
await Promise.all([
fetcher(`/api/v1/manga/${mangaId}/?onlineFetch=true`)
.then((res) => mutate(`/api/v1/manga/${mangaId}/?onlineFetch=false`, res, { revalidate: false })),
fetcher(`/api/v1/manga/${mangaId}/chapters?onlineFetch=true`)
.then((res) => mutate(`/api/v1/manga/${mangaId}/chapters?onlineFetch=false`, res, { revalidate: false })),
fetcher(`/api/v1/manga/${mangaId}/?onlineFetch=true`).then((res) =>
mutate(`/api/v1/manga/${mangaId}/?onlineFetch=false`, res, { revalidate: false }),
),
fetcher(`/api/v1/manga/${mangaId}/chapters?onlineFetch=true`).then((res) =>
mutate(`/api/v1/manga/${mangaId}/chapters?onlineFetch=false`, res, {
revalidate: false,
}),
),
]).finally(() => setFetchingOnline(false));
}, [mangaId]);

View File

@@ -17,15 +17,15 @@ const defaultChapterOptions: ChapterListOptions = {
showChapterNumber: false,
};
function chapterOptionsReducer(state: ChapterListOptions,
actions: ChapterOptionsReducerAction)
: ChapterListOptions {
function chapterOptionsReducer(
state: ChapterListOptions,
actions: ChapterOptionsReducerAction,
): ChapterListOptions {
switch (actions.type) {
case 'filter':
// eslint-disable-next-line no-case-declarations
const active = state.unread !== false
&& state.downloaded !== false
&& state.bookmarked !== false;
const active =
state.unread !== false && state.downloaded !== false && state.bookmarked !== false;
return {
...state,
active,
@@ -53,8 +53,10 @@ export function unreadFilter(unread: NullAndUndefined<boolean>, { read: isChapte
}
}
function downloadFilter(downloaded: NullAndUndefined<boolean>,
{ downloaded: chapterDownload }: IChapter) {
function downloadFilter(
downloaded: NullAndUndefined<boolean>,
{ downloaded: chapterDownload }: IChapter,
) {
switch (downloaded) {
case true:
return chapterDownload;
@@ -65,8 +67,10 @@ function downloadFilter(downloaded: NullAndUndefined<boolean>,
}
}
function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>,
{ bookmarked: chapterBookmarked }: IChapter) {
function bookmarkedFilter(
bookmarked: NullAndUndefined<boolean>,
{ bookmarked: chapterBookmarked }: IChapter,
) {
switch (bookmarked) {
case true:
return chapterBookmarked;
@@ -77,29 +81,34 @@ function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>,
}
}
export function filterAndSortChapters(chapters: IChapter[], options: ChapterListOptions)
: IChapter[] {
export function filterAndSortChapters(
chapters: IChapter[],
options: ChapterListOptions,
): IChapter[] {
const filtered = options.active
? chapters.filter((chp) => unreadFilter(options.unread, chp)
&& downloadFilter(options.downloaded, chp)
&& bookmarkedFilter(options.bookmarked, chp))
? chapters.filter(
(chp) =>
unreadFilter(options.unread, chp) &&
downloadFilter(options.downloaded, chp) &&
bookmarkedFilter(options.bookmarked, chp),
)
: [...chapters];
const Sorted = options.sortBy === 'fetchedAt'
? filtered.sort((a, b) => a.fetchedAt - b.fetchedAt)
: filtered;
const Sorted =
options.sortBy === 'fetchedAt'
? filtered.sort((a, b) => a.fetchedAt - b.fetchedAt)
: filtered;
if (options.reverse) {
Sorted.reverse();
}
return Sorted;
}
export const useChapterOptions = (mangaId: string) => useReducerLocalStorage<
ChapterListOptions,
ChapterOptionsReducerAction
>(
chapterOptionsReducer,
`${mangaId}filterOptions`, defaultChapterOptions,
);
export const useChapterOptions = (mangaId: string) =>
useReducerLocalStorage<ChapterListOptions, ChapterOptionsReducerAction>(
chapterOptionsReducer,
`${mangaId}filterOptions`,
defaultChapterOptions,
);
export const SORT_OPTIONS: [ChapterSortMode, string][] = [
['source', 'By Source'],