mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 14:27:02 +08:00
chore: move client folders (#948)
This commit is contained in:
64
apps/web/src/providers/ConfirmProvider.tsx
Normal file
64
apps/web/src/providers/ConfirmProvider.tsx
Normal 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;
|
||||
114
apps/web/src/providers/ThemeProvider.tsx
Normal file
114
apps/web/src/providers/ThemeProvider.tsx
Normal 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;
|
||||
196
apps/web/src/providers/app-state-provider/Provider.tsx
Normal file
196
apps/web/src/providers/app-state-provider/Provider.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
2
apps/web/src/providers/app-state-provider/index.ts
Normal file
2
apps/web/src/providers/app-state-provider/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './Provider';
|
||||
export * from './interface';
|
||||
44
apps/web/src/providers/app-state-provider/interface.ts
Normal file
44
apps/web/src/providers/app-state-provider/interface.ts
Normal 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;
|
||||
9
apps/web/src/providers/app-state-provider/utils.ts
Normal file
9
apps/web/src/providers/app-state-provider/utils.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user