mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 10:22:55 +08:00
refactor: use zustand in global modal (#940)
This commit is contained in:
@@ -37,7 +37,8 @@
|
|||||||
"quill-cursors": "^4.0.0",
|
"quill-cursors": "^4.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"yjs": "^13.5.45"
|
"yjs": "^13.5.45",
|
||||||
|
"zustand": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.7.18",
|
"@types/node": "18.7.18",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, IconButtonProps } from '@affine/component';
|
import { IconButton, IconButtonProps } from '@affine/component';
|
||||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
import { styled } from '@affine/component';
|
import { styled } from '@affine/component';
|
||||||
|
|
||||||
const StyledIconButtonWithAnimate = styled(IconButton)(({ theme }) => {
|
const StyledIconButtonWithAnimate = styled(IconButton)(({ theme }) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CloudUnsyncedIcon } from '@blocksuite/icons';
|
import { CloudUnsyncedIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
|||||||
import { Tooltip } from '@affine/component';
|
import { Tooltip } from '@affine/component';
|
||||||
|
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
import { MuiFade } from '@affine/component';
|
import { MuiFade } from '@affine/component';
|
||||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
export type IslandItemNames = 'contact' | 'shortcuts';
|
||||||
export const HelpIsland = ({
|
export const HelpIsland = ({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Results } from './Results';
|
|||||||
import { Footer } from './Footer';
|
import { Footer } from './Footer';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
import { getUaHelper } from '@/utils';
|
import { getUaHelper } from '@/utils';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { PublishedResults } from './PublishedResults';
|
import { PublishedResults } from './PublishedResults';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
import { styled } from '@affine/component';
|
import { styled } from '@affine/component';
|
||||||
import { AffineIcon } from '../../icons/Icons';
|
import { AffineIcon } from '../../icons/Icons';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { MuiCollapse } from '@affine/component';
|
import { MuiCollapse } from '@affine/component';
|
||||||
import { Tooltip } from '@affine/component';
|
import { Tooltip } from '@affine/component';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import useLocalStorage from '@/hooks/use-local-storage';
|
import useLocalStorage from '@/hooks/use-local-storage';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
|||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import { AppStateProvider } from '@/providers/app-state-provider';
|
import { AppStateProvider } from '@/providers/app-state-provider';
|
||||||
import ConfirmProvider from '@/providers/ConfirmProvider';
|
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||||
import { ModalProvider } from '@/providers/GlobalModalProvider';
|
import { ModalProvider } from '@/store/globalModal';
|
||||||
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
|
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
|
||||||
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { IconButton } from '@affine/component';
|
|||||||
import NextLink from 'next/link';
|
import NextLink from 'next/link';
|
||||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
|
|
||||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import { Breadcrumbs } from '@affine/component';
|
import { Breadcrumbs } from '@affine/component';
|
||||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||||
import { SearchIcon } from '@blocksuite/icons';
|
import { SearchIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/store/globalModal';
|
||||||
const All = () => {
|
const All = () => {
|
||||||
const { dataCenter } = useAppState();
|
const { dataCenter } = useAppState();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
import {
|
|
||||||
createContext,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import type { PropsWithChildren } from 'react';
|
|
||||||
import ShortcutsModal from '@/components/shortcuts-modal';
|
|
||||||
import ContactModal from '@/components/contact-modal';
|
|
||||||
import QuickSearch from '@/components/quick-search';
|
|
||||||
import { ImportModal } from '@/components/import';
|
|
||||||
import { LoginModal } from '@/components/login-modal';
|
|
||||||
|
|
||||||
type ModalContextValue = {
|
|
||||||
triggerShortcutsModal: () => void;
|
|
||||||
triggerContactModal: () => void;
|
|
||||||
triggerQuickSearchModal: (visible?: boolean) => void;
|
|
||||||
triggerImportModal: () => void;
|
|
||||||
triggerLoginModal: () => void;
|
|
||||||
};
|
|
||||||
type ModalContextProps = PropsWithChildren<Record<string, unknown>>;
|
|
||||||
type ModalMap = {
|
|
||||||
contact: boolean;
|
|
||||||
shortcuts: boolean;
|
|
||||||
quickSearch: boolean;
|
|
||||||
import: boolean;
|
|
||||||
login: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ModalContext = createContext<ModalContextValue>({
|
|
||||||
triggerShortcutsModal: () => {},
|
|
||||||
triggerContactModal: () => {},
|
|
||||||
triggerQuickSearchModal: () => {},
|
|
||||||
triggerImportModal: () => {},
|
|
||||||
triggerLoginModal: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useModal = () => useContext(ModalContext);
|
|
||||||
|
|
||||||
export const ModalProvider = ({
|
|
||||||
children,
|
|
||||||
}: PropsWithChildren<ModalContextProps>) => {
|
|
||||||
const [modalMap, setModalMap] = useState<ModalMap>({
|
|
||||||
contact: false,
|
|
||||||
shortcuts: false,
|
|
||||||
quickSearch: false,
|
|
||||||
import: false,
|
|
||||||
login: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerHandler = useCallback(
|
|
||||||
(key: keyof ModalMap, visible?: boolean) => {
|
|
||||||
setModalMap({
|
|
||||||
...modalMap,
|
|
||||||
[key]: visible ?? !modalMap[key],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[modalMap]
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
window.triggerHandler = () => triggerHandler('login');
|
|
||||||
}, [triggerHandler]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContext.Provider
|
|
||||||
value={{
|
|
||||||
triggerShortcutsModal: () => {
|
|
||||||
triggerHandler('shortcuts');
|
|
||||||
},
|
|
||||||
triggerContactModal: () => {
|
|
||||||
triggerHandler('contact');
|
|
||||||
},
|
|
||||||
triggerQuickSearchModal: (visible?) => {
|
|
||||||
triggerHandler('quickSearch', visible);
|
|
||||||
},
|
|
||||||
triggerImportModal: () => {
|
|
||||||
triggerHandler('import');
|
|
||||||
},
|
|
||||||
triggerLoginModal: () => {
|
|
||||||
triggerHandler('login');
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ContactModal
|
|
||||||
open={modalMap.contact}
|
|
||||||
onClose={() => {
|
|
||||||
triggerHandler('contact', false);
|
|
||||||
}}
|
|
||||||
></ContactModal>
|
|
||||||
<ShortcutsModal
|
|
||||||
open={modalMap.shortcuts}
|
|
||||||
onClose={() => {
|
|
||||||
triggerHandler('shortcuts', false);
|
|
||||||
}}
|
|
||||||
></ShortcutsModal>
|
|
||||||
<QuickSearch
|
|
||||||
open={modalMap.quickSearch}
|
|
||||||
onClose={() => {
|
|
||||||
triggerHandler('quickSearch', false);
|
|
||||||
}}
|
|
||||||
></QuickSearch>
|
|
||||||
<ImportModal
|
|
||||||
open={modalMap.import}
|
|
||||||
onClose={() => {
|
|
||||||
triggerHandler('import', false);
|
|
||||||
}}
|
|
||||||
></ImportModal>
|
|
||||||
<LoginModal
|
|
||||||
open={modalMap.login}
|
|
||||||
onClose={() => {
|
|
||||||
triggerHandler('login', false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{children}
|
|
||||||
</ModalContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ModalProvider;
|
|
||||||
146
packages/app/src/store/globalModal/index.tsx
Normal file
146
packages/app/src/store/globalModal/index.tsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import type React from 'react';
|
||||||
|
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||||
|
import { createStore, useStore } from 'zustand';
|
||||||
|
import { combine, subscribeWithSelector } from 'zustand/middleware';
|
||||||
|
import { UseBoundStore } from 'zustand/react';
|
||||||
|
import ContactModal from '@/components/contact-modal';
|
||||||
|
import ShortcutsModal from '@/components/shortcuts-modal';
|
||||||
|
import QuickSearch from '@/components/quick-search';
|
||||||
|
import { LoginModal } from '@/components/login-modal';
|
||||||
|
import ImportModal from '@/components/import';
|
||||||
|
|
||||||
|
export type ModalState = {
|
||||||
|
contact: boolean;
|
||||||
|
shortcuts: boolean;
|
||||||
|
quickSearch: boolean;
|
||||||
|
import: boolean;
|
||||||
|
login: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModalActions = {
|
||||||
|
triggerShortcutsModal: () => void;
|
||||||
|
triggerContactModal: () => void;
|
||||||
|
triggerQuickSearchModal: (visible?: boolean) => void;
|
||||||
|
triggerImportModal: () => void;
|
||||||
|
triggerLoginModal: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = () =>
|
||||||
|
createStore(
|
||||||
|
subscribeWithSelector(
|
||||||
|
combine<ModalState, ModalActions>(
|
||||||
|
{
|
||||||
|
contact: false,
|
||||||
|
shortcuts: false,
|
||||||
|
quickSearch: false,
|
||||||
|
import: false,
|
||||||
|
login: false,
|
||||||
|
},
|
||||||
|
set => ({
|
||||||
|
triggerShortcutsModal: () => {
|
||||||
|
set(({ shortcuts }) => ({
|
||||||
|
shortcuts: !shortcuts,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
triggerContactModal: () => {
|
||||||
|
set(({ contact }) => ({
|
||||||
|
contact: !contact,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
triggerQuickSearchModal: (visible?: boolean) => {
|
||||||
|
set(({ quickSearch }) => ({
|
||||||
|
quickSearch: visible ?? !quickSearch,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
triggerImportModal: () => {
|
||||||
|
set(state => ({
|
||||||
|
import: !state.import,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
triggerLoginModal: () => {
|
||||||
|
set(({ login }) => ({
|
||||||
|
login: !login,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
type Store = ReturnType<typeof create>;
|
||||||
|
|
||||||
|
const ModalContext = createContext<Store | null>(null);
|
||||||
|
|
||||||
|
export const useModalApi = () => {
|
||||||
|
const api = useContext(ModalContext);
|
||||||
|
if (!api) {
|
||||||
|
throw new Error('cannot find modal context');
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useModal: UseBoundStore<Store> = ((
|
||||||
|
selector: Parameters<UseBoundStore<Store>>[0],
|
||||||
|
equals: Parameters<UseBoundStore<Store>>[1]
|
||||||
|
) => {
|
||||||
|
const api = useModalApi();
|
||||||
|
return useStore(api, selector, equals);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
const Modals: React.FC = function Modal() {
|
||||||
|
const api = useModalApi();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContactModal
|
||||||
|
open={useModal(state => state.contact)}
|
||||||
|
onClose={useCallback(() => {
|
||||||
|
api.setState({
|
||||||
|
contact: false,
|
||||||
|
});
|
||||||
|
}, [api])}
|
||||||
|
></ContactModal>
|
||||||
|
<ShortcutsModal
|
||||||
|
open={useModal(state => state.shortcuts)}
|
||||||
|
onClose={useCallback(() => {
|
||||||
|
api.setState({
|
||||||
|
shortcuts: false,
|
||||||
|
});
|
||||||
|
}, [api])}
|
||||||
|
></ShortcutsModal>
|
||||||
|
<QuickSearch
|
||||||
|
open={useModal(state => state.quickSearch)}
|
||||||
|
onClose={useCallback(() => {
|
||||||
|
api.setState({
|
||||||
|
quickSearch: false,
|
||||||
|
});
|
||||||
|
}, [api])}
|
||||||
|
></QuickSearch>
|
||||||
|
<ImportModal
|
||||||
|
open={useModal(state => state.import)}
|
||||||
|
onClose={useCallback(() => {
|
||||||
|
api.setState({
|
||||||
|
import: false,
|
||||||
|
});
|
||||||
|
}, [api])}
|
||||||
|
></ImportModal>
|
||||||
|
<LoginModal
|
||||||
|
open={useModal(state => state.login)}
|
||||||
|
onClose={useCallback(() => {
|
||||||
|
api.setState({
|
||||||
|
login: false,
|
||||||
|
});
|
||||||
|
}, [api])}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModalProvider: React.FC<React.PropsWithChildren> =
|
||||||
|
function ModelProvider({ children }) {
|
||||||
|
return (
|
||||||
|
<ModalContext.Provider value={useMemo(() => create(), [])}>
|
||||||
|
<Modals />
|
||||||
|
{children}
|
||||||
|
</ModalContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@@ -90,6 +90,7 @@ importers:
|
|||||||
react-dom: 18.2.0
|
react-dom: 18.2.0
|
||||||
typescript: ^4.9.5
|
typescript: ^4.9.5
|
||||||
yjs: ^13.5.45
|
yjs: ^13.5.45
|
||||||
|
zustand: ^4.3.2
|
||||||
dependencies:
|
dependencies:
|
||||||
'@affine/component': link:../component
|
'@affine/component': link:../component
|
||||||
'@affine/datacenter': link:../data-center
|
'@affine/datacenter': link:../data-center
|
||||||
@@ -120,6 +121,7 @@ importers:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
yjs: 13.5.45
|
yjs: 13.5.45
|
||||||
|
zustand: 4.3.2_react@18.2.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node': 18.7.18
|
'@types/node': 18.7.18
|
||||||
'@types/react': 18.0.20
|
'@types/react': 18.0.20
|
||||||
@@ -18152,6 +18154,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==}
|
resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/zustand/4.3.2_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
immer: '>=9.0'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
use-sync-external-store: 1.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/zwitch/1.0.5:
|
/zwitch/1.0.5:
|
||||||
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
|
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
Reference in New Issue
Block a user