chore: move client folders (#948)

This commit is contained in:
DarkSky
2023-02-10 20:41:01 +08:00
committed by GitHub
parent cb118149f3
commit 8a7393a961
235 changed files with 114 additions and 215 deletions

View File

@@ -0,0 +1,64 @@
import { createContext, useContext, useState, ReactNode } from 'react';
import type { PropsWithChildren } from 'react';
import { Confirm, ConfirmProps } from '@affine/component';
type ConfirmContextValue = {
confirm: (props: ConfirmProps) => Promise<boolean>;
};
type ConfirmContextProps = PropsWithChildren<Record<string, unknown>>;
export const ConfirmContext = createContext<ConfirmContextValue>({
confirm: () => Promise.resolve(false),
});
export const useConfirm = () => useContext(ConfirmContext);
export const ConfirmProvider = ({
children,
}: PropsWithChildren<ConfirmContextProps>) => {
const [confirmRecord, setConfirmRecord] = useState<Record<string, ReactNode>>(
{}
);
return (
<ConfirmContext.Provider
value={{
confirm: ({ onCancel, onConfirm, ...props }: ConfirmProps) => {
return new Promise(resolve => {
const confirmId = String(Date.now());
const closeHandler = () => {
delete confirmRecord[confirmId];
setConfirmRecord({ ...confirmRecord });
};
setConfirmRecord(oldConfirmRecord => {
return {
...oldConfirmRecord,
[confirmId]: (
<Confirm
{...props}
onCancel={() => {
closeHandler();
onCancel?.();
resolve(false);
}}
onConfirm={() => {
closeHandler();
onConfirm?.();
resolve(true);
}}
/>
),
};
});
});
},
}}
>
{children}
{Object.entries(confirmRecord).map(([confirmId, confirmNode]) => {
return <div key={confirmId}>{confirmNode}</div>;
})}
</ConfirmContext.Provider>
);
};
export default ConfirmProvider;

View File

@@ -0,0 +1,114 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { Global, css } from '@emotion/react';
import {
ThemeProvider as MuiThemeProvider,
createTheme as MuiCreateTheme,
} from '@mui/material/styles';
import type { PropsWithChildren } from 'react';
import {
Theme,
ThemeMode,
ThemeProviderProps,
ThemeProviderValue,
} from '@affine/component';
import {
getLightTheme,
getDarkTheme,
globalThemeVariables,
ThemeProvider as ComponentThemeProvider,
} from '@affine/component';
import { SystemThemeHelper, localStorageThemeHelper } from '@affine/component';
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
export const ThemeContext = createContext<ThemeProviderValue>({
mode: 'light',
// eslint-disable-next-line @typescript-eslint/no-empty-function
changeMode: () => {},
theme: getLightTheme('page'),
});
export const useTheme = () => useContext(ThemeContext);
const muiTheme = MuiCreateTheme();
export const ThemeProvider = ({
defaultTheme = 'light',
children,
}: PropsWithChildren<ThemeProviderProps>) => {
const [theme, setTheme] = useState<Theme>(defaultTheme);
const [mode, setMode] = useState<ThemeMode>('auto');
const { mode: editorMode = 'page' } = useCurrentPageMeta() || {};
const themeStyle =
theme === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode);
const changeMode = (themeMode: ThemeMode) => {
themeMode !== mode && setMode(themeMode);
// Remember the theme mode which user selected for next time
localStorageThemeHelper.set(themeMode);
};
// ===================== A temporary solution, just use system theme and not remember the user selected ====================
useEffect(() => {
const systemThemeHelper = new SystemThemeHelper();
const systemTheme = systemThemeHelper.get();
setMode(systemTheme);
systemThemeHelper.onChange(() => {
setMode(systemThemeHelper.get());
});
}, []);
useEffect(() => {
setTheme(mode === 'auto' ? theme : mode);
}, [mode, setTheme, theme]);
// ===================== ====================
// useEffect(() => {
// setMode(localStorageThemeHelper.get() || 'auto');
// }, []);
//
// useEffect(() => {
// const systemThemeHelper = new SystemThemeHelper();
// const selectedThemeMode = localStorageThemeHelper.get();
//
// const themeMode = selectedThemeMode || mode;
// if (themeMode === 'auto') {
// setTheme(systemThemeHelper.get());
// } else {
// setTheme(themeMode);
// }
//
// // When system theme changed, change the theme mode
// systemThemeHelper.onChange(() => {
// // TODO: There may be should be provided a way to let user choose whether to
// if (mode === 'auto') {
// setTheme(systemThemeHelper.get());
// }
// });
//
// return () => {
// systemThemeHelper.dispose();
// };
// }, [mode]);
return (
// Use MuiThemeProvider is just because some Transitions in Mui components need it
<MuiThemeProvider theme={muiTheme}>
<ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
<Global
styles={css`
:root {
${
// eslint-disable-next-line @typescript-eslint/no-explicit-any
globalThemeVariables(mode, themeStyle) as any
}
}
`}
/>
<ComponentThemeProvider theme={themeStyle}>
{children}
</ComponentThemeProvider>
</ThemeContext.Provider>
</MuiThemeProvider>
);
};
export default ThemeProvider;

View File

