Move core files into new folder

This commit is contained in:
schroda
2024-10-05 16:09:34 +02:00
parent 996bb62888
commit 23a86a77f0
173 changed files with 514 additions and 481 deletions

View File

@@ -1,133 +0,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 React, { useState, useEffect } from 'react';
import SearchIcon from '@mui/icons-material/Search';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import { useQueryParam, StringParam } from 'use-query-params';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { useTheme } from '@mui/material/styles';
import { SearchTextField } from '@/components/atoms/SearchTextField.tsx';
interface IProps {
isClosable?: boolean;
}
export const AppbarSearch: React.FunctionComponent<IProps> = (props) => {
const { isClosable = true } = props;
const theme = useTheme();
const { t } = useTranslation();
const [prevLocationKey, setPrevLocationKey] = useState<string>();
const location = useLocation();
const [query, setQuery] = useQueryParam('query', StringParam);
const [isSearchOpen, setIsSearchOpen] = useState(!isClosable || !!query);
const inputRef = React.useRef<HTMLInputElement>();
const [searchString, setSearchString] = useState(query ?? '');
if (prevLocationKey !== location.key) {
setPrevLocationKey(location.key);
setSearchString(query ?? '');
setIsSearchOpen(!isClosable || !!query);
}
const isOpen = isSearchOpen || !!query;
const updateSearchOpenState = (open: boolean) => {
if (!isClosable) {
return;
}
setIsSearchOpen(open);
// try to focus input component since in case of navigating to the previous/next page in the browser history
// the "openSearch" state might not change and thus, won't trigger a focus
if (open) {
inputRef.current?.focus();
}
};
function handleChange(newQuery: string) {
if (newQuery === '') {
return;
}
setQuery(newQuery);
updateSearchOpenState(false);
}
const cancelSearch = () => {
setSearchString('');
setQuery(undefined);
updateSearchOpenState(false);
};
const handleBlur = () => {
if (!searchString) updateSearchOpenState(false);
};
const handleKeyboardEvent = (e: KeyboardEvent) => {
if (e.key === 'F3' || (e.ctrlKey && e.key === 'f')) {
e.preventDefault();
updateSearchOpenState(true);
}
};
useEffect(() => {
window.addEventListener('keydown', handleKeyboardEvent);
return () => {
window.removeEventListener('keydown', handleKeyboardEvent);
};
}, [handleKeyboardEvent]);
if (isOpen) {
return (
<SearchTextField
autoFocus
variant="standard"
value={searchString}
onCancel={cancelSearch}
onChange={(e) => setSearchString(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleChange(searchString);
}
}}
onBlur={handleBlur}
inputRef={inputRef}
sx={{
...theme.applyStyles('light', {
'& .MuiInput-underline:before': {
borderBottomColor: 'primary.contrastText', // Default color
},
'& .MuiInput-underline:hover:before': {
borderBottomColor: 'primary.contrastText', // Hover color
},
'& .MuiInput-underline:after': {
borderBottomColor: 'primary.dark', // Focused color
},
}),
}}
cancelButtonProps={{ sx: { ...theme.applyStyles('light', { color: 'primary.contrastText' }) } }}
/>
);
}
return (
<Tooltip title={t('search.title.search')}>
<IconButton onClick={() => updateSearchOpenState(true)} color="inherit">
<SearchIcon />
</IconButton>
</Tooltip>
);
};

View File

@@ -1,14 +0,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 { createSvgIcon } from '@mui/material/utils';
const d =
'M 11 1 C 9.3550302 1 8 2.3550302 8 4 L 4 4 C 2.9069372 4 2 4.9069372 2 6 L 2 12 L 4 12 C 4.5650302 12 5 12.43497 5 13 C 5 13.56503 4.5650302 14 4 14 L 2 14 L 2 20 C 2 21.093063 2.9069372 22 4 22 L 10 22 L 10 20 C 10 19.43497 10.43497 19 11 19 C 11.56503 19 12 19.43497 12 20 L 12 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 16 C 21.64497 16 23 14.64497 23 13 C 23 11.35503 21.64497 10 20 10 L 20 6 C 20 4.9069372 19.093063 4 18 4 L 14 4 C 14 2.3550302 12.64497 1 11 1 z M 11 3 C 11.56503 3 12 3.4349698 12 4 L 12 6 L 18 6 L 18 12 L 20 12 C 20.56503 12 21 12.43497 21 13 C 21 13.56503 20.56503 14 20 14 L 18 14 L 18 20 L 14 20 C 14 18.35503 12.64497 17 11 17 C 9.3550302 17 8 18.35503 8 20 L 4 20 L 4 16 C 5.6449698 16 7 14.64497 7 13 C 7 11.35503 5.6449698 10 4 10 L 4 6 L 10 6 L 10 4 C 10 3.4349698 10.43497 3 11 3 z';
export const ExtensionOutlinedIcon = createSvgIcon(<path d={d} />, 'CustomExtensionOutlined');

