diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts index fb9aa6b9e7..5d0a514e53 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.css.ts @@ -2,4 +2,14 @@ import { style } from '@vanilla-extract/css'; export const viewport = style({ height: '100%', width: '100%', + position: 'relative', +}); + +export const container = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + width: '100%', }); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx index 2606256a19..3a5432139d 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/affine-error-fallback.tsx @@ -1,9 +1,12 @@ +import { useI18n } from '@affine/i18n'; import { getCurrentStore } from '@toeverything/infra'; import { Provider } from 'jotai/react'; import type { FC } from 'react'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; +import { useRouteError } from 'react-router-dom'; import * as styles from './affine-error-fallback.css'; +import { ErrorDetail } from './error-basic/error-detail'; import type { FallbackProps } from './error-basic/fallback-creator'; import { ERROR_REFLECT_KEY } from './error-basic/fallback-creator'; import { DumpInfo } from './error-basic/info-logger'; @@ -44,3 +47,25 @@ export const AffineErrorFallback: FC = props => { ); }; + +export const AffineErrorComponent = () => { + const error = useRouteError() as Error; + + const t = useI18n(); + + const reloadPage = useCallback(() => { + document.location.reload(); + }, []); + + return ( + + ); +}; diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404-status.assets.svg b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404-status.assets.svg deleted file mode 100644 index f996812afa..0000000000 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404-status.assets.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/frontend/core/src/components/affine/empty/assets/404.dark.png b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404.dark.png similarity index 100% rename from packages/frontend/core/src/components/affine/empty/assets/404.dark.png rename to packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404.dark.png diff --git a/packages/frontend/core/src/components/affine/empty/assets/404.light.png b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404.light.png similarity index 100% rename from packages/frontend/core/src/components/affine/empty/assets/404.light.png rename to packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/404.light.png diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500.dark.png b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500.dark.png new file mode 100644 index 0000000000..5cb26e6f4f Binary files /dev/null and b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500.dark.png differ diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500.light.png b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500.light.png new file mode 100644 index 0000000000..627affbc88 Binary files /dev/null and b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/500.light.png differ diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/dark-500-status.assets.svg b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/dark-500-status.assets.svg deleted file mode 100644 index f87766bfd6..0000000000 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/dark-500-status.assets.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/light-500-status.assets.svg b/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/light-500-status.assets.svg deleted file mode 100644 index 69823de351..0000000000 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-assets/light-500-status.assets.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts index 3b2091b8b5..e4c7a0bf48 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.css.ts @@ -1,35 +1,97 @@ +import { cssVarV2 } from '@toeverything/theme/v2'; import { style } from '@vanilla-extract/css'; export const errorLayout = style({ + position: 'absolute', display: 'flex', + flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%', width: '100%', - gap: '20px', + transition: 'all 0.3s ease-in-out', }); -export const errorDetailStyle = style({ +export const errorContainer = style({ display: 'flex', flexDirection: 'column', - maxWidth: '420px', -}); -export const errorTitle = style({ - fontSize: '32px', - lineHeight: '44px', - fontWeight: 700, -}); -export const errorImage = style({ - height: '178px', - maxWidth: '400px', - flexGrow: 1, - backgroundSize: 'cover', -}); -export const errorDescription = style({ - marginTop: '24px', -}); -export const errorFooter = style({ - marginTop: '24px', -}); -export const errorDivider = style({ - width: '20px', + justifyContent: 'center', + alignItems: 'flex-start', height: '100%', + width: '100%', + gap: '8px', + padding: '16px', + maxWidth: '400px', + transition: 'max-width 0.3s ease-in-out', + selectors: { + '&[data-show-stack="true"]': { + maxWidth: '600px', + }, + }, +}); + +export const label = style({ + width: '100%', + overflow: 'hidden', + textWrap: 'wrap', + wordBreak: 'break-word', +}); + +export const scrollArea = style({ + height: 'auto', + maxHeight: 0, + transition: 'max-height 0.3s ease-in-out', + color: cssVarV2('text/secondary'), + fontSize: 14, + lineHeight: '22px', + fontWeight: 400, + selectors: { + '&[data-show-stack="true"]': { + maxHeight: '200px', + }, + }, +}); + +export const illustration = style({ + maxWidth: '100%', + width: 300, + alignSelf: 'center', +}); + +export const text = style({ + fontSize: 14, + lineHeight: '22px', + fontWeight: 400, + color: cssVarV2('text/primary'), + marginBottom: 4, +}); + +export const actionContainer = style({ + display: 'flex', + marginTop: '16px', + width: '100%', + gap: '32px', + justifyContent: 'center', + alignItems: 'center', +}); + +export const actionButton = style({ + padding: '4px 8px', + fontSize: 12, + minWidth: '120px', +}); + +export const actionContent = style({ + display: 'flex', + alignItems: 'center', +}); + +export const arrowIcon = style({ + transition: 'transform 0.3s ease-in-out', + marginLeft: '8px', + width: '16px', + height: '16px', + selectors: { + '&[data-show-stack="true"]': { + transform: 'rotate(180deg)', + }, + }, }); diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx index 929534efe9..5ac66a6fde 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-basic/error-detail.tsx @@ -1,13 +1,15 @@ -import { Button } from '@affine/component/ui/button'; +import { Scrollable, ThemedImg } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { Trans, useI18n } from '@affine/i18n'; -import { useTheme } from 'next-themes'; +import { ArrowDownSmallIcon } from '@blocksuite/icons/rc'; import type { FC, PropsWithChildren, ReactNode } from 'react'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; -import imageUrlFor404 from '../error-assets/404-status.assets.svg'; -import imageUrlForDark500 from '../error-assets/dark-500-status.assets.svg'; -import imageUrlForLight500 from '../error-assets/light-500-status.assets.svg'; +import { ActionButton } from '../../empty/action-button'; +import imageUrlForDark404 from '../error-assets/404.dark.png'; +import imageUrlForLight404 from '../error-assets/404.light.png'; +import imageUrlForDark500 from '../error-assets/500.dark.png'; +import imageUrlForLight500 from '../error-assets/500.light.png'; import * as styles from './error-detail.css'; export enum ErrorStatus { @@ -17,21 +19,20 @@ export enum ErrorStatus { export interface ErrorDetailProps extends PropsWithChildren { status?: ErrorStatus; - direction?: 'column' | 'row'; title: string; description: ReactNode | Array; buttonText?: string; onButtonClick?: () => void | Promise; resetError?: () => void; - withoutImage?: boolean; + error?: Error; } const imageMap = new Map([ [ ErrorStatus.NotFound, { - light: imageUrlFor404, // TODO(@catsjuice): Ask designer for dark/light mode image. - dark: imageUrlFor404, + light: imageUrlForLight404, + dark: imageUrlForDark404, }, ], [ @@ -49,16 +50,19 @@ const imageMap = new Map([ export const ErrorDetail: FC = props => { const { status = ErrorStatus.Unexpected, - direction = 'row', description, onButtonClick, resetError, - withoutImage, + error, } = props; const descriptions = Array.isArray(description) ? description : [description]; const [isBtnLoading, setBtnLoading] = useState(false); + const [showStack, setShowStack] = useState(false); const t = useI18n(); - const { resolvedTheme } = useTheme(); + + const onToggleStack = useCallback(() => { + setShowStack(!showStack); + }, [showStack]); const onBtnClick = useAsyncCallback(async () => { try { @@ -70,36 +74,62 @@ export const ErrorDetail: FC = props => { } }, [onButtonClick, resetError]); + const desc = descriptions.map((item, i) => ( +

+ {item} +

+ )); + return ( -
-
-

{props.title}

- {descriptions.map((item, i) => ( -

- {item} -

- ))} -
- + {props.buttonText ?? t['com.affine.error.reload']()} +
- {withoutImage ? null : ( -
- )}
); }; diff --git a/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx index 3463e6829c..b195397d48 100644 --- a/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx +++ b/packages/frontend/core/src/components/affine/affine-error-boundary/error-fallbacks/any-error-fallback.tsx @@ -24,6 +24,7 @@ export const AnyErrorFallback: FC = props => { description={ 'message' in (error as Error) ? (error as Error).message : `${error}` } + error={error as Error} /> ); }; diff --git a/packages/frontend/core/src/desktop/router.tsx b/packages/frontend/core/src/desktop/router.tsx index be6d41dc94..6aa3c41883 100644 --- a/packages/frontend/core/src/desktop/router.tsx +++ b/packages/frontend/core/src/desktop/router.tsx @@ -8,6 +8,7 @@ import { useNavigate, } from 'react-router-dom'; +import { AffineErrorComponent } from '../components/affine/affine-error-boundary/affine-error-fallback'; import { NavigateContext } from '../components/hooks/use-navigate-helper'; import { RootWrapper } from './pages/root'; @@ -31,6 +32,7 @@ export function RootRouter() { export const topLevelRoutes = [ { element: , + errorElement: , children: [ { path: '/', diff --git a/packages/frontend/core/src/mobile/pages/workspace/index.tsx b/packages/frontend/core/src/mobile/pages/workspace/index.tsx index 16f39ca6f6..c0173b532b 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/index.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/index.tsx @@ -1,4 +1,5 @@ import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; +import { AffineErrorComponent } from '@affine/core/components/affine/affine-error-boundary/affine-error-fallback'; import { AppFallback } from '@affine/core/components/affine/app-container'; import { PageNotFound } from '@affine/core/desktop/pages/404'; import { MobileWorkbenchRoot } from '@affine/core/desktop/pages/workspace/workbench-root'; @@ -59,6 +60,7 @@ const warpedRoutes = workbenchRoutes.map((originalRoute: RouteObject) => { Component: () => { return ; }, + errorElement: , }; }); diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-root.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-root.tsx index ef7ffb5a21..6f0c1ee2c1 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-root.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-root.tsx @@ -1,4 +1,5 @@ import { ResizePanel } from '@affine/component/resize-panel'; +import { AffineErrorComponent } from '@affine/core/components/affine/affine-error-boundary/affine-error-fallback'; import { rightSidebarWidthAtom } from '@affine/core/components/atoms'; import { workbenchRoutes } from '@affine/core/desktop/workbench-router'; import { @@ -29,6 +30,7 @@ const useAdapter = BUILD_CONFIG.isElectron const routes: RouteObject[] = [ { element: , + errorElement: , children: workbenchRoutes, }, ]; diff --git a/packages/frontend/i18n/src/resources/ar.json b/packages/frontend/i18n/src/resources/ar.json index b0a6c48435..c0244ead4b 100644 --- a/packages/frontend/i18n/src/resources/ar.json +++ b/packages/frontend/i18n/src/resources/ar.json @@ -687,7 +687,7 @@ "com.affine.error.no-page-root.title": "محتوى المستند مفقود", "com.affine.error.page-not-found.title": "تحديث", "com.affine.error.refetch": "إعادة جلب", - "com.affine.error.reload": "إعادة تحميل", + "com.affine.error.reload": "AFFiNE إعادة تحميل", "com.affine.error.retry": "تحديث", "com.affine.error.unexpected-error.title": "هناك خطأ ما...", "com.affine.expired.page.subtitle": "يرجى طلب رابط إعادة تعيين كلمة المرور جديد.", diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 7adc1bd747..26841af466 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -704,7 +704,8 @@ "com.affine.error.no-page-root.title": "Doc content is missing", "com.affine.error.page-not-found.title": "Refresh", "com.affine.error.refetch": "Refetch", - "com.affine.error.reload": "Reload", + "com.affine.error.reload": "Reload AFFiNE", + "com.affine.error.hide-error": "Hide error", "com.affine.error.retry": "Refresh", "com.affine.error.unexpected-error.title": "Something is wrong...", "com.affine.expired.page.subtitle": "Please request a new reset password link.", diff --git a/packages/frontend/i18n/src/resources/fr.json b/packages/frontend/i18n/src/resources/fr.json index 757182a85c..65d126533f 100644 --- a/packages/frontend/i18n/src/resources/fr.json +++ b/packages/frontend/i18n/src/resources/fr.json @@ -674,7 +674,7 @@ "com.affine.error.no-page-root.title": "Le contenu du document est manquant.", "com.affine.error.page-not-found.title": "Rafraichir", "com.affine.error.refetch": "Récupérer", - "com.affine.error.reload": "Récupérer", + "com.affine.error.reload": "Récupérer AFFiNE", "com.affine.error.retry": "Rafraichir", "com.affine.error.unexpected-error.title": "Quelque chose ne va pas...", "com.affine.expired.page.subtitle": "Merci de demander un nouveau lien pour réinitialiser votre mot de passe", diff --git a/packages/frontend/i18n/src/resources/ko.json b/packages/frontend/i18n/src/resources/ko.json index c1ba8dc821..08515754e6 100644 --- a/packages/frontend/i18n/src/resources/ko.json +++ b/packages/frontend/i18n/src/resources/ko.json @@ -651,7 +651,7 @@ "com.affine.error.no-page-root.title": "페이지 콘텐츠가 누락됨", "com.affine.error.page-not-found.title": "새로고침", "com.affine.error.refetch": "다시 가져오기", - "com.affine.error.reload": "다시 로드", + "com.affine.error.reload": "다시 로드 AFFiNE", "com.affine.error.retry": "새로고침", "com.affine.error.unexpected-error.title": "뭔가 잘못되었습니다...", "com.affine.expired.page.subtitle": "비밀번호 재설정 링크를 요청해 주세요.", diff --git a/packages/frontend/i18n/src/resources/pt-BR.json b/packages/frontend/i18n/src/resources/pt-BR.json index bbb5668a7a..6aa04d97c3 100644 --- a/packages/frontend/i18n/src/resources/pt-BR.json +++ b/packages/frontend/i18n/src/resources/pt-BR.json @@ -518,7 +518,7 @@ "com.affine.error.no-page-root.title": "Conteúdo da página está faltando", "com.affine.error.page-not-found.title": "Atualizar", "com.affine.error.refetch": "Recarregar", - "com.affine.error.reload": "Recarregar", + "com.affine.error.reload": "Recarregar AFFiNE", "com.affine.error.retry": "Atualizar", "com.affine.error.unexpected-error.title": "Algo está errado...", "com.affine.expired.page.subtitle": "Por favor, solicite um novo link de redefinição de senha.", diff --git a/packages/frontend/i18n/src/resources/ru.json b/packages/frontend/i18n/src/resources/ru.json index 75c7b2a139..ef2548fe2f 100644 --- a/packages/frontend/i18n/src/resources/ru.json +++ b/packages/frontend/i18n/src/resources/ru.json @@ -691,7 +691,7 @@ "com.affine.error.no-page-root.title": "Содержимое документа отсутствует", "com.affine.error.page-not-found.title": "Обновить", "com.affine.error.refetch": "Повторно получить данные", - "com.affine.error.reload": "Перезагрузить", + "com.affine.error.reload": "Перезагрузить AFFiNE", "com.affine.error.retry": "Обновить", "com.affine.error.unexpected-error.title": "Что-то не так...", "com.affine.expired.page.subtitle": "Пожалуйста, запросите новую ссылку для восстановления пароля.", diff --git a/packages/frontend/i18n/src/resources/zh-Hans.json b/packages/frontend/i18n/src/resources/zh-Hans.json index 19e670bae5..cdc2bcadaa 100644 --- a/packages/frontend/i18n/src/resources/zh-Hans.json +++ b/packages/frontend/i18n/src/resources/zh-Hans.json @@ -692,7 +692,8 @@ "com.affine.error.no-page-root.title": "文档内容丢失", "com.affine.error.page-not-found.title": "刷新", "com.affine.error.refetch": "重新连接", - "com.affine.error.reload": "重新加载", + "com.affine.error.reload": "重新加载 AFFiNE", + "com.affine.error.hide-error": "隐藏错误详情", "com.affine.error.retry": "刷新", "com.affine.error.unexpected-error.title": "出了点问题...", "com.affine.expired.page.subtitle": "请重新请求重置密码链接。",