refactor(store): move datacenter into global (#1118)
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
"@blocksuite/react": "0.4.1",
|
||||
"@blocksuite/store": "0.4.1",
|
||||
"react": "^18.2.0",
|
||||
"swr": "^2.0.3",
|
||||
"zustand": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -2,7 +2,9 @@ import { getDataCenter, WorkspaceUnit } from '@affine/datacenter';
|
||||
import { DataCenter } from '@affine/datacenter';
|
||||
import { Disposable, DisposableGroup } from '@blocksuite/global/utils';
|
||||
import type { PageMeta as StorePageMeta } from '@blocksuite/store';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const DEFAULT_WORKSPACE_NAME = 'Demo Workspace';
|
||||
|
||||
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||
@@ -11,6 +13,27 @@ export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||
});
|
||||
};
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var dataCenterPromise: Promise<DataCenter>;
|
||||
// eslint-disable-next-line no-var
|
||||
var dc: DataCenter;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
let dataCenterPromise: Promise<DataCenter> = null!;
|
||||
if (!globalThis.dataCenterPromise) {
|
||||
dataCenterPromise = getDataCenter();
|
||||
dataCenterPromise.then(dataCenter => {
|
||||
globalThis.dc = dataCenter;
|
||||
return dataCenter;
|
||||
});
|
||||
} else {
|
||||
dataCenterPromise = globalThis.dataCenterPromise;
|
||||
}
|
||||
|
||||
export { dataCenterPromise };
|
||||
|
||||
export interface PageMeta extends StorePageMeta {
|
||||
favorite: boolean;
|
||||
trash: boolean;
|
||||
@@ -19,11 +42,9 @@ export interface PageMeta extends StorePageMeta {
|
||||
mode: 'edgeless' | 'page';
|
||||
}
|
||||
|
||||
import { GlobalActionsCreator, useGlobalState, useGlobalStateApi } from '..';
|
||||
import { GlobalActionsCreator, useGlobalStateApi } from '..';
|
||||
|
||||
export type DataCenterState = {
|
||||
readonly dataCenter: DataCenter;
|
||||
readonly dataCenterPromise: Promise<DataCenter>;
|
||||
currentDataCenterWorkspace: WorkspaceUnit | null;
|
||||
dataCenterPageList: PageMeta[];
|
||||
blobDataSynced: boolean;
|
||||
@@ -37,10 +58,6 @@ export type DataCenterActions = {
|
||||
};
|
||||
|
||||
export const createDataCenterState = (): DataCenterState => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
dataCenter: null!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
dataCenterPromise: null!,
|
||||
currentDataCenterWorkspace: null,
|
||||
dataCenterPageList: [],
|
||||
blobDataSynced: false,
|
||||
@@ -50,7 +67,8 @@ export const createDataCenterActions: GlobalActionsCreator<
|
||||
DataCenterActions
|
||||
> = (set, get) => ({
|
||||
loadWorkspace: async (workspaceId, signal) => {
|
||||
const { dataCenter, currentDataCenterWorkspace } = get();
|
||||
const dataCenter = await dataCenterPromise;
|
||||
const { currentDataCenterWorkspace } = get();
|
||||
if (!dataCenter.workspaces.find(v => v.id.toString() === workspaceId)) {
|
||||
return null;
|
||||
}
|
||||
@@ -91,11 +109,14 @@ export const createDataCenterActions: GlobalActionsCreator<
|
||||
},
|
||||
});
|
||||
|
||||
export function useDataCenter() {
|
||||
const { data } = useSWR<DataCenter>(['datacenter'], {
|
||||
fallbackData: DataCenter.initEmpty(),
|
||||
});
|
||||
return data as DataCenter;
|
||||
}
|
||||
|
||||
export function DataCenterPreloader({ children }: React.PropsWithChildren) {
|
||||
const dataCenter = useGlobalState(useCallback(store => store.dataCenter, []));
|
||||
const dataCenterPromise = useGlobalState(
|
||||
useCallback(store => store.dataCenterPromise, [])
|
||||
);
|
||||
const api = useGlobalStateApi();
|
||||
//# region effect for updating workspace page list
|
||||
useEffect(() => {
|
||||
@@ -163,31 +184,5 @@ export function DataCenterPreloader({ children }: React.PropsWithChildren) {
|
||||
[api]
|
||||
);
|
||||
//# endregion
|
||||
|
||||
if (!dataCenter && !dataCenterPromise) {
|
||||
const promise = getDataCenter();
|
||||
api.setState({ dataCenterPromise: promise });
|
||||
promise.then(async dataCenter => {
|
||||
// Ensure datacenter has at least one workspace
|
||||
if (dataCenter.workspaces.length === 0) {
|
||||
await createDefaultWorkspace(dataCenter);
|
||||
}
|
||||
// set initial state
|
||||
api.setState({
|
||||
dataCenter,
|
||||
currentWorkspace: null,
|
||||
currentDataCenterWorkspace: null,
|
||||
dataCenterPageList: [],
|
||||
user:
|
||||
(await dataCenter.getUserInfo(
|
||||
dataCenter.providers.filter(p => p.id !== 'local')[0]?.id
|
||||
)) || null,
|
||||
});
|
||||
});
|
||||
throw promise;
|
||||
}
|
||||
if (!dataCenter) {
|
||||
throw dataCenterPromise;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import type React from 'react';
|
||||
import { createContext, useContext, useMemo } from 'react';
|
||||
import { preload, SWRConfig, SWRConfiguration } from 'swr';
|
||||
import { createStore, StateCreator, useStore } from 'zustand';
|
||||
import { combine, subscribeWithSelector } from 'zustand/middleware';
|
||||
import type { UseBoundStore } from 'zustand/react';
|
||||
@@ -13,7 +15,9 @@ import {
|
||||
import {
|
||||
createDataCenterActions,
|
||||
createDataCenterState,
|
||||
createDefaultWorkspace,
|
||||
DataCenterActions,
|
||||
dataCenterPromise,
|
||||
DataCenterState,
|
||||
} from './datacenter';
|
||||
import {
|
||||
@@ -80,11 +84,37 @@ export const useGlobalState: UseBoundStore<Store> = ((
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
}) as any;
|
||||
|
||||
export type DataKey = ['datacenter', string] | ['datacenter'];
|
||||
|
||||
const swrFetcher = async (keys: DataKey) => {
|
||||
assertEquals(keys[0], 'datacenter');
|
||||
if (keys.length === 1) {
|
||||
return await dataCenterPromise.then(async dataCenter => {
|
||||
if (dataCenter.workspaces.length === 0) {
|
||||
await createDefaultWorkspace(dataCenter);
|
||||
}
|
||||
return dataCenter;
|
||||
});
|
||||
} else {
|
||||
const dataCenter = await dataCenterPromise;
|
||||
return dataCenter.loadWorkspace(keys[1]);
|
||||
}
|
||||
};
|
||||
|
||||
preload(['datacenter'], swrFetcher);
|
||||
|
||||
const swrConfig: SWRConfiguration = {
|
||||
fetcher: swrFetcher,
|
||||
suspense: true,
|
||||
};
|
||||
|
||||
export const GlobalAppProvider: React.FC<React.PropsWithChildren> =
|
||||
function ModelProvider({ children }) {
|
||||
return (
|
||||
<GlobalStateContext.Provider value={useMemo(() => create(), [])}>
|
||||
{children}
|
||||
</GlobalStateContext.Provider>
|
||||
<SWRConfig value={swrConfig}>
|
||||
<GlobalStateContext.Provider value={useMemo(() => create(), [])}>
|
||||
{children}
|
||||
</GlobalStateContext.Provider>
|
||||
</SWRConfig>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { User } from '@affine/datacenter';
|
||||
|
||||
import { GlobalActionsCreator } from '..';
|
||||
import { dataCenterPromise } from '../datacenter';
|
||||
|
||||
export interface UserState {
|
||||
user: User | null;
|
||||
@@ -24,7 +25,8 @@ export const createUserActions: GlobalActionsCreator<UserActions> = (
|
||||
) => {
|
||||
return {
|
||||
login: async () => {
|
||||
const { dataCenter, currentDataCenterWorkspace: workspace } = get();
|
||||
const { currentDataCenterWorkspace: workspace } = get();
|
||||
const dataCenter = await dataCenterPromise;
|
||||
try {
|
||||
await dataCenter.login();
|
||||
const user = (await dataCenter.getUserInfo()) as User;
|
||||
@@ -49,7 +51,7 @@ export const createUserActions: GlobalActionsCreator<UserActions> = (
|
||||
}
|
||||
},
|
||||
logout: async () => {
|
||||
const { dataCenter } = get();
|
||||
const dataCenter = await dataCenterPromise;
|
||||
await dataCenter.logout();
|
||||
set({ user: null });
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './app';
|
||||
export type { PageMeta } from './app/datacenter';
|
||||
export { createDefaultWorkspace, DataCenterPreloader } from './app/datacenter';
|
||||
export { dataCenterPromise, useDataCenter } from './app/datacenter';
|
||||
|
||||
@@ -7,11 +7,10 @@ import { DataCenter, getDataCenter } from '@affine/datacenter';
|
||||
import {
|
||||
createDefaultWorkspace,
|
||||
GlobalAppProvider,
|
||||
useDataCenter,
|
||||
useGlobalState,
|
||||
useGlobalStateApi,
|
||||
} from '@affine/store';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
describe('App Store', () => {
|
||||
@@ -21,29 +20,16 @@ describe('App Store', () => {
|
||||
await createDefaultWorkspace(dataCenter);
|
||||
const Inner = () => {
|
||||
const state = useGlobalState();
|
||||
const dataCenter = useDataCenter();
|
||||
expect(state).toBeTypeOf('object');
|
||||
expect(state.dataCenter).toBeInstanceOf(DataCenter);
|
||||
expect(state.dataCenterPromise).toBeInstanceOf(Promise);
|
||||
state.dataCenterPromise.then(dc => expect(dc).toBe(state.dataCenter));
|
||||
expect(dataCenter).toBeInstanceOf(DataCenter);
|
||||
return <div>Test2</div>;
|
||||
};
|
||||
|
||||
const Loader = ({ children }: React.PropsWithChildren) => {
|
||||
const api = useGlobalStateApi();
|
||||
if (!api.getState().dataCenter) {
|
||||
api.setState({
|
||||
dataCenter,
|
||||
dataCenterPromise,
|
||||
});
|
||||
}
|
||||
return <>{children}</>;
|
||||
};
|
||||
const App = () => (
|
||||
<GlobalAppProvider>
|
||||
<div>Test1</div>
|
||||
<Loader>
|
||||
<Inner />
|
||||
</Loader>
|
||||
<Inner />
|
||||
</GlobalAppProvider>
|
||||
);
|
||||
const app = render(<App />);
|
||||
|
||||
Reference in New Issue
Block a user