Files
suwayomi-material-you-webui/src/modules/extension/components/ExtensionCard.tsx

187 lines
6.8 KiB
TypeScript
Raw Normal View History

/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
2021-01-26 23:32:12 +03:30
* 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/.
*/
2021-01-26 23:32:12 +03:30
import { useState } from 'react';
2021-09-09 17:51:22 +04:30
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Button from '@mui/material/Button';
import Avatar from '@mui/material/Avatar';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { useTranslation } from 'react-i18next';
import { requestManager } from '@/lib/requests/RequestManager.ts';
2024-10-05 16:09:34 +02:00
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
2024-10-05 23:22:42 +02:00
import { makeToast } from '@/modules/core/utils/Toast.ts';
2024-10-05 16:09:34 +02:00
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
import {
ExtensionAction,
ExtensionState,
InstalledState,
InstalledStates,
TExtension,
} from '@/modules/extension/Extensions.types.ts';
import {
EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP,
EXTENSION_ACTION_TO_NEXT_ACTION_MAP,
EXTENSION_ACTION_TO_STATE_MAP,
INSTALLED_STATE_TO_TRANSLATION_KEY_MAP,
} from '@/modules/extension/Extensions.constants.ts';
import { getInstalledState } from '@/modules/extension/Extensions.utils.ts';
2020-12-24 16:50:50 +01:00
interface IProps {
extension: TExtension;
handleUpdate: () => void;
showSourceRepo: boolean;
2020-12-24 16:50:50 +01:00
}
2023-10-28 00:32:02 +02:00
export function ExtensionCard(props: IProps) {
const { t } = useTranslation();
2020-12-25 03:08:08 +03:30
const {
extension: { name, lang, versionName, isInstalled, hasUpdate, isObsolete, pkgName, iconUrl, isNsfw, repo },
handleUpdate,
showSourceRepo,
2020-12-25 03:08:08 +03:30
} = props;
const [installedState, setInstalledState] = useState<InstalledStates>(
getInstalledState(isInstalled, isObsolete, hasUpdate),
);
2020-12-25 03:08:08 +03:30
const langPress = lang === 'all' ? t('extension.language.all') : lang.toUpperCase();
2020-12-24 16:50:50 +01:00
const requestExtensionAction = async (action: ExtensionAction): Promise<void> => {
const nextAction = EXTENSION_ACTION_TO_NEXT_ACTION_MAP[action];
const state = EXTENSION_ACTION_TO_STATE_MAP[action];
2020-12-25 03:08:08 +03:30
try {
setInstalledState(state);
switch (action) {
case ExtensionAction.INSTALL:
await requestManager.updateExtension(pkgName, { install: true, isObsolete }).response;
break;
case ExtensionAction.UNINSTALL:
await requestManager.updateExtension(pkgName, { uninstall: true, isObsolete }).response;
break;
case ExtensionAction.UPDATE:
await requestManager.updateExtension(pkgName, { update: true, isObsolete }).response;
break;
default:
throw new Error(`Unexpected ExtensionAction "${action}"`);
}
setInstalledState(nextAction);
handleUpdate();
} catch (e) {
setInstalledState(getInstalledState(isInstalled, isObsolete, hasUpdate));
makeToast(t(EXTENSION_ACTION_TO_FAILURE_TRANSLATION_KEY_MAP[action]), 'error');
}
};
2021-01-29 15:23:29 +03:30
function handleButtonClick() {
2021-03-29 02:47:51 +04:30
switch (installedState) {
case ExtensionAction.INSTALL:
case ExtensionAction.UPDATE:
case ExtensionAction.UNINSTALL:
requestExtensionAction(installedState).catch(
defaultPromiseErrorHandler(`ExtensionCard:handleButtonClick(${installedState})`),
);
2021-03-29 02:47:51 +04:30
break;
case ExtensionState.OBSOLETE:
requestExtensionAction(ExtensionAction.UNINSTALL).catch(
defaultPromiseErrorHandler(`ExtensionCard:handleButtonClick(${installedState})`),
);
2021-03-29 02:47:51 +04:30
break;
default:
break;
2021-01-29 15:23:29 +03:30
}
}
2020-12-24 16:50:50 +01:00
return (
<Card>
<CardContent
sx={{
display: 'flex',
alignItems: 'center',
2024-08-30 21:34:58 +02:00
gap: 1,
p: 1.5,
'&:last-child': {
2024-08-30 21:34:58 +02:00
paddingBottom: 1.5,
},
}}
>
<Avatar
variant="rounded"
sx={{
width: 56,
height: 56,
flex: '0 0 auto',
background: 'transparent',
}}
alt={name}
>
<SpinnerImage
spinnerStyle={{ small: true }}
imgStyle={{ objectFit: 'cover', width: '100%', height: '100%' }}
2020-12-24 16:50:50 +01:00
alt={name}
src={requestManager.getValidImgUrlFor(iconUrl)}
/>
</Avatar>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
flexShrink: 1,
wordBreak: 'break-word',
2024-08-30 21:34:58 +02:00
justifyContent: 'center',
}}
>
2024-08-30 21:34:58 +02:00
<Typography variant="h6" component="h3">
{name}
</Typography>
2024-09-03 17:15:47 +02:00
<Typography
variant="caption"
sx={{
display: 'block',
}}
>
{langPress} {versionName}
{isNsfw && (
2024-09-03 17:15:47 +02:00
<Typography
variant="caption"
color="error"
sx={{
display: 'inline',
}}
>
{' 18+'}
</Typography>
)}
</Typography>
{showSourceRepo && (
2024-09-03 17:15:47 +02:00
<Typography
variant="caption"
sx={{
display: 'block',
}}
>
{repo}
</Typography>
)}
</Box>
2021-03-29 02:47:51 +04:30
<Button
variant="outlined"
sx={{ color: installedState === InstalledState.OBSOLETE ? 'red' : 'inherit', flexShrink: 0 }}
2021-03-29 02:47:51 +04:30
onClick={() => handleButtonClick()}
>
{t(INSTALLED_STATE_TO_TRANSLATION_KEY_MAP[installedState])}
2021-03-29 02:47:51 +04:30
</Button>
2020-12-24 16:50:50 +01:00
</CardContent>
</Card>
);
}