mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-26 05:56:19 +03:00
refactor: use zustand in global modal (#940)
This commit is contained in:
parent
323d7c96f7
commit
094e73b7fb
@ -37,7 +37,8 @@
|
||||
"quill-cursors": "^4.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"yjs": "^13.5.45"
|
||||
"yjs": "^13.5.45",
|
||||
"zustand": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.7.18",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { IconButton, IconButtonProps } from '@affine/component';
|
||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
const StyledIconButtonWithAnimate = styled(IconButton)(({ theme }) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CloudUnsyncedIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { IconButton } from '@affine/component';
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||
import { Tooltip } from '@affine/component';
|
||||
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
import { MuiFade } from '@affine/component';
|
||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
||||
export const HelpIsland = ({
|
||||
|
@ -11,7 +11,7 @@ import { Results } from './Results';
|
||||
import { Footer } from './Footer';
|
||||
import { Command } from 'cmdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PublishedResults } from './PublishedResults';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
import { styled } from '@affine/component';
|
||||
import { AffineIcon } from '../../icons/Icons';
|
||||
import {
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
import Link from 'next/link';
|
||||
import { MuiCollapse } 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 { IconButton } from '@affine/component';
|
||||
import useLocalStorage from '@/hooks/use-local-storage';
|
||||
|
@ -12,7 +12,7 @@ import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
||||
import type { NextPage } from 'next';
|
||||
import { AppStateProvider } from '@/providers/app-state-provider';
|
||||
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||
import { ModalProvider } from '@/providers/GlobalModalProvider';
|
||||
import { ModalProvider } from '@/store/globalModal';
|
||||
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
|
@ -11,7 +11,7 @@ import { IconButton } from '@affine/component';
|
||||
import NextLink from 'next/link';
|
||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
import { Breadcrumbs } from '@affine/component';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useModal } from '@/store/globalModal';
|
||||
const All = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
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>
|
||||
);
|
||||
};
|
@ -90,6 +90,7 @@ importers:
|
||||
react-dom: 18.2.0
|
||||
typescript: ^4.9.5
|
||||
yjs: ^13.5.45
|
||||
zustand: ^4.3.2
|
||||
dependencies:
|
||||
'@affine/component': link:../component
|
||||
'@affine/datacenter': link:../data-center
|
||||
@ -120,6 +121,7 @@ importers:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
yjs: 13.5.45
|
||||
zustand: 4.3.2_react@18.2.0
|
||||
devDependencies:
|
||||
'@types/node': 18.7.18
|
||||
'@types/react': 18.0.20
|
||||
@ -18152,6 +18154,22 @@ packages:
|
||||
resolution: {integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
|
||||
dev: true
|
||||
|
Loading…
Reference in New Issue
Block a user