diff --git a/packages/app/src/components/editor/index.tsx b/packages/app/src/components/editor/index.tsx new file mode 100644 index 000000000..b0edaa684 --- /dev/null +++ b/packages/app/src/components/editor/index.tsx @@ -0,0 +1,71 @@ +import { useEffect, useRef } from 'react'; +import type { Page, Workspace } from '@blocksuite/store'; +import '@blocksuite/blocks'; +import { EditorContainer } from '@blocksuite/editor'; +import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md'; +import { styled } from '@/styles'; + +const StyledEditorContainer = styled('div')(() => { + return { + height: 'calc(100vh - 60px)', + padding: '0 32px', + }; +}); + +type Props = { + page: Page; + workspace: Workspace; + setEditor: (editor: EditorContainer) => void; +}; + +export const Editor = ({ page, workspace, setEditor }: Props) => { + const editorContainer = useRef(null); + // const { currentWorkspace, currentPage, setEditor } = useAppState(); + useEffect(() => { + const ret = () => { + const node = editorContainer.current; + while (node?.firstChild) { + node.removeChild(node.firstChild); + } + }; + + const editor = new EditorContainer(); + editor.page = page; + + editorContainer.current?.appendChild(editor); + if (page.isEmpty) { + const isFirstPage = workspace?.meta.pageMetas.length === 1; + // Can not use useCurrentPageMeta to get new title, cause meta title will trigger rerender, but the second time can not remove title + const { title: metaTitle } = page.meta; + const title = metaTitle + ? metaTitle + : isFirstPage + ? 'Welcome to AFFiNE Alpha "Abbey Wood"' + : ''; + workspace?.setPageMeta(page.id, { title }); + + const pageId = page.addBlock({ + flavour: 'affine:page', + title, + }); + page.addBlock({ flavour: 'affine:surface' }); + const frameId = page.addBlock({ flavour: 'affine:frame' }, pageId); + page.addBlock({ flavour: 'affine:frame' }, pageId); + // If this is a first page in workspace, init an introduction markdown + if (isFirstPage) { + editor.clipboard.importMarkdown(exampleMarkdown, `${frameId}`); + workspace.setPageMeta(page.id, { title }); + page.resetHistory(); + } + page.resetHistory(); + } + + setEditor(editor); + document.title = page?.meta.title || 'Untitled'; + return ret; + }, [workspace, page, setEditor]); + + return ; +}; + +export default Editor; diff --git a/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx b/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx index eba930db9..2fc86ff58 100644 --- a/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx +++ b/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx @@ -2,88 +2,42 @@ import { PropsWithChildren, ReactElement, + useCallback, useEffect, - useRef, useState, } from 'react'; -import { styled } from '@/styles'; import { EditorHeader } from '@/components/header'; import EdgelessToolbar from '@/components/edgeless-toolbar'; import MobileModal from '@/components/mobile-modal'; import { useAppState } from '@/providers/app-state-provider'; -import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md'; import type { NextPageWithLayout } from '../..//_app'; import WorkspaceLayout from '@/components/workspace-layout'; import { useRouter } from 'next/router'; import { usePageHelper } from '@/hooks/use-page-helper'; - -const StyledEditorContainer = styled('div')(() => { - return { - height: 'calc(100vh - 60px)', - padding: '0 32px', - }; +import dynamic from 'next/dynamic'; +import { EditorContainer } from '@blocksuite/editor'; +const DynamicBlocksuite = dynamic(() => import('@/components/editor'), { + ssr: false, }); - const Page: NextPageWithLayout = () => { - const editorContainer = useRef(null); - const { createEditor, setEditor, currentPage, currentWorkspace } = - useAppState(); - - useEffect(() => { - const ret = () => { - const node = editorContainer.current; - while (node?.firstChild) { - node.removeChild(node.firstChild); - } - }; - - const editor = createEditor?.current?.(currentPage!); - if (editor) { - editorContainer.current?.appendChild(editor); - setEditor?.current?.(editor); - if (currentPage!.isEmpty) { - const isFirstPage = currentWorkspace?.meta.pageMetas.length === 1; - // Can not use useCurrentPageMeta to get new title, cause meta title will trigger rerender, but the second time can not remove title - const { title: metaTitle } = currentPage!.meta; - const title = metaTitle - ? metaTitle - : isFirstPage - ? 'Welcome to AFFiNE Alpha "Abbey Wood"' - : ''; - currentWorkspace?.setPageMeta(currentPage!.id, { title }); - - const pageId = currentPage!.addBlock({ - flavour: 'affine:page', - title, - }); - currentPage!.addBlock({ flavour: 'affine:surface' }, null); - const frameId = currentPage!.addBlock( - { flavour: 'affine:frame' }, - pageId - ); - currentPage!.addBlock({ flavour: 'affine:frame' }, pageId); - // If this is a first page in workspace, init an introduction markdown - if (isFirstPage) { - editor.clipboard - .importMarkdown(exampleMarkdown, `${frameId}`) - .then(() => { - currentWorkspace!.setPageMeta(currentPage!.id, { title }); - currentPage!.resetHistory(); - }); - } - currentPage!.resetHistory(); - } - } - - document.title = currentPage?.meta.title || 'Untitled'; - return ret; - }, [currentWorkspace, currentPage, createEditor, setEditor]); + const { currentPage, currentWorkspace, setEditor } = useAppState(); + const setEditorHandler = useCallback( + (editor: EditorContainer) => setEditor.current(editor), + [setEditor] + ); return ( <> - + + {currentPage && ( + + )} ); diff --git a/packages/app/src/providers/app-state-provider/DynamicBlocksuite.tsx b/packages/app/src/providers/app-state-provider/DynamicBlocksuite.tsx deleted file mode 100644 index 611f316a4..000000000 --- a/packages/app/src/providers/app-state-provider/DynamicBlocksuite.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useEffect } from 'react'; -import type { Page } from '@blocksuite/store'; -import '@blocksuite/blocks'; -import { EditorContainer } from '@blocksuite/editor'; -import { CreateEditorHandler } from './interface'; - -type Props = { - setCreateEditorHandler: (handler: CreateEditorHandler) => void; -}; - -const DynamicBlocksuite = ({ setCreateEditorHandler }: Props) => { - useEffect(() => { - const createEditorHandler: CreateEditorHandler = (page: Page) => { - const editor = new EditorContainer(); - editor.page = page; - return editor; - }; - - setCreateEditorHandler(createEditorHandler); - }, [setCreateEditorHandler]); - - return <>; -}; - -export default DynamicBlocksuite; diff --git a/packages/app/src/providers/app-state-provider/Provider.tsx b/packages/app/src/providers/app-state-provider/Provider.tsx index 9e3b451bc..2ad5927de 100644 --- a/packages/app/src/providers/app-state-provider/Provider.tsx +++ b/packages/app/src/providers/app-state-provider/Provider.tsx @@ -1,22 +1,16 @@ -import { - createContext, - useCallback, - useContext, - useEffect, - useState, -} from 'react'; +import { createContext, useContext, useEffect, useState, useRef } from 'react'; import type { PropsWithChildren } from 'react'; import { getDataCenter } from '@affine/datacenter'; -import { AppStateContext, AppStateValue } from './interface'; +import { + AppStateContext, + AppStateFunction, + AppStateValue, + PageMeta, +} from './interface'; import { createDefaultWorkspace } from './utils'; type AppStateContextProps = PropsWithChildren>; -import dynamic from 'next/dynamic'; -import { CreateEditorHandler } from '@/providers/app-state-provider3'; -const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), { - ssr: false, -}); export const AppState = createContext({} as AppStateContext); export const useAppState = () => useContext(AppState); @@ -26,16 +20,6 @@ export const AppStateProvider = ({ }: PropsWithChildren) => { const [appState, setAppState] = useState({} as AppStateValue); - const [createEditorHandler, _setCreateEditorHandler] = - useState(); - - const setCreateEditorHandler = useCallback( - (handler: CreateEditorHandler) => { - _setCreateEditorHandler(() => handler); - }, - [_setCreateEditorHandler] - ); - useEffect(() => { const init = async () => { const dataCenter = await getDataCenter(); @@ -43,20 +27,18 @@ export const AppStateProvider = ({ if (dataCenter.workspaces.length === 0) { await createDefaultWorkspace(dataCenter); } - let currentWorkspace = appState.currentWorkspace; - if (!currentWorkspace) { - currentWorkspace = await dataCenter.loadWorkspace( - dataCenter.workspaces[0].id - ); - } + + const currentWorkspace = await dataCenter.loadWorkspace( + dataCenter.workspaces[0].id + ); setAppState({ dataCenter, - user: await dataCenter.getUserInfo(), + user: (await dataCenter.getUserInfo()) || null, workspaceList: dataCenter.workspaces, currentWorkspaceId: dataCenter.workspaces[0].id, currentWorkspace, - pageList: currentWorkspace.meta.pageMetas, + pageList: currentWorkspace.meta.pageMetas as PageMeta[], currentPage: null, editor: null, synced: true, @@ -74,7 +56,7 @@ export const AppStateProvider = ({ const dispose = currentWorkspace.meta.pagesUpdated.on(() => { setAppState({ ...appState, - pageList: currentWorkspace.meta.pageMetas, + pageList: currentWorkspace.meta.pageMetas as PageMeta[], }); }).dispose; return () => { @@ -93,7 +75,8 @@ export const AppStateProvider = ({ }); }, [appState]); - const loadPage = (pageId: string) => { + const loadPage = useRef(); + loadPage.current = (pageId: string) => { const { currentWorkspace, currentPage } = appState; if (pageId === currentPage?.id) { return; @@ -105,49 +88,29 @@ export const AppStateProvider = ({ }); }; - const loadWorkspace = async (workspaceId: string) => { - console.log('workspaceId: ', workspaceId); - const { dataCenter, workspaceList, currentWorkspaceId } = appState; + const loadWorkspace = useRef(); + loadWorkspace.current = async (workspaceId: string) => { + const { dataCenter, workspaceList, currentWorkspaceId, currentWorkspace } = + appState; if (!workspaceList.find(v => v.id === workspaceId)) { - return; + return null; } if (workspaceId === currentWorkspaceId) { - return; + return currentWorkspace; } const workspace = await dataCenter.loadWorkspace(workspaceId); - console.log('workspace: ', workspace); - setAppState({ ...appState, currentWorkspace: await dataCenter.loadWorkspace(workspaceId), currentWorkspaceId: workspaceId, }); + + return workspace; }; - const createEditor = () => { - const { currentPage, currentWorkspace } = appState; - if (!currentPage || !currentWorkspace) { - return null; - } - - const editor = createEditorHandler?.(currentPage) || null; - - if (editor) { - const pageMeta = currentWorkspace.meta.pageMetas.find( - p => p.id === currentPage.id - ); - if (pageMeta?.mode) { - editor.mode = pageMeta.mode as 'page' | 'edgeless' | undefined; - } - if (pageMeta?.trash) { - editor.readonly = true; - } - } - - return editor; - }; - - const setEditor = (editor: AppStateValue['editor']) => { + const setEditor: AppStateFunction['setEditor'] = + useRef() as AppStateFunction['setEditor']; + setEditor.current = editor => { setAppState({ ...appState, editor, @@ -159,16 +122,11 @@ export const AppStateProvider = ({ value={{ ...appState, setEditor, - loadPage, - loadWorkspace, - createEditor, + loadPage: loadPage.current, + loadWorkspace: loadWorkspace.current, }} > - - {children} ); }; - -export default AppStateProvider; diff --git a/packages/app/src/providers/app-state-provider/index.ts b/packages/app/src/providers/app-state-provider/index.ts index 505094109..b6581f766 100644 --- a/packages/app/src/providers/app-state-provider/index.ts +++ b/packages/app/src/providers/app-state-provider/index.ts @@ -1 +1,2 @@ export * from './Provider'; +export * from './interface'; diff --git a/packages/app/src/providers/app-state-provider/interface.ts b/packages/app/src/providers/app-state-provider/interface.ts index ed8847c6b..a56a347ec 100644 --- a/packages/app/src/providers/app-state-provider/interface.ts +++ b/packages/app/src/providers/app-state-provider/interface.ts @@ -4,12 +4,20 @@ import type { EditorContainer } from '@blocksuite/editor'; import type { Page as StorePage, Workspace as StoreWorkspace, - PageMeta, + PageMeta as StorePageMeta, } from '@blocksuite/store'; +import { MutableRefObject } from 'react'; +export interface PageMeta extends StorePageMeta { + favorite: boolean; + trash: boolean; + trashDate: number; + updatedDate: number; + mode: 'edgeless' | 'page'; +} export type AppStateValue = { dataCenter: DataCenter; - user: User | undefined; + user: User | null; workspaceList: Workspace[]; currentWorkspace: StoreWorkspace; currentWorkspaceId: string; @@ -20,9 +28,9 @@ export type AppStateValue = { }; export type AppStateFunction = { - createEditor: (page: StorePage) => EditorContainer | null; - setEditor: (page: EditorContainer) => void; - loadWorkspace: (workspaceId: string) => Promise; + setEditor: MutableRefObject<(page: EditorContainer) => void>; + + loadWorkspace: (workspaceId: string) => Promise; loadPage: (pageId: string) => void; }; diff --git a/packages/app/src/providers/app-state-provider3/DynamicBlocksuite.tsx b/packages/app/src/providers/app-state-provider3/DynamicBlocksuite.tsx deleted file mode 100644 index 4b7b87654..000000000 --- a/packages/app/src/providers/app-state-provider3/DynamicBlocksuite.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useEffect } from 'react'; -import type { Page } from '@blocksuite/store'; -import '@blocksuite/blocks'; -import { EditorContainer } from '@blocksuite/editor'; -import type { CreateEditorHandler } from './context'; - -interface Props { - setCreateEditorHandler: (handler: CreateEditorHandler) => void; -} - -const DynamicBlocksuite = ({ setCreateEditorHandler }: Props) => { - useEffect(() => { - const createEditorHandler: CreateEditorHandler = (page: Page) => { - const editor = new EditorContainer(); - editor.page = page; - return editor; - }; - - setCreateEditorHandler(createEditorHandler); - }, [setCreateEditorHandler]); - - return <>; -}; - -export default DynamicBlocksuite; diff --git a/packages/app/src/providers/app-state-provider3/Provider.tsx b/packages/app/src/providers/app-state-provider3/Provider.tsx deleted file mode 100644 index f104e9be3..000000000 --- a/packages/app/src/providers/app-state-provider3/Provider.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { useMemo, useState, useCallback, useRef } from 'react'; -import type { ReactNode } from 'react'; -import dynamic from 'next/dynamic'; -import { getDataCenter } from '@affine/datacenter'; -import { AppState, AppStateContext } from './context'; -import type { - AppStateValue, - CreateEditorHandler, - LoadWorkspaceHandler, -} from './context'; -import { Page } from '@blocksuite/store'; -import { EditorContainer } from '@blocksuite/editor'; -const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), { - ssr: false, -}); - -const loadWorkspaceHandler: LoadWorkspaceHandler = async workspaceId => { - if (workspaceId) { - const dc = await getDataCenter(); - return dc.load(workspaceId, { providerId: 'affine' }); - } else { - return null; - } -}; -export const AppStateProvider = ({ children }: { children?: ReactNode }) => { - const refreshWorkspacesMeta = async () => { - const dc = await getDataCenter(); - const workspacesMeta = await dc.apis.getWorkspaces().catch(() => { - return []; - }); - setState(state => ({ ...state, workspacesMeta })); - }; - - const [state, setState] = useState({ - user: null, - workspacesMeta: [], - currentWorkspaceId: '', - currentWorkspace: null, - currentPage: null, - editor: null, - refreshWorkspacesMeta, - synced: true, - }); - - const [createEditorHandler, _setCreateEditorHandler] = - useState(); - - const setCreateEditorHandler = useCallback( - (handler: CreateEditorHandler) => { - _setCreateEditorHandler(() => handler); - }, - [_setCreateEditorHandler] - ); - - const loadWorkspace = useRef(() => - Promise.resolve(null) - ); - loadWorkspace.current = async (workspaceId: string) => { - if (state.currentWorkspaceId === workspaceId) { - return state.currentWorkspace; - } - const workspace = - (await loadWorkspaceHandler(workspaceId, state.user)) || null; - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - window.workspace = workspace; - // FIXME: there needs some method to destroy websocket. - // Or we need a manager to manage websocket. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - state.currentWorkspace?.__ws__?.destroy(); - - setState(state => ({ - ...state, - currentWorkspace: workspace, - currentWorkspaceId: workspaceId, - })); - return workspace; - }; - - const loadPage = useRef(() => - Promise.resolve(null) - ); - loadPage.current = async (pageId: string) => { - const { currentWorkspace, currentPage } = state; - if (pageId === currentPage?.id) { - return currentPage; - } - const page = (pageId ? currentWorkspace?.getPage(pageId) : null) || null; - setState(state => ({ ...state, currentPage: page })); - return page; - }; - - const createEditor = useRef< - ((page: Page) => EditorContainer | null) | undefined - >(); - createEditor.current = () => { - const { currentPage, currentWorkspace } = state; - if (!currentPage || !currentWorkspace) { - return null; - } - const editor = createEditorHandler?.(currentPage) || null; - - if (editor) { - const pageMeta = currentWorkspace.meta.pageMetas.find( - p => p.id === currentPage.id - ); - if (pageMeta?.mode) { - editor.mode = pageMeta.mode as 'page' | 'edgeless' | undefined; - } - if (pageMeta?.trash) { - editor.readonly = true; - } - } - - return editor; - }; - - const setEditor = useRef<(editor: AppStateValue['editor']) => void>(); - - setEditor.current = (editor: AppStateValue['editor']) => { - setState(state => ({ ...state, editor })); - }; - - const context = useMemo( - () => ({ - ...state, - setState, - createEditor, - setEditor, - loadWorkspace: loadWorkspace.current, - loadPage: loadPage.current, - }), - [state, setState, loadPage, loadWorkspace] - ); - - return ( - - - {children} - - ); -}; diff --git a/packages/app/src/providers/app-state-provider3/context.ts b/packages/app/src/providers/app-state-provider3/context.ts deleted file mode 100644 index f907246ed..000000000 --- a/packages/app/src/providers/app-state-provider3/context.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createContext, MutableRefObject, useContext } from 'react'; -import type { AccessTokenMessage, Workspace } from '@affine/datacenter'; -import type { - Page as StorePage, - Workspace as StoreWorkspace, -} from '@blocksuite/store'; -import type { EditorContainer } from '@blocksuite/editor'; - -export type LoadWorkspaceHandler = ( - workspaceId: string, - user?: AccessTokenMessage | null -) => Promise | null; -export type CreateEditorHandler = (page: StorePage) => EditorContainer | null; - -export interface AppStateValue { - user: AccessTokenMessage | null; - workspacesMeta: Workspace[]; - - currentWorkspaceId: string; - currentWorkspace: StoreWorkspace | null; - - currentPage: StorePage | null; - - // workspaces: Record; - - editor: EditorContainer | null; - synced: boolean; - refreshWorkspacesMeta: () => void; -} - -export interface AppStateContext extends AppStateValue { - setState: (state: AppStateValue) => void; - createEditor?: MutableRefObject< - ((page: StorePage) => EditorContainer | null) | undefined - >; - setEditor?: MutableRefObject<((page: EditorContainer) => void) | undefined>; - loadWorkspace: (workspaceId: string) => Promise; - loadPage: (pageId: string) => Promise; -} - -export const AppState = createContext({ - user: null, - workspacesMeta: [], - currentWorkspaceId: '', - currentWorkspace: null, - currentPage: null, - editor: null, - // eslint-disable-next-line @typescript-eslint/no-empty-function - setState: () => {}, - createEditor: undefined, - setEditor: undefined, - loadWorkspace: () => Promise.resolve(null), - loadPage: () => Promise.resolve(null), - synced: false, - // eslint-disable-next-line @typescript-eslint/no-empty-function - refreshWorkspacesMeta: () => {}, -}); - -export const useAppState = () => { - return useContext(AppState); -}; diff --git a/packages/app/src/providers/app-state-provider3/index.ts b/packages/app/src/providers/app-state-provider3/index.ts deleted file mode 100644 index 88c38fd7c..000000000 --- a/packages/app/src/providers/app-state-provider3/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './context'; -export * from './interface'; diff --git a/packages/app/src/providers/app-state-provider3/interface.ts b/packages/app/src/providers/app-state-provider3/interface.ts deleted file mode 100644 index 19b028703..000000000 --- a/packages/app/src/providers/app-state-provider3/interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PageMeta as OriginalPageMeta } from '@blocksuite/store/dist/workspace/workspace'; - -// export type PageMeta = { -// favorite: boolean; -// trash: boolean; -// trashDate: number | void; -// updatedDate: number | void; -// mode: EditorContainer['mode']; -// } & OriginalPageMeta; - -export interface PageMeta extends OriginalPageMeta { - favorite: boolean; - trash: boolean; - trashDate: number; - updatedDate: number; - mode: 'edgeless' | 'page'; -}