increase prettier line length to 120 (#233)
* [Prettier] Increase max line length to 120 100 is quite low, especially on modern monitors. The length is also reached quite quickly with 4 spaces per tab depending on the indentation depth * [Prettier] Increase max line length to 120 - Fix formatting
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 100,
|
"printWidth": 120,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,17 +22,7 @@ interface IProps {
|
|||||||
|
|
||||||
export default function ExtensionCard(props: IProps) {
|
export default function ExtensionCard(props: IProps) {
|
||||||
const {
|
const {
|
||||||
extension: {
|
extension: { name, lang, versionName, installed, hasUpdate, obsolete, pkgName, iconUrl, isNsfw },
|
||||||
name,
|
|
||||||
lang,
|
|
||||||
versionName,
|
|
||||||
installed,
|
|
||||||
hasUpdate,
|
|
||||||
obsolete,
|
|
||||||
pkgName,
|
|
||||||
iconUrl,
|
|
||||||
isNsfw,
|
|
||||||
},
|
|
||||||
notifyInstall,
|
notifyInstall,
|
||||||
} = props;
|
} = props;
|
||||||
const [installedState, setInstalledState] = useState<string>(() => {
|
const [installedState, setInstalledState] = useState<string>(() => {
|
||||||
@@ -123,12 +113,7 @@ export default function ExtensionCard(props: IProps) {
|
|||||||
<Typography variant="caption" display="block" gutterBottom>
|
<Typography variant="caption" display="block" gutterBottom>
|
||||||
{langPress} {versionName}
|
{langPress} {versionName}
|
||||||
{isNsfw && (
|
{isNsfw && (
|
||||||
<Typography
|
<Typography variant="caption" display="inline" gutterBottom color="red">
|
||||||
variant="caption"
|
|
||||||
display="inline"
|
|
||||||
gutterBottom
|
|
||||||
color="red"
|
|
||||||
>
|
|
||||||
{' 18+'}
|
{' 18+'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -100,10 +100,7 @@ const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref)
|
|||||||
const cols = Math.ceil(dimensions / ItemWidth);
|
const cols = Math.ceil(dimensions / ItemWidth);
|
||||||
return (
|
return (
|
||||||
<Grid item columns={cols} xs={1}>
|
<Grid item columns={cols} xs={1}>
|
||||||
<Link
|
<Link to={mangaLinkTo} style={gridLayout === GridLayout.Comfortable ? { textDecoration: 'none' } : {}}>
|
||||||
to={mangaLinkTo}
|
|
||||||
style={gridLayout === GridLayout.Comfortable ? { textDecoration: 'none' } : {}}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -132,16 +129,12 @@ const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref)
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{inLibraryIndicator && inLibrary && (
|
{inLibraryIndicator && inLibrary && (
|
||||||
<Typography
|
<Typography sx={{ backgroundColor: 'primary.dark', zIndex: '1' }}>
|
||||||
sx={{ backgroundColor: 'primary.dark', zIndex: '1' }}
|
|
||||||
>
|
|
||||||
In library
|
In library
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{showUnreadBadge && unread! > 0 && (
|
{showUnreadBadge && unread! > 0 && (
|
||||||
<Typography sx={{ backgroundColor: 'primary.dark' }}>
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
|
||||||
{unread}
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
{showDownloadBadge && downloadCount! > 0 && (
|
{showDownloadBadge && downloadCount! > 0 && (
|
||||||
<Typography
|
<Typography
|
||||||
@@ -266,14 +259,10 @@ const MangaCard = React.forwardRef<HTMLDivElement, IProps>((props: IProps, ref)
|
|||||||
</Box>
|
</Box>
|
||||||
<BadgeContainer>
|
<BadgeContainer>
|
||||||
{inLibraryIndicator && inLibrary && (
|
{inLibraryIndicator && inLibrary && (
|
||||||
<Typography sx={{ backgroundColor: 'primary.dark' }}>
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>In library</Typography>
|
||||||
In library
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
{showUnreadBadge && unread! > 0 && (
|
{showUnreadBadge && unread! > 0 && (
|
||||||
<Typography sx={{ backgroundColor: 'primary.dark' }}>
|
<Typography sx={{ backgroundColor: 'primary.dark' }}>{unread}</Typography>
|
||||||
{unread}
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
{showDownloadBadge && downloadCount! > 0 && (
|
{showDownloadBadge && downloadCount! > 0 && (
|
||||||
<Typography
|
<Typography
|
||||||
|
|||||||
@@ -97,12 +97,7 @@ const SourceCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
<Typography variant="caption" display="block" gutterBottom>
|
<Typography variant="caption" display="block" gutterBottom>
|
||||||
{langCodeToName(lang)}
|
{langCodeToName(lang)}
|
||||||
{isNsfw && (
|
{isNsfw && (
|
||||||
<Typography
|
<Typography variant="caption" display="inline" gutterBottom color="red">
|
||||||
variant="caption"
|
|
||||||
display="inline"
|
|
||||||
gutterBottom
|
|
||||||
color="red"
|
|
||||||
>
|
|
||||||
{' 18+'}
|
{' 18+'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
@@ -113,29 +108,18 @@ const SourceCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
<>
|
<>
|
||||||
<MobileWidthButtons>
|
<MobileWidthButtons>
|
||||||
{supportsLatest && (
|
{supportsLatest && (
|
||||||
<Button
|
<Button variant="outlined" onClick={(e) => redirectTo(e, `/sources/${id}/latest/`)}>
|
||||||
variant="outlined"
|
|
||||||
onClick={(e) => redirectTo(e, `/sources/${id}/latest/`)}
|
|
||||||
>
|
|
||||||
Latest
|
Latest
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</MobileWidthButtons>
|
</MobileWidthButtons>
|
||||||
<WiderWidthButtons>
|
<WiderWidthButtons>
|
||||||
{supportsLatest && (
|
{supportsLatest && (
|
||||||
<Button
|
<Button component={Link} to={`/sources/${id}/latest/`} variant="outlined">
|
||||||
component={Link}
|
|
||||||
to={`/sources/${id}/latest/`}
|
|
||||||
variant="outlined"
|
|
||||||
>
|
|
||||||
Latest
|
Latest
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button component={Link} to={`/sources/${id}/popular/`} variant="outlined">
|
||||||
component={Link}
|
|
||||||
to={`/sources/${id}/popular/`}
|
|
||||||
variant="outlined"
|
|
||||||
>
|
|
||||||
Browse
|
Browse
|
||||||
</Button>
|
</Button>
|
||||||
</WiderWidthButtons>
|
</WiderWidthButtons>
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ interface IProps extends RadioInputProps {
|
|||||||
|
|
||||||
const SortRadioInput: React.FC<IProps> = ({ sortDescending, ...rest }) => (
|
const SortRadioInput: React.FC<IProps> = ({ sortDescending, ...rest }) => (
|
||||||
<RadioInput
|
<RadioInput
|
||||||
checkedIcon={
|
checkedIcon={sortDescending ? <ArrowDownward color="primary" /> : <ArrowUpward color="primary" />}
|
||||||
sortDescending ? <ArrowDownward color="primary" /> : <ArrowUpward color="primary" />
|
|
||||||
}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ const unreadFilter = (unread: NullAndUndefined<boolean>, { unreadCount }: IManga
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadedFilter = (
|
const downloadedFilter = (downloaded: NullAndUndefined<boolean>, { downloadCount }: IMangaCard): boolean => {
|
||||||
downloaded: NullAndUndefined<boolean>,
|
|
||||||
{ downloadCount }: IMangaCard,
|
|
||||||
): boolean => {
|
|
||||||
switch (downloaded) {
|
switch (downloaded) {
|
||||||
case true:
|
case true:
|
||||||
return !!downloadCount && downloadCount >= 1;
|
return !!downloadCount && downloadCount >= 1;
|
||||||
@@ -117,9 +114,7 @@ const LibraryMangaGrid: React.FC<IMangaGridProps & { lastLibraryUpdate: number }
|
|||||||
);
|
);
|
||||||
|
|
||||||
const showFilteredOutMessage =
|
const showFilteredOutMessage =
|
||||||
(unread != null || downloaded != null || query) &&
|
(unread != null || downloaded != null || query) && filteredManga.length === 0 && mangas.length > 0;
|
||||||
filteredManga.length === 0 &&
|
|
||||||
mangas.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MangaGrid
|
<MangaGrid
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ interface IProps {
|
|||||||
const LibraryOptionsPanel: React.FC<IProps> = ({ open, onClose }) => {
|
const LibraryOptionsPanel: React.FC<IProps> = ({ open, onClose }) => {
|
||||||
const { options, setOptions } = useLibraryOptionsContext();
|
const { options, setOptions } = useLibraryOptionsContext();
|
||||||
|
|
||||||
const handleFilterChange = <T extends keyof LibraryOptions>(
|
const handleFilterChange = <T extends keyof LibraryOptions>(key: T, value: LibraryOptions[T]) => {
|
||||||
key: T,
|
|
||||||
value: LibraryOptions[T],
|
|
||||||
) => {
|
|
||||||
setOptions((v) => ({ ...v, [key]: value }));
|
setOptions((v) => ({ ...v, [key]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,17 +82,13 @@ const LibraryOptionsPanel: React.FC<IProps> = ({ open, onClose }) => {
|
|||||||
<>
|
<>
|
||||||
<FormLabel>Display mode</FormLabel>
|
<FormLabel>Display mode</FormLabel>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onChange={(e) =>
|
onChange={(e) => handleFilterChange('gridLayout', Number(e.target.value))}
|
||||||
handleFilterChange('gridLayout', Number(e.target.value))
|
|
||||||
}
|
|
||||||
value={gridLayout}
|
value={gridLayout}
|
||||||
>
|
>
|
||||||
<RadioInput
|
<RadioInput
|
||||||
label="Compact grid"
|
label="Compact grid"
|
||||||
value={GridLayout.Compact}
|
value={GridLayout.Compact}
|
||||||
checked={
|
checked={gridLayout == null || gridLayout === GridLayout.Compact}
|
||||||
gridLayout == null || gridLayout === GridLayout.Compact
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<RadioInput
|
<RadioInput
|
||||||
label="Comfortable grid"
|
label="Comfortable grid"
|
||||||
@@ -113,16 +106,12 @@ const LibraryOptionsPanel: React.FC<IProps> = ({ open, onClose }) => {
|
|||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
label="Unread Badges"
|
label="Unread Badges"
|
||||||
checked={showUnreadBadge === true}
|
checked={showUnreadBadge === true}
|
||||||
onChange={() =>
|
onChange={() => handleFilterChange('showUnreadBadge', !showUnreadBadge)}
|
||||||
handleFilterChange('showUnreadBadge', !showUnreadBadge)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
label="Download Badges"
|
label="Download Badges"
|
||||||
checked={showDownloadBadge === true}
|
checked={showDownloadBadge === true}
|
||||||
onChange={() =>
|
onChange={() => handleFilterChange('showDownloadBadge', !showDownloadBadge)}
|
||||||
handleFilterChange('showDownloadBadge', !showDownloadBadge)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import LibraryOptionsContext, {
|
import LibraryOptionsContext, { DefaultLibraryOptions } from 'components/context/LibraryOptionsContext';
|
||||||
DefaultLibraryOptions,
|
|
||||||
} from 'components/context/LibraryOptionsContext';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
|
||||||
@@ -16,16 +14,9 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LibraryOptionsContextProvider: React.FC<IProps> = ({ children }) => {
|
const LibraryOptionsContextProvider: React.FC<IProps> = ({ children }) => {
|
||||||
const [options, setOptions] = useLocalStorage<LibraryOptions>(
|
const [options, setOptions] = useLocalStorage<LibraryOptions>('libraryOptions', DefaultLibraryOptions);
|
||||||
'libraryOptions',
|
|
||||||
DefaultLibraryOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return <LibraryOptionsContext.Provider value={{ options, setOptions }}>{children}</LibraryOptionsContext.Provider>;
|
||||||
<LibraryOptionsContext.Provider value={{ options, setOptions }}>
|
|
||||||
{children}
|
|
||||||
</LibraryOptionsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LibraryOptionsContextProvider;
|
export default LibraryOptionsContextProvider;
|
||||||
|
|||||||
@@ -22,10 +22,7 @@ function Progress({ progress }: IProgressProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace(
|
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
|
||||||
'http',
|
|
||||||
'ws',
|
|
||||||
);
|
|
||||||
|
|
||||||
interface IUpdateCheckerProps {
|
interface IUpdateCheckerProps {
|
||||||
handleFinishedUpdate: (time: number) => void;
|
handleFinishedUpdate: (time: number) => void;
|
||||||
@@ -58,8 +55,7 @@ function UpdateChecker({ handleFinishedUpdate }: IUpdateCheckerProps) {
|
|||||||
const { running, statusMap } = JSON.parse(e.data) as IUpdateStatus;
|
const { running, statusMap } = JSON.parse(e.data) as IUpdateStatus;
|
||||||
const { COMPLETE = [], RUNNING = [], PENDING = [] } = statusMap;
|
const { COMPLETE = [], RUNNING = [], PENDING = [] } = statusMap;
|
||||||
|
|
||||||
const currentProgress =
|
const currentProgress = 100 * (COMPLETE.length / (COMPLETE.length + RUNNING.length + PENDING.length));
|
||||||
100 * (COMPLETE.length / (COMPLETE.length + RUNNING.length + PENDING.length));
|
|
||||||
|
|
||||||
const isUpdateFinished = currentProgress === 100;
|
const isUpdateFinished = currentProgress === 100;
|
||||||
const ignoreFaultyMessage = !updateStarted && !running && isUpdateFinished;
|
const ignoreFaultyMessage = !updateStarted && !running && isUpdateFinished;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace(
|
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
|
||||||
'http',
|
|
||||||
'ws',
|
|
||||||
);
|
|
||||||
|
|
||||||
const useSubscription = <T>(path: string, callback?: (newValue: T) => boolean | void) => {
|
const useSubscription = <T>(path: string, callback?: (newValue: T) => boolean | void) => {
|
||||||
const [state, setState] = useState<T | undefined>();
|
const [state, setState] = useState<T | undefined>();
|
||||||
|
|||||||
@@ -42,14 +42,7 @@ interface IProps {
|
|||||||
const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const {
|
const { chapter, triggerChaptersUpdate, downloadChapter: dc, showChapterNumber, onSelect, selected } = props;
|
||||||
chapter,
|
|
||||||
triggerChaptersUpdate,
|
|
||||||
downloadChapter: dc,
|
|
||||||
showChapterNumber,
|
|
||||||
onSelect,
|
|
||||||
selected,
|
|
||||||
} = props;
|
|
||||||
const isSelecting = selected !== null;
|
const isSelecting = selected !== null;
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
@@ -85,9 +78,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteChapter = () => {
|
const deleteChapter = () => {
|
||||||
client
|
client.delete(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`).then(() => triggerChaptersUpdate());
|
||||||
.delete(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`)
|
|
||||||
.then(() => triggerChaptersUpdate());
|
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,9 +134,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
sx={{ mr: 0.5, position: 'relative', top: '0.15em' }}
|
sx={{ mr: 0.5, position: 'relative', top: '0.15em' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showChapterNumber
|
{showChapterNumber ? `Chapter ${chapter.chapterNumber}` : chapter.name}
|
||||||
? `Chapter ${chapter.chapterNumber}`
|
|
||||||
: chapter.name}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption">{chapter.scanlator}</Typography>
|
<Typography variant="caption">{chapter.scanlator}</Typography>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
@@ -165,12 +154,7 @@ const ChapterCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
<Menu
|
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
|
||||||
anchorEl={anchorEl}
|
|
||||||
keepMounted
|
|
||||||
open={Boolean(anchorEl)}
|
|
||||||
onClose={handleClose}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={handleSelect}>
|
<MenuItem onClick={handleSelect}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<CheckBoxOutlineBlank fontSize="small" />
|
<CheckBoxOutlineBlank fontSize="small" />
|
||||||
|
|||||||
@@ -140,10 +140,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
|||||||
setSelection(null);
|
setSelection(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFabAction: ComponentProps<typeof SelectionFAB>['onAction'] = (
|
const handleFabAction: ComponentProps<typeof SelectionFAB>['onAction'] = (action, actionChapters) => {
|
||||||
action,
|
|
||||||
actionChapters,
|
|
||||||
) => {
|
|
||||||
if (actionChapters.length === 0) return;
|
if (actionChapters.length === 0) return;
|
||||||
const chapterIds = actionChapters.map(({ chapter }) => chapter.id);
|
const chapterIds = actionChapters.map(({ chapter }) => chapter.id);
|
||||||
|
|
||||||
@@ -164,16 +161,9 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actionPromise
|
actionPromise
|
||||||
.then(() =>
|
.then(() => makeToast(interpolate(chapterIds.length, actionsStrings[action].success), 'success'))
|
||||||
makeToast(
|
|
||||||
interpolate(chapterIds.length, actionsStrings[action].success),
|
|
||||||
'success',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then(() => mutate())
|
.then(() => mutate())
|
||||||
.catch(() =>
|
.catch(() => makeToast(interpolate(chapterIds.length, actionsStrings[action].error), 'error'));
|
||||||
makeToast(interpolate(chapterIds.length, actionsStrings[action].error), 'error'),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -206,9 +196,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedChapters =
|
const selectedChapters =
|
||||||
selection === null
|
selection === null ? null : chaptersWithMeta.filter(({ chapter }) => selection.includes(chapter.id));
|
||||||
? null
|
|
||||||
: chaptersWithMeta.filter(({ chapter }) => selection.includes(chapter.id));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -225,9 +213,7 @@ const ChapterList: React.FC<IProps> = ({ mangaId }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
{`${visibleChapters.length} Chapter${
|
{`${visibleChapters.length} Chapter${visibleChapters.length === 1 ? '' : 's'}`}
|
||||||
visibleChapters.length === 1 ? '' : 's'
|
|
||||||
}`}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{selection === null ? (
|
{selection === null ? (
|
||||||
|
|||||||
@@ -134,22 +134,14 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
const classes = useStyles(manga.inLibrary)();
|
const classes = useStyles(manga.inLibrary)();
|
||||||
|
|
||||||
const addToLibrary = () => {
|
const addToLibrary = () => {
|
||||||
mutate(
|
mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`, { ...manga, inLibrary: true }, { revalidate: false });
|
||||||
`/api/v1/manga/${manga.id}/?onlineFetch=false`,
|
|
||||||
{ ...manga, inLibrary: true },
|
|
||||||
{ revalidate: false },
|
|
||||||
);
|
|
||||||
client
|
client
|
||||||
.get(`/api/v1/manga/${manga.id}/library/`)
|
.get(`/api/v1/manga/${manga.id}/library/`)
|
||||||
.then(() => mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`));
|
.then(() => mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`));
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFromLibrary = () => {
|
const removeFromLibrary = () => {
|
||||||
mutate(
|
mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`, { ...manga, inLibrary: false }, { revalidate: false });
|
||||||
`/api/v1/manga/${manga.id}/?onlineFetch=false`,
|
|
||||||
{ ...manga, inLibrary: false },
|
|
||||||
{ revalidate: false },
|
|
||||||
);
|
|
||||||
client
|
client
|
||||||
.delete(`/api/v1/manga/${manga.id}/library/`)
|
.delete(`/api/v1/manga/${manga.id}/library/`)
|
||||||
.then(() => mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`));
|
.then(() => mutate(`/api/v1/manga/${manga.id}/?onlineFetch=false`));
|
||||||
@@ -160,10 +152,7 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
<div className={classes.top}>
|
<div className={classes.top}>
|
||||||
<div className={classes.leftRight}>
|
<div className={classes.leftRight}>
|
||||||
<div className={classes.leftSide}>
|
<div className={classes.leftSide}>
|
||||||
<img
|
<img src={`${serverAddress}${manga.thumbnailUrl}?useCache=${useCache}`} alt="Manga Thumbnail" />
|
||||||
src={`${serverAddress}${manga.thumbnailUrl}?useCache=${useCache}`}
|
|
||||||
alt="Manga Thumbnail"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.rightSide}>
|
<div className={classes.rightSide}>
|
||||||
<h1>{manga.title}</h1>
|
<h1>{manga.title}</h1>
|
||||||
@@ -181,15 +170,8 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={classes.buttons}>
|
<div className={classes.buttons}>
|
||||||
<div>
|
<div>
|
||||||
<IconButton
|
<IconButton onClick={manga.inLibrary ? removeFromLibrary : addToLibrary} size="large">
|
||||||
onClick={manga.inLibrary ? removeFromLibrary : addToLibrary}
|
{manga.inLibrary ? <FavoriteIcon sx={{ mr: 1 }} /> : <FavoriteBorderIcon sx={{ mr: 1 }} />}
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
{manga.inLibrary ? (
|
|
||||||
<FavoriteIcon sx={{ mr: 1 }} />
|
|
||||||
) : (
|
|
||||||
<FavoriteBorderIcon sx={{ mr: 1 }} />
|
|
||||||
)}
|
|
||||||
<Typography sx={{ fontSize: { xs: '0.75em', sm: '0.85em' } }}>
|
<Typography sx={{ fontSize: { xs: '0.75em', sm: '0.85em' } }}>
|
||||||
{manga.inLibrary ? 'In Library' : 'Add To Library'}
|
{manga.inLibrary ? 'In Library' : 'Add To Library'}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -198,9 +180,7 @@ const MangaDetails: React.FC<IProps> = ({ manga }) => {
|
|||||||
<a href={manga.realUrl} target="_blank" rel="noreferrer">
|
<a href={manga.realUrl} target="_blank" rel="noreferrer">
|
||||||
<IconButton size="large">
|
<IconButton size="large">
|
||||||
<PublicIcon sx={{ mr: 1 }} />
|
<PublicIcon sx={{ mr: 1 }} />
|
||||||
<Typography sx={{ fontSize: { xs: '0.75em', sm: '0.85em' } }}>
|
<Typography sx={{ fontSize: { xs: '0.75em', sm: '0.85em' } }}>Open Site</Typography>
|
||||||
Open Site
|
|
||||||
</Typography>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,13 +13,7 @@ import React, { useRef, useState } from 'react';
|
|||||||
import type { IChapterWithMeta } from 'components/manga/ChapterList';
|
import type { IChapterWithMeta } from 'components/manga/ChapterList';
|
||||||
import SelectionFABActionItem from 'components/manga/SelectionFABActionItem';
|
import SelectionFABActionItem from 'components/manga/SelectionFABActionItem';
|
||||||
|
|
||||||
export type SelectionAction =
|
export type SelectionAction = 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread';
|
||||||
| 'download'
|
|
||||||
| 'delete'
|
|
||||||
| 'bookmark'
|
|
||||||
| 'unbookmark'
|
|
||||||
| 'mark_as_read'
|
|
||||||
| 'mark_as_unread';
|
|
||||||
|
|
||||||
interface SelectionFABProps {
|
interface SelectionFABProps {
|
||||||
selectedChapters: IChapterWithMeta[];
|
selectedChapters: IChapterWithMeta[];
|
||||||
@@ -49,12 +43,7 @@ const SelectionFAB: React.FC<SelectionFABProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
ref={anchorEl}
|
ref={anchorEl}
|
||||||
>
|
>
|
||||||
<Fab
|
<Fab variant="extended" color="primary" id="selectionMenuButton" onClick={() => setOpen(true)}>
|
||||||
variant="extended"
|
|
||||||
color="primary"
|
|
||||||
id="selectionMenuButton"
|
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
>
|
|
||||||
{`${count} ${pluralize(count, 'chapter')}`}
|
{`${count} ${pluralize(count, 'chapter')}`}
|
||||||
<MoreHoriz sx={{ ml: 1 }} />
|
<MoreHoriz sx={{ ml: 1 }} />
|
||||||
</Fab>
|
</Fab>
|
||||||
|
|||||||
@@ -17,15 +17,11 @@ const defaultChapterOptions: ChapterListOptions = {
|
|||||||
showChapterNumber: false,
|
showChapterNumber: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function chapterOptionsReducer(
|
function chapterOptionsReducer(state: ChapterListOptions, actions: ChapterOptionsReducerAction): ChapterListOptions {
|
||||||
state: ChapterListOptions,
|
|
||||||
actions: ChapterOptionsReducerAction,
|
|
||||||
): ChapterListOptions {
|
|
||||||
switch (actions.type) {
|
switch (actions.type) {
|
||||||
case 'filter':
|
case 'filter':
|
||||||
// eslint-disable-next-line no-case-declarations
|
// eslint-disable-next-line no-case-declarations
|
||||||
const active =
|
const active = state.unread !== false && state.downloaded !== false && state.bookmarked !== false;
|
||||||
state.unread !== false && state.downloaded !== false && state.bookmarked !== false;
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
active,
|
active,
|
||||||
@@ -53,10 +49,7 @@ export function unreadFilter(unread: NullAndUndefined<boolean>, { read: isChapte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFilter(
|
function downloadFilter(downloaded: NullAndUndefined<boolean>, { downloaded: chapterDownload }: IChapter) {
|
||||||
downloaded: NullAndUndefined<boolean>,
|
|
||||||
{ downloaded: chapterDownload }: IChapter,
|
|
||||||
) {
|
|
||||||
switch (downloaded) {
|
switch (downloaded) {
|
||||||
case true:
|
case true:
|
||||||
return chapterDownload;
|
return chapterDownload;
|
||||||
@@ -67,10 +60,7 @@ function downloadFilter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bookmarkedFilter(
|
function bookmarkedFilter(bookmarked: NullAndUndefined<boolean>, { bookmarked: chapterBookmarked }: IChapter) {
|
||||||
bookmarked: NullAndUndefined<boolean>,
|
|
||||||
{ bookmarked: chapterBookmarked }: IChapter,
|
|
||||||
) {
|
|
||||||
switch (bookmarked) {
|
switch (bookmarked) {
|
||||||
case true:
|
case true:
|
||||||
return chapterBookmarked;
|
return chapterBookmarked;
|
||||||
@@ -81,10 +71,7 @@ function bookmarkedFilter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterAndSortChapters(
|
export function filterAndSortChapters(chapters: IChapter[], options: ChapterListOptions): IChapter[] {
|
||||||
chapters: IChapter[],
|
|
||||||
options: ChapterListOptions,
|
|
||||||
): IChapter[] {
|
|
||||||
const filtered = options.active
|
const filtered = options.active
|
||||||
? chapters.filter(
|
? chapters.filter(
|
||||||
(chp) =>
|
(chp) =>
|
||||||
@@ -93,10 +80,7 @@ export function filterAndSortChapters(
|
|||||||
bookmarkedFilter(options.bookmarked, chp),
|
bookmarkedFilter(options.bookmarked, chp),
|
||||||
)
|
)
|
||||||
: [...chapters];
|
: [...chapters];
|
||||||
const Sorted =
|
const Sorted = options.sortBy === 'fetchedAt' ? filtered.sort((a, b) => a.fetchedAt - b.fetchedAt) : filtered;
|
||||||
options.sortBy === 'fetchedAt'
|
|
||||||
? filtered.sort((a, b) => a.fetchedAt - b.fetchedAt)
|
|
||||||
: filtered;
|
|
||||||
if (options.reverse) {
|
if (options.reverse) {
|
||||||
Sorted.reverse();
|
Sorted.reverse();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ const DownloadStateIndicator: React.FC<DownloadStateIndicatorProps> = ({ downloa
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{download.progress !== 0 && (
|
{download.progress !== 0 && <CircularProgress variant="determinate" value={download.progress * 100} />}
|
||||||
<CircularProgress variant="determinate" value={download.progress * 100} />
|
|
||||||
)}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
top: 0,
|
top: 0,
|
||||||
|
|||||||
@@ -100,9 +100,7 @@ export default function DefaultNavBar() {
|
|||||||
let navbar = <></>;
|
let navbar = <></>;
|
||||||
if (isMobileWidth) {
|
if (isMobileWidth) {
|
||||||
if (isMainRoute) {
|
if (isMainRoute) {
|
||||||
navbar = (
|
navbar = <MobileBottomBar navBarItems={navbarItems.filter((it) => it.show !== 'desktop')} />;
|
||||||
<MobileBottomBar navBarItems={navbarItems.filter((it) => it.show !== 'desktop')} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navbar = <DesktopSideBar navBarItems={navbarItems.filter((it) => it.show !== 'mobile')} />;
|
navbar = <DesktopSideBar navBarItems={navbarItems.filter((it) => it.show !== 'mobile')} />;
|
||||||
|
|||||||
@@ -126,9 +126,7 @@ export default function ReaderNavBar(props: IProps) {
|
|||||||
const [updateDrawerOnRender, setUpdateDrawerOnRender] = useState(true);
|
const [updateDrawerOnRender, setUpdateDrawerOnRender] = useState(true);
|
||||||
const [hideOpenButton, setHideOpenButton] = useState(settings.staticNav || prevDrawerOpen);
|
const [hideOpenButton, setHideOpenButton] = useState(settings.staticNav || prevDrawerOpen);
|
||||||
const [prevScrollPos, setPrevScrollPos] = useState(0);
|
const [prevScrollPos, setPrevScrollPos] = useState(0);
|
||||||
const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(
|
const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(prevSettingsCollapseOpen ?? true);
|
||||||
prevSettingsCollapseOpen ?? true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
|
const updateSettingValue = (key: keyof IReaderSettings, value: string | boolean) => {
|
||||||
// prevent closing the navBar when updating the "staticNav" setting
|
// prevent closing the navBar when updating the "staticNav" setting
|
||||||
@@ -181,14 +179,7 @@ export default function ReaderNavBar(props: IProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Slide
|
<Slide direction="right" in={drawerOpen} timeout={200} appear={false} mountOnEnter unmountOnExit>
|
||||||
direction="right"
|
|
||||||
in={drawerOpen}
|
|
||||||
timeout={200}
|
|
||||||
appear={false}
|
|
||||||
mountOnEnter
|
|
||||||
unmountOnExit
|
|
||||||
>
|
|
||||||
<Root
|
<Root
|
||||||
sx={{
|
sx={{
|
||||||
position: settings.staticNav ? 'sticky' : 'fixed',
|
position: settings.staticNav ? 'sticky' : 'fixed',
|
||||||
@@ -207,12 +198,7 @@ export default function ReaderNavBar(props: IProps) {
|
|||||||
<KeyboardArrowLeftIcon />
|
<KeyboardArrowLeftIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
<Typography
|
<Typography variant="h1" textOverflow="ellipsis" overflow="hidden" sx={{ py: 1 }}>
|
||||||
variant="h1"
|
|
||||||
textOverflow="ellipsis"
|
|
||||||
overflow="hidden"
|
|
||||||
sx={{ py: 1 }}
|
|
||||||
>
|
|
||||||
{chapter.name}
|
{chapter.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ interface IProps {
|
|||||||
export default function CategorySelect(props: IProps) {
|
export default function CategorySelect(props: IProps) {
|
||||||
const { open, setOpen, mangaId } = props;
|
const { open, setOpen, mangaId } = props;
|
||||||
|
|
||||||
const { data: mangaCategoriesData, mutate } = useQuery<ICategory[]>(
|
const { data: mangaCategoriesData, mutate } = useQuery<ICategory[]>(`/api/v1/manga/${mangaId}/category`);
|
||||||
`/api/v1/manga/${mangaId}/category`,
|
|
||||||
);
|
|
||||||
const { data: categoriesData } = useQuery<ICategory[]>('/api/v1/category');
|
const { data: categoriesData } = useQuery<ICategory[]>('/api/v1/category');
|
||||||
|
|
||||||
const allCategories = useMemo(() => {
|
const allCategories = useMemo(() => {
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ interface IProps {
|
|||||||
export default function LangSelect(props: IProps) {
|
export default function LangSelect(props: IProps) {
|
||||||
const { shownLangs, setShownLangs, allLangs, forcedLangs } = props;
|
const { shownLangs, setShownLangs, allLangs, forcedLangs } = props;
|
||||||
// hold a copy and only sate state on parent when OK pressed, improves performance
|
// hold a copy and only sate state on parent when OK pressed, improves performance
|
||||||
const [mShownLangs, setMShownLangs] = useState(
|
const [mShownLangs, setMShownLangs] = useState(removeAll(cloneObject(shownLangs), forcedLangs!));
|
||||||
removeAll(cloneObject(shownLangs), forcedLangs!),
|
|
||||||
);
|
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ export default function DesktopSideBar({ navBarItems }: IProps) {
|
|||||||
if (location.pathname === path)
|
if (location.pathname === path)
|
||||||
return <SelectedIconComponent sx={{ color: 'primary.main' }} fontSize="large" />;
|
return <SelectedIconComponent sx={{ color: 'primary.main' }} fontSize="large" />;
|
||||||
return (
|
return (
|
||||||
<IconComponent
|
<IconComponent sx={{ color: theme.palette.mode === 'dark' ? 'grey.A400' : 'grey.600' }} fontSize="large" />
|
||||||
sx={{ color: theme.palette.mode === 'dark' ? 'grey.A400' : 'grey.600' }}
|
|
||||||
fontSize="large"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,13 +43,8 @@ export default function DesktopSideBar({ navBarItems }: IProps) {
|
|||||||
<SideNavBarContainer>
|
<SideNavBarContainer>
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line react/destructuring-assignment
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
navBarItems.map(
|
navBarItems.map(({ path, title, IconComponent, SelectedIconComponent }: NavbarItem) => (
|
||||||
({ path, title, IconComponent, SelectedIconComponent }: NavbarItem) => (
|
<Link to={path} style={{ color: 'inherit', textDecoration: 'none' }} key={path}>
|
||||||
<Link
|
|
||||||
to={path}
|
|
||||||
style={{ color: 'inherit', textDecoration: 'none' }}
|
|
||||||
key={path}
|
|
||||||
>
|
|
||||||
<ListItem disableRipple button key={title}>
|
<ListItem disableRipple button key={title}>
|
||||||
<ListItemIcon sx={{ minWidth: '0' }}>
|
<ListItemIcon sx={{ minWidth: '0' }}>
|
||||||
<Tooltip placement="right" title={title}>
|
<Tooltip placement="right" title={title}>
|
||||||
@@ -61,8 +53,7 @@ export default function DesktopSideBar({ navBarItems }: IProps) {
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
),
|
))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</SideNavBarContainer>
|
</SideNavBarContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,24 +44,15 @@ export default function MobileBottomBar({ navBarItems }: IProps) {
|
|||||||
if (location.pathname === path)
|
if (location.pathname === path)
|
||||||
return <SelectedIconComponent sx={{ color: 'primary.main' }} fontSize="medium" />;
|
return <SelectedIconComponent sx={{ color: 'primary.main' }} fontSize="medium" />;
|
||||||
return (
|
return (
|
||||||
<IconComponent
|
<IconComponent sx={{ color: theme.palette.mode === 'dark' ? 'grey.A400' : 'grey.600' }} fontSize="medium" />
|
||||||
sx={{ color: theme.palette.mode === 'dark' ? 'grey.A400' : 'grey.600' }}
|
|
||||||
fontSize="medium"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomNavContainer>
|
<BottomNavContainer>
|
||||||
{navBarItems.map(
|
{navBarItems.map(({ path, title, IconComponent, SelectedIconComponent }: NavbarItem) => (
|
||||||
({ path, title, IconComponent, SelectedIconComponent }: NavbarItem) => (
|
|
||||||
<Link to={path} key={path}>
|
<Link to={path} key={path}>
|
||||||
<ListItem
|
<ListItem disableRipple button sx={{ justifyContent: 'center', padding: '8px' }} key={title}>
|
||||||
disableRipple
|
|
||||||
button
|
|
||||||
sx={{ justifyContent: 'center', padding: '8px' }}
|
|
||||||
key={title}
|
|
||||||
>
|
|
||||||
<Box display="flex" flexDirection="column" alignItems="center">
|
<Box display="flex" flexDirection="column" alignItems="center">
|
||||||
{iconFor(path, IconComponent, SelectedIconComponent)}
|
{iconFor(path, IconComponent, SelectedIconComponent)}
|
||||||
<Box
|
<Box
|
||||||
@@ -81,8 +72,7 @@ export default function MobileBottomBar({ navBarItems }: IProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
),
|
))}
|
||||||
)}
|
|
||||||
</BottomNavContainer>
|
</BottomNavContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,10 +164,8 @@ export default function HorizontalPager(props: IReaderProps) {
|
|||||||
ref={selfRef}
|
ref={selfRef}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection:
|
flexDirection: settings.readerType === 'ContinuesHorizontalLTR' ? 'row' : 'row-reverse',
|
||||||
settings.readerType === 'ContinuesHorizontalLTR' ? 'row' : 'row-reverse',
|
justifyContent: settings.readerType === 'ContinuesHorizontalLTR' ? 'flex-start' : 'flex-end',
|
||||||
justifyContent:
|
|
||||||
settings.readerType === 'ContinuesHorizontalLTR' ? 'flex-start' : 'flex-end',
|
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
|
|||||||
@@ -98,13 +98,7 @@ export default function PagedReader(props: IReaderProps) {
|
|||||||
height: '100vh',
|
height: '100vh',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Page
|
<Page key={curPage} index={curPage} onImageLoad={() => {}} src={pages[curPage].src} settings={settings} />
|
||||||
key={curPage}
|
|
||||||
index={curPage}
|
|
||||||
onImageLoad={() => {}}
|
|
||||||
src={pages[curPage].src}
|
|
||||||
settings={settings}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,9 +85,7 @@ export default function VerticalPager(props: IReaderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.scroll({
|
window.scroll({
|
||||||
top:
|
top: window.scrollY + window.innerHeight * SCROLL_OFFSET * (direction === 'up' ? -1 : 1),
|
||||||
window.scrollY +
|
|
||||||
window.innerHeight * SCROLL_OFFSET * (direction === 'up' ? -1 : 1),
|
|
||||||
behavior: SCROLL_BEHAVIOR,
|
behavior: SCROLL_BEHAVIOR,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,10 +58,7 @@ export default function SourceGridLayout() {
|
|||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
name={GridLayout.Compact.toString()}
|
name={GridLayout.Compact.toString()}
|
||||||
checked={
|
checked={SourcegridLayout === GridLayout.Compact || SourcegridLayout === undefined}
|
||||||
SourcegridLayout === GridLayout.Compact ||
|
|
||||||
SourcegridLayout === undefined
|
|
||||||
}
|
|
||||||
onChange={setGridContextOptions}
|
onChange={setGridContextOptions}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,7 @@ function filterManga(mangas: IMangaCard[]): IMangaCard[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SourceMangaGrid(props: IMangaGridProps) {
|
export default function SourceMangaGrid(props: IMangaGridProps) {
|
||||||
const {
|
const { mangas, isLoading, hasNextPage, lastPageNum, setLastPageNum, message, messageExtra, gridLayout } = props;
|
||||||
mangas,
|
|
||||||
isLoading,
|
|
||||||
hasNextPage,
|
|
||||||
lastPageNum,
|
|
||||||
setLastPageNum,
|
|
||||||
message,
|
|
||||||
messageExtra,
|
|
||||||
gridLayout,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const filteredManga = filterManga(mangas);
|
const filteredManga = filterManga(mangas);
|
||||||
const showFilteredOutMessage = filteredManga.length === 0 && mangas.length > 0;
|
const showFilteredOutMessage = filteredManga.length === 0 && mangas.length > 0;
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ export function Options({ sourceFilter, group, updateFilterValue, update }: IFil
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'Header':
|
case 'Header':
|
||||||
return (
|
return <HeaderFilter key={`filters ${e.filter.name}`} name={e.filter.name} />;
|
||||||
<HeaderFilter key={`filters ${e.filter.name}`} name={e.filter.name} />
|
|
||||||
);
|
|
||||||
case 'Select':
|
case 'Select':
|
||||||
return (
|
return (
|
||||||
<SelectFilter
|
<SelectFilter
|
||||||
@@ -90,12 +88,7 @@ export function Options({ sourceFilter, group, updateFilterValue, update }: IFil
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'Separator':
|
case 'Separator':
|
||||||
return (
|
return <SeperatorFilter key={`filters ${e.filter.name}`} name={e.filter.name} />;
|
||||||
<SeperatorFilter
|
|
||||||
key={`filters ${e.filter.name}`}
|
|
||||||
name={e.filter.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'Sort':
|
case 'Sort':
|
||||||
return (
|
return (
|
||||||
<SortFilter
|
<SortFilter
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ const CheckBoxFilter: React.FC<Props> = (props: Props) => {
|
|||||||
const handleChange = (event: { target: { name: any; checked: any } }) => {
|
const handleChange = (event: { target: { name: any; checked: any } }) => {
|
||||||
setval(event.target.checked);
|
setval(event.target.checked);
|
||||||
const upd = update.filter(
|
const upd = update.filter(
|
||||||
(e: { position: number; group: number | undefined }) =>
|
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
|
||||||
!(position === e.position && group === e.group),
|
|
||||||
);
|
);
|
||||||
updateFilterValue([...upd, { position, state: event.target.checked.toString(), group }]);
|
updateFilterValue([...upd, { position, state: event.target.checked.toString(), group }]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ function hasSelect(
|
|||||||
const vall = values.map((e) => e.displayname).indexOf(`${event.target.value}`);
|
const vall = values.map((e) => e.displayname).indexOf(`${event.target.value}`);
|
||||||
setval(vall);
|
setval(vall);
|
||||||
const upd = update.filter(
|
const upd = update.filter(
|
||||||
(e: { position: number; group: number | undefined }) =>
|
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
|
||||||
!(position === e.position && group === e.group),
|
|
||||||
);
|
);
|
||||||
updateFilterValue([...upd, { position, state: vall.toString(), group }]);
|
updateFilterValue([...upd, { position, state: vall.toString(), group }]);
|
||||||
};
|
};
|
||||||
@@ -58,12 +57,7 @@ function hasSelect(
|
|||||||
return (
|
return (
|
||||||
<FormControl sx={{ my: 1 }} variant="standard">
|
<FormControl sx={{ my: 1 }} variant="standard">
|
||||||
<InputLabel>{name}</InputLabel>
|
<InputLabel>{name}</InputLabel>
|
||||||
<Select
|
<Select name={name} value={values[val].displayname} label={name} onChange={handleChange}>
|
||||||
name={name}
|
|
||||||
value={values[val].displayname}
|
|
||||||
label={name}
|
|
||||||
onChange={handleChange}
|
|
||||||
>
|
|
||||||
{rett}
|
{rett}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -88,8 +82,7 @@ function noSelect(
|
|||||||
const vall = values.indexOf(`${event.target.value}`);
|
const vall = values.indexOf(`${event.target.value}`);
|
||||||
setval(vall);
|
setval(vall);
|
||||||
const upd = update.filter(
|
const upd = update.filter(
|
||||||
(e: { position: number; group: number | undefined }) =>
|
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
|
||||||
!(position === e.position && group === e.group),
|
|
||||||
);
|
);
|
||||||
updateFilterValue([...upd, { position, state: vall.toString(), group }]);
|
updateFilterValue([...upd, { position, state: vall.toString(), group }]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ const SortFilter: React.FC<Props> = (props: Props) => {
|
|||||||
tmp.index = index;
|
tmp.index = index;
|
||||||
setval(tmp);
|
setval(tmp);
|
||||||
const upd = update.filter(
|
const upd = update.filter(
|
||||||
(e: { position: number; group: number | undefined }) =>
|
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
|
||||||
!(position === e.position && group === e.group),
|
|
||||||
);
|
);
|
||||||
updateFilterValue([...upd, { position, state: JSON.stringify(tmp), group }]);
|
updateFilterValue([...upd, { position, state: JSON.stringify(tmp), group }]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ const TextFilter: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
function doneTyping(e: React.ChangeEvent<HTMLInputElement>) {
|
function doneTyping(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const upd = update.filter(
|
const upd = update.filter(
|
||||||
(el: { position: number; group: number | undefined }) =>
|
(el: { position: number; group: number | undefined }) => !(position === el.position && group === el.group),
|
||||||
!(position === el.position && group === el.group),
|
|
||||||
);
|
);
|
||||||
updateFilterValue([...upd, { position, state: e.target.value, group }]);
|
updateFilterValue([...upd, { position, state: e.target.value, group }]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ const TriStateFilter: React.FC<Props> = (props) => {
|
|||||||
const newState = checked === undefined ? 0 : checked ? 1 : 2;
|
const newState = checked === undefined ? 0 : checked ? 1 : 2;
|
||||||
setval(newState);
|
setval(newState);
|
||||||
const upd = update.filter(
|
const upd = update.filter(
|
||||||
(e: { position: number; group: number | undefined }) =>
|
(e: { position: number; group: number | undefined }) => !(position === e.position && group === e.group),
|
||||||
!(position === e.position && group === e.group),
|
|
||||||
);
|
);
|
||||||
updateFilterValue([
|
updateFilterValue([
|
||||||
...upd,
|
...upd,
|
||||||
|
|||||||
@@ -65,12 +65,7 @@ function ListDialog(props: IListDialogProps) {
|
|||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<RadioGroup ref={radioGroupRef} value={value} onChange={handleChange}>
|
<RadioGroup ref={radioGroupRef} value={value} onChange={handleChange}>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<FormControlLabel
|
<FormControlLabel value={option} key={option} control={<Radio />} label={option} />
|
||||||
value={option}
|
|
||||||
key={option}
|
|
||||||
control={<Radio />}
|
|
||||||
label={option}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -64,11 +64,7 @@ function ListDialog(props: IListDialogProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }} maxWidth="xs" open={open}>
|
||||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
|
||||||
maxWidth="xs"
|
|
||||||
open={open}
|
|
||||||
>
|
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
@@ -76,9 +72,7 @@ function ListDialog(props: IListDialogProps) {
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedValues.some(
|
checked={selectedValues.some((selectedValue) => value === selectedValue)}
|
||||||
(selectedValue) => value === selectedValue,
|
|
||||||
)}
|
|
||||||
onChange={(e) => handleChange(e, value)}
|
onChange={(e) => handleChange(e, value)}
|
||||||
color="default"
|
color="default"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -61,20 +61,14 @@ export default function makeToast(message: string, severity: Severity) {
|
|||||||
setTimeout(() => removeToast(container.id), 3500);
|
setTimeout(() => removeToast(container.id), 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeToaster([toasts, setToasts]: [
|
export function makeToaster([toasts, setToasts]: [React.ReactElement[], (arg0: React.ReactElement[]) => void]): [
|
||||||
React.ReactElement[],
|
React.ReactElement[],
|
||||||
(arg0: React.ReactElement[]) => void,
|
(message: string, severity: Severity) => void,
|
||||||
]): [React.ReactElement[], (message: string, severity: Severity) => void] {
|
] {
|
||||||
return [
|
return [
|
||||||
toasts,
|
toasts,
|
||||||
(message: string, severity: Severity) => {
|
(message: string, severity: Severity) => {
|
||||||
setToasts([
|
setToasts([<Toast key={Math.floor(Math.random() * 1000) + 1} message={message} severity={severity} />]);
|
||||||
<Toast
|
|
||||||
key={Math.floor(Math.random() * 1000) + 1}
|
|
||||||
message={message}
|
|
||||||
severity={severity}
|
|
||||||
/>,
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,9 +97,7 @@ const DownloadQueue: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: snapshot.isDragging
|
backgroundColor: snapshot.isDragging ? 'custom.light' : undefined,
|
||||||
? 'custom.light'
|
|
||||||
: undefined,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardActionArea
|
<CardActionArea
|
||||||
@@ -117,18 +115,9 @@ const DownloadQueue: React.FC = () => {
|
|||||||
<IconButton sx={{ pointerEvents: 'none' }}>
|
<IconButton sx={{ pointerEvents: 'none' }}>
|
||||||
<DragHandle />
|
<DragHandle />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Stack
|
<Stack sx={{ flex: 1, ml: 1 }} direction="column">
|
||||||
sx={{ flex: 1, ml: 1 }}
|
<Typography variant="h6">{item.manga.title}</Typography>
|
||||||
direction="column"
|
<Typography variant="caption" display="block" gutterBottom>
|
||||||
>
|
|
||||||
<Typography variant="h6">
|
|
||||||
{item.manga.title}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
display="block"
|
|
||||||
gutterBottom
|
|
||||||
>
|
|
||||||
{item.chapter.name}
|
{item.chapter.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -67,10 +67,7 @@ function groupExtensions(extensions: IExtension[]) {
|
|||||||
export default function MangaExtensions() {
|
export default function MangaExtensions() {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const { setTitle, setAction } = useContext(NavbarContext);
|
||||||
const [shownLangs, setShownLangs] = useLocalStorage<string[]>(
|
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownExtensionLangs', extensionDefaultLangs());
|
||||||
'shownExtensionLangs',
|
|
||||||
extensionDefaultLangs(),
|
|
||||||
);
|
|
||||||
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
@@ -84,20 +81,12 @@ export default function MangaExtensions() {
|
|||||||
<IconButton onClick={() => inputRef.current?.click()} size="large">
|
<IconButton onClick={() => inputRef.current?.click()} size="large">
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<LangSelect
|
<LangSelect shownLangs={shownLangs} setShownLangs={setShownLangs} allLangs={allLangs} />
|
||||||
shownLangs={shownLangs}
|
|
||||||
setShownLangs={setShownLangs}
|
|
||||||
allLangs={allLangs}
|
|
||||||
/>
|
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
}, [shownLangs]);
|
}, [shownLangs]);
|
||||||
|
|
||||||
const {
|
const { data: allExtensions, mutate, loading } = useQuery<IExtension[]>('/api/v1/extension/list');
|
||||||
data: allExtensions,
|
|
||||||
mutate,
|
|
||||||
loading,
|
|
||||||
} = useQuery<IExtension[]>('/api/v1/extension/list');
|
|
||||||
|
|
||||||
const filteredExtensions = useMemo(
|
const filteredExtensions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -113,11 +102,7 @@ export default function MangaExtensions() {
|
|||||||
() =>
|
() =>
|
||||||
groupExtensions(filteredExtensions)
|
groupExtensions(filteredExtensions)
|
||||||
.filter((group) => group[EXTENSIONS].length > 0)
|
.filter((group) => group[EXTENSIONS].length > 0)
|
||||||
.filter((group) =>
|
.filter((group) => ['installed', 'updates pending', 'all', ...shownLangs].includes(group[LANGUAGE])),
|
||||||
['installed', 'updates pending', 'all', ...shownLangs].includes(
|
|
||||||
group[LANGUAGE],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
[shownLangs, filteredExtensions],
|
[shownLangs, filteredExtensions],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -60,12 +60,7 @@ export default function Library() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (tabsError != null) {
|
if (tabsError != null) {
|
||||||
return (
|
return <EmptyView message="Could not load categories" messageExtra={tabsError?.message ?? tabsError} />;
|
||||||
<EmptyView
|
|
||||||
message="Could not load categories"
|
|
||||||
messageExtra={tabsError?.message ?? tabsError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ const Manga: React.FC = () => {
|
|||||||
const [refresh, { loading: refreshing }] = useRefreshManga(id);
|
const [refresh, { loading: refreshing }] = useRefreshManga(id);
|
||||||
|
|
||||||
useSetDefaultBackTo(
|
useSetDefaultBackTo(
|
||||||
manga?.inLibrary === false && manga.sourceId != null
|
manga?.inLibrary === false && manga.sourceId != null ? `/sources/${manga.sourceId}/popular` : '/library',
|
||||||
? `/sources/${manga.sourceId}/popular`
|
|
||||||
: '/library',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -89,13 +87,7 @@ const Manga: React.FC = () => {
|
|||||||
<CircularProgress size={16} />
|
<CircularProgress size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
{manga && (
|
{manga && <MangaToolbarMenu manga={manga} onRefresh={refresh} refreshing={refreshing} />}
|
||||||
<MangaToolbarMenu
|
|
||||||
manga={manga}
|
|
||||||
onRefresh={refresh}
|
|
||||||
refreshing={refreshing}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</NavbarToolbar>
|
</NavbarToolbar>
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ export default function Reader() {
|
|||||||
const [curPage, setCurPage] = useState<number>(0);
|
const [curPage, setCurPage] = useState<number>(0);
|
||||||
const { setOverride, setTitle } = useContext(NavbarContext);
|
const { setOverride, setTitle } = useContext(NavbarContext);
|
||||||
|
|
||||||
const { settings: defaultSettings, loading: areDefaultSettingsLoading } =
|
const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings();
|
||||||
useDefaultReaderSettings();
|
|
||||||
const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings));
|
const [settings, setSettings] = useState(getReaderSettingsFor(manga, defaultSettings));
|
||||||
const [isMangaLoading, setIsMangaLoading] = useState(true);
|
const [isMangaLoading, setIsMangaLoading] = useState(true);
|
||||||
|
|
||||||
@@ -97,9 +96,7 @@ export default function Reader() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!areDefaultSettingsLoading && !isMangaLoading) {
|
if (!areDefaultSettingsLoading && !isMangaLoading) {
|
||||||
checkAndHandleMissingStoredReaderSettings(manga, 'manga', defaultSettings).catch(
|
checkAndHandleMissingStoredReaderSettings(manga, 'manga', defaultSettings).catch(() => {});
|
||||||
() => {},
|
|
||||||
);
|
|
||||||
setSettings(getReaderSettingsFor(manga, defaultSettings));
|
setSettings(getReaderSettingsFor(manga, defaultSettings));
|
||||||
}
|
}
|
||||||
}, [areDefaultSettingsLoading, isMangaLoading]);
|
}, [areDefaultSettingsLoading, isMangaLoading]);
|
||||||
|
|||||||
@@ -16,12 +16,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { StringParam, useQueryParam } from 'use-query-params';
|
import { StringParam, useQueryParam } from 'use-query-params';
|
||||||
import client from 'util/client';
|
import client from 'util/client';
|
||||||
import {
|
import { langCodeToName, langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from 'util/language';
|
||||||
langCodeToName,
|
|
||||||
langSortCmp,
|
|
||||||
sourceDefualtLangs,
|
|
||||||
sourceForcedDefaultLangs,
|
|
||||||
} from 'util/language';
|
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
|
|
||||||
function sourceToLangList(sources: ISource[]) {
|
function sourceToLangList(sources: ISource[]) {
|
||||||
@@ -43,10 +38,7 @@ const SearchAll: React.FC = () => {
|
|||||||
const [triggerUpdate, setTriggerUpdate] = useState<number>(2);
|
const [triggerUpdate, setTriggerUpdate] = useState<number>(2);
|
||||||
const [mangas, setMangas] = useState<any>({});
|
const [mangas, setMangas] = useState<any>({});
|
||||||
|
|
||||||
const [shownLangs, setShownLangs] = useLocalStorage<string[]>(
|
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
|
||||||
'shownSourceLangs',
|
|
||||||
sourceDefualtLangs(),
|
|
||||||
);
|
|
||||||
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||||
|
|
||||||
const [sources, setSources] = useState<ISource[]>([]);
|
const [sources, setSources] = useState<ISource[]>([]);
|
||||||
@@ -210,9 +202,7 @@ const SearchAll: React.FC = () => {
|
|||||||
sx={{ p: 3 }}
|
sx={{ p: 3 }}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">{displayName}</Typography>
|
<Typography variant="h5">{displayName}</Typography>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">{langCodeToName(lang)}</Typography>
|
||||||
{langCodeToName(lang)}
|
|
||||||
</Typography>
|
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
<MangaGrid
|
<MangaGrid
|
||||||
|
|||||||
@@ -118,11 +118,7 @@ export default function Settings() {
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Dark Theme" />
|
<ListItemText primary="Dark Theme" />
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Switch
|
<Switch edge="end" checked={darkTheme} onChange={() => setDarkTheme(!darkTheme)} />
|
||||||
edge="end"
|
|
||||||
checked={darkTheme}
|
|
||||||
onChange={() => setDarkTheme(!darkTheme)}
|
|
||||||
/>
|
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItemButton>
|
<ListItemButton>
|
||||||
@@ -141,16 +137,9 @@ export default function Settings() {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<FavoriteIcon />
|
<FavoriteIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText primary="Show NSFW" secondary="Hide NSFW extensions and sources" />
|
||||||
primary="Show NSFW"
|
|
||||||
secondary="Hide NSFW extensions and sources"
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Switch
|
<Switch edge="end" checked={showNsfw} onChange={() => setShowNsfw(!showNsfw)} />
|
||||||
edge="end"
|
|
||||||
checked={showNsfw}
|
|
||||||
onChange={() => setShowNsfw(!showNsfw)}
|
|
||||||
/>
|
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
@@ -163,11 +152,7 @@ export default function Settings() {
|
|||||||
but uses it much more internet traffic in turn"
|
but uses it much more internet traffic in turn"
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Switch
|
<Switch edge="end" checked={useCache} onChange={() => setUseCache(!useCache)} />
|
||||||
edge="end"
|
|
||||||
checked={useCache}
|
|
||||||
onChange={() => setUseCache(!useCache)}
|
|
||||||
/>
|
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ import React, { useContext, useEffect } from 'react';
|
|||||||
import NavbarContext from 'components/context/NavbarContext';
|
import NavbarContext from 'components/context/NavbarContext';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import client, { useQuery } from 'util/client';
|
import client, { useQuery } from 'util/client';
|
||||||
import {
|
import { SwitchPreferenceCompat, CheckBoxPreference } from 'components/sourceConfiguration/TwoStatePreference';
|
||||||
SwitchPreferenceCompat,
|
|
||||||
CheckBoxPreference,
|
|
||||||
} from 'components/sourceConfiguration/TwoStatePreference';
|
|
||||||
import ListPreference from 'components/sourceConfiguration/ListPreference';
|
import ListPreference from 'components/sourceConfiguration/ListPreference';
|
||||||
import EditTextPreference from 'components/sourceConfiguration/EditTextPreference';
|
import EditTextPreference from 'components/sourceConfiguration/EditTextPreference';
|
||||||
import MultiSelectListPreference from 'components/sourceConfiguration/MultiSelectListPreference';
|
import MultiSelectListPreference from 'components/sourceConfiguration/MultiSelectListPreference';
|
||||||
|
|||||||
@@ -221,9 +221,7 @@ export default function SourceMangas(props: { popular: boolean }) {
|
|||||||
messageExtra = (
|
messageExtra = (
|
||||||
<>
|
<>
|
||||||
<span>Check out </span>
|
<span>Check out </span>
|
||||||
<a href="https://github.com/Suwayomi/Tachidesk-Server/wiki/Local-Source">
|
<a href="https://github.com/Suwayomi/Tachidesk-Server/wiki/Local-Source">Local source guide</a>
|
||||||
Local source guide
|
|
||||||
</a>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,7 @@ import React, { useContext, useEffect } from 'react';
|
|||||||
import LangSelect from 'components/navbar/action/LangSelect';
|
import LangSelect from 'components/navbar/action/LangSelect';
|
||||||
import SourceCard from 'components/SourceCard';
|
import SourceCard from 'components/SourceCard';
|
||||||
import NavbarContext from 'components/context/NavbarContext';
|
import NavbarContext from 'components/context/NavbarContext';
|
||||||
import {
|
import { sourceDefualtLangs, sourceForcedDefaultLangs, langCodeToName, langSortCmp } from 'util/language';
|
||||||
sourceDefualtLangs,
|
|
||||||
sourceForcedDefaultLangs,
|
|
||||||
langCodeToName,
|
|
||||||
langSortCmp,
|
|
||||||
} from 'util/language';
|
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||||
import { IconButton } from '@mui/material';
|
import { IconButton } from '@mui/material';
|
||||||
@@ -50,10 +45,7 @@ function groupByLang(sources: ISource[]) {
|
|||||||
export default function Sources() {
|
export default function Sources() {
|
||||||
const { setTitle, setAction } = useContext(NavbarContext);
|
const { setTitle, setAction } = useContext(NavbarContext);
|
||||||
|
|
||||||
const [shownLangs, setShownLangs] = useLocalStorage<string[]>(
|
const [shownLangs, setShownLangs] = useLocalStorage<string[]>('shownSourceLangs', sourceDefualtLangs());
|
||||||
'shownSourceLangs',
|
|
||||||
sourceDefualtLangs(),
|
|
||||||
);
|
|
||||||
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
const [showNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||||
|
|
||||||
const { data: sources, loading } = useQuery<ISource[]>('/api/v1/source/list');
|
const { data: sources, loading } = useQuery<ISource[]>('/api/v1/source/list');
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ function getDateString(date: Date) {
|
|||||||
return date.toLocaleDateString();
|
return date.toLocaleDateString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupByDate(
|
function groupByDate(updates: IMangaChapter[]): [string, { item: IMangaChapter; globalIdx: number }[]][] {
|
||||||
updates: IMangaChapter[],
|
|
||||||
): [string, { item: IMangaChapter; globalIdx: number }[]][] {
|
|
||||||
if (updates.length === 0) return [];
|
if (updates.length === 0) return [];
|
||||||
|
|
||||||
const groups = {};
|
const groups = {};
|
||||||
@@ -64,10 +62,7 @@ function groupByDate(
|
|||||||
return Object.keys(groups).map((key) => [key, groups[key]]);
|
return Object.keys(groups).map((key) => [key, groups[key]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace(
|
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
|
||||||
'http',
|
|
||||||
'ws',
|
|
||||||
);
|
|
||||||
const initialQueue = {
|
const initialQueue = {
|
||||||
status: 'Stopped',
|
status: 'Stopped',
|
||||||
queue: [],
|
queue: [],
|
||||||
@@ -205,11 +200,7 @@ const Updates: React.FC = () => {
|
|||||||
<Typography variant="h5" component="h2">
|
<Typography variant="h5" component="h2">
|
||||||
{manga.title}
|
{manga.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography variant="caption" display="block" gutterBottom>
|
||||||
variant="caption"
|
|
||||||
display="block"
|
|
||||||
gutterBottom
|
|
||||||
>
|
|
||||||
{chapter.name}
|
{chapter.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -74,10 +74,7 @@ export default function Backup() {
|
|||||||
<>
|
<>
|
||||||
<List sx={{ padding: 0 }}>
|
<List sx={{ padding: 0 }}>
|
||||||
<ListItemLink to={`${baseURL}/api/v1/backup/export/file`} directLink>
|
<ListItemLink to={`${baseURL}/api/v1/backup/export/file`} directLink>
|
||||||
<ListItemText
|
<ListItemText primary="Create Backup" secondary="Backup library as a Tachiyomi backup" />
|
||||||
primary="Create Backup"
|
|
||||||
secondary="Backup library as a Tachiyomi backup"
|
|
||||||
/>
|
|
||||||
</ListItemLink>
|
</ListItemLink>
|
||||||
<ListItem button onClick={() => document.getElementById('backup-file')?.click()}>
|
<ListItem button onClick={() => document.getElementById('backup-file')?.click()}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
|
|||||||
@@ -139,11 +139,7 @@ export default function Categories() {
|
|||||||
{(provided) => (
|
{(provided) => (
|
||||||
<List ref={provided.innerRef}>
|
<List ref={provided.innerRef}>
|
||||||
{categories.map((item, index) => (
|
{categories.map((item, index) => (
|
||||||
<Draggable
|
<Draggable key={item.id} draggableId={item.id.toString()} index={index}>
|
||||||
key={item.id}
|
|
||||||
draggableId={item.id.toString()}
|
|
||||||
index={index}
|
|
||||||
>
|
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
ContainerProps={{ ref: provided.innerRef } as any}
|
ContainerProps={{ ref: provided.innerRef } as any}
|
||||||
|
|||||||
@@ -51,11 +51,7 @@ export default function DefaultReaderSettings() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAndHandleMissingStoredReaderSettings(
|
checkAndHandleMissingStoredReaderSettings({ meta: metadata }, 'server', getDefaultSettings()).catch(() => {});
|
||||||
{ meta: metadata },
|
|
||||||
'server',
|
|
||||||
getDefaultSettings(),
|
|
||||||
).catch(() => {});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReaderSettingsOptions
|
<ReaderSettingsOptions
|
||||||
|
|||||||
@@ -83,10 +83,7 @@ export function langCodeToName(code: string): string {
|
|||||||
let result = `language with code: ${code}`;
|
let result = `language with code: ${code}`;
|
||||||
|
|
||||||
for (let i = 0; i < ISOLanguages.length; i++) {
|
for (let i = 0; i < ISOLanguages.length; i++) {
|
||||||
if (
|
if (ISOLanguages[i].code === proccessedCode || ISOLanguages[i].code === code.toLocaleLowerCase()) {
|
||||||
ISOLanguages[i].code === proccessedCode ||
|
|
||||||
ISOLanguages[i].code === code.toLocaleLowerCase()
|
|
||||||
) {
|
|
||||||
result = ISOLanguages[i].nativeName;
|
result = ISOLanguages[i].nativeName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,10 @@ const migrations: IMetadataMigration[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) =>
|
const getMetadataKey = (key: string, appPrefix: string = APP_METADATA_KEY_PREFIX) => `${appPrefix}${key}`;
|
||||||
`${appPrefix}${key}`;
|
|
||||||
|
|
||||||
const doesMetadataKeyExistIn = (
|
const doesMetadataKeyExistIn = (meta: IMetadata | undefined, key: string, appPrefix?: string): boolean =>
|
||||||
meta: IMetadata | undefined,
|
Object.prototype.hasOwnProperty.call(meta ?? {}, getMetadataKey(key, appPrefix));
|
||||||
key: string,
|
|
||||||
appPrefix?: string,
|
|
||||||
): boolean => Object.prototype.hasOwnProperty.call(meta ?? {}, getMetadataKey(key, appPrefix));
|
|
||||||
|
|
||||||
const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
|
const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
|
||||||
value: string,
|
value: string,
|
||||||
@@ -44,10 +40,7 @@ const convertValueFromMetadata = <T extends AllowedMetadataValueTypes = AllowedM
|
|||||||
return value as T;
|
return value as T;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAppMetadataFrom = (
|
const getAppMetadataFrom = (meta: IMetadata, appPrefix: string = APP_METADATA_KEY_PREFIX): IMetadata => {
|
||||||
meta: IMetadata,
|
|
||||||
appPrefix: string = APP_METADATA_KEY_PREFIX,
|
|
||||||
): IMetadata => {
|
|
||||||
const appMetadata: IMetadata = {};
|
const appMetadata: IMetadata = {};
|
||||||
|
|
||||||
Object.entries(meta).forEach(([key, value]) => {
|
Object.entries(meta).forEach(([key, value]) => {
|
||||||
@@ -71,9 +64,7 @@ const applyAppKeyPrefixMigration = (meta: IMetadata, migration: IMetadataMigrati
|
|||||||
const oldAppMetadata = getAppMetadataFrom(meta, oldPrefix);
|
const oldAppMetadata = getAppMetadataFrom(meta, oldPrefix);
|
||||||
const newAppMetadata = getAppMetadataFrom(meta, newPrefix);
|
const newAppMetadata = getAppMetadataFrom(meta, newPrefix);
|
||||||
|
|
||||||
const missingMetadataKeys = Object.keys(oldAppMetadata).filter(
|
const missingMetadataKeys = Object.keys(oldAppMetadata).filter((key) => !Object.keys(newAppMetadata).includes(key));
|
||||||
(key) => !Object.keys(newAppMetadata).includes(key),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isMissingOldMetadata = missingMetadataKeys.length;
|
const isMissingOldMetadata = missingMetadataKeys.length;
|
||||||
if (isMissingOldMetadata) {
|
if (isMissingOldMetadata) {
|
||||||
@@ -134,9 +125,7 @@ const applyMetadataMigrations = (meta?: IMetadata): IMetadata | undefined => {
|
|||||||
return migrationToMetadata.pop()![1];
|
return migrationToMetadata.pop()![1];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMetadataValueFrom = <
|
export const getMetadataValueFrom = <T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes>(
|
||||||
T extends AllowedMetadataValueTypes = AllowedMetadataValueTypes,
|
|
||||||
>(
|
|
||||||
{ meta }: IMetadataHolder,
|
{ meta }: IMetadataHolder,
|
||||||
key: AppMetadataKeys,
|
key: AppMetadataKeys,
|
||||||
defaultValue?: T,
|
defaultValue?: T,
|
||||||
@@ -220,22 +209,14 @@ export const requestUpdateMetadata = async (
|
|||||||
): Promise<void[]> =>
|
): Promise<void[]> =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
keysToValues.map(([key, value]) =>
|
keysToValues.map(([key, value]) =>
|
||||||
requestUpdateMetadataValue(
|
requestUpdateMetadataValue(endpoint, metadataHolder, key, value, endpointToMutate, wrapWithMetaKey),
|
||||||
endpoint,
|
|
||||||
metadataHolder,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
endpointToMutate,
|
|
||||||
wrapWithMetaKey,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const requestUpdateServerMetadata = async (
|
export const requestUpdateServerMetadata = async (
|
||||||
serverMetadata: IMetadata,
|
serverMetadata: IMetadata,
|
||||||
keysToValues: MetadataKeyValuePair[],
|
keysToValues: MetadataKeyValuePair[],
|
||||||
): Promise<void[]> =>
|
): Promise<void[]> => requestUpdateMetadata('', { meta: serverMetadata }, keysToValues, '/meta', false);
|
||||||
requestUpdateMetadata('', { meta: serverMetadata }, keysToValues, '/meta', false);
|
|
||||||
|
|
||||||
export const requestUpdateMangaMetadata = async (
|
export const requestUpdateMangaMetadata = async (
|
||||||
manga: IMangaCard | IManga,
|
manga: IMangaCard | IManga,
|
||||||
|
|||||||
@@ -6,11 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { getMetadataFrom, requestUpdateMangaMetadata, requestUpdateServerMetadata } from 'util/metadata';
|
||||||
getMetadataFrom,
|
|
||||||
requestUpdateMangaMetadata,
|
|
||||||
requestUpdateServerMetadata,
|
|
||||||
} from 'util/metadata';
|
|
||||||
import { useQuery } from 'util/client';
|
import { useQuery } from 'util/client';
|
||||||
|
|
||||||
export const getDefaultSettings = (forceUndefined: boolean = false) =>
|
export const getDefaultSettings = (forceUndefined: boolean = false) =>
|
||||||
@@ -75,9 +71,7 @@ export const checkAndHandleMissingStoredReaderSettings = async (
|
|||||||
const settingsToCheck = getReaderSettingsFor({ meta }, getDefaultSettings(true), false);
|
const settingsToCheck = getReaderSettingsFor({ meta }, getDefaultSettings(true), false);
|
||||||
const newSettings = getReaderSettingsFor({ meta }, defaultSettings);
|
const newSettings = getReaderSettingsFor({ meta }, defaultSettings);
|
||||||
|
|
||||||
const undefinedSettings = Object.entries(settingsToCheck).filter(
|
const undefinedSettings = Object.entries(settingsToCheck).filter((setting) => setting[1] === undefined);
|
||||||
(setting) => setting[1] === undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
const settingsToUpdate: MetadataKeyValuePair[] = [];
|
const settingsToUpdate: MetadataKeyValuePair[] = [];
|
||||||
undefinedSettings.forEach((setting) => {
|
undefinedSettings.forEach((setting) => {
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ import React, { useState, Dispatch, SetStateAction, useReducer, Reducer, useCall
|
|||||||
import storage from 'util/localStorage';
|
import storage from 'util/localStorage';
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export default function useLocalStorage<T>(
|
export default function useLocalStorage<T>(key: string, defaultValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
|
||||||
key: string,
|
|
||||||
defaultValue: T | (() => T),
|
|
||||||
): [T, Dispatch<SetStateAction<T>>] {
|
|
||||||
const initialState = defaultValue instanceof Function ? defaultValue() : defaultValue;
|
const initialState = defaultValue instanceof Function ? defaultValue() : defaultValue;
|
||||||
const [storedValue, setStoredValue] = useState<T>(storage.getItem(key, initialState));
|
const [storedValue, setStoredValue] = useState<T>(storage.getItem(key, initialState));
|
||||||
|
|
||||||
@@ -31,11 +28,7 @@ export default function useLocalStorage<T>(
|
|||||||
return [storedValue, setValue];
|
return [storedValue, setValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useReducerLocalStorage<S, A>(
|
export function useReducerLocalStorage<S, A>(reducer: Reducer<S, A>, key: string, defaultState: S | (() => S)) {
|
||||||
reducer: Reducer<S, A>,
|
|
||||||
key: string,
|
|
||||||
defaultState: S | (() => S),
|
|
||||||
) {
|
|
||||||
const [storedValue, setValue] = useLocalStorage(key, defaultState);
|
const [storedValue, setValue] = useLocalStorage(key, defaultState);
|
||||||
return useReducer((state: S, action: A): S => {
|
return useReducer((state: S, action: A): S => {
|
||||||
const newState = reducer(state, action);
|
const newState = reducer(state, action);
|
||||||
|
|||||||
Reference in New Issue
Block a user