refactor(component): editor component (#1834)

This commit is contained in:
Himself65
2023-04-06 11:14:25 -05:00
committed by GitHub
parent 999796f988
commit 2e354ae59e
8 changed files with 147 additions and 47 deletions

View File

@@ -77,6 +77,7 @@ __metadata:
react-dnd: ^16.0.1 react-dnd: ^16.0.1
react-dnd-html5-backend: ^16.0.1 react-dnd-html5-backend: ^16.0.1
react-dom: ^18.2.0 react-dom: ^18.2.0
react-error-boundary: ^4.0.3
react-is: ^18.2.0 react-is: ^18.2.0
serve: ^14.2.0 serve: ^14.2.0
storybook: ^7.0.2 storybook: ^7.0.2
@@ -14357,6 +14358,17 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "react-i18next@npm:^12.2.0":
version: 12.2.0 version: 12.2.0
resolution: "react-i18next@npm:12.2.0" resolution: "react-i18next@npm:12.2.0"

View File

@@ -40,13 +40,7 @@ const Editor: React.FC<{
return <>loading...</>; return <>loading...</>;
} }
return ( return (
<BlockSuiteEditor <BlockSuiteEditor page={page} mode="page" onInit={onInit} onLoad={onLoad} />
blockSuiteWorkspace={blockSuiteWorkspace}
page={page}
mode="page"
onInit={onInit}
onLoad={onLoad}
/>
); );
}; };

View File

