feat: normalize load page

This commit is contained in:
alt0
2022-12-20 00:04:33 +08:00
parent 857504fd86
commit fba0cb9015
13 changed files with 359 additions and 67 deletions
@@ -1,6 +1,6 @@
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/global-modal-provider';
import { useAppState } from '@/providers/app-state-provider';
import { useAppState } from '@/providers/app-state-provider/context';
import { IconButton } from '@/ui/button';
export const SyncIcon = () => {
@@ -1,7 +1,7 @@
import { InformationIcon, LogOutIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { Divider } from '@/ui/divider';
import { useAppState } from '@/providers/app-state-provider';
import { useAppState } from '@/providers/app-state-provider/context';
import { SelectorPopperContainer } from './styles';
import {
PrivateWorkspaceItem,
@@ -1,5 +1,5 @@
import { styled } from '@/styles';
import { useAppState } from '@/providers/app-state-provider';
import { useAppState } from '@/providers/app-state-provider/context';
import {
WorkspaceItemAvatar,
PrivateWorkspaceWrapper,
+1 -1
View File
@@ -5,7 +5,7 @@ import '../../public/variable.css';
import './temporary.css';
import { EditorProvider } from '@/providers/editor-provider';
import { ModalProvider } from '@/providers/global-modal-provider';
import { AppStateProvider } from '@/providers/app-state-provider';
import { AppStateProvider } from '@/providers/app-state-provider/provider';
import { Logger } from '@toeverything/pathfinder-logger';
import { WorkSpaceSliderBar } from '@/components/workspace-slider-bar';
import '@fontsource/space-mono';
@@ -1,9 +1,15 @@
import { useRef, useEffect } from 'react';
import type { NextPage } from 'next';
import { styled } from '@/styles';
import { EditorHeader } from '@/components/header';
import EdgelessToolbar from '@/components/edgeless-toolbar';
import MobileModal from '@/components/mobile-modal';
import Editor from '@/components/editor';
import {
useLoadWorkspace,
useLoadPage,
} from '@/providers/app-state-provider/hooks';
import { useAppState } from '@/providers/app-state-provider/context';
import exampleMarkdown from '@/providers/editor-provider/example-markdown';
const StyledEditorContainer = styled('div')(({ theme }) => {
return {
@@ -12,13 +18,47 @@ const StyledEditorContainer = styled('div')(({ theme }) => {
});
const Home: NextPage = () => {
const editorContainer = useRef<HTMLDivElement>(null);
const workspace = useLoadWorkspace();
const page = useLoadPage();
const { createEditor } = useAppState();
useEffect(() => {
const ret = () => {
const node = editorContainer.current;
while (node?.firstChild) {
node.removeChild(node.firstChild);
}
};
if (!workspace || !page) {
return ret;
}
const editor = createEditor(page);
if (editor) {
editorContainer.current?.appendChild(editor);
if (page.isEmpty) {
const title = 'Welcome to the AFFiNE Alpha';
const pageId = page.addBlock({
flavour: 'affine:page',
title,
});
const groupId = page.addBlock({ flavour: 'affine:group' }, pageId);
editor.clipboard.importMarkdown(exampleMarkdown, `${groupId}`);
workspace.setPageMeta(page.id, { title });
page.resetHistory();
}
}
return ret;
}, [workspace, page]);
return (
<>
<EditorHeader />
<MobileModal />
<StyledEditorContainer>
<Editor />
</StyledEditorContainer>
<StyledEditorContainer ref={editorContainer} />
<EdgelessToolbar />
</>
);
@@ -1,4 +1,32 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useLoadWorkspace } from '@/providers/app-state-provider/hooks';
import { useAppState } from '@/providers/app-state-provider/context';
const Page = () => {
const router = useRouter();
const workspace = useLoadWorkspace();
const { createPage, currentWorkspaceId } = useAppState();
useEffect(() => {
if (!workspace) {
return;
}
const savedPageId = workspace.meta.pageMetas[0]?.id;
if (savedPageId) {
router.push(`/workspace/${currentWorkspaceId}/${savedPageId}`);
return;
}
createPage()?.then(async pageId => {
if (!pageId) {
return;
}
router.push(`/workspace/${currentWorkspaceId}/${pageId}`);
});
}, [workspace, currentWorkspaceId]);
return <div>Hello, there is [pageId]/index</div>;
};
@@ -1,57 +0,0 @@
import { createContext, useMemo, useContext, useState, useEffect } from 'react';
import type { ReactNode } from 'react';
import {
authorizationEvent,
AccessTokenMessage,
getWorkspaces,
} from '@pathfinder/data-services';
import type { Workspace } from '@pathfinder/data-services';
interface AppStateValue {
user: AccessTokenMessage | null;
workspacesMeta: Workspace[];
}
interface AppStateContext extends AppStateValue {
setState: (state: AppStateValue) => void;
}
const AppState = createContext<AppStateContext>({
user: null,
workspacesMeta: [],
setState: () => {},
});
export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
const [state, setState] = useState<AppStateValue>({
user: null,
workspacesMeta: [],
});
useEffect(() => {
const callback = async (user: AccessTokenMessage | null) => {
const workspaces = user ? await getWorkspaces() : [];
setState(state => ({ ...state, user: user, workspaces }));
};
authorizationEvent.onChange(callback);
return () => {
authorizationEvent.removeCallback(callback);
};
}, []);
const context = useMemo(
() => ({
...state,
setState,
}),
[state, setState]
);
return <AppState.Provider value={context}>{children}</AppState.Provider>;
};
export const useAppState = () => {
const state = useContext(AppState);
return state;
};
@@ -0,0 +1,49 @@
import { createContext, useContext } from 'react';
import { AccessTokenMessage } from '@pathfinder/data-services';
import type { Workspace } from '@pathfinder/data-services';
import type {
Workspace as StoreWorkspace,
Page as StorePage,
} from '@blocksuite/store';
import type { EditorContainer } from '@blocksuite/editor';
export interface AppStateValue {
user: AccessTokenMessage | null;
workspacesMeta: Workspace[];
currentWorkspaceId: string;
currentWorkspace: StoreWorkspace | null;
currentPage: StorePage | null;
}
export interface AppStateContext extends AppStateValue {
setState: (state: AppStateValue) => void;
createEditor: (page: StorePage) => EditorContainer | null;
loadWorkspace?: (
workspaceId: string
) => Promise<StoreWorkspace | null> | null;
loadPage: (pageId: string) => Promise<StorePage | null> | null;
createPage: (pageId?: string) => Promise<string | null> | null;
}
export const AppState = createContext<AppStateContext>({
user: null,
workspacesMeta: [],
currentWorkspaceId: '',
currentWorkspace: null,
currentPage: null,
setState: () => {},
createEditor: () => null,
loadWorkspace: undefined,
loadPage: () => null,
createPage: () => null,
});
export const useAppState = () => {
const state = useContext(AppState);
return state;
};
@@ -0,0 +1,76 @@
import { useEffect } from 'react';
import {
Workspace,
createWebsocketDocProvider,
IndexedDBDocProvider,
} from '@blocksuite/store';
import type { Page } from '@blocksuite/store';
import { BlockSchema, createEditor } from '@blocksuite/editor';
import type { AppStateContext } from './context';
const getEditorParams = (workspaceId: string) => {
const providers = [];
const params = new URLSearchParams(location.search);
// const room = params.get('room') ?? 'AFFINE-pathfinder';
if (params.get('syncMode') === 'websocket') {
const WebsocketDocProvider = createWebsocketDocProvider(
`ws://${window.location.host}/collaboration/`
);
providers.push(WebsocketDocProvider);
}
providers.push(IndexedDBDocProvider);
return {
room: workspaceId,
providers,
};
};
type LoadWorkspaceHandler = AppStateContext['loadWorkspace'];
type CreateEditorHandler = AppStateContext['createEditor'];
interface Props {
setLoadWorkspaceHandler: (handler: LoadWorkspaceHandler) => void;
setCreateEditorHandler: (handler: CreateEditorHandler) => void;
}
const DynamicBlocksuite = ({
setLoadWorkspaceHandler,
setCreateEditorHandler,
}: Props) => {
useEffect(() => {
const openWorkspace: LoadWorkspaceHandler = (workspaceId: string) =>
new Promise(resolve => {
const workspace = new Workspace({
...getEditorParams(workspaceId as string),
}).register(BlockSchema);
const indexDBProvider = workspace.providers.find(
p => p instanceof IndexedDBDocProvider
);
if (indexDBProvider) {
(indexDBProvider as IndexedDBDocProvider)?.on('synced', () => {
resolve(workspace);
});
} else {
resolve(workspace);
}
});
setLoadWorkspaceHandler(openWorkspace);
}, [setLoadWorkspaceHandler]);
useEffect(() => {
const createEditorHandler: CreateEditorHandler = (page: Page) => {
const editor = createEditor(page);
return editor;
};
setCreateEditorHandler(createEditorHandler);
}, [setCreateEditorHandler]);
return <></>;
};
export default DynamicBlocksuite;
@@ -0,0 +1,30 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from './context';
export const useLoadWorkspace = () => {
const router = useRouter();
const { loadWorkspace, currentWorkspace, currentWorkspaceId } = useAppState();
const workspaceId = router.query.workspaceId as string;
useEffect(() => {
loadWorkspace?.(workspaceId);
}, [workspaceId]);
return currentWorkspaceId === workspaceId ? currentWorkspace : null;
};
export const useLoadPage = () => {
const router = useRouter();
const { loadPage, currentPage } = useAppState();
const workspace = useLoadWorkspace();
const pageId = router.query.pageId as string;
useEffect(() => {
loadPage(pageId);
}, [workspace, pageId]);
return currentPage?.pageId === pageId ? currentPage : null;
};
@@ -0,0 +1,125 @@
import { useMemo, useState, useEffect, useCallback } from 'react';
import type { ReactNode } from 'react';
import dynamic from 'next/dynamic';
import {
authorizationEvent,
AccessTokenMessage,
getWorkspaces,
} from '@pathfinder/data-services';
import { AppState } from './context';
import type { AppStateValue, AppStateContext } from './context';
const DynamicBlocksuite = dynamic(() => import('./dynamic-blocksuite'), {
ssr: false,
});
export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
const [state, setState] = useState<AppStateValue>({
user: null,
workspacesMeta: [],
currentWorkspaceId: '',
currentWorkspace: null,
currentPage: null,
});
const [loadWorkspaceHandler, _setLoadWorkspaceHandler] =
useState<AppStateContext['loadWorkspace']>();
const setLoadWorkspaceHandler = useCallback(
(handler: AppStateContext['loadWorkspace']) => {
_setLoadWorkspaceHandler(() => handler);
},
[_setLoadWorkspaceHandler]
);
const [createEditorHandler, _setCreateEditorHandler] =
useState<AppStateContext['createEditor']>();
const setCreateEditorHandler = useCallback(
(handler: AppStateContext['createEditor']) => {
_setCreateEditorHandler(() => handler);
},
[_setCreateEditorHandler]
);
useEffect(() => {
const callback = async (user: AccessTokenMessage | null) => {
const workspaces = user ? await getWorkspaces() : [];
setState(state => ({ ...state, user: user, workspaces }));
};
authorizationEvent.onChange(callback);
return () => {
authorizationEvent.removeCallback(callback);
};
}, []);
const context = useMemo(
() => ({
...state,
setState,
createEditor: () => {
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.pageId
);
if (pageMeta?.mode) {
editor.mode = pageMeta.mode as 'page' | 'edgeless' | undefined;
}
if (pageMeta?.trash) {
editor.readonly = true;
}
}
return editor;
},
loadWorkspace: async (workspaceId: string) => {
const workspace = (await loadWorkspaceHandler?.(workspaceId)) || null;
setState(state => ({
...state,
currentWorkspace: workspace,
currentWorkspaceId: workspaceId,
}));
return workspace;
},
loadPage: async (pageId: string) => {
const { currentWorkspace, currentPage } = state;
if (pageId === currentPage?.pageId) {
return currentPage;
}
const page = currentWorkspace?.getPage(pageId) || null;
setState(state => ({ ...state, currentPage: page }));
return page;
},
createPage: (pageId: string = Date.now().toString()) =>
new Promise<string | null>(resolve => {
const { currentWorkspace } = state;
if (!currentWorkspace) {
resolve(null);
return;
}
currentWorkspace.createPage(pageId);
currentWorkspace.signals.pageAdded.once(addedPageId => {
resolve(addedPageId);
});
}),
}),
[state, setState, loadWorkspaceHandler, createEditorHandler]
);
return (
<AppState.Provider value={context}>
<DynamicBlocksuite
setLoadWorkspaceHandler={setLoadWorkspaceHandler}
setCreateEditorHandler={setCreateEditorHandler}
/>
{loadWorkspaceHandler ? children : null}
</AppState.Provider>
);
};
@@ -49,7 +49,7 @@ export const EditorProvider = ({
const setPage = useCallback(
(page: Page) => {
_setPage(page);
router.push(`/workspace/${workspaceId}/${page.id.replace('space:', '')}`);
// router.push(`/workspace/${workspaceId}/${page.id.replace('space:', '')}`);
},
[_setPage, workspaceId]
);
@@ -53,8 +53,9 @@ const EditorReactor = ({
}) => {
const shouldInitIntroduction = useRef(false);
const {
query: { pageId: routerPageId, workspaceId },
query: { pageId0: routerPageId },
} = useRouter();
const workspaceId = 'sfas';
useEffect(() => {
const workspace = new Workspace({