refactor(store): port to useGlobalState with zustand (#1012)

This commit is contained in:
Himself65
2023-02-14 23:38:21 -06:00
committed by GitHub
parent 2b3ec1240a
commit 6a8aff9e56
29 changed files with 304 additions and 207 deletions
@@ -0,0 +1,60 @@
import { Page, Workspace } from '@blocksuite/store';
import { EditorContainer } from '@blocksuite/editor';
import { BlockHub } from '@blocksuite/blocks';
import { GlobalActionsCreator } from '@/store/app';
export interface BlockSuiteState {
currentWorkspace: Workspace | null;
editor: EditorContainer | null;
currentPage: Page | null;
blockHub: BlockHub | null;
}
export const createBlockSuiteState = (): BlockSuiteState => ({
currentWorkspace: null,
currentPage: null,
blockHub: null,
editor: null,
});
export interface BlockSuiteActions {
loadPage: (pageId: string) => void;
setEditor: (editor: EditorContainer) => void;
setWorkspace: (workspace: Workspace) => void;
setBlockHub: (blockHub: BlockHub) => void;
}
export const createBlockSuiteActions: GlobalActionsCreator<
BlockSuiteActions
> = (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,
});
},
});
+106
View File
@@ -0,0 +1,106 @@
import type React from 'react';
import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import { createStore, StateCreator, useStore } from 'zustand';
import { combine, subscribeWithSelector } from 'zustand/middleware';
import type { UseBoundStore } from 'zustand/react';
import {
BlockSuiteActions,
BlockSuiteState,
createBlockSuiteActions,
createBlockSuiteState,
} from '@/store/app/blocksuite';
import {
createUserActions,
createUserState,
UserActions,
UserState,
} from '@/store/app/user';
import { DataCenter, getDataCenter } from '@affine/datacenter';
import { createDefaultWorkspace } from '@/providers/app-state-provider/utils';
export type GlobalActionsCreator<Actions, Store = GlobalState> = StateCreator<
Store,
[['zustand/subscribeWithSelector', unknown]],
[],
Actions
>;
export interface GlobalState extends BlockSuiteState, UserState {
readonly dataCenter: DataCenter;
}
export interface GlobalActions extends BlockSuiteActions, UserActions {}
const create = () =>
createStore(
subscribeWithSelector(
combine<GlobalState, GlobalActions>(
{
...createBlockSuiteState(),
...createUserState(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataCenter: null!,
},
/* deepscan-disable TOO_MANY_ARGS */
(set, get, api) => ({
...createBlockSuiteActions(set, get, api),
...createUserActions(set, get, api),
})
/* deepscan-enable TOO_MANY_ARGS */
)
)
);
type Store = ReturnType<typeof create>;
const GlobalStateContext = createContext<Store | null>(null);
export const useGlobalStateApi = () => {
const api = useContext(GlobalStateContext);
if (!api) {
throw new Error('cannot find modal context');
}
return api;
};
export const useGlobalState: UseBoundStore<Store> = ((
selector: Parameters<UseBoundStore<Store>>[0],
equals: Parameters<UseBoundStore<Store>>[1]
) => {
const api = useGlobalStateApi();
return useStore(api, selector, equals);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
function DataCenterSideEffect() {
const onceRef = useRef(true);
const api = useGlobalStateApi();
useEffect(() => {
async function init() {
const dataCenterPromise = getDataCenter();
dataCenterPromise.then(async dataCenter => {
// Ensure datacenter has at least one workspace
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);
}
api.setState({ dataCenter });
});
}
if (onceRef.current) {
onceRef.current = false;
init().then(() => {
console.log('datacenter init success');
});
}
}, [api]);
return null;
}
export const GlobalAppProvider: React.FC<React.PropsWithChildren> =
function ModelProvider({ children }) {
return (
<GlobalStateContext.Provider value={useMemo(() => create(), [])}>
<DataCenterSideEffect />
{children}
</GlobalStateContext.Provider>
);
};
+45
View File
@@ -0,0 +1,45 @@
import { GlobalActionsCreator } from '@/store/app';
import { User } from '@affine/datacenter';
export interface UserState {
user: User | null;
isOwner: boolean;
}
export interface UserActions {
login: () => Promise<User | null>;
logout: () => Promise<void>;
}
export const createUserState = (): UserState => ({
user: null,
isOwner: false,
});
export const createUserActions: GlobalActionsCreator<UserActions> = (
set,
get
) => {
return {
login: async () => {
const { dataCenter } = get();
try {
await dataCenter.login();
const user = (await dataCenter.getUserInfo()) as User;
if (!user) {
// Add ErrorBoundary
throw new Error('User info not found');
}
set({ user });
return user;
} catch (error) {
return null; // login failed
}
},
logout: async () => {
const { dataCenter } = get();
await dataCenter.logout();
set({ user: null });
},
};
};
-98
View File
@@ -1,98 +0,0 @@
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>
);
};