mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
refactor!: next generation AFFiNE code structure (#1176)
This commit is contained in:
15
apps/web/src/providers/AffineSWRConfigProvider.tsx
Normal file
15
apps/web/src/providers/AffineSWRConfigProvider.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { SWRConfig, SWRConfiguration } from 'swr';
|
||||
|
||||
import { fetcher } from '../plugins/affine/fetcher';
|
||||
|
||||
const config: SWRConfiguration = {
|
||||
suspense: true,
|
||||
fetcher,
|
||||
};
|
||||
|
||||
export const AffineSWRConfigProvider = React.memo<React.PropsWithChildren>(
|
||||
function AffineSWRConfigProvider({ children }) {
|
||||
return <SWRConfig value={config}>{children}</SWRConfig>;
|
||||
}
|
||||
);
|
||||
@@ -1,101 +0,0 @@
|
||||
import { Confirm, ConfirmProps } from '@affine/component';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createContext, useContext, useMemo } from 'react';
|
||||
import { createStore, useStore } from 'zustand';
|
||||
import { combine, subscribeWithSelector } from 'zustand/middleware';
|
||||
import { UseBoundStore } from 'zustand/react';
|
||||
|
||||
type ConfirmActions = {
|
||||
confirm: (props: ConfirmProps) => Promise<boolean>;
|
||||
};
|
||||
|
||||
type ConfirmState = {
|
||||
record: Record<string, JSX.Element>;
|
||||
};
|
||||
|
||||
const create = () =>
|
||||
createStore(
|
||||
subscribeWithSelector(
|
||||
combine<ConfirmState, ConfirmActions>(
|
||||
{
|
||||
record: {},
|
||||
},
|
||||
(set, get) => ({
|
||||
confirm: ({ onCancel, onConfirm, ...props }: ConfirmProps) => {
|
||||
return new Promise(resolve => {
|
||||
const confirmRecord = { ...get().record };
|
||||
const confirmId = String(Date.now());
|
||||
const closeHandler = () => {
|
||||
delete confirmRecord[confirmId];
|
||||
set({ record: { ...confirmRecord } });
|
||||
};
|
||||
set(({ record }) => {
|
||||
return {
|
||||
record: {
|
||||
...record,
|
||||
[confirmId]: (
|
||||
<Confirm
|
||||
{...props}
|
||||
onCancel={() => {
|
||||
closeHandler();
|
||||
onCancel?.();
|
||||
resolve(false);
|
||||
}}
|
||||
onConfirm={() => {
|
||||
closeHandler();
|
||||
onConfirm?.();
|
||||
resolve(true);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
type Store = ReturnType<typeof create>;
|
||||
|
||||
export const ConfirmContext = createContext<Store | null>(null);
|
||||
|
||||
export const useConfirmApi = () => {
|
||||
const api = useContext(ConfirmContext);
|
||||
if (!api) {
|
||||
throw new Error('cannot find confirm context');
|
||||
}
|
||||
return api;
|
||||
};
|
||||
|
||||
export const useConfirm: UseBoundStore<Store> = ((
|
||||
selector: Parameters<UseBoundStore<Store>>[0],
|
||||
equals: Parameters<UseBoundStore<Store>>[1]
|
||||
) => {
|
||||
const api = useConfirmApi();
|
||||
return useStore(api, selector, equals);
|
||||
}) as any;
|
||||
|
||||
function Records() {
|
||||
const conform = useConfirm(store => store.record);
|
||||
return (
|
||||
<>
|
||||
{Object.entries(conform).map(([confirmId, confirmNode]) => {
|
||||
return <div key={confirmId}>{confirmNode}</div>;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const ConfirmProvider = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<ConfirmContext.Provider value={useMemo(() => create(), [])}>
|
||||
{children}
|
||||
<Records />
|
||||
</ConfirmContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmProvider;
|
||||
118
apps/web/src/providers/ModalProvider.tsx
Normal file
118
apps/web/src/providers/ModalProvider.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
openCreateWorkspaceModalAtom,
|
||||
openQuickSearchModalAtom,
|
||||
openWorkspacesModalAtom,
|
||||
} from '../atoms';
|
||||
import { CreateWorkspaceModal } from '../components/pure/create-workspace-modal';
|
||||
import QuickSearchModal from '../components/pure/quick-search-modal';
|
||||
import { WorkspaceListModal } from '../components/pure/workspace-list-modal';
|
||||
import { useCurrentUser } from '../hooks/current/use-current-user';
|
||||
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
|
||||
import { useWorkspaces, useWorkspacesHelper } from '../hooks/use-workspaces';
|
||||
import { apis } from '../shared/apis';
|
||||
|
||||
export function Modals() {
|
||||
const [openWorkspacesModal, setOpenWorkspacesModal] = useAtom(
|
||||
openWorkspacesModalAtom
|
||||
);
|
||||
const [openCreateWorkspaceModal, setOpenCreateWorkspaceModal] = useAtom(
|
||||
openCreateWorkspaceModalAtom
|
||||
);
|
||||
const [openQuickSearchModal, setOpenQuickSearchModalAtom] = useAtom(
|
||||
openQuickSearchModalAtom
|
||||
);
|
||||
const router = useRouter();
|
||||
const user = useCurrentUser();
|
||||
const workspaces = useWorkspaces();
|
||||
const [currentWorkspace, setCurrentWorkspace] = useCurrentWorkspace();
|
||||
const { createRemLocalWorkspace } = useWorkspacesHelper();
|
||||
|
||||
const disableShortCut = router.pathname.startsWith('/404');
|
||||
return (
|
||||
<>
|
||||
<WorkspaceListModal
|
||||
user={user}
|
||||
workspaces={workspaces}
|
||||
currentWorkspaceId={currentWorkspace?.id ?? null}
|
||||
open={openWorkspacesModal}
|
||||
onClose={useCallback(() => {
|
||||
setOpenWorkspacesModal(false);
|
||||
}, [setOpenWorkspacesModal])}
|
||||
onClickWorkspace={useCallback(
|
||||
workspace => {
|
||||
setCurrentWorkspace(workspace.id);
|
||||
router.push({
|
||||
pathname: `/workspace/[workspaceId]/all`,
|
||||
query: {
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
});
|
||||
setOpenWorkspacesModal(false);
|
||||
},
|
||||
[router, setCurrentWorkspace, setOpenWorkspacesModal]
|
||||
)}
|
||||
onClickLogin={useCallback(() => {
|
||||
apis.signInWithGoogle().then(() => {
|
||||
router.reload();
|
||||
});
|
||||
}, [router])}
|
||||
onClickLogout={useCallback(() => {
|
||||
apis.auth.clear();
|
||||
router.reload();
|
||||
}, [router])}
|
||||
onCreateWorkspace={useCallback(() => {
|
||||
setOpenCreateWorkspaceModal(true);
|
||||
}, [setOpenCreateWorkspaceModal])}
|
||||
/>
|
||||
<CreateWorkspaceModal
|
||||
open={openCreateWorkspaceModal}
|
||||
onClose={useCallback(() => {
|
||||
setOpenCreateWorkspaceModal(false);
|
||||
}, [setOpenCreateWorkspaceModal])}
|
||||
onCreate={useCallback(
|
||||
name => {
|
||||
const id = createRemLocalWorkspace(name);
|
||||
setOpenCreateWorkspaceModal(false);
|
||||
setOpenWorkspacesModal(false);
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
workspaceId: id,
|
||||
},
|
||||
});
|
||||
},
|
||||
[
|
||||
createRemLocalWorkspace,
|
||||
router,
|
||||
setOpenCreateWorkspaceModal,
|
||||
setOpenWorkspacesModal,
|
||||
]
|
||||
)}
|
||||
/>
|
||||
{currentWorkspace?.blockSuiteWorkspace && (
|
||||
<QuickSearchModal
|
||||
enableShortCut={!disableShortCut}
|
||||
blockSuiteWorkspace={currentWorkspace?.blockSuiteWorkspace}
|
||||
open={openQuickSearchModal}
|
||||
setOpen={setOpenQuickSearchModalAtom}
|
||||
router={router}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const ModalProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Modals />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AffineTheme,
|
||||
Theme,
|
||||
ThemeMode,
|
||||
ThemeProviderProps,
|
||||
@@ -10,16 +11,27 @@ import {
|
||||
globalThemeVariables,
|
||||
ThemeProvider as ComponentThemeProvider,
|
||||
} from '@affine/component';
|
||||
import { localStorageThemeHelper, SystemThemeHelper } from '@affine/component';
|
||||
import { css, Global } from '@emotion/react';
|
||||
import { GlobalStyles } from '@mui/material';
|
||||
import {
|
||||
createTheme as MuiCreateTheme,
|
||||
ThemeProvider as MuiThemeProvider,
|
||||
} from '@mui/material/styles';
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { useCurrentPageId } from '../hooks/current/use-current-page-id';
|
||||
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
|
||||
import { usePageMeta } from '../hooks/use-page-meta';
|
||||
import { useSystemTheme } from '../hooks/use-system-theme';
|
||||
|
||||
export const ThemeContext = createContext<ThemeProviderValue>({
|
||||
mode: 'light',
|
||||
@@ -31,76 +43,65 @@ export const ThemeContext = createContext<ThemeProviderValue>({
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
const muiTheme = MuiCreateTheme();
|
||||
|
||||
const ThemeInjector = React.memo<{
|
||||
theme: Theme;
|
||||
themeStyle: AffineTheme;
|
||||
}>(function ThemeInjector({ theme, themeStyle }) {
|
||||
return (
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
':root': globalThemeVariables(theme, themeStyle) as any,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const themeAtom = atomWithStorage<ThemeMode>('affine-theme', 'auto');
|
||||
|
||||
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);
|
||||
};
|
||||
const [theme, setTheme] = useAtom(themeAtom);
|
||||
const systemTheme = useSystemTheme();
|
||||
// fixme: use mode detect
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const [currentPage] = useCurrentPageId();
|
||||
const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null);
|
||||
const editorMode =
|
||||
pageMeta.find(page => page.id === currentPage)?.mode ?? 'page';
|
||||
const themeStyle = useMemo(
|
||||
() =>
|
||||
theme === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode),
|
||||
[editorMode, theme]
|
||||
);
|
||||
const changeMode = useCallback(
|
||||
(themeMode: Theme) => {
|
||||
setTheme(themeMode);
|
||||
},
|
||||
[setTheme]
|
||||
);
|
||||
|
||||
// ===================== 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());
|
||||
});
|
||||
}, []);
|
||||
const onceRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTheme(mode === 'auto' ? theme : mode);
|
||||
}, [mode, setTheme, theme]);
|
||||
// ===================== ====================
|
||||
if (onceRef.current) {
|
||||
return;
|
||||
}
|
||||
if (theme !== 'auto') {
|
||||
setTheme(systemTheme);
|
||||
}
|
||||
onceRef.current = true;
|
||||
}, [setTheme, systemTheme, 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]);
|
||||
const realTheme: ThemeMode = theme === 'auto' ? systemTheme : theme;
|
||||
|
||||
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 {
|
||||
${globalThemeVariables(mode, themeStyle) as any}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<ThemeContext.Provider
|
||||
value={{ mode: realTheme, changeMode, theme: themeStyle }}
|
||||
>
|
||||
<ThemeInjector theme={realTheme} themeStyle={themeStyle} />
|
||||
<ComponentThemeProvider theme={themeStyle}>
|
||||
{children}
|
||||
</ComponentThemeProvider>
|
||||
@@ -108,5 +109,3 @@ export const ThemeProvider = ({
|
||||
</MuiThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { Disposable } from '@blocksuite/global/utils';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { useGlobalState } from '@/store/app';
|
||||
|
||||
import { AppStateContext } from './interface';
|
||||
|
||||
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 currentDataCenterWorkspace = useGlobalState(
|
||||
store => store.currentDataCenterWorkspace
|
||||
);
|
||||
const [blobState, setBlobState] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let syncChangeDisposable: Disposable | undefined;
|
||||
const currentWorkspace = currentDataCenterWorkspace;
|
||||
if (!currentWorkspace) {
|
||||
return;
|
||||
}
|
||||
const getBlobStorage = async () => {
|
||||
const blobStorage = await currentWorkspace?.blocksuiteWorkspace?.blobs;
|
||||
syncChangeDisposable = blobStorage?.signals.onBlobSyncStateChange.on(
|
||||
() => {
|
||||
setBlobState(blobStorage?.uploading);
|
||||
}
|
||||
);
|
||||
};
|
||||
getBlobStorage();
|
||||
return () => {
|
||||
syncChangeDisposable?.dispose();
|
||||
};
|
||||
}, [currentDataCenterWorkspace]);
|
||||
|
||||
return (
|
||||
<AppState.Provider
|
||||
value={{
|
||||
blobDataSynced: blobState,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppState.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './interface';
|
||||
export * from './Provider';
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type {
|
||||
Page as StorePage,
|
||||
PageMeta as StorePageMeta,
|
||||
} from '@blocksuite/store';
|
||||
|
||||
export interface PageMeta extends StorePageMeta {
|
||||
favorite: boolean;
|
||||
trash: boolean;
|
||||
trashDate: number;
|
||||
updatedDate: number;
|
||||
mode: 'edgeless' | 'page';
|
||||
}
|
||||
|
||||
export type AppStateValue = {
|
||||
blobDataSynced: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type AppStateFunction = {
|
||||
// todo: remove this in the future
|
||||
};
|
||||
|
||||
export type AppStateContext = AppStateValue & AppStateFunction;
|
||||
|
||||
export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { DataCenter } from '@affine/datacenter';
|
||||
|
||||
const DEFAULT_WORKSPACE_NAME = 'Demo Workspace';
|
||||
|
||||
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||
return dataCenter.createWorkspace({
|
||||
name: DEFAULT_WORKSPACE_NAME,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user