mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 17:46:18 +08:00
feat: normalize load page
This commit is contained in:
@@ -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
-1
@@ -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
-1
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user