diff --git a/apps/web/package.json b/apps/web/package.json index 64e92b3839..1e74b98d6f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,6 +45,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", "rxjs": "^7.8.1", "swr": "^2.1.5", "y-protocols": "^1.0.5", diff --git a/apps/web/src/components/page-detail-editor.css.ts b/apps/web/src/components/page-detail-editor.css.ts new file mode 100644 index 0000000000..52f84856bb --- /dev/null +++ b/apps/web/src/components/page-detail-editor.css.ts @@ -0,0 +1,5 @@ +import { globalStyle } from '@vanilla-extract/css'; + +globalStyle('.mosaic.mosaic-blueprint-theme', { + backgroundColor: 'var(--background-color)', +}); diff --git a/apps/web/src/components/page-detail-editor.tsx b/apps/web/src/components/page-detail-editor.tsx index d7bdccabac..44eaeba980 100644 --- a/apps/web/src/components/page-detail-editor.tsx +++ b/apps/web/src/components/page-detail-editor.tsx @@ -1,4 +1,6 @@ -import { PageNotFoundError } from '@affine/env/constant'; +import './page-detail-editor.css'; + +import { PageNotFoundError, Unreachable } from '@affine/env/constant'; import type { EditorContainer } from '@blocksuite/editor'; import type { Page } from '@blocksuite/store'; import { assertExists } from '@blocksuite/store'; @@ -8,12 +10,18 @@ import { useBlockSuiteWorkspacePageTitle } from '@toeverything/hooks/use-block-s import { useAtomValue, useSetAtom } from 'jotai'; import Head from 'next/head'; import type React from 'react'; -import { startTransition, useCallback } from 'react'; +import { lazy, memo, startTransition, useCallback } from 'react'; import { currentEditorAtom, workspacePreferredModeAtom } from '../atoms'; 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; @@ -22,19 +30,18 @@ export type PageDetailEditorProps = { onLoad?: (page: Page, editor: EditorContainer) => () => void; }; -export const PageDetailEditor: React.FC = ({ +const EditorWrapper = memo(function EditorWrapper({ workspace, pageId, onInit, onLoad, isPublic, -}) => { +}: PageDetailEditorProps) { const blockSuiteWorkspace = workspace.blockSuiteWorkspace; const page = useBlockSuiteWorkspacePage(blockSuiteWorkspace, pageId); if (!page) { throw new PageNotFoundError(blockSuiteWorkspace, pageId); } - const title = useBlockSuiteWorkspacePageTitle(blockSuiteWorkspace, pageId); const meta = useBlockSuitePageMeta(blockSuiteWorkspace).find( meta => meta.id === pageId ); @@ -42,43 +49,67 @@ export const PageDetailEditor: React.FC = ({ useAtomValue(workspacePreferredModeAtom)[pageId] ?? 'page'; const setEditor = useSetAtom(currentEditorAtom); assertExists(meta); + return ( + ) => { + startTransition(() => { + setEditor(editor); + }); + onInit(page, editor); + }, + [onInit, setEditor] + )} + onLoad={useCallback( + (page: Page, editor: EditorContainer) => { + startTransition(() => { + setEditor(editor); + }); + page.workspace.setPageMeta(page.id, { + updatedDate: Date.now(), + }); + localStorage.setItem('last_page_id', page.id); + if (onLoad) { + return onLoad(page, editor); + } + return () => {}; + }, + [onLoad, setEditor] + )} + /> + ); +}); + +export const PageDetailEditor: React.FC = props => { + const { workspace, pageId } = props; + const blockSuiteWorkspace = workspace.blockSuiteWorkspace; + const page = useBlockSuiteWorkspacePage(blockSuiteWorkspace, pageId); + if (!page) { + throw new PageNotFoundError(blockSuiteWorkspace, pageId); + } + const title = useBlockSuiteWorkspacePageTitle(blockSuiteWorkspace, pageId); return ( <> {title} - {}, [])} + renderTile={id => { + if (id === 'editor') { + return ; + } else { + // @affine/copilot and other plugins will be added in the future + throw new Unreachable(); + } }} - key={`${workspace.flavour}-${workspace.id}-${pageId}`} - mode={isPublic ? 'page' : currentMode} - page={page} - onInit={useCallback( - (page: Page, editor: Readonly) => { - startTransition(() => { - setEditor(editor); - }); - onInit(page, editor); - }, - [onInit, setEditor] - )} - onLoad={useCallback( - (page: Page, editor: EditorContainer) => { - startTransition(() => { - setEditor(editor); - }); - page.workspace.setPageMeta(page.id, { - updatedDate: Date.now(), - }); - localStorage.setItem('last_page_id', page.id); - if (onLoad) { - return onLoad(page, editor); - } - return () => {}; - }, - [onLoad, setEditor] - )} + value="editor" /> ); diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx index d7817ef7d7..40bbfd1219 100644 --- a/apps/web/src/pages/_app.tsx +++ b/apps/web/src/pages/_app.tsx @@ -1,5 +1,6 @@ import '@affine/component/theme/global.css'; import '@affine/component/theme/theme.css'; +import 'react-mosaic-component/react-mosaic-component.css'; import { WorkspaceFallback } from '@affine/component/workspace'; import { config, setupGlobal } from '@affine/env'; diff --git a/yarn.lock b/yarn.lock index bc39c6f509..6551e70bb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -360,6 +360,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 redux: ^4.2.1 rxjs: ^7.8.1 swc-plugin-coverage-instrument: ^0.0.18 @@ -12433,6 +12434,13 @@ __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" @@ -13956,6 +13964,13 @@ __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,6 +17560,13 @@ __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" @@ -23073,6 +23095,17 @@ __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" @@ -23098,7 +23131,49 @@ __metadata: languageName: node linkType: hard -"react-dnd@npm:*": +"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": version: 16.0.1 resolution: "react-dnd@npm:16.0.1" dependencies: @@ -23269,6 +23344,26 @@ __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"