@@ -0,0 +1,196 @@
import { createContext, useContext, useEffect, useState, useRef } from 'react';
import type { PropsWithChildren } from 'react';
import { getDataCenter } from '@affine/datacenter';
import {
AppStateContext,
AppStateFunction,
AppStateValue,
PageMeta,
} from './interface';
import { createDefaultWorkspace } from './utils';
import { User } from '@affine/datacenter';
export interface Disposable {
dispose(): void;
}
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
export const AppState = createContext<AppStateContext>({} as AppStateContext);
export const useAppState = () => useContext(AppState);
export const AppStateProvider = ({
children,
}: PropsWithChildren<AppStateContextProps>) => {
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
const [blobState, setBlobState] = useState(false);
const [userInfo, setUser] = useState<User | null>({} as User);
useEffect(() => {
const initState = async () => {
const dataCenter = await getDataCenter();
// Ensure datacenter has at least one workspace
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);
}
setUser(
(await dataCenter.getUserInfo(dataCenter.providers[0]?.id)) || null
);
setAppState({
dataCenter,
workspaceList: dataCenter.workspaces,
currentWorkspace: null,
pageList: [],
currentPage: null,
editor: null,
synced: true,
isOwner: false,
});
};
initState();
}, []);
useEffect(() => {
if (!appState?.currentWorkspace?.blocksuiteWorkspace) {
return;
}
const currentWorkspace = appState.currentWorkspace;
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(
() => {
setAppState({
...appState,
pageList: currentWorkspace.blocksuiteWorkspace?.meta
.pageMetas as PageMeta[],
});
}
).dispose;
return () => {
dispose && dispose();
};
}, [appState]);
useEffect(() => {
const { dataCenter } = appState;
// FIXME: onWorkspacesChange should have dispose function
dataCenter?.onWorkspacesChange(
() => {
setAppState({
...appState,
workspaceList: dataCenter.workspaces,
});
},
{ immediate: false }
);
}, [appState]);
const loadPage = useRef<AppStateFunction['loadPage']>();
loadPage.current = (pageId: string) => {
const { currentWorkspace, currentPage } = appState;
if (pageId === currentPage?.id) {
return;
}
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId) || null;
setAppState({
...appState,
currentPage: page,
});
};
const loadWorkspace: AppStateFunction['loadWorkspace'] =
useRef() as AppStateFunction['loadWorkspace'];
loadWorkspace.current = async (workspaceId: string) => {
const { dataCenter, workspaceList, currentWorkspace } = appState;
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
return null;
}
if (workspaceId === currentWorkspace?.id) {
return currentWorkspace;
}
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
let isOwner;
if (workspace?.provider === 'local') {
// isOwner is useful only in the cloud
isOwner = true;
} else {
// We must ensure workspace.owner exists, then ensure id same.
isOwner = workspace?.owner && userInfo?.id === workspace.owner.id;
}
const pageList =
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
setAppState({
...appState,
currentWorkspace: workspace,
pageList: pageList,
currentPage: null,
editor: null,
isOwner,
});
return workspace;
};
useEffect(() => {
let syncChangeDisposable: Disposable | undefined;
const currentWorkspace = appState.currentWorkspace;
if (!currentWorkspace) {
return;
}
const getBlobStorage = async () => {
const blobStorage = await currentWorkspace?.blocksuiteWorkspace?.blobs;
syncChangeDisposable = blobStorage?.signals.onBlobSyncStateChange.on(
() => {
setBlobState(blobStorage?.uploading);
}
);
};
getBlobStorage();
return () => {
syncChangeDisposable?.dispose();
};
}, [appState.currentWorkspace]);
const setEditor: AppStateFunction['setEditor'] =
useRef() as AppStateFunction['setEditor'];
setEditor.current = editor => {
setAppState({
...appState,
editor,
});
};
const login = async () => {
const { dataCenter } = appState;
await dataCenter.login();
const user = (await dataCenter.getUserInfo()) as User;
if (!user) {
throw new Error('User info not found');
}
setUser(user);
return user;
};
const logout = async () => {
const { dataCenter } = appState;
await dataCenter.logout();
setUser(null);
};
return (
<AppState.Provider
value={{
...appState,
setEditor,
loadPage: loadPage.current,
loadWorkspace: loadWorkspace,
login,
logout,
blobDataSynced: blobState,
user: userInfo,
}}
>
{children}
</AppState.Provider>
);
};

View File

@@ -0,0 +1,2 @@
export * from './Provider';
export * from './interface';

View File

@@ -0,0 +1,44 @@
import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter';
import type { EditorContainer } from '@blocksuite/editor';
import type {
Page as StorePage,
PageMeta as StorePageMeta,
} from '@blocksuite/store';
import { MutableRefObject } from 'react';
export interface PageMeta extends StorePageMeta {
favorite: boolean;
trash: boolean;
trashDate: number;
updatedDate: number;
mode: 'edgeless' | 'page';
}
export type AppStateValue = {
dataCenter: DataCenter;
user?: User | null;
workspaceList: WorkspaceUnit[];
currentWorkspace: WorkspaceUnit | null;
pageList: PageMeta[];
currentPage: StorePage | null;
editor?: EditorContainer | null;
synced: boolean;
isOwner?: boolean;
blobDataSynced?: boolean;
};
export type AppStateFunction = {
setEditor: MutableRefObject<(page: EditorContainer) => void>;
loadWorkspace: MutableRefObject<
(workspaceId: string) => Promise<WorkspaceUnit | null>
>;
loadPage: (pageId: string) => void;
login: () => Promise<User>;
logout: () => Promise<void>;
};
export type AppStateContext = AppStateValue & AppStateFunction;
export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;

View File

@@ -0,0 +1,9 @@
import { DataCenter } from '@affine/datacenter';
const DEFAULT_WORKSPACE_NAME = 'AFFiNE Test';
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
return dataCenter.createWorkspace({
name: DEFAULT_WORKSPACE_NAME,
});
};