@@ -71,7 +71,6 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
height: 'calc(100% - 52px)', height: 'calc(100% - 52px)',
}} }}
key={pageId} key={pageId}
blockSuiteWorkspace={blockSuiteWorkspace}
mode={isPublic ? 'page' : currentMode} mode={isPublic ? 'page' : currentMode}
page={page} page={page}
onInit={useCallback( onInit={useCallback(

View File

@@ -31,9 +31,6 @@ const BroadcastPage: React.FC = () => {
const [provider, setProvider] = useState<BroadCastChannelProvider | null>( const [provider, setProvider] = useState<BroadCastChannelProvider | null>(
null null
); );
useEffect(() => {
globalThis.currentBlockSuiteWorkspace = blockSuiteWorkspace;
}, [blockSuiteWorkspace]);
useEffect(() => { useEffect(() => {
const provider = createBroadCastChannelProvider(blockSuiteWorkspace); const provider = createBroadCastChannelProvider(blockSuiteWorkspace);
setProvider(provider); setProvider(provider);

View File

@@ -37,6 +37,7 @@
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-error-boundary": "^4.0.3",
"react-is": "^18.2.0" "react-is": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -5,6 +5,7 @@ import type { Page } from '@blocksuite/store';
import { Workspace } from '@blocksuite/store'; import { Workspace } from '@blocksuite/store';
import { expect } from '@storybook/jest'; import { expect } from '@storybook/jest';
import type { Meta, StoryFn } from '@storybook/react'; import type { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
import type { EditorProps } from '.'; import type { EditorProps } from '.';
import { BlockSuiteEditor } from '.'; import { BlockSuiteEditor } from '.';
@@ -40,19 +41,26 @@ export default {
component: BlockSuiteEditor, component: BlockSuiteEditor,
} satisfies BlockSuiteMeta; } satisfies BlockSuiteMeta;
const Template: StoryFn<EditorProps> = (args: EditorProps) => { const Template: StoryFn<EditorProps> = (props: EditorProps) => {
return ( return (
<BlockSuiteEditor <>
{...args} <BlockSuiteEditor {...props} page={page} mode="page" />
blockSuiteWorkspace={blockSuiteWorkspace} <div
page={page} style={{
/> position: 'absolute',
right: 12,
bottom: 12,
}}
id="toolWrapper"
/>
</>
); );
}; };
export const Empty = Template.bind({}); export const Empty = Template.bind({});
Empty.play = async ({ canvasElement }) => { Empty.play = async ({ canvasElement }) => {
const editorContainer = canvasElement.querySelector( const editorContainer = canvasElement.querySelector(
'[data-testid="editor-test-page0"]' '[data-testid="editor-page0"]'
) as HTMLDivElement; ) as HTMLDivElement;
expect(editorContainer).not.toBeNull(); expect(editorContainer).not.toBeNull();
await new Promise<void>(resolve => { await new Promise<void>(resolve => {
@@ -67,3 +75,45 @@ Empty.play = async ({ canvasElement }) => {
Empty.args = { Empty.args = {
mode: 'page', mode: 'page',
}; };
export const Error: StoryFn = () => {
const [props, setProps] = useState<Pick<EditorProps, 'page' | 'onInit'>>({
page: null!,
onInit: null!,
});
return (
<BlockSuiteEditor
{...props}
mode="page"
onReset={() => {
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<void>(resolve => setTimeout(() => resolve(), 50));
}
{
const editorContainer = canvasElement.querySelector(
'[data-testid="editor-page0"]'
);
expect(editorContainer).not.toBeNull();
}
};

View File

@@ -1,11 +1,13 @@
import type { BlockHub } from '@blocksuite/blocks'; import type { BlockHub } from '@blocksuite/blocks';
import { EditorContainer } from '@blocksuite/editor'; import { EditorContainer } from '@blocksuite/editor';
import type { Page, Workspace } from '@blocksuite/store'; import { assertExists } from '@blocksuite/global/utils';
import type { CSSProperties } from 'react'; import type { Page } from '@blocksuite/store';
import { useEffect, useRef } from 'react'; 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 = { export type EditorProps = {
blockSuiteWorkspace: Workspace;
page: Page; page: Page;
mode: 'page' | 'edgeless'; mode: 'page' | 'edgeless';
onInit: (page: Page, editor: Readonly<EditorContainer>) => void; onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
@@ -13,51 +15,45 @@ export type EditorProps = {
style?: CSSProperties; style?: CSSProperties;
}; };
export type ErrorBoundaryProps = {
onReset?: () => void;
};
declare global { declare global {
// eslint-disable-next-line no-var
var currentBlockSuiteWorkspace: Workspace | undefined;
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var currentPage: Page | undefined; var currentPage: Page | undefined;
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var currentEditor: EditorContainer | undefined; var currentEditor: EditorContainer | undefined;
} }
export const BlockSuiteEditor = (props: EditorProps) => { const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
const page = props.page; const page = props.page;
assertExists(page, 'page should not be null');
const editorRef = useRef<EditorContainer | null>(null); const editorRef = useRef<EditorContainer | null>(null);
const blockHubRef = useRef<BlockHub | null>(null); const blockHubRef = useRef<BlockHub | null>(null);
if (editorRef.current === null) { if (editorRef.current === null) {
editorRef.current = new EditorContainer(); editorRef.current = new EditorContainer();
editorRef.current.page = props.page;
editorRef.current.mode = props.mode;
globalThis.currentEditor = editorRef.current; globalThis.currentEditor = editorRef.current;
} }
const ref = useRef<HTMLDivElement>(null); const editor = editorRef.current;
useEffect(() => { assertExists(editorRef, 'editorRef.current should not be null');
if (editorRef.current) { if (editor.mode !== props.mode) {
editorRef.current.mode = props.mode; editor.mode = props.mode;
} }
}, [props.mode]); if (editor.page !== props.page) {
editor.page = props.page;
useEffect(() => {
const editor = editorRef.current;
if (!editor || !ref.current || !page) {
return;
}
editor.page = page;
if (page.root === null) { if (page.root === null) {
props.onInit(page, editor); props.onInit(page, editor);
} }
props.onLoad?.(page, editor); props.onLoad?.(page, editor);
return; }
}, [page, props]); const ref = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
const editor = editorRef.current; const editor = editorRef.current;
assertExists(editor);
const container = ref.current; const container = ref.current;
if (!container) {
if (!editor || !container || !page) {
return; return;
} }
if (page.awarenessStore.getFlag('enable_block_hub')) { if (page.awarenessStore.getFlag('enable_block_hub')) {
@@ -82,13 +78,52 @@ export const BlockSuiteEditor = (props: EditorProps) => {
blockHubRef.current?.remove(); blockHubRef.current?.remove();
container.removeChild(editor); container.removeChild(editor);
}; };
}, [page, props.mode]); }, [page]);
return ( return (
<div <div
data-testid={`editor-${props.blockSuiteWorkspace.id}-${props.page.id}`} data-testid={`editor-${props.page.id}`}
className="editor-wrapper" className="editor-wrapper"
style={props.style} style={props.style}
ref={ref} ref={ref}
/> />
); );
}; };
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 BlockSuiteEditor = memo(function BlockSuiteEditor(
props: EditorProps & ErrorBoundaryProps
): ReactElement {
return (
<ErrorBoundary
fallbackRender={useCallback(
(fallbackProps: FallbackProps) => (
<BlockSuiteErrorFallback {...fallbackProps} onReset={props.onReset} />
),
[props.onReset]
)}
>
<BlockSuiteEditorImpl {...props} />
</ErrorBoundary>
);
});
BlockSuiteEditor.displayName = 'BlockSuiteEditor';

View File

@@ -77,6 +77,7 @@ __metadata:
react-dnd: ^16.0.1 react-dnd: ^16.0.1
react-dnd-html5-backend: ^16.0.1 react-dnd-html5-backend: ^16.0.1
react-dom: ^18.2.0 react-dom: ^18.2.0
react-error-boundary: ^4.0.3
react-is: ^18.2.0 react-is: ^18.2.0
serve: ^14.2.0 serve: ^14.2.0
storybook: ^7.0.2 storybook: ^7.0.2
@@ -16017,6 +16018,17 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "react-i18next@npm:^12.2.0":
version: 12.2.0 version: 12.2.0
resolution: "react-i18next@npm:12.2.0" resolution: "react-i18next@npm:12.2.0"