refactor: extract useBlockSuite from useAppState (#1001)

This commit is contained in:
Himself65
2023-02-14 21:12:35 -06:00
committed by GitHub
parent 6c0db247b7
commit 5a0e4895cd
13 changed files with 160 additions and 110 deletions

View File

@@ -17,9 +17,9 @@ import {
import { MuiSlide } from '@affine/component'; import { MuiSlide } from '@affine/component';
import { Tooltip } from '@affine/component'; import { Tooltip } from '@affine/component';
import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { useAppState } from '@/providers/app-state-provider';
import useHistoryUpdated from '@/hooks/use-history-update'; import useHistoryUpdated from '@/hooks/use-history-update';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useBlockSuite } from '@/store/workspace';
const useToolbarList1 = () => { const useToolbarList1 = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -87,7 +87,7 @@ const useToolbarList1 = () => {
const UndoRedo = () => { const UndoRedo = () => {
const [canUndo, setCanUndo] = useState(false); const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false); const [canRedo, setCanRedo] = useState(false);
const { currentPage } = useAppState(); const currentPage = useBlockSuite(store => store.currentPage);
const onHistoryUpdated = useHistoryUpdated(); const onHistoryUpdated = useHistoryUpdated();
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {

View File

@@ -6,17 +6,17 @@ import {
StyledTitleWrapper, StyledTitleWrapper,
} from './styles'; } from './styles';
import { Content } from '@affine/component'; import { Content } from '@affine/component';
import { useAppState } from '@/providers/app-state-provider';
import EditorModeSwitch from '@/components/editor-mode-switch'; import EditorModeSwitch from '@/components/editor-mode-switch';
import QuickSearchButton from './QuickSearchButton'; import QuickSearchButton from './QuickSearchButton';
import Header from './Header'; import Header from './Header';
import usePropsUpdated from '@/hooks/use-props-updated'; import usePropsUpdated from '@/hooks/use-props-updated';
import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { useBlockSuite } from '@/store/workspace';
export const EditorHeader = () => { export const EditorHeader = () => {
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [isHover, setIsHover] = useState(false); const [isHover, setIsHover] = useState(false);
const { editor } = useAppState(); const editor = useBlockSuite(store => store.editor);
const { trash: isTrash = false } = useCurrentPageMeta() || {}; const { trash: isTrash = false } = useCurrentPageMeta() || {};
const onPropsUpdated = usePropsUpdated(); const onPropsUpdated = usePropsUpdated();

View File

@@ -11,14 +11,14 @@ import {
PaperIcon, PaperIcon,
TrashIcon, TrashIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import { useAppState } from '@/providers/app-state-provider';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import { useConfirm } from '@/providers/ConfirmProvider'; import { useConfirm } from '@/providers/ConfirmProvider';
import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { toast } from '@affine/component'; import { toast } from '@affine/component';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useBlockSuite } from '@/store/workspace';
const PopoverContent = () => { const PopoverContent = () => {
const { editor } = useAppState(); const editor = useBlockSuite(store => store.editor);
const { toggleFavoritePage, toggleDeletePage } = usePageHelper(); const { toggleFavoritePage, toggleDeletePage } = usePageHelper();
const { changePageMode } = usePageHelper(); const { changePageMode } = usePageHelper();
const confirm = useConfirm(store => store.confirm); const confirm = useConfirm(store => store.confirm);

View File

@@ -11,7 +11,7 @@ import { Tooltip } from '@affine/component';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useModal } from '@/store/globalModal'; import { useModal } from '@/store/globalModal';
import { MuiFade } from '@affine/component'; import { MuiFade } from '@affine/component';
import { useAppState } from '@/providers/app-state-provider'; import { useBlockSuite } from '@/store/workspace';
export type IslandItemNames = 'contact' | 'shortcuts'; export type IslandItemNames = 'contact' | 'shortcuts';
export const HelpIsland = ({ export const HelpIsland = ({
showList = ['contact', 'shortcuts'], showList = ['contact', 'shortcuts'],
@@ -20,7 +20,7 @@ export const HelpIsland = ({
}) => { }) => {
const [spread, setShowSpread] = useState(false); const [spread, setShowSpread] = useState(false);
const { triggerShortcutsModal, triggerContactModal } = useModal(); const { triggerShortcutsModal, triggerContactModal } = useModal();
const { blockHub } = useAppState(); const blockHub = useBlockSuite(store => store.blockHub);
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {

View File

@@ -1,19 +1,23 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useAppState, PageMeta } from '@/providers/app-state-provider'; import { PageMeta } from '@/providers/app-state-provider';
import { useBlockSuite } from '@/store/workspace';
export const useCurrentPageMeta = (): PageMeta | null => { export const useCurrentPageMeta = (): PageMeta | null => {
const { currentPage, currentWorkspace } = useAppState(); const currentPage = useBlockSuite(store => store.currentPage);
const currentBlockSuiteWorkspace = useBlockSuite(
store => store.currentWorkspace
);
const pageMetaHandler = useCallback((): PageMeta | null => { const pageMetaHandler = useCallback((): PageMeta | null => {
if (!currentPage || !currentWorkspace) { if (!currentPage || !currentBlockSuiteWorkspace) {
return null; return null;
} }
return ( return (
(currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find( (currentBlockSuiteWorkspace.meta.pageMetas.find(
p => p.id === currentPage.id p => p.id === currentPage.id
) as PageMeta) ?? null ) as PageMeta) ?? null
); );
}, [currentPage, currentWorkspace]); }, [currentPage, currentBlockSuiteWorkspace]);
const [currentPageMeta, setCurrentPageMeta] = useState<PageMeta | null>( const [currentPageMeta, setCurrentPageMeta] = useState<PageMeta | null>(
pageMetaHandler pageMetaHandler
@@ -22,16 +26,14 @@ export const useCurrentPageMeta = (): PageMeta | null => {
useEffect(() => { useEffect(() => {
setCurrentPageMeta(pageMetaHandler); setCurrentPageMeta(pageMetaHandler);
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on( const dispose = currentBlockSuiteWorkspace?.meta.pagesUpdated.on(() => {
() => { setCurrentPageMeta(pageMetaHandler);
setCurrentPageMeta(pageMetaHandler); }).dispose;
}
).dispose;
return () => { return () => {
dispose?.(); dispose?.();
}; };
}, [currentPage, currentWorkspace, pageMetaHandler]); }, [currentPage, currentBlockSuiteWorkspace, pageMetaHandler]);
return currentPageMeta; return currentPageMeta;
}; };

View File

@@ -1,13 +1,12 @@
import { Page } from '@blocksuite/store'; import { Page } from '@blocksuite/store';
import { useAppState } from '@/providers/app-state-provider';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useBlockSuite } from '@/store/workspace';
export type EventCallBack<T> = (callback: (props: T) => void) => void; export type EventCallBack<T> = (callback: (props: T) => void) => void;
export type UseHistoryUpdated = (page?: Page) => EventCallBack<Page>; export type UseHistoryUpdated = (page?: Page) => EventCallBack<Page>;
export const useHistoryUpdate: UseHistoryUpdated = () => { export const useHistoryUpdate: UseHistoryUpdated = () => {
const { currentPage } = useAppState(); const currentPage = useBlockSuite(store => store.currentPage);
const callbackQueue = useRef<((page: Page) => void)[]>([]); const callbackQueue = useRef<((page: Page) => void)[]>([]);
useEffect(() => { useEffect(() => {

View File

@@ -5,6 +5,7 @@ import { EditorContainer } from '@blocksuite/editor';
import { useChangePageMeta } from '@/hooks/use-change-page-meta'; import { useChangePageMeta } from '@/hooks/use-change-page-meta';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import { useBlockSuite } from '@/store/workspace';
export type EditorHandlers = { export type EditorHandlers = {
createPage: (params?: { createPage: (params?: {
@@ -39,7 +40,8 @@ const getPageMeta = (workspace: WorkspaceUnit | null, pageId: string) => {
export const usePageHelper = (): EditorHandlers => { export const usePageHelper = (): EditorHandlers => {
const router = useRouter(); const router = useRouter();
const changePageMeta = useChangePageMeta(); const changePageMeta = useChangePageMeta();
const { currentWorkspace, editor } = useAppState(); const editor = useBlockSuite(store => store.editor);
const { currentWorkspace } = useAppState();
return { return {
createPage: ({ createPage: ({

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { EditorContainer } from '@blocksuite/editor'; import { EditorContainer } from '@blocksuite/editor';
import { useAppState } from '@/providers/app-state-provider'; import { useBlockSuite } from '@/store/workspace';
export type EventCallBack<T> = (callback: (props: T) => void) => void; export type EventCallBack<T> = (callback: (props: T) => void) => void;
export type UsePropsUpdated = ( export type UsePropsUpdated = (
@@ -8,7 +8,7 @@ export type UsePropsUpdated = (
) => EventCallBack<EditorContainer>; ) => EventCallBack<EditorContainer>;
export const usePropsUpdated: UsePropsUpdated = () => { export const usePropsUpdated: UsePropsUpdated = () => {
const { editor } = useAppState(); const editor = useBlockSuite(store => store.editor);
const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]); const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]);

View File

@@ -23,6 +23,7 @@ import Head from 'next/head';
import '@affine/i18n'; import '@affine/i18n';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import React from 'react'; import React from 'react';
import { BlockSuiteProvider } from '@/store/workspace';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), { const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false, ssr: false,
@@ -67,20 +68,22 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<title>AFFiNE</title> <title>AFFiNE</title>
</Head> </Head>
<Logger /> <Logger />
<ProviderComposer <BlockSuiteProvider key="BlockSuiteProvider">
contexts={[ <ProviderComposer
<ThemeProvider key="ThemeProvider" />, contexts={[
<AppStateProvider key="appStateProvider" />, <ThemeProvider key="ThemeProvider" />,
<ModalProvider key="ModalProvider" />, <AppStateProvider key="appStateProvider" />,
<ConfirmProvider key="ConfirmProvider" />, <ModalProvider key="ModalProvider" />,
]} <ConfirmProvider key="ConfirmProvider" />,
> ]}
{NoNeedAppStatePageList.includes(router.route) ? ( >
getLayout(<Component {...pageProps} />) {NoNeedAppStatePageList.includes(router.route) ? (
) : ( getLayout(<Component {...pageProps} />)
<AppDefender>{getLayout(<Component {...pageProps} />)}</AppDefender> ) : (
)} <AppDefender>{getLayout(<Component {...pageProps} />)}</AppDefender>
</ProviderComposer> )}
</ProviderComposer>
</BlockSuiteProvider>
</> </>
); );
}; };

View File

@@ -1,10 +1,4 @@
import { import { PropsWithChildren, ReactElement, useEffect, useState } from 'react';
PropsWithChildren,
ReactElement,
useCallback,
useEffect,
useState,
} from 'react';
import { EditorHeader } from '@/components/header'; import { EditorHeader } from '@/components/header';
import MobileModal from '@/components/mobile-modal'; import MobileModal from '@/components/mobile-modal';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
@@ -13,20 +7,16 @@ import WorkspaceLayout from '@/components/workspace-layout';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { EditorContainer } from '@blocksuite/editor';
import Head from 'next/head'; import Head from 'next/head';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { BlockHub } from '@blocksuite/blocks'; import { useBlockSuite } from '@/store/workspace';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), { const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false, ssr: false,
}); });
const BlockHubAppender = () => { const BlockHubAppender = () => {
const { setBlockHub, editor } = useAppState(); const setBlockHub = useBlockSuite(store => store.setBlockHub);
const setBlockHubHandler = useCallback( const editor = useBlockSuite(store => store.editor);
(blockHub: BlockHub) => setBlockHub.current(blockHub),
[setBlockHub]
);
useEffect(() => { useEffect(() => {
let blockHubElement: HTMLElement | null = null; let blockHubElement: HTMLElement | null = null;
@@ -37,23 +27,20 @@ const BlockHubAppender = () => {
return; return;
} }
blockHubElement = blockHub; blockHubElement = blockHub;
// setBlockHubHandler(blockHub); setBlockHub(blockHub);
toolWrapper.appendChild(blockHub); toolWrapper.appendChild(blockHub);
}); });
return () => { return () => {
blockHubElement?.remove(); blockHubElement?.remove();
}; };
}, [editor, setBlockHubHandler]); }, [editor, setBlockHub]);
return null; return null;
}; };
const Page: NextPageWithLayout = () => { const Page: NextPageWithLayout = () => {
const { currentPage, currentWorkspace, setEditor } = useAppState(); const currentPage = useBlockSuite(store => store.currentPage);
const setEditor = useBlockSuite(store => store.setEditor);
const setEditorHandler = useCallback( const { currentWorkspace } = useAppState();
(editor: EditorContainer) => setEditor.current(editor),
[setEditor]
);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -70,7 +57,7 @@ const Page: NextPageWithLayout = () => {
<DynamicBlocksuite <DynamicBlocksuite
page={currentPage} page={currentPage}
workspace={currentWorkspace.blocksuiteWorkspace} workspace={currentWorkspace.blocksuiteWorkspace}
setEditor={setEditorHandler} setEditor={setEditor}
/> />
<BlockHubAppender /> <BlockHubAppender />
</> </>
@@ -82,7 +69,8 @@ const Page: NextPageWithLayout = () => {
const PageDefender = ({ children }: PropsWithChildren) => { const PageDefender = ({ children }: PropsWithChildren) => {
const router = useRouter(); const router = useRouter();
const [pageLoaded, setPageLoaded] = useState(false); const [pageLoaded, setPageLoaded] = useState(false);
const { currentWorkspace, loadPage } = useAppState(); const loadPage = useBlockSuite(store => store.loadPage);
const { currentWorkspace } = useAppState();
const { createPage } = usePageHelper(); const { createPage } = usePageHelper();
useEffect(() => { useEffect(() => {

View File

@@ -9,6 +9,7 @@ import {
} from './interface'; } from './interface';
import { createDefaultWorkspace } from './utils'; import { createDefaultWorkspace } from './utils';
import { User } from '@affine/datacenter'; import { User } from '@affine/datacenter';
import { useBlockSuiteApi } from '@/store/workspace';
export interface Disposable { export interface Disposable {
dispose(): void; dispose(): void;
@@ -22,6 +23,7 @@ export const useAppState = () => useContext(AppState);
export const AppStateProvider = ({ export const AppStateProvider = ({
children, children,
}: PropsWithChildren<AppStateContextProps>) => { }: PropsWithChildren<AppStateContextProps>) => {
const blocksuiteApi = useBlockSuiteApi();
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue); const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
const { dataCenter } = appState; const { dataCenter } = appState;
const [blobState, setBlobState] = useState(false); const [blobState, setBlobState] = useState(false);
@@ -43,9 +45,6 @@ export const AppStateProvider = ({
workspaceList: dataCenter.workspaces, workspaceList: dataCenter.workspaces,
currentWorkspace: null, currentWorkspace: null,
pageList: [], pageList: [],
currentPage: null,
editor: null,
blockHub: null,
synced: true, synced: true,
isOwner: false, isOwner: false,
}); });
@@ -86,19 +85,6 @@ export const AppStateProvider = ({
); );
}, [dataCenter]); }, [dataCenter]);
const loadPage = useRef<AppStateFunction['loadPage']>();
loadPage.current = pageId => {
const { currentWorkspace, currentPage } = appState;
if (pageId === currentPage?.id) {
return;
}
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId) || null;
setAppState({
...appState,
currentPage: page,
});
};
const loadWorkspace: AppStateFunction['loadWorkspace'] = const loadWorkspace: AppStateFunction['loadWorkspace'] =
useRef() as AppStateFunction['loadWorkspace']; useRef() as AppStateFunction['loadWorkspace'];
loadWorkspace.current = async (workspaceId, abort) => { loadWorkspace.current = async (workspaceId, abort) => {
@@ -136,13 +122,13 @@ export const AppStateProvider = ({
const pageList = const pageList =
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? []; (workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
if (workspace?.blocksuiteWorkspace) {
blocksuiteApi.getState().setWorkspace(workspace.blocksuiteWorkspace);
}
setAppState({ setAppState({
...appState, ...appState,
currentWorkspace: workspace, currentWorkspace: workspace,
pageList: pageList, pageList: pageList,
currentPage: null,
editor: null,
blockHub: null,
isOwner, isOwner,
}); });
@@ -171,23 +157,6 @@ export const AppStateProvider = ({
}; };
}, [appState.currentWorkspace]); }, [appState.currentWorkspace]);
const setEditor: AppStateFunction['setEditor'] =
useRef() as AppStateFunction['setEditor'];
setEditor.current = editor => {
setAppState({
...appState,
editor,
});
};
const setBlockHub: AppStateFunction['setBlockHub'] =
useRef() as AppStateFunction['setBlockHub'];
setBlockHub.current = blockHub => {
setAppState({
...appState,
blockHub,
});
};
const login = async () => { const login = async () => {
const { dataCenter } = appState; const { dataCenter } = appState;
try { try {
@@ -213,9 +182,6 @@ export const AppStateProvider = ({
<AppState.Provider <AppState.Provider
value={{ value={{
...appState, ...appState,
setEditor,
setBlockHub,
loadPage: loadPage.current,
loadWorkspace: loadWorkspace, loadWorkspace: loadWorkspace,
login, login,
logout, logout,

View File

@@ -1,6 +1,5 @@
import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter'; import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter';
import type { EditorContainer } from '@blocksuite/editor'; import type { EditorContainer } from '@blocksuite/editor';
import { BlockHub } from '@blocksuite/blocks';
import type { import type {
Page as StorePage, Page as StorePage,
@@ -21,22 +20,15 @@ export type AppStateValue = {
workspaceList: WorkspaceUnit[]; workspaceList: WorkspaceUnit[];
currentWorkspace: WorkspaceUnit | null; currentWorkspace: WorkspaceUnit | null;
pageList: PageMeta[]; pageList: PageMeta[];
currentPage: StorePage | null;
editor?: EditorContainer | null;
blockHub?: BlockHub | null;
synced: boolean; synced: boolean;
isOwner?: boolean; isOwner?: boolean;
blobDataSynced?: boolean; blobDataSynced?: boolean;
}; };
export type AppStateFunction = { export type AppStateFunction = {
setEditor: MutableRefObject<(page: EditorContainer) => void>;
setBlockHub: MutableRefObject<(BlockHub: BlockHub) => void>;
loadWorkspace: MutableRefObject< loadWorkspace: MutableRefObject<
(workspaceId: string, abort?: AbortSignal) => Promise<WorkspaceUnit | null> (workspaceId: string, abort?: AbortSignal) => Promise<WorkspaceUnit | null>
>; >;
loadPage: (pageId: string) => void;
login: () => Promise<User | null>; login: () => Promise<User | null>;
logout: () => Promise<void>; logout: () => Promise<void>;

View File

@@ -0,0 +1,98 @@
import type React from 'react';
import { createContext, useContext, useMemo } from 'react';
import { createStore, useStore } from 'zustand';
import { combine, subscribeWithSelector } from 'zustand/middleware';
import type { UseBoundStore } from 'zustand/react';
import type { Page } from '@blocksuite/store';
import type { BlockHub } from '@blocksuite/blocks';
import type { Workspace } from '@blocksuite/store';
import type { EditorContainer } from '@blocksuite/editor';
export type BlockSuiteState = {
currentWorkspace: Workspace | null;
editor: EditorContainer | null;
currentPage: Page | null;
blockHub: BlockHub | null;
};
export type BlockSuiteActions = {
loadPage: (pageId: string) => void;
setEditor: (editor: EditorContainer) => void;
setWorkspace: (workspace: Workspace) => void;
setBlockHub: (blockHub: BlockHub) => void;
};
const create = () =>
createStore(
subscribeWithSelector(
combine<BlockSuiteState, BlockSuiteActions>(
{
currentWorkspace: null,
currentPage: null,
blockHub: null,
editor: null,
},
(set, get) => ({
setWorkspace: workspace => {
set({
currentWorkspace: workspace,
});
},
setEditor: editor => {
set({
editor,
});
},
loadPage: pageId => {
const { currentWorkspace } = get();
if (currentWorkspace === null) {
console.warn('currentWorkspace is null');
return;
}
const page = currentWorkspace.getPage(pageId);
if (page === null) {
console.warn('cannot find page ', pageId);
return;
}
set({
currentPage: page,
});
},
setBlockHub: blockHub => {
set({
blockHub,
});
},
})
)
)
);
type Store = ReturnType<typeof create>;
const BlockSuiteContext = createContext<Store | null>(null);
export const useBlockSuiteApi = () => {
const api = useContext(BlockSuiteContext);
if (!api) {
throw new Error('cannot find modal context');
}
return api;
};
export const useBlockSuite: UseBoundStore<Store> = ((
selector: Parameters<UseBoundStore<Store>>[0],
equals: Parameters<UseBoundStore<Store>>[1]
) => {
const api = useBlockSuiteApi();
return useStore(api, selector, equals);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
export const BlockSuiteProvider: React.FC<React.PropsWithChildren> =
function ModelProvider({ children }) {
return (
<BlockSuiteContext.Provider value={useMemo(() => create(), [])}>
{children}
</BlockSuiteContext.Provider>
);
};