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

@@ -11,16 +11,12 @@ import DeleteIcon from '@mui/icons-material/Delete';
import DragHandle from '@mui/icons-material/DragHandle';
import PauseIcon from '@mui/icons-material/Pause';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import {
Card, CardActionArea, Stack,
} from '@mui/material';
import { Card, CardActionArea, Stack } from '@mui/material';
import IconButton from '@mui/material/IconButton';
import NavbarContext from 'components/context/NavbarContext';
import EmptyView from 'components/util/EmptyView';
import React, { useContext, useEffect } from 'react';
import {
DragDropContext, Draggable, Droppable, DropResult,
} from 'react-beautiful-dnd';
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
import client from 'util/client';
import Typography from '@mui/material/Typography';
@@ -56,8 +52,7 @@ const DownloadQueue: React.FC = () => {
}, []);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onDragEnd = (result: DropResult) => {
};
const onDragEnd = (result: DropResult) => {};
if (queue.length === 0) {
return <EmptyView message="No downloads" />;
@@ -65,14 +60,15 @@ const DownloadQueue: React.FC = () => {
const handleDelete = (chapter: IChapter) => {
// required to stop before deleting otherwise the download kept going. Server issue?
client.get('/api/v1/downloads/stop')
.then(() => Promise.all([
client.get('/api/v1/downloads/stop').then(() =>
Promise.all([
// remove from download queue
client.delete(`/api/v1/download/${chapter.mangaId}/chapter/${chapter.index}`),
// delete partial download, should be handle server side?
// bug: The folder and the last image downloaded are not deleted
client.delete(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`),
]));
]),
);
};
return (
@@ -101,22 +97,38 @@ const DownloadQueue: React.FC = () => {
>
<Card
sx={{
backgroundColor: snapshot.isDragging ? 'custom.light' : undefined,
backgroundColor: snapshot.isDragging
? 'custom.light'
: undefined,
}}
>
<CardActionArea
component={Link}
to={{ pathname: `/manga/${item.chapter.mangaId}`, state: { backLink: BACK } }}
sx={{ display: 'flex', alignItems: 'center', p: 1 }}
to={{
pathname: `/manga/${item.chapter.mangaId}`,
state: { backLink: BACK },
}}
sx={{
display: 'flex',
alignItems: 'center',
p: 1,
}}
>
<IconButton sx={{ pointerEvents: 'none' }}>
<DragHandle />
</IconButton>
<Stack sx={{ flex: 1, ml: 1 }} direction="column">
<Stack
sx={{ flex: 1, ml: 1 }}
direction="column"
>
<Typography variant="h6">
{item.manga.title}
</Typography>
<Typography variant="caption" display="block" gutterBottom>
<Typography
variant="caption"
display="block"
gutterBottom
>
{item.chapter.name}
</Typography>
</Stack>

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 React, {
useContext, useEffect, useState, useMemo, useRef,
} from 'react';
import React, { useContext, useEffect, useState, useMemo, useRef } from 'react';
import { fromEvent } from 'file-selector';
import IconButton from '@mui/material/IconButton';
import AddIcon from '@mui/icons-material/Add';
@@ -30,7 +28,7 @@ const EXTENSIONS = 1;
const allLangs: string[] = [];
interface GroupedExtension {
[key: string]: IExtension[]
[key: string]: IExtension[];
}
function groupExtensions(extensions: IExtension[]) {
@@ -39,7 +37,9 @@ function groupExtensions(extensions: IExtension[]) {
extensions.forEach((extension) => {
if (sortedExtenions[extension.lang] === undefined) {
sortedExtenions[extension.lang] = [];
if (extension.lang !== 'all') { allLangs.push(extension.lang); }
if (extension.lang !== 'all') {
allLangs.push(extension.lang);
}
}
if (extension.installed) {
if (extension.hasUpdate) {
@@ -67,7 +67,10 @@ function groupExtensions(extensions: IExtension[]) {
export default function MangaExtensions() {
const inputRef = useRef<HTMLInputElement>(null);
const { setTitle, setAction } = useContext(NavbarContext);
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
const [shownLangs, setShownLangs] = useLocalStorage<string[]>(
'shownExtensionLangs',
extensionDefaultLangs(),
);
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
@@ -78,10 +81,7 @@ export default function MangaExtensions() {
setAction(
<>
<AppbarSearch />
<IconButton
onClick={() => inputRef.current?.click()}
size="large"
>
<IconButton onClick={() => inputRef.current?.click()} size="large">
<AddIcon />
</IconButton>
<LangSelect
@@ -93,17 +93,33 @@ export default function MangaExtensions() {
);
}, [shownLangs]);
const { data: allExtensions, mutate, loading } = useQuery<IExtension[]>('/api/v1/extension/list');
const {
data: allExtensions,
mutate,
loading,
} = useQuery<IExtension[]>('/api/v1/extension/list');
const filteredExtensions = useMemo(() => (allExtensions ?? []).filter((ext) => {
const nsfwFilter = showNsfw || !ext.isNsfw;
if (!query) return nsfwFilter;
return nsfwFilter && ext.name.toLowerCase().includes(query.toLowerCase());
}), [allExtensions, showNsfw, query]);
const filteredExtensions = useMemo(
() =>
(allExtensions ?? []).filter((ext) => {
const nsfwFilter = showNsfw || !ext.isNsfw;
if (!query) return nsfwFilter;
return nsfwFilter && ext.name.toLowerCase().includes(query.toLowerCase());
}),
[allExtensions, showNsfw, query],
);
const groupedExtensions = useMemo(() => groupExtensions(filteredExtensions)
.filter((group) => group[EXTENSIONS].length > 0)
.filter((group) => ['installed', 'updates pending', 'all', ...shownLangs].includes(group[LANGUAGE])), [shownLangs, filteredExtensions]);
const groupedExtensions = useMemo(
() =>
groupExtensions(filteredExtensions)
.filter((group) => group[EXTENSIONS].length > 0)
.filter((group) =>
['installed', 'updates pending', 'all', ...shownLangs].includes(
group[LANGUAGE],
),
),
[shownLangs, filteredExtensions],
);
const flatRenderItems: (IExtension | string)[] = groupedExtensions.flat(2);
@@ -119,8 +135,10 @@ export default function MangaExtensions() {
}
makeToast('Installing Extension File....', 'info');
client.post('/api/v1/extension/install',
formData, { headers: { 'Content-Type': 'multipart/form-data' } })
client
.post('/api/v1/extension/install', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(() => {
makeToast('Installed extension successfully!', 'success');
mutate();
@@ -175,7 +193,7 @@ export default function MangaExtensions() {
}}
totalCount={flatRenderItems.length}
itemContent={(index) => {
if (typeof (flatRenderItems[index]) === 'string') {
if (typeof flatRenderItems[index] === 'string') {
const item = flatRenderItems[index] as string;
return (
<Typography

View File

@@ -6,9 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { Tab, Tabs } from '@mui/material';
import React, {
useContext, useEffect, useState,
} from 'react';
import React, { useContext, useEffect, useState } from 'react';
import NavbarContext from 'components/context/NavbarContext';
import EmptyView from 'components/util/EmptyView';
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
@@ -62,7 +60,12 @@ export default function Library() {
};
if (tabsError != null) {
return <EmptyView message="Could not load categories" messageExtra={tabsError?.message ?? tabsError} />;
return (
<EmptyView
message="Could not load categories"
messageExtra={tabsError?.message ?? tabsError}
/>
);
}
if (loading) {
@@ -109,11 +112,13 @@ export default function Library() {
</Tabs>
{tabs.map((tab) => (
<TabPanel key={tab.order} index={tab.order} currentIndex={activeTab.order}>
{tab === activeTab && (mangaError
? (
<EmptyView message="Could not load manga" messageExtra={mangaError?.message ?? mangaError} />
)
: (
{tab === activeTab &&
(mangaError ? (
<EmptyView
message="Could not load manga"
messageExtra={mangaError?.message ?? mangaError}
/>
) : (
<LibraryMangaGrid
mangas={mangas}
lastLibraryUpdate={lastLibraryUpdate}

View File

@@ -6,9 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { Warning } from '@mui/icons-material';
import {
CircularProgress, IconButton, Stack, Tooltip,
} from '@mui/material';
import { CircularProgress, IconButton, Stack, Tooltip } from '@mui/material';
import { Box } from '@mui/system';
import NavbarContext, { useSetDefaultBackTo } from 'components/context/NavbarContext';
import ChapterList from 'components/manga/ChapterList';
@@ -18,9 +16,7 @@ import MangaToolbarMenu from 'components/manga/MangaToolbarMenu';
import { NavbarToolbar } from 'components/navbar/DefaultNavBar';
import EmptyView from 'components/util/EmptyView';
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
import React, {
useContext, useEffect, useRef,
} from 'react';
import React, { useContext, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
import { useQuery } from 'util/client';
@@ -32,12 +28,20 @@ const Manga: React.FC = () => {
const autofetchedRef = useRef(false);
const {
data: manga, error, loading, isValidating, mutate,
data: manga,
error,
loading,
isValidating,
mutate,
} = useQuery<IManga>(`/api/v1/manga/${id}/?onlineFetch=false`);
const [refresh, { loading: refreshing }] = useRefreshManga(id);
useSetDefaultBackTo(manga?.inLibrary === false && manga.sourceId != null ? `/sources/${manga.sourceId}/popular` : '/library');
useSetDefaultBackTo(
manga?.inLibrary === false && manga.sourceId != null
? `/sources/${manga.sourceId}/popular`
: '/library',
);
useEffect(() => {
// Automatically fetch manga from source if data is older then 24 hours
@@ -45,9 +49,9 @@ const Manga: React.FC = () => {
// not update age for some reason (ie. error on source side)
if (manga == null) return;
if (
manga.inLibrary
&& (manga.age > AUTOFETCH_AGE || manga.chaptersAge > AUTOFETCH_AGE)
&& autofetchedRef.current === false
manga.inLibrary &&
(manga.age > AUTOFETCH_AGE || manga.chaptersAge > AUTOFETCH_AGE) &&
autofetchedRef.current === false
) {
autofetchedRef.current = true;
refresh();
@@ -59,29 +63,28 @@ const Manga: React.FC = () => {
}, [manga?.title]);
if (error && !manga) {
return (
<EmptyView message="Could not load manga" messageExtra={error.message ?? error} />
);
return <EmptyView message="Could not load manga" messageExtra={error.message ?? error} />;
}
return (
<Box sx={{ display: { md: 'flex' }, overflow: 'hidden' }}>
<NavbarToolbar>
<Stack direction="row" alignItems="center">
{error && !isValidating && !refreshing && (
<Tooltip title={(
<>
Could not fetch manga data
<br />
{error.message ?? error}
</>
)}
<Tooltip
title={
<>
Could not fetch manga data
<br />
{error.message ?? error}
</>
}
>
<IconButton onClick={() => mutate()}>
<Warning color="error" />
</IconButton>
</Tooltip>
)}
{(manga && (refreshing || isValidating)) && (
{manga && (refreshing || isValidating) && (
<IconButton disabled>
<CircularProgress size={16} />
</IconButton>

View File

@@ -6,9 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import CircularProgress from '@mui/material/CircularProgress';
import React, {
useCallback, useContext, useEffect, useState,
} from 'react';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import HorizontalPager from 'components/reader/pager/HorizontalPager';
import PageNumber from 'components/reader/PageNumber';
@@ -53,7 +51,7 @@ const getReaderComponent = (readerType: ReaderType) => {
}
};
const range = (n:number) => Array.from({ length: n }, (value, key) => key);
const range = (n: number) => Array.from({ length: n }, (value, key) => key);
const initialChapter = () => ({
pageCount: -1,
index: -1,
@@ -67,22 +65,26 @@ export default function Reader() {
const [serverAddress] = useLocalStorage<String>('serverBaseURL', '');
const { chapterIndex, mangaId } = useParams<{ chapterIndex: string, mangaId: string }>();
const [manga, setManga] = useState<IMangaCard | IManga>({ id: +mangaId, title: '', thumbnailUrl: '' });
const { chapterIndex, mangaId } = useParams<{ chapterIndex: string; mangaId: string }>();
const [manga, setManga] = useState<IMangaCard | IManga>({
id: +mangaId,
title: '',
thumbnailUrl: '',
});
const [chapter, setChapter] = useState<IChapter | IPartialChapter>(initialChapter());
const [curPage, setCurPage] = useState<number>(0);
const { setOverride, setTitle } = useContext(NavbarContext);
const {
settings: defaultSettings,
loading: areDefaultSettingsLoading,
} = useDefaultReaderSettings();
const { settings: defaultSettings, loading: areDefaultSettingsLoading } =
useDefaultReaderSettings();
const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings));
const [isMangaLoading, setIsMangaLoading] = useState(true);
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
setSettings({ ...settings, [key]: value });
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() => makeToast('Failed to save the reader settings to the server', 'warning'));
requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
makeToast('Failed to save the reader settings to the server', 'warning'),
);
};
useEffect(() => {
@@ -95,27 +97,27 @@ export default function Reader() {
useEffect(() => {
if (!areDefaultSettingsLoading && !isMangaLoading) {
checkAndHandleMissingStoredReaderSettings(manga, 'manga', defaultSettings).catch(() => {});
checkAndHandleMissingStoredReaderSettings(manga, 'manga', defaultSettings).catch(
() => {},
);
setSettings(getReaderSettingsFor(manga, defaultSettings));
}
}, [areDefaultSettingsLoading, isMangaLoading]);
useEffect(() => {
// set the custom navbar
setOverride(
{
status: true,
value: (
<ReaderNavBar
settings={settings}
setSettingValue={setSettingValue}
manga={manga}
chapter={chapter as IChapter}
curPage={curPage}
/>
),
},
);
setOverride({
status: true,
value: (
<ReaderNavBar
settings={settings}
setSettingValue={setSettingValue}
manga={manga}
chapter={chapter as IChapter}
curPage={curPage}
/>
),
});
// clean up for when we leave the reader
return () => setOverride({ status: false, value: <div /> });
@@ -123,7 +125,8 @@ export default function Reader() {
useEffect(() => {
setIsMangaLoading(true);
client.get(`/api/v1/manga/${mangaId}/`)
client
.get(`/api/v1/manga/${mangaId}/`)
.then((response) => response.data)
.then((data: IManga) => {
setManga(data);
@@ -133,9 +136,10 @@ export default function Reader() {
useEffect(() => {
setChapter(initialChapter);
client.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`)
client
.get(`/api/v1/manga/${mangaId}/chapter/${chapterIndex}`)
.then((response) => response.data)
.then((data:IChapter) => {
.then((data: IChapter) => {
setChapter(data);
if (data.lastPageRead === data.pageCount - 1) {
@@ -166,22 +170,32 @@ export default function Reader() {
formData.append('read', 'true');
client.patch(`/api/v1/manga/${manga.id}/chapter/${chapter.index}`, formData);
history.replace({ pathname: `/manga/${manga.id}/chapter/${chapter.index + 1}`, state: history.location.state });
history.replace({
pathname: `/manga/${manga.id}/chapter/${chapter.index + 1}`,
state: history.location.state,
});
}
}, [chapter.index, chapter.chapterCount, chapter.pageCount, manga.id]);
const prevChapter = useCallback(() => {
if (chapter.index > 1) {
history.replace({ pathname: `/manga/${manga.id}/chapter/${chapter.index - 1}`, state: history.location.state });
history.replace({
pathname: `/manga/${manga.id}/chapter/${chapter.index - 1}`,
state: history.location.state,
});
}
}, [chapter.index, manga.id]);
// return spinner while chpater data is loading
if (chapter.pageCount === -1) {
return (
<Box sx={{
height: '100vh', width: '100vw', display: 'grid', placeItems: 'center',
}}
<Box
sx={{
height: '100vh',
width: '100vw',
display: 'grid',
placeItems: 'center',
}}
>
<CircularProgress thickness={5} />
</Box>
@@ -196,15 +210,11 @@ export default function Reader() {
const ReaderComponent = getReaderComponent(settings.readerType);
// last page, also probably read = true, we will load the first page.
const initialPage = (chapter.lastPageRead === chapter.pageCount - 1) ? 0 : chapter.lastPageRead;
const initialPage = chapter.lastPageRead === chapter.pageCount - 1 ? 0 : chapter.lastPageRead;
return (
<Box sx={{ width: settings.staticNav ? 'calc(100vw - 300px)' : '100vw' }}>
<PageNumber
settings={settings}
curPage={curPage}
pageCount={chapter.pageCount}
/>
<PageNumber settings={settings} curPage={curPage} pageCount={chapter.pageCount} />
<ReaderComponent
pages={pages}
pageCount={chapter.pageCount}

View File

@@ -1,10 +1,10 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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/.
*/
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 { Card, CardActionArea, Typography } from '@mui/material';
import NavbarContext from 'components/context/NavbarContext';
@@ -17,7 +17,10 @@ import { Link } from 'react-router-dom';
import { StringParam, useQueryParam } from 'use-query-params';
import client from 'util/client';
import {
langCodeToName, langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs,
langCodeToName,
langSortCmp,
sourceDefualtLangs,
sourceForcedDefaultLangs,
} from 'util/language';
import useLocalStorage from 'util/useLocalStorage';
@@ -25,7 +28,9 @@ function sourceToLangList(sources: ISource[]) {
const result: string[] = [];
sources.forEach((source) => {
if (result.indexOf(source.lang) === -1) { result.push(source.lang); }
if (result.indexOf(source.lang) === -1) {
result.push(source.lang);
}
});
result.sort(langSortCmp);
@@ -38,7 +43,10 @@ const SearchAll: React.FC = () => {
const [triggerUpdate, setTriggerUpdate] = useState<number>(2);
const [mangas, setMangas] = useState<any>({});
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
const [shownLangs, setShownLangs] = useLocalStorage<string[]>(
'shownSourceLangs',
sourceDefualtLangs(),
);
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
const [sources, setSources] = useState<ISource[]>([]);
@@ -56,35 +64,46 @@ const SearchAll: React.FC = () => {
setAction(
<>
<AppbarSearch />
</>
,
</>,
);
}, []);
useEffect(() => {
client.get('/api/v1/source/list')
client
.get('/api/v1/source/list')
.then((response) => response.data)
.then((data) => {
setSources(data.sort((a: { displayName: string; }, b: { displayName: string; }) => {
if (a.displayName < b.displayName) { return -1; }
if (a.displayName > b.displayName) { return 1; }
return 0;
})); setFetchedSources(true);
setSources(
data.sort((a: { displayName: string }, b: { displayName: string }) => {
if (a.displayName < b.displayName) {
return -1;
}
if (a.displayName > b.displayName) {
return 1;
}
return 0;
}),
);
setFetchedSources(true);
});
}, []);
async function doIT(elem: any[]) {
elem.map((ele) => limit.add(async () => {
const response = await client.get(`/api/v1/source/${ele.id}/search?searchTerm=${query || ''}&pageNum=1`);
const data = await response.data;
const tmp = mangas;
tmp[ele.id] = data.mangaList;
setMangas(tmp);
const tmp2 = fetched;
tmp2[ele.id] = true;
setFetched(tmp2);
setResetUI(1);
}));
elem.map((ele) =>
limit.add(async () => {
const response = await client.get(
`/api/v1/source/${ele.id}/search?searchTerm=${query || ''}&pageNum=1`,
);
const data = await response.data;
const tmp = mangas;
tmp[ele.id] = data.mangaList;
setMangas(tmp);
const tmp2 = fetched;
tmp2[ele.id] = true;
setFetched(tmp2);
setResetUI(1);
}),
);
}
useEffect(() => {
@@ -98,7 +117,11 @@ const SearchAll: React.FC = () => {
setFetched({});
setMangas({});
// eslint-disable-next-line max-len
doIT(sources.filter(({ lang }) => shownLangs.indexOf(lang) !== -1).filter((source) => showNsfw || !source.isNsfw));
doIT(
sources
.filter(({ lang }) => shownLangs.indexOf(lang) !== -1)
.filter((source) => showNsfw || !source.isNsfw),
);
}, [triggerUpdate]);
useEffect(() => {
@@ -137,9 +160,7 @@ const SearchAll: React.FC = () => {
setTitle('Sources');
setAction(
<>
<AppbarSearch
autoOpen
/>
<AppbarSearch autoOpen />
<LangSelect
shownLangs={shownLangs}
setShownLangs={setShownLangs}
@@ -154,20 +175,33 @@ const SearchAll: React.FC = () => {
return (
<>
{/* eslint-disable-next-line max-len */}
{sources.filter(({ lang }) => shownLangs.indexOf(lang) !== -1).filter((source) => showNsfw || !source.isNsfw).sort((a, b) => {
const af = fetched[a.id];
const bf = fetched[b.id];
if (af && !bf) { return -1; }
if (!af && bf) { return 1; }
if (!af && !bf) { return 0; }
{sources
.filter(({ lang }) => shownLangs.indexOf(lang) !== -1)
.filter((source) => showNsfw || !source.isNsfw)
.sort((a, b) => {
const af = fetched[a.id];
const bf = fetched[b.id];
if (af && !bf) {
return -1;
}
if (!af && bf) {
return 1;
}
if (!af && !bf) {
return 0;
}
const al = mangas[a.id].length === 0;
const bl = mangas[b.id].length === 0;
if (al && !bl) { return 1; }
if (bl && !al) { return -1; }
return 0;
}).map(({ lang, id, displayName }) => (
(
const al = mangas[a.id].length === 0;
const bl = mangas[b.id].length === 0;
if (al && !bl) {
return 1;
}
if (bl && !al) {
return -1;
}
return 0;
})
.map(({ lang, id, displayName }) => (
<>
<Card sx={{ margin: '10px' }}>
<CardActionArea
@@ -175,9 +209,7 @@ const SearchAll: React.FC = () => {
to={`/sources/${id}/popular/?R&query=${query}`}
sx={{ p: 3 }}
>
<Typography variant="h5">
{displayName}
</Typography>
<Typography variant="h5">{displayName}</Typography>
<Typography variant="caption">
{langCodeToName(lang)}
</Typography>
@@ -195,13 +227,11 @@ const SearchAll: React.FC = () => {
inLibraryIndicator
/>
</>
)
))}
))}
</>
);
}
return (<></>);
return <></>;
};
export default SearchAll;

View File

@@ -38,7 +38,10 @@ import ListItemLink from 'components/util/ListItemLink';
export default function Settings() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('Settings'); setAction(<></>); }, []);
useEffect(() => {
setTitle('Settings');
setAction(<></>);
}, []);
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
const [serverAddress, setServerAddress] = useLocalStorage<String>('serverBaseURL', '');
@@ -193,9 +196,7 @@ export default function Settings() {
<Dialog open={dialogOpen} onClose={handleDialogCancel}>
<DialogContent>
<DialogContentText>
Enter Server Address
</DialogContentText>
<DialogContentText>Enter Server Address</DialogContentText>
<TextField
autoFocus
margin="dense"
@@ -219,9 +220,7 @@ export default function Settings() {
</Dialog>
<Dialog open={dialogOpenItemWidth} onClose={handleDialogCancelItemWidth}>
<DialogTitle>
Manga Item width
</DialogTitle>
<DialogTitle>Manga Item width</DialogTitle>
<DialogContent
sx={{
width: '98%',

View File

@@ -9,7 +9,10 @@ import React, { useContext, useEffect } from 'react';
import NavbarContext from 'components/context/NavbarContext';
import { useParams } from 'react-router-dom';
import client, { useQuery } from 'util/client';
import { SwitchPreferenceCompat, CheckBoxPreference } from 'components/sourceConfiguration/TwoStatePreference';
import {
SwitchPreferenceCompat,
CheckBoxPreference,
} from 'components/sourceConfiguration/TwoStatePreference';
import ListPreference from 'components/sourceConfiguration/ListPreference';
import EditTextPreference from 'components/sourceConfiguration/EditTextPreference';
import MultiSelectListPreference from 'components/sourceConfiguration/MultiSelectListPreference';
@@ -36,10 +39,15 @@ function getPrefComponent(type: string) {
export default function SourceConfigure() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('Source Configuration'); setAction(<></>); }, []);
useEffect(() => {
setTitle('Source Configuration');
setAction(<></>);
}, []);
const { sourceId } = useParams<{ sourceId: string }>();
const { data: sourcePreferences = [], mutate } = useQuery<SourcePreferences[]>(`/api/v1/source/${sourceId}/preferences`);
const { data: sourcePreferences = [], mutate } = useQuery<SourcePreferences[]>(
`/api/v1/source/${sourceId}/preferences`,
);
const convertToString = (position: number, value: any): string => {
switch (sourcePreferences[position].props.defaultValueType) {
@@ -50,28 +58,27 @@ export default function SourceConfigure() {
}
};
const updateValue = (position: number) => (
(value: any) => {
client.post(`/api/v1/source/${sourceId}/preferences`,
JSON.stringify({ position, value: convertToString(position, value) }))
.then(() => mutate());
}
);
const updateValue = (position: number) => (value: any) => {
client
.post(
`/api/v1/source/${sourceId}/preferences`,
JSON.stringify({ position, value: convertToString(position, value) }),
)
.then(() => mutate());
};
return (
<>
<List sx={{ padding: 0 }}>
{sourcePreferences.map(
(it, index) => {
const props = cloneObject(it.props);
props.updateValue = updateValue(index);
props.key = index;
{sourcePreferences.map((it, index) => {
const props = cloneObject(it.props);
props.updateValue = updateValue(index);
props.key = index;
// TypeScript is dumb in detecting extra props
// @ts-ignore
return React.createElement(getPrefComponent(it.type), props);
},
)}
// TypeScript is dumb in detecting extra props
// @ts-ignore
return React.createElement(getPrefComponent(it.type), props);
})}
</List>
</>
);

View File

@@ -19,9 +19,9 @@ import SourceGridLayout from 'components/source/GridLayouts';
import { useLibraryOptionsContext } from 'components/context/LibraryOptionsContext';
interface IPos {
position: number
state: any
group?: number
position: number;
state: any;
group?: number;
}
export default function SourceMangas(props: { popular: boolean }) {
@@ -48,7 +48,8 @@ export default function SourceMangas(props: { popular: boolean }) {
const { options } = useLibraryOptionsContext();
function makeFilters() {
client.get(`/api/v1/source/${sourceId}/filters`)
client
.get(`/api/v1/source/${sourceId}/filters`)
.then((response) => response.data)
.then((data: ISourceFilters[]) => {
SetData(data);
@@ -60,7 +61,8 @@ export default function SourceMangas(props: { popular: boolean }) {
}, []);
useEffect(() => {
client.get(`/api/v1/source/${sourceId}`)
client
.get(`/api/v1/source/${sourceId}`)
.then((response) => response.data)
.then((data: ISource) => {
setTitle(data.displayName);
@@ -79,20 +81,25 @@ export default function SourceMangas(props: { popular: boolean }) {
if (update.length > 0) {
const rep = update;
setUpdate([]);
client.post(`/api/v1/source/${sourceId}/filters`,
rep.map((e: IPos) => {
const { position, state, group }: IPos = e;
return group === undefined ? {
position,
state,
} : {
position: group,
state: JSON.stringify({
position,
state,
}),
};
}))
client
.post(
`/api/v1/source/${sourceId}/filters`,
rep.map((e: IPos) => {
const { position, state, group }: IPos = e;
return group === undefined
? {
position,
state,
}
: {
position: group,
state: JSON.stringify({
position,
state,
}),
};
}),
)
.then(() => {
setTriggerUpdate(0);
makeFilters();
@@ -101,7 +108,9 @@ export default function SourceMangas(props: { popular: boolean }) {
setFetched(false);
setMangas([]);
setLastPageNum(0);
if (Noreset === undefined && Search) { setNoreset(null); }
if (Noreset === undefined && Search) {
setNoreset(null);
}
}
}, [triggerUpdate]);
@@ -111,14 +120,13 @@ export default function SourceMangas(props: { popular: boolean }) {
setNoreset(undefined);
setReset(1);
} else if (Noreset === undefined) {
client.get(`/api/v1/source/${sourceId}/filters?reset=true`)
.then(() => {
makeFilters();
setSearch(false);
if (reset === 1) {
setTriggerUpdate(0);
}
});
client.get(`/api/v1/source/${sourceId}/filters?reset=true`).then(() => {
makeFilters();
setSearch(false);
if (reset === 1) {
setTriggerUpdate(0);
}
});
return;
}
makeFilters();
@@ -140,8 +148,7 @@ export default function SourceMangas(props: { popular: boolean }) {
<SettingsIcon />
</IconButton>
)}
</>
,
</>,
);
return () => {
@@ -152,8 +159,12 @@ export default function SourceMangas(props: { popular: boolean }) {
useEffect(() => {
if (query) {
setSearch(true);
} else { setSearch(false); }
if (Noreset === undefined) { setInit(null); }
} else {
setSearch(false);
}
if (Noreset === undefined) {
setInit(null);
}
}, [query]);
useEffect(() => {
@@ -163,16 +174,27 @@ export default function SourceMangas(props: { popular: boolean }) {
}, 1000);
return () => clearTimeout(delayDebounceFn);
}
if (Search !== undefined) { setInit(null); }
if (Search !== undefined) {
setInit(null);
}
return () => {};
}, [Search, query]);
useEffect(() => {
if (lastPageNum !== 0) {
const sourceType = props.popular ? 'popular' : 'latest';
client.get(`/api/v1/source/${sourceId}/${query !== undefined || Search || Noreset === null ? 'search' : sourceType}${query !== undefined || Search || Noreset === null ? `?searchTerm=${query || ''}&pageNum=${lastPageNum}` : `/${lastPageNum}`}`)
client
.get(
`/api/v1/source/${sourceId}/${
query !== undefined || Search || Noreset === null ? 'search' : sourceType
}${
query !== undefined || Search || Noreset === null
? `?searchTerm=${query || ''}&pageNum=${lastPageNum}`
: `/${lastPageNum}`
}`,
)
.then((response) => response.data)
.then((data: { mangaList: IManga[], hasNextPage: boolean }) => {
.then((data: { mangaList: IManga[]; hasNextPage: boolean }) => {
setMangas([
...mangas,
...data.mangaList.map((it) => ({
@@ -180,11 +202,14 @@ export default function SourceMangas(props: { popular: boolean }) {
thumbnailUrl: it.thumbnailUrl,
id: it.id,
inLibrary: it.inLibrary,
}))]);
})),
]);
setHasNextPage(data.hasNextPage);
setFetched(true);
});
} else { setLastPageNum(1); }
} else {
setLastPageNum(1);
}
}, [lastPageNum]);
let message;
@@ -196,7 +221,9 @@ export default function SourceMangas(props: { popular: boolean }) {
messageExtra = (
<>
<span>Check out </span>
<a href="https://github.com/Suwayomi/Tachidesk-Server/wiki/Local-Source">Local source guide</a>
<a href="https://github.com/Suwayomi/Tachidesk-Server/wiki/Local-Source">
Local source guide
</a>
</>
);
}

View File

@@ -10,7 +10,10 @@ import LangSelect from 'components/navbar/action/LangSelect';
import SourceCard from 'components/SourceCard';
import NavbarContext from 'components/context/NavbarContext';
import {
sourceDefualtLangs, sourceForcedDefaultLangs, langCodeToName, langSortCmp,
sourceDefualtLangs,
sourceForcedDefaultLangs,
langCodeToName,
langSortCmp,
} from 'util/language';
import useLocalStorage from 'util/useLocalStorage';
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
@@ -23,7 +26,9 @@ function sourceToLangList(sources: ISource[]) {
const result: string[] = [];
sources.forEach((source) => {
if (result.indexOf(source.lang) === -1) { result.push(source.lang); }
if (result.indexOf(source.lang) === -1) {
result.push(source.lang);
}
});
result.sort(langSortCmp);
@@ -33,7 +38,9 @@ function sourceToLangList(sources: ISource[]) {
function groupByLang(sources: ISource[]) {
const result = {} as any;
sources.forEach((source) => {
if (result[source.lang] === undefined) { result[source.lang] = [] as ISource[]; }
if (result[source.lang] === undefined) {
result[source.lang] = [] as ISource[];
}
result[source.lang].push(source);
});
@@ -43,7 +50,10 @@ function groupByLang(sources: ISource[]) {
export default function Sources() {
const { setTitle, setAction } = useContext(NavbarContext);
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
const [shownLangs, setShownLangs] = useLocalStorage<string[]>(
'shownSourceLangs',
sourceDefualtLangs(),
);
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
const { data: sources, loading } = useQuery<ISource[]>('/api/v1/source/list');
@@ -70,10 +80,7 @@ export default function Sources() {
setTitle('Sources');
setAction(
<>
<IconButton
onClick={() => history.push('/sources/all/search/')}
size="large"
>
<IconButton onClick={() => history.push('/sources/all/search/')} size="large">
<TravelExploreIcon />
</IconButton>
<LangSelect
@@ -89,27 +96,29 @@ export default function Sources() {
if (loading) return <LoadingPlaceholder />;
if (sources?.length === 0) {
return (<h3>No sources found. Install Some Extensions first.</h3>);
return <h3>No sources found. Install Some Extensions first.</h3>;
}
return (
<>
{/* eslint-disable-next-line max-len */}
{Object.entries(groupByLang(sources ?? [])).sort((a, b) => langSortCmp(a[0], b[0])).map(([lang, list]) => (
shownLangs.indexOf(lang) !== -1 && (
<React.Fragment key={lang}>
<h1 key={lang} style={{ marginLeft: 25 }}>{langCodeToName(lang)}</h1>
{(list as ISource[])
.filter((source) => showNsfw || !source.isNsfw)
.map((source) => (
<SourceCard
key={source.id}
source={source}
/>
))}
</React.Fragment>
)
))}
{Object.entries(groupByLang(sources ?? []))
.sort((a, b) => langSortCmp(a[0], b[0]))
.map(
([lang, list]) =>
shownLangs.indexOf(lang) !== -1 && (
<React.Fragment key={lang}>
<h1 key={lang} style={{ marginLeft: 25 }}>
{langCodeToName(lang)}
</h1>
{(list as ISource[])
.filter((source) => showNsfw || !source.isNsfw)
.map((source) => (
<SourceCard key={source.id} source={source} />
))}
</React.Fragment>
),
)}
</>
);
}

View File

@@ -17,9 +17,7 @@ import NavbarContext from 'components/context/NavbarContext';
import DownloadStateIndicator from 'components/molecules/DownloadStateIndicator';
import EmptyView from 'components/util/EmptyView';
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
import React, {
useContext, useEffect, useRef, useState,
} from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import client from 'util/client';
import useLocalStorage from 'util/useLocalStorage';
@@ -30,10 +28,12 @@ function epochToDate(epoch: number) {
return date;
}
function isTheSameDay(first:Date, second:Date) {
return first.getDate() === second.getDate()
&& first.getMonth() === second.getMonth()
&& first.getFullYear() === second.getFullYear();
function isTheSameDay(first: Date, second: Date) {
return (
first.getDate() === second.getDate() &&
first.getMonth() === second.getMonth() &&
first.getFullYear() === second.getFullYear()
);
}
function getDateString(date: Date) {
@@ -46,8 +46,9 @@ function getDateString(date: Date) {
return date.toLocaleDateString();
}
function groupByDate(updates: IMangaChapter[]):
[string, { item: IMangaChapter, globalIdx: number }[] ][] {
function groupByDate(
updates: IMangaChapter[],
): [string, { item: IMangaChapter; globalIdx: number }[]][] {
if (updates.length === 0) return [];
const groups = {};
@@ -63,7 +64,10 @@ function groupByDate(updates: IMangaChapter[]):
return Object.keys(groups).map((key) => [key, groups[key]]);
}
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace(
'http',
'ws',
);
const initialQueue = {
status: 'Stopped',
queue: [],
@@ -104,13 +108,11 @@ const Updates: React.FC = () => {
useEffect(() => {
if (hasNextPage) {
client.get(`/api/v1/update/recentChapters/${lastPageNum}`)
client
.get(`/api/v1/update/recentChapters/${lastPageNum}`)
.then((response) => response.data)
.then(({ hasNextPage: fetchedHasNextPage, page }: PaginatedList<IMangaChapter>) => {
setUpdateEntries([
...updateEntries,
...page,
]);
setUpdateEntries([...updateEntries, ...page]);
setHasNextPage(fetchedHasNextPage);
setFetched(true);
});
@@ -122,7 +124,7 @@ const Updates: React.FC = () => {
const scrollHandler = () => {
if (lastEntry.current) {
const rect = lastEntry.current.getBoundingClientRect();
if (((rect.y + rect.height) / window.innerHeight < 2) && hasNextPage) {
if ((rect.y + rect.height) / window.innerHeight < 2 && hasNextPage) {
setLastPageNum(lastPageNum + 1);
}
}
@@ -134,8 +136,12 @@ const Updates: React.FC = () => {
};
}, [hasNextPage, updateEntries]);
if (!fetched) { return <LoadingPlaceholder />; }
if (fetched && updateEntries.length === 0) { return <EmptyView message="You don't have any updates yet." />; }
if (!fetched) {
return <LoadingPlaceholder />;
}
if (fetched && updateEntries.length === 0) {
return <EmptyView message="You don't have any updates yet." />;
}
const downloadForChapter = (chapter: IChapter) => {
const { index, mangaId } = chapter;
@@ -170,7 +176,10 @@ const Updates: React.FC = () => {
>
<CardActionArea
component={Link}
to={{ pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`, state: history.location.state }}
to={{
pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`,
state: history.location.state,
}}
>
<CardContent
sx={{
@@ -196,7 +205,11 @@ const Updates: React.FC = () => {
<Typography variant="h5" component="h2">
{manga.title}
</Typography>
<Typography variant="caption" display="block" gutterBottom>
<Typography
variant="caption"
display="block"
gutterBottom
>
{chapter.name}
</Typography>
</Box>

View File

@@ -10,7 +10,10 @@ import { useQuery } from 'util/client';
export default function About() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('About'); setAction(<></>); }, []);
useEffect(() => {
setTitle('About');
setAction(<></>);
}, []);
const { data: about } = useQuery<IAbout>('/api/v1/settings/about');

View File

@@ -17,7 +17,10 @@ import NavbarContext from 'components/context/NavbarContext';
export default function Backup() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('Backup'); setAction(<></>); }, []);
useEffect(() => {
setTitle('Backup');
setAction(<></>);
}, []);
const { baseURL } = client.defaults;
@@ -27,8 +30,10 @@ export default function Backup() {
formData.append('backup.proto.gz', file);
makeToast('Restoring backup....', 'info');
client.post('/api/v1/backup/import/file',
formData, { headers: { 'Content-Type': 'multipart/form-data' } })
client
.post('/api/v1/backup/import/file', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then(() => makeToast('Backup restore finished!', 'success'))
.catch(() => makeToast('Backup restore failed!', 'error'));
} else if (file.name.toLowerCase().endsWith('json')) {
@@ -81,12 +86,7 @@ export default function Backup() {
/>
</ListItem>
</List>
<input
type="file"
id="backup-file"
style={{ display: 'none' }}
/>
<input type="file" id="backup-file" style={{ display: 'none' }} />
</>
);
}

View File

@@ -7,18 +7,15 @@
* 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 React, {
useMemo, useState, useContext, useEffect,
} from 'react';
import React, { useMemo, useState, useContext, useEffect } from 'react';
import { List, ListItem, ListItemText, ListItemIcon, IconButton } from '@mui/material';
import {
List,
ListItem,
ListItemText,
ListItemIcon,
IconButton,
} from '@mui/material';
import {
DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle,
DragDropContext,
Droppable,
Draggable,
DropResult,
DraggingStyle,
NotDraggingStyle,
} from 'react-beautiful-dnd';
import DragHandleIcon from '@mui/icons-material/DragHandle';
import EditIcon from '@mui/icons-material/Edit';
@@ -37,8 +34,11 @@ import FormControlLabel from '@mui/material/FormControlLabel';
import NavbarContext from 'components/context/NavbarContext';
import client, { useQuery } from 'util/client';
const getItemStyle = (isDragging: boolean,
draggableStyle: DraggingStyle | NotDraggingStyle | undefined, palette: Palette) => ({
const getItemStyle = (
isDragging: boolean,
draggableStyle: DraggingStyle | NotDraggingStyle | undefined,
palette: Palette,
) => ({
// styles we need to apply on draggables
...draggableStyle,
@@ -49,11 +49,14 @@ const getItemStyle = (isDragging: boolean,
export default function Categories() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => { setTitle('Categories'); setAction(<></>); }, []);
useEffect(() => {
setTitle('Categories');
setAction(<></>);
}, []);
const { data, mutate } = useQuery<ICategory[]>('/api/v1/category/');
const categories = useMemo(() => {
const res = [...data ?? []];
const res = [...(data ?? [])];
if (res.length > 0 && res[0].name === 'Default') {
res.shift();
}
@@ -84,11 +87,7 @@ export default function Categories() {
return;
}
categoryReorder(
categories,
result.source.index,
result.destination.index,
);
categoryReorder(categories, result.source.index, result.destination.index);
};
const resetDialog = () => {
@@ -102,7 +101,7 @@ export default function Categories() {
setDialogOpen(true);
};
const handleEditDialogOpen = (index:number) => {
const handleEditDialogOpen = (index: number) => {
setDialogName(categories[index].name);
setDialogDefault(categories[index].default);
setCategoryToEdit(index);
@@ -121,19 +120,16 @@ export default function Categories() {
formData.append('default', dialogDefault.toString());
if (categoryToEdit === -1) {
client.post('/api/v1/category/', formData)
.finally(() => mutate());
client.post('/api/v1/category/', formData).finally(() => mutate());
} else {
const category = categories[categoryToEdit];
client.patch(`/api/v1/category/${category.id}`, formData)
.finally(() => mutate());
client.patch(`/api/v1/category/${category.id}`, formData).finally(() => mutate());
}
};
const deleteCategory = (index:number) => {
const deleteCategory = (index: number) => {
const category = categories[index];
client.delete(`/api/v1/category/${category.id}`)
.finally(() => mutate());
client.delete(`/api/v1/category/${category.id}`).finally(() => mutate());
};
return (
@@ -163,9 +159,7 @@ export default function Categories() {
<ListItemIcon>
<DragHandleIcon />
</ListItemIcon>
<ListItemText
primary={item.name}
/>
<ListItemText primary={item.name} />
<IconButton
onClick={() => {
handleEditDialogOpen(index);
@@ -219,13 +213,13 @@ export default function Categories() {
onChange={(e) => setDialogName(e.target.value)}
/>
<FormControlLabel
control={(
control={
<Checkbox
checked={dialogDefault}
onChange={(e) => setDialogDefault(e.target.checked)}
color="default"
/>
)}
}
label="Default category when adding new manga to library"
/>
</DialogContent>

View File

@@ -31,22 +31,31 @@ export default function DefaultReaderSettings() {
const { metadata, settings, loading } = useDefaultReaderSettings();
const setSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() => makeToast('Failed to save the default reader settings to the server', 'warning'));
requestUpdateServerMetadata(metadata ?? {}, [[key, value]]).catch(() =>
makeToast('Failed to save the default reader settings to the server', 'warning'),
);
};
if (loading) {
return (
<Box sx={{
height: '100vh', width: '100vw', display: 'grid', placeItems: 'center',
}}
<Box
sx={{
height: '100vh',
width: '100vw',
display: 'grid',
placeItems: 'center',
}}
>
<CircularProgress thickness={5} />
</Box>
);
}
checkAndHandleMissingStoredReaderSettings({ meta: metadata }, 'server', getDefaultSettings())
.catch(() => {});
checkAndHandleMissingStoredReaderSettings(
{ meta: metadata },
'server',
getDefaultSettings(),
).catch(() => {});
return (
<ReaderSettingsOptions