refactor(store): move datacenter into global (#1118)

This commit is contained in:
Himself65
2023-02-19 01:35:18 -06:00
committed by GitHub
parent d7bbb0978f
commit b1298c4d3e
21 changed files with 120 additions and 98 deletions

View File

@@ -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": {

View File

@@ -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}</>;
}

View File

@@ -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>
);
};

View File

@@ -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 });
},

View File

@@ -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';

View File

@@ -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 />);