fix: use react-resizable-panels (#2679)

This commit is contained in:
Himself65
2023-06-04 23:33:36 +08:00
committed by GitHub
parent acda594cba
commit 1716e7a397
6 changed files with 88 additions and 165 deletions
+1 -1
View File
@@ -47,7 +47,7 @@
"react": "18.3.0-canary-16d053d59-20230506",
"react-dom": "18.3.0-canary-16d053d59-20230506",
"react-is": "^18.2.0",
"react-mosaic-component": "^6.0.1",
"react-resizable-panels": "^0.0.48",
"rxjs": "^7.8.1",
"swr": "^2.1.5",
"y-protocols": "^1.0.5",
+62 -62
View File
@@ -1,6 +1,6 @@
import './page-detail-editor.css';
import { PageNotFoundError, Unreachable } from '@affine/env/constant';
import { PageNotFoundError } from '@affine/env/constant';
import { rootCurrentEditorAtom } from '@affine/workspace/atom';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
@@ -9,33 +9,29 @@ import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import { useBlockSuiteWorkspacePageTitle } from '@toeverything/hooks/use-block-suite-workspace-page-title';
import { affinePluginsAtom } from '@toeverything/plugin-infra/manager';
import type { PluginUIAdapter } from '@toeverything/plugin-infra/type';
import type { ExpectedLayout } from '@toeverything/plugin-infra/type';
import type {
AffinePlugin,
LayoutNode,
PluginUIAdapter,
} from '@toeverything/plugin-infra/type';
import type { PluginBlockSuiteAdapter } from '@toeverything/plugin-infra/type';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { useAtomValue, useSetAtom } from 'jotai';
import Head from 'next/head';
import type { FC } from 'react';
import type { FC, ReactElement } from 'react';
import React, {
lazy,
memo,
startTransition,
Suspense,
useCallback,
useMemo,
} from 'react';
import type { MosaicNode } from 'react-mosaic-component';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { workspacePreferredModeAtom } from '../atoms';
import { contentLayoutAtom } from '../atoms/layout';
import type { AffineOfficialWorkspace } from '../shared';
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
const Mosaic = lazy(() =>
import('react-mosaic-component').then(({ Mosaic }) => ({
default: Mosaic,
}))
);
export type PageDetailEditorProps = {
isPublic?: boolean;
workspace: AffineOfficialWorkspace;
@@ -127,6 +123,54 @@ const PluginContentAdapter = memo<{
);
});
type LayoutPanelProps = {
node: LayoutNode;
editorProps: PageDetailEditorProps;
plugins: AffinePlugin<string>[];
};
const LayoutPanel = memo(function LayoutPanel(
props: LayoutPanelProps
): ReactElement {
const node = props.node;
if (typeof node === 'string') {
if (node === 'editor') {
return <EditorWrapper {...props.editorProps} />;
} else {
const plugin = props.plugins.find(
plugin => plugin.definition.id === node
);
const Content = plugin?.uiAdapter.detailContent;
assertExists(Content);
return <PluginContentAdapter detailContent={Content} />;
}
} else {
return (
<PanelGroup direction={node.direction}>
<Panel defaultSize={node.splitPercentage}>
<Suspense>
<LayoutPanel
node={node.first}
editorProps={props.editorProps}
plugins={props.plugins}
/>
</Suspense>
</Panel>
<PanelResizeHandle />
<Panel defaultSize={100 - node.splitPercentage}>
<Suspense>
<LayoutPanel
node={node.second}
editorProps={props.editorProps}
plugins={props.plugins}
/>
</Suspense>
</Panel>
</PanelGroup>
);
}
});
export const PageDetailEditor: FC<PageDetailEditorProps> = props => {
const { workspace, pageId } = props;
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
@@ -135,66 +179,22 @@ export const PageDetailEditor: FC<PageDetailEditorProps> = props => {
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
}
const title = useBlockSuiteWorkspacePageTitle(blockSuiteWorkspace, pageId);
const layout = useAtomValue(contentLayoutAtom);
const affinePluginsMap = useAtomValue(affinePluginsAtom);
const plugins = useMemo(
() => Object.values(affinePluginsMap),
[affinePluginsMap]
);
const [layout, setLayout] = useAtom(contentLayoutAtom);
const onChange = useCallback(
(_: MosaicNode<string | number> | null) => {
// type cast
const node = _ as MosaicNode<string> | null;
if (node) {
if (typeof node === 'string') {
console.error('unexpected layout');
} else {
if (node.splitPercentage && node.splitPercentage < 70) {
return;
} else if (node.first !== 'editor') {
return;
}
setLayout(node as ExpectedLayout);
}
}
},
[setLayout]
);
return (
<>
<Head>
<title>{title}</title>
</Head>
{layout === 'editor' ? (
<EditorWrapper {...props} />
) : (
<Mosaic
onChange={onChange}
renderTile={id => {
if (id === 'editor') {
return <EditorWrapper {...props} />;
} else {
const plugin = plugins.find(
plugin => plugin.definition.id === id
);
if (plugin && plugin.uiAdapter.detailContent) {
return (
<Suspense>
<PluginContentAdapter
detailContent={plugin.uiAdapter.detailContent}
/>
</Suspense>
);
}
}
throw new Unreachable();
}}
value={layout}
/>
)}
<Suspense>
<LayoutPanel node={layout} editorProps={props} plugins={plugins} />
</Suspense>
</>
);
};
-1
View File
@@ -1,6 +1,5 @@
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import 'react-mosaic-component/react-mosaic-component.css';
// bootstrap code before everything
import '../bootstrap';
+12 -4
View File
@@ -10,7 +10,6 @@ import type { Workspace } from '@blocksuite/store';
import type { Page } from '@playwright/test';
import type { WritableAtom } from 'jotai';
import type { ReactElement } from 'react';
import type { MosaicDirection, MosaicNode } from 'react-mosaic-component';
/**
* A code loader interface of the plugin API.
@@ -81,14 +80,23 @@ export enum ReleaseStage {
DEV = 'dev',
}
export type LayoutDirection = 'horizontal' | 'vertical';
export type LayoutNode = LayoutParentNode | string;
export type LayoutParentNode = {
direction: LayoutDirection;
splitPercentage: number; // 0 - 100
first: LayoutNode;
second: LayoutNode;
};
export type ExpectedLayout =
| {
direction: MosaicDirection;
direction: LayoutDirection;
// the first element is always the editor
first: 'editor';
second: MosaicNode<string>;
second: LayoutNode;
// the percentage should be greater than 70
splitPercentage?: number;
splitPercentage: number;
}
| 'editor';
+1 -1
View File
@@ -16,7 +16,7 @@ export const HeaderItem: PluginUIAdapter['headerItem'] = ({
setLayout(layout => {
if (layout === 'editor') {
return {
direction: 'row',
direction: 'horizontal',
first: 'editor',
second: 'com.affine.copilot',
splitPercentage: 80,
+12 -96
View File
@@ -395,7 +395,7 @@ __metadata:
react: 18.3.0-canary-16d053d59-20230506
react-dom: 18.3.0-canary-16d053d59-20230506
react-is: ^18.2.0
react-mosaic-component: ^6.0.1
react-resizable-panels: ^0.0.48
redux: ^4.2.1
rxjs: ^7.8.1
swc-plugin-coverage-instrument: ^0.0.18
@@ -12483,13 +12483,6 @@ __metadata:
languageName: node
linkType: hard
"classnames@npm:^2.3.2":
version: 2.3.2
resolution: "classnames@npm:2.3.2"
checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e
languageName: node
linkType: hard
"clean-regexp@npm:^1.0.0":
version: 1.0.0
resolution: "clean-regexp@npm:1.0.0"
@@ -14013,13 +14006,6 @@ __metadata:
languageName: node
linkType: hard
"dnd-multi-backend@npm:^8.0.0":
version: 8.0.0
resolution: "dnd-multi-backend@npm:8.0.0"
checksum: ce19b14c509e3740f6ab8cab725f8df2e7ad79fd04cb7d57d4b3f9e579cd5ab1f9439517b4325e014ca1caeab6152adced993085b734aff5a5d40cbb766a5da0
languageName: node
linkType: hard
"doctrine@npm:^2.1.0":
version: 2.1.0
resolution: "doctrine@npm:2.1.0"
@@ -17545,13 +17531,6 @@ __metadata:
languageName: node
linkType: hard
"immutability-helper@npm:^3.1.1":
version: 3.1.1
resolution: "immutability-helper@npm:3.1.1"
checksum: 6fdbf6d2123efa567263e904bbaff07aca0e24560d270d34967b03aab8ec20bd3e4057f394d59e50eb6c4718c9415591a6281692bb0aafd522ad72cf4887133f
languageName: node
linkType: hard
"immutable@npm:^4.2.2":
version: 4.3.0
resolution: "immutable@npm:4.3.0"
@@ -23367,17 +23346,6 @@ __metadata:
languageName: node
linkType: hard
"rdndmb-html5-to-touch@npm:^8.0.0":
version: 8.0.0
resolution: "rdndmb-html5-to-touch@npm:8.0.0"
dependencies:
dnd-multi-backend: ^8.0.0
react-dnd-html5-backend: ^16.0.1
react-dnd-touch-backend: ^16.0.1
checksum: 9ede5db33820ca592f0c0aaed3b9a6cd9026cd7ffe60c2cdabd62275ab253b3a69db2b4eb909c250602b98d413bf635380ba6d5df574ca002d076fdc12f94606
languageName: node
linkType: hard
"react-base16-styling@npm:^0.9.1":
version: 0.9.1
resolution: "react-base16-styling@npm:0.9.1"
@@ -23403,49 +23371,7 @@ __metadata:
languageName: node
linkType: hard
"react-dnd-html5-backend@npm:^16.0.1":
version: 16.0.1
resolution: "react-dnd-html5-backend@npm:16.0.1"
dependencies:
dnd-core: ^16.0.1
checksum: e2368bf85d5632a5cd867b743feb54c9052d909ea5331608860fa455edf3c633ac791f5b338e3db29b19ea8670c0ba5fb43c9c1c2510760bea030811d726cdfa
languageName: node
linkType: hard
"react-dnd-multi-backend@npm:^8.0.0":
version: 8.0.0
resolution: "react-dnd-multi-backend@npm:8.0.0"
dependencies:
dnd-multi-backend: ^8.0.0
react-dnd-preview: ^8.0.0
peerDependencies:
react: ^16.14.0 || ^17.0.2 || ^18.0.0
react-dom: ^16.14.0 || ^17.0.2 || ^18.0.0
checksum: a0871a4845df814f3a57b5c110e1601439d74d6433d2115a2078a4d49a200aa33a91b37a23d6dafaebc2ab857a25caa7d83d28cee034aa6907f7224c6257e56f
languageName: node
linkType: hard
"react-dnd-preview@npm:^8.0.0":
version: 8.0.0
resolution: "react-dnd-preview@npm:8.0.0"
peerDependencies:
react: ^16.14.0 || ^17.0.2 || ^18.0.0
react-dnd: ^16.0.1
checksum: abed644a2ccd454a8fc54fa5bc4904beeb8ccf40c1ea20fb3c5ff178733062fd9f5d657435fb671c5736c93d9e7b2ce8e7de8c07cbc9822b459f02fdbfc99b77
languageName: node
linkType: hard
"react-dnd-touch-backend@npm:^16.0.1":
version: 16.0.1
resolution: "react-dnd-touch-backend@npm:16.0.1"
dependencies:
"@react-dnd/invariant": ^4.0.1
dnd-core: ^16.0.1
checksum: 5362c5f4266e3655d02c716f341dc55c61fae53e12eb3b239245bafb7a3f3cdb953e27e187e8dcb9d86c01bc738853d764d7c85b87104a49a8a8f1ecc4d35755
languageName: node
linkType: hard
"react-dnd@npm:*, react-dnd@npm:^16.0.1":
"react-dnd@npm:*":
version: 16.0.1
resolution: "react-dnd@npm:16.0.1"
dependencies:
@@ -23616,26 +23542,6 @@ __metadata:
languageName: node
linkType: hard
"react-mosaic-component@npm:^6.0.1":
version: 6.0.1
resolution: "react-mosaic-component@npm:6.0.1"
dependencies:
classnames: ^2.3.2
immutability-helper: ^3.1.1
lodash: ^4.17.21
prop-types: ^15.8.1
rdndmb-html5-to-touch: ^8.0.0
react-dnd: ^16.0.1
react-dnd-html5-backend: ^16.0.1
react-dnd-multi-backend: ^8.0.0
react-dnd-touch-backend: ^16.0.1
uuid: ^9.0.0
peerDependencies:
react: 16 - 18
checksum: fb31b655fd841d09a5ca922f391c29cad8c7b0e87ac5d84386a434cb7d01f5a633f116b8c7ef4b79203cf0b1768a43c27ba65d41f22feba46a141be366b31de2
languageName: node
linkType: hard
"react-refresh@npm:^0.14.0":
version: 0.14.0
resolution: "react-refresh@npm:0.14.0"
@@ -23707,6 +23613,16 @@ __metadata:
languageName: node
linkType: hard
"react-resizable-panels@npm:^0.0.48":
version: 0.0.48
resolution: "react-resizable-panels@npm:0.0.48"
peerDependencies:
react: ^16.14.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0
checksum: dbc4ef9ac6a1b74a95bb81185b6bffbf8f1f1ab544090383e766e065979258bdafcf4485bd5507a5fd247eb02e6d060bcc05765c0e12c0ff47c4800a2cc3d7bb
languageName: node
linkType: hard
"react-style-singleton@npm:^2.2.1":
version: 2.2.1
resolution: "react-style-singleton@npm:2.2.1"