mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
fix(core): implement editor timeout and report error from boundary (#5105) ci: add sentry env when frontend assets build (#5131) fix(core): expose catched editor load error (#5133) fix(infra): use blocksuite api to check compatibility (#5137) fix(infra): compatibility logic follow blocksuite (#5143) fix(core): rerender error boundary when route change and improve sentry report (#5147)
This commit is contained in:
@@ -7,14 +7,12 @@ import type { CSSProperties, ReactElement } from 'react';
|
||||
import {
|
||||
memo,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { FallbackProps } from 'react-error-boundary';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { type Map as YMap } from 'yjs';
|
||||
|
||||
import { Skeleton } from '../../ui/skeleton';
|
||||
import {
|
||||
@@ -77,6 +75,67 @@ const useBlockElementById = (
|
||||
return blockElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Define error to unexpected state together in the future.
|
||||
*/
|
||||
export class NoPageRootError extends Error {
|
||||
constructor(public page: Page) {
|
||||
super('Page root not found when render editor!');
|
||||
|
||||
// Log info to let sentry collect more message
|
||||
const hasExpectSpace = Array.from(page.doc.spaces.values()).some(
|
||||
doc => page.spaceDoc.guid === doc.guid
|
||||
);
|
||||
const blocks = page.spaceDoc.getMap('blocks') as YMap<YMap<any>>;
|
||||
const havePageBlock = Array.from(blocks.values()).some(
|
||||
block => block.get('sys:flavour') === 'affine:page'
|
||||
);
|
||||
console.info(
|
||||
'NoPageRootError current data: %s',
|
||||
JSON.stringify({
|
||||
expectPageId: page.id,
|
||||
expectGuid: page.spaceDoc.guid,
|
||||
hasExpectSpace,
|
||||
blockSize: blocks.size,
|
||||
havePageBlock,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Defined async cache to support suspense, instead of reflect symbol to provider persistent error cache.
|
||||
*/
|
||||
const PAGE_LOAD_KEY = Symbol('PAGE_LOAD');
|
||||
const PAGE_ROOT_KEY = Symbol('PAGE_ROOT');
|
||||
function usePageRoot(page: Page) {
|
||||
let load$ = Reflect.get(page, PAGE_LOAD_KEY);
|
||||
if (!load$) {
|
||||
load$ = page.load();
|
||||
Reflect.set(page, PAGE_LOAD_KEY, load$);
|
||||
}
|
||||
use(load$);
|
||||
|
||||
if (!page.root) {
|
||||
let root$: Promise<void> | undefined = Reflect.get(page, PAGE_ROOT_KEY);
|
||||
if (!root$) {
|
||||
root$ = new Promise((resolve, reject) => {
|
||||
const disposable = page.slots.rootAdded.once(() => {
|
||||
resolve();
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
disposable.dispose();
|
||||
reject(new NoPageRootError(page));
|
||||
}, 20 * 1000);
|
||||
});
|
||||
Reflect.set(page, PAGE_ROOT_KEY, root$);
|
||||
}
|
||||
use(root$);
|
||||
}
|
||||
|
||||
return page.root;
|
||||
}
|
||||
|
||||
const BlockSuiteEditorImpl = ({
|
||||
mode,
|
||||
page,
|
||||
@@ -86,9 +145,8 @@ const BlockSuiteEditorImpl = ({
|
||||
onModeChange,
|
||||
style,
|
||||
}: EditorProps): ReactElement => {
|
||||
if (!page.loaded) {
|
||||
use(page.waitForLoaded());
|
||||
}
|
||||
usePageRoot(page);
|
||||
|
||||
assertExists(page, 'page should not be null');
|
||||
const editorRef = useRef<EditorContainer | null>(null);
|
||||
if (editorRef.current === null) {
|
||||
@@ -176,27 +234,7 @@ const BlockSuiteEditorImpl = ({
|
||||
);
|
||||
};
|
||||
|
||||
const BlockSuiteErrorFallback = (
|
||||
props: FallbackProps & ErrorBoundaryProps
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Sorry.. there was an error</h1>
|
||||
<div>{props.error.message}</div>
|
||||
<button
|
||||
data-testid="error-fallback-reset-button"
|
||||
onClick={() => {
|
||||
props.onReset?.();
|
||||
props.resetErrorBoundary();
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
|
||||
export const EditorLoading = memo(function EditorLoading() {
|
||||
return (
|
||||
<div className={blockSuiteEditorStyle}>
|
||||
<Skeleton
|
||||
@@ -210,21 +248,12 @@ export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
|
||||
});
|
||||
|
||||
export const BlockSuiteEditor = memo(function BlockSuiteEditor(
|
||||
props: EditorProps & ErrorBoundaryProps
|
||||
props: EditorProps
|
||||
): ReactElement {
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallbackRender={useCallback(
|
||||
(fallbackProps: FallbackProps) => (
|
||||
<BlockSuiteErrorFallback {...fallbackProps} onReset={props.onReset} />
|
||||
),
|
||||
[props.onReset]
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={<BlockSuiteFallback />}>
|
||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
<Suspense fallback={<EditorLoading />}>
|
||||
<BlockSuiteEditorImpl key={props.page.id} {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BlockSuiteFallback } from '../block-suite-editor';
|
||||
import { EditorLoading } from '../block-suite-editor';
|
||||
import {
|
||||
pageDetailSkeletonStyle,
|
||||
pageDetailSkeletonTitleStyle,
|
||||
@@ -8,7 +8,7 @@ export const PageDetailSkeleton = () => {
|
||||
return (
|
||||
<div className={pageDetailSkeletonStyle}>
|
||||
<div className={pageDetailSkeletonTitleStyle} />
|
||||
<BlockSuiteFallback />
|
||||
<EditorLoading />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user