mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-06 16:54:59 -05:00
I learned that the inline selector syntax recreates the selector function on every render: ```ts const val = useAppSelector((s) => s.slice.val) ``` Not good! Better is to create a selector outside the function and use it. Doing that for all selectors now, most of the way through now. Feels snappier.
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
|
import { createSelector } from '@reduxjs/toolkit';
|
|
import { useAppSelector } from 'app/store/storeHooks';
|
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
|
import { toast } from 'features/toast/toast';
|
|
import newGithubIssueUrl from 'new-github-issue-url';
|
|
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
|
import { memo, useCallback, useMemo } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { PiArrowCounterClockwiseBold, PiArrowSquareOutBold, PiCopyBold } from 'react-icons/pi';
|
|
import { serializeError } from 'serialize-error';
|
|
|
|
type Props = {
|
|
error: Error;
|
|
resetErrorBoundary: () => void;
|
|
};
|
|
|
|
const selectIsLocal = createSelector(selectConfigSlice, (config) => config.isLocal);
|
|
|
|
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
|
const { t } = useTranslation();
|
|
const isLocal = useAppSelector(selectIsLocal);
|
|
|
|
const handleCopy = useCallback(() => {
|
|
const text = JSON.stringify(serializeError(error), null, 2);
|
|
navigator.clipboard.writeText(`\`\`\`\n${text}\n\`\`\``);
|
|
toast({
|
|
id: 'ERROR_COPIED',
|
|
title: t('toast.errorCopied'),
|
|
});
|
|
}, [error, t]);
|
|
|
|
const url = useMemo(() => {
|
|
if (isLocal) {
|
|
return newGithubIssueUrl({
|
|
user: 'invoke-ai',
|
|
repo: 'InvokeAI',
|
|
template: 'BUG_REPORT.yml',
|
|
title: `[bug]: ${error.name}: ${error.message}`,
|
|
});
|
|
} else {
|
|
return 'https://support.invoke.ai/support/tickets/new';
|
|
}
|
|
}, [error.message, error.name, isLocal]);
|
|
|
|
return (
|
|
<Flex layerStyle="body" w="100vw" h="100vh" alignItems="center" justifyContent="center" p={4}>
|
|
<Flex layerStyle="first" flexDir="column" borderRadius="base" justifyContent="center" gap={8} p={16}>
|
|
<Flex alignItems="center" gap="2">
|
|
<Image src={InvokeLogoYellow} alt="invoke-logo" w="24px" h="24px" minW="24px" minH="24px" userSelect="none" />
|
|
<Heading fontSize="2xl">{t('common.somethingWentWrong')}</Heading>
|
|
</Flex>
|
|
|
|
<Flex
|
|
layerStyle="second"
|
|
px={8}
|
|
py={4}
|
|
gap={4}
|
|
borderRadius="base"
|
|
justifyContent="space-between"
|
|
alignItems="center"
|
|
>
|
|
<Text fontWeight="semibold" color="error.400">
|
|
{error.name}: {error.message}
|
|
</Text>
|
|
</Flex>
|
|
<Flex gap={4}>
|
|
<Button leftIcon={<PiArrowCounterClockwiseBold />} onClick={resetErrorBoundary}>
|
|
{t('accessibility.resetUI')}
|
|
</Button>
|
|
<Button leftIcon={<PiCopyBold />} onClick={handleCopy}>
|
|
{t('common.copyError')}
|
|
</Button>
|
|
<Link href={url} isExternal>
|
|
<Button leftIcon={<PiArrowSquareOutBold />}>
|
|
{isLocal ? t('accessibility.createIssue') : t('accessibility.submitSupportTicket')}
|
|
</Button>
|
|
</Link>
|
|
</Flex>
|
|
</Flex>
|
|
</Flex>
|
|
);
|
|
};
|
|
|
|
export default memo(AppErrorBoundaryFallback);
|