View File

@@ -1,58 +0,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/.
*/
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt
import { useMemo } from 'react';
import Typography from '@mui/material/Typography';
import { SxProps, Theme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
const ERROR_FACES = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
function getRandomErrorFace() {
const randIndex = Math.floor(Math.random() * ERROR_FACES.length);
return ERROR_FACES[randIndex];
}
export interface EmptyViewProps {
message: string;
messageExtra?: JSX.Element | string;
retry?: () => void;
noFaces?: boolean;
sx?: SxProps<Theme>;
}
export function EmptyView({ message, messageExtra, retry, noFaces, sx }: EmptyViewProps) {
const { t } = useTranslation();
const errorFace = useMemo(() => getRandomErrorFace(), []);
return (
<Stack
sx={{
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
...sx,
}}
>
{!noFaces && (
<Typography variant="h3" gutterBottom>
{errorFace}
</Typography>
)}
<Typography variant="h5">{message}</Typography>
{messageExtra}
{retry && <Button onClick={retry}>{t('global.button.retry')}</Button>}
</Stack>
);
}

View File

@@ -1,25 +0,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 { EmptyView, EmptyViewProps } from '@/components/util/EmptyView.tsx';
export function EmptyViewAbsoluteCentered({ sx, ...emptyViewProps }: EmptyViewProps) {
return (
<EmptyView
{...emptyViewProps}
sx={{
position: 'absolute',
height: undefined,
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
...sx,
}}
/>
);
}

View File

@@ -1,14 +0,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 { Link } from 'react-router-dom';
import ListItemButton, { ListItemButtonProps } from '@mui/material/ListItemButton';
export function ListItemLink(props: ListItemButtonProps<typeof Link>) {
return <ListItemButton component={Link} {...props} />;
}

View File

@@ -1,53 +0,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 React from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
interface IProps {
shouldRender?: boolean | (() => boolean);
children?: React.ReactNode;
component?: string | React.FunctionComponent<any> | React.ComponentClass<any, any>;
componentProps?: any;
usePadding?: boolean;
}
export function LoadingPlaceholder(props: IProps) {
const { children, shouldRender, component, componentProps, usePadding } = props;
let condition = true;
if (shouldRender !== undefined) {
condition = shouldRender instanceof Function ? shouldRender() : shouldRender;
}
if (condition) {
if (component) {
return React.createElement(component, componentProps);
}
if (children) {
return children as JSX.Element;
}
}
return (
<Box
sx={{
margin: '0px auto',
marginTop: usePadding ? 'unset' : '10px',
marginBottom: usePadding ? 'unset' : '10px',
padding: usePadding ? '10px 0' : 'unset',
display: 'flex',
justifyContent: 'center',
}}
>
<CircularProgress thickness={5} />
</Box>
);
}

View File

@@ -1,34 +0,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 Box from '@mui/material/Box';
import CircularProgress, { CircularProgressProps } from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';
export const Progress = ({
progress,
showText = true,
progressProps = {},
}: {
progress: number;
showText?: boolean;
progressProps?: CircularProgressProps;
}) => (
<Box sx={{ display: 'grid', placeItems: 'center', position: 'relative' }}>
<CircularProgress {...progressProps} variant="determinate" value={progress} />
{showText && (
<Box sx={{ position: 'absolute' }}>
<Typography
sx={{
fontSize: '0.8rem',
}}
>{`${Math.round(progress)}%`}</Typography>
</Box>
)}
</Box>
);

View File

@@ -14,12 +14,12 @@ import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
import { getVersion } from '@/screens/settings/About.tsx';
import { useUpdateChecker } from '@/util/useUpdateChecker.tsx';
import { VersionUpdateInfoDialog } from '@/components/util/VersionUpdateInfoDialog.tsx';
import { useMetadataServerSettings } from '@/lib/metadata/metadataServerSettings.ts';
import { useLocalStorage } from '@/util/useStorage.tsx';
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
const disabledUpdateCheck = () => Promise.resolve();

View File

@@ -1,162 +0,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 { useState, CSSProperties, useEffect, forwardRef, ForwardedRef } from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Button from '@mui/material/Button';
import BrokenImageIcon from '@mui/icons-material/BrokenImage';
import RefreshIcon from '@mui/icons-material/Refresh';
import { useTranslation } from 'react-i18next';
import ImageIcon from '@mui/icons-material/Image';
import { SxProps, Theme } from '@mui/material/styles';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { Priority } from '@/lib/Queue.ts';
interface IProps {
src: string;
alt: string;
spinnerStyle?: SxProps<Theme> & { small?: boolean };
imgStyle?: CSSProperties;
onImageLoad?: () => void;
useFetchApi?: boolean;
}
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => {
const { useFetchApi, src, alt, onImageLoad, spinnerStyle: { small, ...spinnerStyle } = {}, imgStyle } = props;
const { t } = useTranslation();
const showMissingImageIcon = !src.length;
const [imageSourceUrl, setImageSourceUrl] = useState('');
const [imgLoadRetryKey, setImgLoadRetryKey] = useState(0);
const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined);
const [hasError, setHasError] = useState(false);
const updateImageState = (loading: boolean, error: boolean = false, aborted: boolean = false) => {
setIsLoading(loading);
setHasError(error);
if (!loading && !error && !aborted) {
onImageLoad?.();
}
};
useEffect(() => {
if (showMissingImageIcon) {
return () => {};
}
const imageRequest = requestManager.requestImage(src, Priority.HIGH, useFetchApi);
let cacheTimeout: NodeJS.Timeout;
const fetchImage = async () => {
try {
const updateImage = async () => {
const image = await imageRequest.response;
updateImageState(false);
setImageSourceUrl(image);
};
const checkCache = await Promise.race([
imageRequest.response,
new Promise((resolve) => {
cacheTimeout = setTimeout(resolve, 50);
}),
]);
const isImageCached = !!checkCache;
if (isImageCached) {
await updateImage();
return;
}
updateImageState(true);
await updateImage();
} catch (e) {
const wasAborted =
e instanceof Error && (e.name === 'AbortError' || e.message === 'Component was unmounted');
updateImageState(false, !wasAborted, wasAborted);
}
};
fetchImage().catch(() => {});
return () => {
imageRequest.cleanup();
clearTimeout(cacheTimeout);
imageRequest.abortRequest(new Error('Component was unmounted'));
};
}, [src, imgLoadRetryKey]);
return (
<>
{(isLoading || hasError) && (
<Box sx={{ height: '100%', ...spinnerStyle }}>
<Stack
sx={{
height: '100%',
alignItems: 'center',
justifyContent: 'center',
}}
>
{isLoading && <CircularProgress thickness={5} />}
{hasError && isLoading === false && (
<>
<BrokenImageIcon />
<Button
startIcon={!small && <RefreshIcon />}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
setImgLoadRetryKey((prevState) => (prevState + 1) % 100);
}}
size={small ? 'small' : 'large'}
>
{small ? <RefreshIcon /> : t('global.button.retry')}
</Button>
</>
)}
</Stack>
</Box>
)}
{showMissingImageIcon ? (
<Stack
sx={{
height: '100%',
alignItems: 'center',
justifyContent: 'center',
background: (theme) => theme.palette.background.default,
...spinnerStyle,
}}
>
<ImageIcon fontSize="large" />
</Stack>
) : (
<img
key={`${src}_${imgLoadRetryKey}`}
style={{
...imgStyle,
display: !imageSourceUrl || isLoading || hasError ? 'none' : imgStyle?.display,
}}
ref={imgRef}
src={imageSourceUrl}
alt={alt}
draggable={false}
/>
)}
</>
);
});

