mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-02 06:39:46 +08:00
refactor(store): port to useGlobalState with zustand (#1012)
This commit is contained in:
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 });
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user