From 2e354ae59e864b0c9992f371859aad35bd7c5c15 Mon Sep 17 00:00:00 2001 From: Himself65 Date: Thu, 6 Apr 2023 11:14:25 -0500 Subject: [PATCH] refactor(component): editor component (#1834) --- apps/electron/yarn.lock | 12 +++ .../components/__debug__/client/Editor.tsx | 8 +- .../web/src/components/page-detail-editor.tsx | 1 - apps/web/src/pages/_debug/broadcast.dev.tsx | 3 - packages/component/package.json | 1 + .../block-suite-editor/index.stories.tsx | 64 +++++++++++-- .../components/block-suite-editor/index.tsx | 93 +++++++++++++------ yarn.lock | 12 +++ 8 files changed, 147 insertions(+), 47 deletions(-) diff --git a/apps/electron/yarn.lock b/apps/electron/yarn.lock index b30388398b..31fd9e2c02 100644 --- a/apps/electron/yarn.lock +++ b/apps/electron/yarn.lock @@ -77,6 +77,7 @@ __metadata: react-dnd: ^16.0.1 react-dnd-html5-backend: ^16.0.1 react-dom: ^18.2.0 + react-error-boundary: ^4.0.3 react-is: ^18.2.0 serve: ^14.2.0 storybook: ^7.0.2 @@ -14357,6 +14358,17 @@ __metadata: languageName: node linkType: hard +"react-error-boundary@npm:^4.0.3": + version: 4.0.3 + resolution: "react-error-boundary@npm:4.0.3" + dependencies: + "@babel/runtime": ^7.12.5 + peerDependencies: + react: ">=16.13.1" + checksum: 50813803d3f03eb2ca07c1250a4a001ffe054f5b3f49b15ea5f0a4e1108f549bc7d8def15db51d518516997d34cebc663f4276246301b4a40a72e83a784f5c38 + languageName: node + linkType: hard + "react-i18next@npm:^12.2.0": version: 12.2.0 resolution: "react-i18next@npm:12.2.0" diff --git a/apps/web/src/components/__debug__/client/Editor.tsx b/apps/web/src/components/__debug__/client/Editor.tsx index 7486db72d5..23d859ba73 100644 --- a/apps/web/src/components/__debug__/client/Editor.tsx +++ b/apps/web/src/components/__debug__/client/Editor.tsx @@ -40,13 +40,7 @@ const Editor: React.FC<{ return <>loading...; } return ( - + ); }; diff --git a/apps/web/src/components/page-detail-editor.tsx b/apps/web/src/components/page-detail-editor.tsx index 83c44678c9..c2ee8c14e5 100644 --- a/apps/web/src/components/page-detail-editor.tsx +++ b/apps/web/src/components/page-detail-editor.tsx @@ -71,7 +71,6 @@ export const PageDetailEditor: React.FC = ({ height: 'calc(100% - 52px)', }} key={pageId} - blockSuiteWorkspace={blockSuiteWorkspace} mode={isPublic ? 'page' : currentMode} page={page} onInit={useCallback( diff --git a/apps/web/src/pages/_debug/broadcast.dev.tsx b/apps/web/src/pages/_debug/broadcast.dev.tsx index ee29429ba6..c4497ffc7f 100644 --- a/apps/web/src/pages/_debug/broadcast.dev.tsx +++ b/apps/web/src/pages/_debug/broadcast.dev.tsx @@ -31,9 +31,6 @@ const BroadcastPage: React.FC = () => { const [provider, setProvider] = useState( null ); - useEffect(() => { - globalThis.currentBlockSuiteWorkspace = blockSuiteWorkspace; - }, [blockSuiteWorkspace]); useEffect(() => { const provider = createBroadCastChannelProvider(blockSuiteWorkspace); setProvider(provider); diff --git a/packages/component/package.json b/packages/component/package.json index 51fc8a866c..3414e89bbe 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -37,6 +37,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.3", "react-is": "^18.2.0" }, "devDependencies": { diff --git a/packages/component/src/components/block-suite-editor/index.stories.tsx b/packages/component/src/components/block-suite-editor/index.stories.tsx index cacae2f9b3..e1494f46ce 100644 --- a/packages/component/src/components/block-suite-editor/index.stories.tsx +++ b/packages/component/src/components/block-suite-editor/index.stories.tsx @@ -5,6 +5,7 @@ import type { Page } from '@blocksuite/store'; import { Workspace } from '@blocksuite/store'; import { expect } from '@storybook/jest'; import type { Meta, StoryFn } from '@storybook/react'; +import { useState } from 'react'; import type { EditorProps } from '.'; import { BlockSuiteEditor } from '.'; @@ -40,19 +41,26 @@ export default { component: BlockSuiteEditor, } satisfies BlockSuiteMeta; -const Template: StoryFn = (args: EditorProps) => { +const Template: StoryFn = (props: EditorProps) => { return ( - + <> + +
+ ); }; + export const Empty = Template.bind({}); Empty.play = async ({ canvasElement }) => { const editorContainer = canvasElement.querySelector( - '[data-testid="editor-test-page0"]' + '[data-testid="editor-page0"]' ) as HTMLDivElement; expect(editorContainer).not.toBeNull(); await new Promise(resolve => { @@ -67,3 +75,45 @@ Empty.play = async ({ canvasElement }) => { Empty.args = { mode: 'page', }; + +export const Error: StoryFn = () => { + const [props, setProps] = useState>({ + page: null!, + onInit: null!, + }); + return ( + { + setProps({ + page, + onInit: initPage, + }); + }} + /> + ); +}; + +Error.play = async ({ canvasElement }) => { + { + const editorContainer = canvasElement.querySelector( + '[data-testid="editor-page0"]' + ); + expect(editorContainer).toBeNull(); + } + { + const button = canvasElement.querySelector( + '[data-testid="error-fallback-reset-button"]' + ) as HTMLButtonElement; + expect(button).not.toBeNull(); + button.click(); + await new Promise(resolve => setTimeout(() => resolve(), 50)); + } + { + const editorContainer = canvasElement.querySelector( + '[data-testid="editor-page0"]' + ); + expect(editorContainer).not.toBeNull(); + } +}; diff --git a/packages/component/src/components/block-suite-editor/index.tsx b/packages/component/src/components/block-suite-editor/index.tsx index 4d91985ff6..f409bdd6c1 100644 --- a/packages/component/src/components/block-suite-editor/index.tsx +++ b/packages/component/src/components/block-suite-editor/index.tsx @@ -1,11 +1,13 @@ import type { BlockHub } from '@blocksuite/blocks'; import { EditorContainer } from '@blocksuite/editor'; -import type { Page, Workspace } from '@blocksuite/store'; -import type { CSSProperties } from 'react'; -import { useEffect, useRef } from 'react'; +import { assertExists } from '@blocksuite/global/utils'; +import type { Page } from '@blocksuite/store'; +import type { CSSProperties, ReactElement } from 'react'; +import { memo, useCallback, useEffect, useRef } from 'react'; +import type { FallbackProps } from 'react-error-boundary'; +import { ErrorBoundary } from 'react-error-boundary'; export type EditorProps = { - blockSuiteWorkspace: Workspace; page: Page; mode: 'page' | 'edgeless'; onInit: (page: Page, editor: Readonly) => void; @@ -13,51 +15,45 @@ export type EditorProps = { style?: CSSProperties; }; +export type ErrorBoundaryProps = { + onReset?: () => void; +}; + declare global { - // eslint-disable-next-line no-var - var currentBlockSuiteWorkspace: Workspace | undefined; // eslint-disable-next-line no-var var currentPage: Page | undefined; // eslint-disable-next-line no-var var currentEditor: EditorContainer | undefined; } -export const BlockSuiteEditor = (props: EditorProps) => { +const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => { const page = props.page; + assertExists(page, 'page should not be null'); const editorRef = useRef(null); const blockHubRef = useRef(null); if (editorRef.current === null) { editorRef.current = new EditorContainer(); - editorRef.current.page = props.page; - editorRef.current.mode = props.mode; globalThis.currentEditor = editorRef.current; } - const ref = useRef(null); - useEffect(() => { - if (editorRef.current) { - editorRef.current.mode = props.mode; - } - }, [props.mode]); - - useEffect(() => { - const editor = editorRef.current; - if (!editor || !ref.current || !page) { - return; - } - - editor.page = page; + const editor = editorRef.current; + assertExists(editorRef, 'editorRef.current should not be null'); + if (editor.mode !== props.mode) { + editor.mode = props.mode; + } + if (editor.page !== props.page) { + editor.page = props.page; if (page.root === null) { props.onInit(page, editor); } props.onLoad?.(page, editor); - return; - }, [page, props]); + } + const ref = useRef(null); useEffect(() => { const editor = editorRef.current; + assertExists(editor); const container = ref.current; - - if (!editor || !container || !page) { + if (!container) { return; } if (page.awarenessStore.getFlag('enable_block_hub')) { @@ -82,13 +78,52 @@ export const BlockSuiteEditor = (props: EditorProps) => { blockHubRef.current?.remove(); container.removeChild(editor); }; - }, [page, props.mode]); + }, [page]); return (
); }; + +const BlockSuiteErrorFallback = ( + props: FallbackProps & ErrorBoundaryProps +): ReactElement => { + return ( +
+

Sorry.. there was an error

+
{props.error.message}
+ +
+ ); +}; + +export const BlockSuiteEditor = memo(function BlockSuiteEditor( + props: EditorProps & ErrorBoundaryProps +): ReactElement { + return ( + ( + + ), + [props.onReset] + )} + > + + + ); +}); + +BlockSuiteEditor.displayName = 'BlockSuiteEditor'; diff --git a/yarn.lock b/yarn.lock index e0d082033f..f9ba352386 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,6 +77,7 @@ __metadata: react-dnd: ^16.0.1 react-dnd-html5-backend: ^16.0.1 react-dom: ^18.2.0 + react-error-boundary: ^4.0.3 react-is: ^18.2.0 serve: ^14.2.0 storybook: ^7.0.2 @@ -16017,6 +16018,17 @@ __metadata: languageName: node linkType: hard +"react-error-boundary@npm:^4.0.3": + version: 4.0.3 + resolution: "react-error-boundary@npm:4.0.3" + dependencies: + "@babel/runtime": ^7.12.5 + peerDependencies: + react: ">=16.13.1" + checksum: 50813803d3f03eb2ca07c1250a4a001ffe054f5b3f49b15ea5f0a4e1108f549bc7d8def15db51d518516997d34cebc663f4276246301b4a40a72e83a784f5c38 + languageName: node + linkType: hard + "react-i18next@npm:^12.2.0": version: 12.2.0 resolution: "react-i18next@npm:12.2.0"