View File

@@ -1,23 +0,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 Fab from '@mui/material/Fab';
import { styled } from '@mui/material/styles';
export const DEFAULT_FAB_STYLE = {
position: 'fixed',
height: '48px',
right: '48px',
bottom: '28px',
} as const;
export const DEFAULT_FULL_FAB_HEIGHT = `calc(${DEFAULT_FAB_STYLE.bottom} + ${DEFAULT_FAB_STYLE.height})`;
export const StyledFab = styled(Fab)({
...DEFAULT_FAB_STYLE,
}) as typeof Fab;

View File

@@ -1,19 +0,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 { enqueueSnackbar, OptionsObject, SnackbarKey } from 'notistack';
export function makeToast(message: string, severity?: OptionsObject['variant']): SnackbarKey;
export function makeToast(message: string, options?: OptionsObject): SnackbarKey;
export function makeToast(message: string, options: OptionsObject['variant'] | OptionsObject = 'default'): SnackbarKey {
if (typeof options === 'string') {
return enqueueSnackbar(message, { variant: options });
}
return enqueueSnackbar(message, options);
}

View File

@@ -14,11 +14,11 @@ import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { UpdateState, WebUiChannel, WebUiUpdateStatus } from '@/lib/graphql/generated/graphql.ts';
import { useLocalStorage } from '@/util/useStorage.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { makeToast } from '@/components/util/Toast.tsx';
import { useLocalStorage } from '@/modules/core/hooks/useStorage.tsx';
import { requestManager } from '@/lib/requests/requests/RequestManager.ts';
import { makeToast } from '@/lib/ui/Toast.ts';
import { ABOUT_WEBUI, WEBUI_UPDATE_CHECK } from '@/lib/graphql/fragments/InfoFragments.ts';
import { VersionUpdateInfoDialog } from '@/components/util/VersionUpdateInfoDialog.tsx';
import { useUpdateChecker } from '@/util/useUpdateChecker';