Files
suwayomi-material-you-webui/src/base/components/feedback/SnackbarWithDescription.tsx

114 lines
4.6 KiB
TypeScript
Raw Normal View History

2024-12-20 17:24:40 +01:00
/*
* 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/.
*/
2025-01-02 03:04:14 +01:00
import { closeSnackbar, CustomContentProps, SnackbarContent, VariantType } from 'notistack';
2025-09-24 00:51:13 +02:00
import { ForwardedRef, Fragment, memo } from 'react';
2024-12-20 17:24:40 +01:00
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
import Button from '@mui/material/Button';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
2024-12-20 17:24:40 +01:00
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
import { extractGraphqlExceptionInfo } from '@/lib/HelperFunctions.ts';
import { TranslationKey } from '@/base/Base.types.ts';
import { GlobalDialogManager } from '@/base/global-dialog/GlobalDialogManager.tsx';
2024-12-20 17:24:40 +01:00
const MAX_DESCRIPTION_LENGTH = 200;
2024-12-20 17:24:40 +01:00
const SNACKBAR_VARIANT_TO_TRANSLATION_KEY: Record<VariantType, TranslationKey> = {
default: 'global.label.info',
info: 'global.label.info',
success: 'global.label.success',
warning: 'global.label.warning',
error: 'global.label.error',
};
export const SnackbarWithDescription = memo(
2025-09-24 00:51:13 +02:00
({
id,
message,
description,
variant,
action,
ref,
}: CustomContentProps & {
description?: string;
ref?: ForwardedRef<HTMLDivElement>;
}) => {
const { t } = useTranslation();
const theme = useTheme();
2024-12-20 17:24:40 +01:00
2025-09-24 00:51:13 +02:00
const severity = variant === 'default' ? 'info' : variant;
const finalAction = typeof action === 'function' ? action(id) : action;
2024-12-20 17:24:40 +01:00
2025-09-24 00:51:13 +02:00
const { isGraphqlException, graphqlError, graphqlStackTrace } = extractGraphqlExceptionInfo(description);
2025-09-24 00:51:13 +02:00
const finalDescription = isGraphqlException ? graphqlError : description;
const isDescriptionTooLong = (finalDescription?.length ?? 0) > MAX_DESCRIPTION_LENGTH;
const actualDescription = isDescriptionTooLong
? finalDescription?.slice(0, MAX_DESCRIPTION_LENGTH)
: finalDescription;
2024-12-20 17:24:40 +01:00
2025-09-24 00:51:13 +02:00
const TitleComponent = actualDescription?.length ? AlertTitle : Fragment;
2025-09-24 00:51:13 +02:00
return (
<SnackbarContent ref={ref}>
<Alert
elevation={1}
severity={severity}
action={finalAction}
sx={{
wordBreak: 'break-word',
minWidth: '300px',
[theme.breakpoints.down(MediaQuery.MOBILE_WIDTH)]: {
maxWidth: '100vw',
},
[theme.breakpoints.between(MediaQuery.MOBILE_WIDTH, MediaQuery.TABLET_WIDTH)]: {
maxWidth: '75vw',
},
[theme.breakpoints.up(MediaQuery.TABLET_WIDTH)]: {
maxWidth: '50vw',
},
}}
onClose={() => closeSnackbar(id)}
>
<TitleComponent>{message}</TitleComponent>
{actualDescription}
{isDescriptionTooLong || (isGraphqlException && graphqlStackTrace) ? (
<Button
onClick={() => {
GlobalDialogManager.confirm({
2025-09-24 00:51:13 +02:00
title:
typeof message === 'string'
? message
: t(SNACKBAR_VARIANT_TO_TRANSLATION_KEY[variant]),
message: description ?? '',
actions: {
cancel: { show: false },
confirm: { title: t('global.label.close') },
},
}).catch(
defaultPromiseErrorHandler(
`SnackbarWithDescription: ${id} - ${message} - ${description}`,
),
);
}}
size="small"
>
{t('global.button.show_more')}
</Button>
) : (
''
)}
</Alert>
</SnackbarContent>
);
},
2024-12-20 17:24:40 +01:00
);