From 8d5afb4f98467caf9527c7e61789076d0c3105b1 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Tue, 4 Jan 2022 20:30:23 +0700 Subject: [PATCH] completely removed modal reducer --- src/components/main/Header/index.tsx | 8 ++- src/components/node/NodeTitle/index.tsx | 4 +- src/constants/dialogs.ts | 30 ------------ src/constants/modal/index.ts | 32 ++++++++++++ src/containers/dialogs/EditorDialog/index.tsx | 2 +- src/containers/dialogs/LoginDialog/index.tsx | 13 ++--- .../LoginSocialRegisterDialog/index.tsx | 2 +- src/containers/dialogs/Modal/index.tsx | 36 ++++++-------- src/containers/dialogs/PhotoSwipe/index.tsx | 37 +++++--------- .../dialogs/ProfileDialog/index.tsx | 2 +- .../dialogs/RestorePasswordDialog/index.tsx | 2 +- .../dialogs/RestoreRequestDialog/index.tsx | 2 +- src/hooks/flow/useFlowLoader.ts | 2 +- src/hooks/modal/useModal.ts | 16 ++++++ src/hooks/modal/useShowModal.ts | 11 +++++ src/hooks/navigation/useImageModal.ts | 15 ++++-- src/hooks/node/useNodeActions.ts | 15 +----- src/index.tsx | 4 +- src/layouts/NodeLayout/index.tsx | 3 +- src/redux/auth/sagas.ts | 44 ++++++++++------- src/redux/modal/actions.ts | 29 ----------- src/redux/modal/constants.ts | 31 ------------ src/redux/modal/handlers.ts | 14 ------ src/redux/modal/index.ts | 24 --------- src/redux/modal/sagas.ts | 49 ------------------- src/redux/modal/selectors.ts | 3 -- src/redux/store.ts | 6 --- src/redux/types.ts | 6 --- src/store/index.ts | 8 +++ src/store/modal/ModalStore.tsx | 14 ++++++ src/store/modal/useModalStore.ts | 3 ++ src/store/photoSwipe/PhotoSwipeStore.tsx | 16 ++++++ src/store/photoSwipe/usePhotoSwipeStore.tsx | 3 ++ src/types/modal/index.ts | 3 ++ 34 files changed, 189 insertions(+), 300 deletions(-) delete mode 100644 src/constants/dialogs.ts create mode 100644 src/constants/modal/index.ts create mode 100644 src/hooks/modal/useModal.ts create mode 100644 src/hooks/modal/useShowModal.ts delete mode 100644 src/redux/modal/actions.ts delete mode 100644 src/redux/modal/constants.ts delete mode 100644 src/redux/modal/handlers.ts delete mode 100644 src/redux/modal/index.ts delete mode 100644 src/redux/modal/sagas.ts delete mode 100644 src/redux/modal/selectors.ts create mode 100644 src/store/modal/ModalStore.tsx create mode 100644 src/store/modal/useModalStore.ts create mode 100644 src/store/photoSwipe/PhotoSwipeStore.tsx create mode 100644 src/store/photoSwipe/usePhotoSwipeStore.tsx create mode 100644 src/types/modal/index.ts diff --git a/src/components/main/Header/index.tsx b/src/components/main/Header/index.tsx index 19c60d02..489c2657 100644 --- a/src/components/main/Header/index.tsx +++ b/src/components/main/Header/index.tsx @@ -6,7 +6,6 @@ import { Logo } from '~/components/main/Logo'; import { Filler } from '~/components/containers/Filler'; import { selectAuthUpdates, selectUser } from '~/redux/auth/selectors'; -import { DIALOGS } from '~/redux/modal/constants'; import { path, pick } from 'ramda'; import { UserButton } from '../UserButton'; import { Notifications } from '../Notifications'; @@ -14,7 +13,6 @@ import { URLS } from '~/constants/urls'; import classNames from 'classnames'; import styles from './styles.module.scss'; -import * as MODAL_ACTIONS from '~/redux/modal/actions'; import * as AUTH_ACTIONS from '~/redux/auth/actions'; import { IState } from '~/redux/store'; import isBefore from 'date-fns/isBefore'; @@ -24,6 +22,8 @@ import { selectLabUpdatesNodes } from '~/redux/lab/selectors'; import { Button } from '~/components/input/Button'; import { useFlowStore } from '~/store/flow/useFlowStore'; import { observer } from 'mobx-react'; +import { useShowModal } from '~/hooks/modal/useShowModal'; +import { Dialog } from '~/constants/modal'; const mapStateToProps = (state: IState) => ({ user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)), @@ -33,7 +33,6 @@ const mapStateToProps = (state: IState) => ({ const mapDispatchToProps = { push: historyPush, - showDialog: MODAL_ACTIONS.modalShowDialog, authLogout: AUTH_ACTIONS.authLogout, authOpenProfile: AUTH_ACTIONS.authOpenProfile, }; @@ -44,7 +43,6 @@ const HeaderUnconnected: FC = observer( ({ user, user: { is_user, last_seen_boris }, - showDialog, pathname, updates: { boris_commented_at }, authLogout, @@ -53,7 +51,7 @@ const HeaderUnconnected: FC = observer( const [is_scrolled, setIsScrolled] = useState(false); const labUpdates = useShallowSelect(selectLabUpdatesNodes); const { updated: flowUpdates } = useFlowStore(); - const onLogin = useCallback(() => showDialog(DIALOGS.LOGIN), [showDialog]); + const onLogin = useShowModal(Dialog.Login); const onScroll = useCallback(() => { const active = window.scrollY > 32; diff --git a/src/components/node/NodeTitle/index.tsx b/src/components/node/NodeTitle/index.tsx index 6438c68d..128f7586 100644 --- a/src/components/node/NodeTitle/index.tsx +++ b/src/components/node/NodeTitle/index.tsx @@ -24,7 +24,6 @@ interface IProps { isLoading: boolean; - onEdit: () => void; onLike: () => void; onStar: () => void; onLock: () => void; @@ -49,7 +48,6 @@ const NodeTitle: VFC = memo( isLoading, onStar, - onEdit, onLike, onLock, }) => { @@ -95,7 +93,7 @@ const NodeTitle: VFC = memo( {!!id && ( - + )} diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts deleted file mode 100644 index 50a0139c..00000000 --- a/src/constants/dialogs.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NODE_TYPES } from '~/constants/node'; -import { LoginDialog } from '~/containers/dialogs/LoginDialog'; -import { LoadingDialog } from '~/containers/dialogs/LoadingDialog'; -import { TestDialog } from '~/containers/dialogs/TestDialog'; -import { ProfileDialog } from '~/containers/dialogs/ProfileDialog'; -import { RestoreRequestDialog } from '~/containers/dialogs/RestoreRequestDialog'; -import { RestorePasswordDialog } from '~/containers/dialogs/RestorePasswordDialog'; -import { DIALOGS } from '~/redux/modal/constants'; -import { PhotoSwipe } from '~/containers/dialogs/PhotoSwipe'; -import { LoginSocialRegisterDialog } from '~/containers/dialogs/LoginSocialRegisterDialog'; -import { IDialogProps } from '~/redux/types'; -import { FC } from 'react'; - -export const DIALOG_CONTENT: Record> = { - [DIALOGS.LOGIN]: LoginDialog, - [DIALOGS.LOGIN_SOCIAL_REGISTER]: LoginSocialRegisterDialog, - [DIALOGS.LOADING]: LoadingDialog, - [DIALOGS.TEST]: TestDialog, - [DIALOGS.PROFILE]: ProfileDialog, - [DIALOGS.RESTORE_REQUEST]: RestoreRequestDialog, - [DIALOGS.RESTORE_PASSWORD]: RestorePasswordDialog, - [DIALOGS.PHOTOSWIPE]: PhotoSwipe, -}; - -export const NODE_EDITOR_DIALOGS = { - [NODE_TYPES.IMAGE]: DIALOGS.EDITOR_IMAGE, - [NODE_TYPES.TEXT]: DIALOGS.EDITOR_TEXT, - [NODE_TYPES.VIDEO]: DIALOGS.EDITOR_VIDEO, - [NODE_TYPES.AUDIO]: DIALOGS.EDITOR_AUDIO, -}; diff --git a/src/constants/modal/index.ts b/src/constants/modal/index.ts new file mode 100644 index 00000000..671d709f --- /dev/null +++ b/src/constants/modal/index.ts @@ -0,0 +1,32 @@ +import { FC } from 'react'; +import { LoginDialog } from '~/containers/dialogs/LoginDialog'; +import { LoginSocialRegisterDialog } from '~/containers/dialogs/LoginSocialRegisterDialog'; +import { LoadingDialog } from '~/containers/dialogs/LoadingDialog'; +import { TestDialog } from '~/containers/dialogs/TestDialog'; +import { ProfileDialog } from '~/containers/dialogs/ProfileDialog'; +import { RestoreRequestDialog } from '~/containers/dialogs/RestoreRequestDialog'; +import { RestorePasswordDialog } from '~/containers/dialogs/RestorePasswordDialog'; +import { PhotoSwipe } from '~/containers/dialogs/PhotoSwipe'; +import { IDialogProps } from '~/types/modal'; + +export enum Dialog { + Login = 'Login', + LoginSocialRegister = 'LoginSocialRegister', + Loading = 'Loading', + Profile = 'Profile', + RestoreRequest = 'RestoreRequest', + RestorePassword = 'RestorePassword', + Test = 'Test', + Photoswipe = 'Photoswipe', +} + +export const DIALOG_CONTENT: Record> = { + [Dialog.Login]: LoginDialog, + [Dialog.LoginSocialRegister]: LoginSocialRegisterDialog, + [Dialog.Loading]: LoadingDialog, + [Dialog.Test]: TestDialog, + [Dialog.Profile]: ProfileDialog, + [Dialog.RestoreRequest]: RestoreRequestDialog, + [Dialog.RestorePassword]: RestorePasswordDialog, + [Dialog.Photoswipe]: PhotoSwipe, +}; diff --git a/src/containers/dialogs/EditorDialog/index.tsx b/src/containers/dialogs/EditorDialog/index.tsx index dcabb3e8..7558dc6a 100644 --- a/src/containers/dialogs/EditorDialog/index.tsx +++ b/src/containers/dialogs/EditorDialog/index.tsx @@ -1,5 +1,4 @@ import React, { createElement, FC, useCallback, useMemo, useState } from 'react'; -import { IDialogProps } from '~/redux/modal/constants'; import styles from './styles.module.scss'; import { NODE_EDITORS } from '~/constants/node'; import { BetterScrollDialog } from '../BetterScrollDialog'; @@ -15,6 +14,7 @@ import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; import { useTranslatedError } from '~/hooks/data/useTranslatedError'; import { useCloseOnEscape } from '~/hooks'; import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose'; +import { IDialogProps } from '~/types/modal'; interface Props extends IDialogProps { node: INode; diff --git a/src/containers/dialogs/LoginDialog/index.tsx b/src/containers/dialogs/LoginDialog/index.tsx index f10f46cc..d4ff0f99 100644 --- a/src/containers/dialogs/LoginDialog/index.tsx +++ b/src/containers/dialogs/LoginDialog/index.tsx @@ -1,6 +1,5 @@ import React, { FC, FormEvent, useCallback, useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { DIALOGS, IDialogProps } from '~/redux/modal/constants'; import { useCloseOnEscape } from '~/hooks'; import { Group } from '~/components/containers/Group'; import { InputText } from '~/components/input/InputText'; @@ -12,13 +11,15 @@ import { BetterScrollDialog } from '../BetterScrollDialog'; import styles from './styles.module.scss'; import * as ACTIONS from '~/redux/auth/actions'; -import * as MODAL_ACTIONS from '~/redux/modal/actions'; import { ISocialProvider } from '~/redux/auth/types'; import { pick } from 'ramda'; import { LoginDialogButtons } from '~/containers/dialogs/LoginDialogButtons'; import { OAUTH_EVENT_TYPES } from '~/redux/types'; import { DialogTitle } from '~/components/dialogs/DialogTitle'; import { useTranslatedError } from '~/hooks/data/useTranslatedError'; +import { IDialogProps } from '~/types/modal'; +import { useShowModal } from '~/hooks/modal/useShowModal'; +import { Dialog } from '~/constants/modal'; const mapStateToProps = state => ({ ...pick(['error', 'is_registering'], selectAuthLogin(state)), @@ -28,7 +29,6 @@ const mapDispatchToProps = { userSendLoginRequest: ACTIONS.userSendLoginRequest, userSetLoginError: ACTIONS.userSetLoginError, authLoginWithSocial: ACTIONS.authLoginWithSocial, - modalShowDialog: MODAL_ACTIONS.modalShowDialog, authGotOauthLoginEvent: ACTIONS.authGotOauthLoginEvent, }; @@ -40,12 +40,13 @@ const LoginDialogUnconnected: FC = ({ onRequestClose, userSendLoginRequest, userSetLoginError, - modalShowDialog, authGotOauthLoginEvent, }) => { const [username, setUserName] = useState(''); const [password, setPassword] = useState(''); + const showRestoreDialog = useShowModal(Dialog.RestoreRequest); + const onSubmit = useCallback( (event: FormEvent) => { event.preventDefault(); @@ -57,9 +58,9 @@ const LoginDialogUnconnected: FC = ({ const onRestoreRequest = useCallback( event => { event.preventDefault(); - modalShowDialog(DIALOGS.RESTORE_REQUEST); + showRestoreDialog(); }, - [modalShowDialog] + [showRestoreDialog] ); const openOauthWindow = useCallback( diff --git a/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx b/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx index 3db43cdf..8c880a7f 100644 --- a/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx +++ b/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx @@ -1,6 +1,5 @@ import React, { FC, FormEvent, useCallback, useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { IDialogProps } from '~/redux/modal/constants'; import { BetterScrollDialog } from '~/containers/dialogs/BetterScrollDialog'; import { Padder } from '~/components/containers/Padder'; import { DialogTitle } from '~/components/dialogs/DialogTitle'; @@ -12,6 +11,7 @@ import * as AUTH_ACTIONS from '~/redux/auth/actions'; import { useCloseOnEscape } from '~/hooks'; import { LoginSocialRegisterButtons } from '~/containers/dialogs/LoginSocialRegisterButtons'; import { Toggle } from '~/components/input/Toggle'; +import { IDialogProps } from '~/types/modal'; const mapStateToProps = selectAuthRegisterSocial; const mapDispatchToProps = { diff --git a/src/containers/dialogs/Modal/index.tsx b/src/containers/dialogs/Modal/index.tsx index d9ee618d..36b4f213 100644 --- a/src/containers/dialogs/Modal/index.tsx +++ b/src/containers/dialogs/Modal/index.tsx @@ -1,34 +1,26 @@ -import React, { FC, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; -import { DIALOG_CONTENT } from '~/constants/dialogs'; -import { useShallowSelect } from '~/hooks/data/useShallowSelect'; -import { selectModal } from '~/redux/modal/selectors'; -import { modalSetDialog, modalSetShown, modalShowDialog } from '~/redux/modal/actions'; +import React, { FC } from 'react'; import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; +import { DIALOG_CONTENT } from '~/constants/modal'; +import { useModalStore } from '~/store/modal/useModalStore'; +import { has } from 'ramda'; +import { observer } from 'mobx-react'; type IProps = {}; -const Modal: FC = ({}) => { - const { is_shown, dialog } = useShallowSelect(selectModal); - const dispatch = useDispatch(); +const Modal: FC = observer(() => { + const { current, hide } = useModalStore(); - const onRequestClose = useCallback(() => { - dispatch(modalSetShown(false)); - dispatch(modalSetDialog('')); - }, [dispatch]); - - const onDialogChange = useCallback((val: string) => dispatch(modalShowDialog(val)), [dispatch]); - - if (!dialog || !DIALOG_CONTENT[dialog] || !is_shown) return null; + if (!current || !has(current, DIALOG_CONTENT)) { + return null; + } return ( - - {React.createElement(DIALOG_CONTENT[dialog], { - onRequestClose, - onDialogChange, + + {React.createElement(DIALOG_CONTENT[current!]!, { + onRequestClose: hide, })} ); -}; +}); export { Modal }; diff --git a/src/containers/dialogs/PhotoSwipe/index.tsx b/src/containers/dialogs/PhotoSwipe/index.tsx index 1a70f0dc..78242f30 100644 --- a/src/containers/dialogs/PhotoSwipe/index.tsx +++ b/src/containers/dialogs/PhotoSwipe/index.tsx @@ -1,33 +1,22 @@ -import React, { FC, useCallback, useEffect, useRef } from 'react'; -import { connect } from 'react-redux'; +import React, { useEffect, useRef, VFC } from 'react'; import PhotoSwipeJs from 'photoswipe/dist/photoswipe.js'; import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default.js'; import 'photoswipe/dist/photoswipe.css'; import 'photoswipe/dist/default-skin/default-skin.css'; -import { IState } from '~/redux/store'; -import { selectModal } from '~/redux/modal/selectors'; import { getURL } from '~/utils/dom'; import { PRESETS } from '~/constants/urls'; -import * as MODAL_ACTIONS from '~/redux/modal/actions'; import styles from './styles.module.scss'; import classNames from 'classnames'; import { useBlockBackButton } from '~/hooks/navigation/useBlockBackButton'; +import { useModal } from '~/hooks/modal/useModal'; +import { usePhotoSwipeStore } from '~/store/photoSwipe/usePhotoSwipeStore'; +import { observer } from 'mobx-react'; -const mapStateToProps = (state: IState) => ({ - photoswipe: selectModal(state).photoswipe, -}); - -const mapDispatchToProps = { - modalSetShown: MODAL_ACTIONS.modalSetShown, -}; - -type Props = ReturnType & typeof mapDispatchToProps & {}; - -const PhotoSwipeUnconnected: FC = ({ photoswipe, modalSetShown }) => { +const PhotoSwipe: VFC = observer(() => { let ref = useRef(null); - - const closeModal = useCallback(() => modalSetShown(false), [modalSetShown]); + const { hideModal } = useModal(); + const photoswipe = usePhotoSwipeStore(); useEffect(() => { new Promise(async resolve => { @@ -63,12 +52,12 @@ const PhotoSwipeUnconnected: FC = ({ photoswipe, modalSetShown }) => { }); ps.init(); - ps.listen('destroy', closeModal); - ps.listen('close', closeModal); + ps.listen('destroy', hideModal); + ps.listen('close', hideModal); }); - }, [closeModal, photoswipe.images, photoswipe.index]); + }, [hideModal, photoswipe.images, photoswipe.index]); - useBlockBackButton(closeModal); + useBlockBackButton(hideModal); return ( ); -}; - -const PhotoSwipe = connect(mapStateToProps, mapDispatchToProps)(PhotoSwipeUnconnected); +}); export { PhotoSwipe }; diff --git a/src/containers/dialogs/ProfileDialog/index.tsx b/src/containers/dialogs/ProfileDialog/index.tsx index e013b0cd..6b547f3e 100644 --- a/src/containers/dialogs/ProfileDialog/index.tsx +++ b/src/containers/dialogs/ProfileDialog/index.tsx @@ -1,7 +1,6 @@ import React, { FC, useCallback } from 'react'; import { BetterScrollDialog } from '../BetterScrollDialog'; import { ProfileInfo } from '~/containers/profile/ProfileInfo'; -import { IDialogProps } from '~/redux/types'; import { connect } from 'react-redux'; import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors'; import * as AUTH_ACTIONS from '~/redux/auth/actions'; @@ -14,6 +13,7 @@ import { ProfileDescription } from '~/components/profile/ProfileDescription'; import { ProfileMessages } from '~/containers/profile/ProfileMessages'; import { ProfileSettings } from '~/components/profile/ProfileSettings'; import { ProfileAccounts } from '~/components/profile/ProfileAccounts'; +import { IDialogProps } from '~/types/modal'; const mapStateToProps = state => ({ profile: selectAuthProfile(state), diff --git a/src/containers/dialogs/RestorePasswordDialog/index.tsx b/src/containers/dialogs/RestorePasswordDialog/index.tsx index 5f00a682..c178c2c0 100644 --- a/src/containers/dialogs/RestorePasswordDialog/index.tsx +++ b/src/containers/dialogs/RestorePasswordDialog/index.tsx @@ -1,5 +1,4 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { IDialogProps } from '~/redux/types'; import { connect } from 'react-redux'; import { BetterScrollDialog } from '../BetterScrollDialog'; import { Group } from '~/components/containers/Group'; @@ -13,6 +12,7 @@ import { selectAuthRestore } from '~/redux/auth/selectors'; import { ERROR_LITERAL, ERRORS } from '~/constants/errors'; import { Icon } from '~/components/input/Icon'; import { useCloseOnEscape } from '~/hooks'; +import { IDialogProps } from '~/types/modal'; const mapStateToProps = state => ({ restore: selectAuthRestore(state), diff --git a/src/containers/dialogs/RestoreRequestDialog/index.tsx b/src/containers/dialogs/RestoreRequestDialog/index.tsx index 3faa6394..fc35bfd7 100644 --- a/src/containers/dialogs/RestoreRequestDialog/index.tsx +++ b/src/containers/dialogs/RestoreRequestDialog/index.tsx @@ -1,5 +1,4 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { IDialogProps } from '~/redux/types'; import { connect } from 'react-redux'; import { BetterScrollDialog } from '../BetterScrollDialog'; import { Group } from '~/components/containers/Group'; @@ -13,6 +12,7 @@ import { selectAuthRestore } from '~/redux/auth/selectors'; import { ERROR_LITERAL } from '~/constants/errors'; import { Icon } from '~/components/input/Icon'; import { useCloseOnEscape } from '~/hooks'; +import { IDialogProps } from '~/types/modal'; const mapStateToProps = state => ({ restore: selectAuthRestore(state), diff --git a/src/hooks/flow/useFlowLoader.ts b/src/hooks/flow/useFlowLoader.ts index db4c0798..8038225b 100644 --- a/src/hooks/flow/useFlowLoader.ts +++ b/src/hooks/flow/useFlowLoader.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; import { getNodeDiff } from '~/api/node'; import { uniq } from 'ramda'; import { useFlowStore } from '~/store/flow/useFlowStore'; diff --git a/src/hooks/modal/useModal.ts b/src/hooks/modal/useModal.ts new file mode 100644 index 00000000..128b0181 --- /dev/null +++ b/src/hooks/modal/useModal.ts @@ -0,0 +1,16 @@ +import { useModalStore } from '~/store/modal/useModalStore'; +import { useCallback } from 'react'; +import { Dialog } from '~/constants/modal'; + +export const useModal = () => { + const { setCurrent, hide } = useModalStore(); + + const showModal = useCallback( + (dialog: Dialog) => { + setCurrent(dialog); + }, + [setCurrent] + ); + + return { showModal, hideModal: hide }; +}; diff --git a/src/hooks/modal/useShowModal.ts b/src/hooks/modal/useShowModal.ts new file mode 100644 index 00000000..e6c0f764 --- /dev/null +++ b/src/hooks/modal/useShowModal.ts @@ -0,0 +1,11 @@ +import { useCallback } from 'react'; +import { useModal } from '~/hooks/modal/useModal'; +import { Dialog } from '~/constants/modal'; + +export const useShowModal = (dialog: Dialog) => { + const modal = useModal(); + + return useCallback(() => { + modal.showModal(dialog); + }, [dialog, modal]); +}; diff --git a/src/hooks/navigation/useImageModal.ts b/src/hooks/navigation/useImageModal.ts index 9091cfed..462d8c0c 100644 --- a/src/hooks/navigation/useImageModal.ts +++ b/src/hooks/navigation/useImageModal.ts @@ -1,13 +1,18 @@ import { useCallback } from 'react'; import { IFile } from '~/redux/types'; -import { modalShowPhotoswipe } from '~/redux/modal/actions'; -import { useDispatch } from 'react-redux'; +import { usePhotoSwipeStore } from '~/store/photoSwipe/usePhotoSwipeStore'; +import { useShowModal } from '~/hooks/modal/useShowModal'; +import { Dialog } from '~/constants/modal'; export const useImageModal = () => { - const dispatch = useDispatch(); + const { setData } = usePhotoSwipeStore(); + const showModal = useShowModal(Dialog.Photoswipe); return useCallback( - (images: IFile[], index: number) => dispatch(modalShowPhotoswipe(images, index)), - [dispatch] + (images: IFile[], index: number) => { + setData(images, index); + showModal(); + }, + [setData, showModal] ); }; diff --git a/src/hooks/node/useNodeActions.ts b/src/hooks/node/useNodeActions.ts index aa9a48a7..dc5da39b 100644 --- a/src/hooks/node/useNodeActions.ts +++ b/src/hooks/node/useNodeActions.ts @@ -1,22 +1,9 @@ import { INode } from '~/redux/types'; import { useCallback } from 'react'; -import { useDispatch } from 'react-redux'; -import { modalShowDialog } from '~/redux/modal/actions'; -import { NODE_EDITOR_DIALOGS } from '~/constants/dialogs'; import { apiLockNode, apiPostNodeHeroic, apiPostNodeLike } from '~/api/node'; import { showErrorToast } from '~/utils/errors/showToast'; export const useNodeActions = (node: INode, update: (node: Partial) => Promise) => { - const dispatch = useDispatch(); - - const onEdit = useCallback(() => { - if (!node.type) { - return; - } - - dispatch(modalShowDialog(NODE_EDITOR_DIALOGS[node.type])); - }, [dispatch, node]); - const onLike = useCallback(async () => { try { const result = await apiPostNodeLike({ id: node.id }); @@ -50,5 +37,5 @@ export const useNodeActions = (node: INode, update: (node: Partial) => Pr } }, [node.deleted_at, node.id, update]); - return { onEdit, onLike, onStar, onLock }; + return { onLike, onStar, onLock }; }; diff --git a/src/index.tsx b/src/index.tsx index 8ef55236..bcee411b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,11 +5,11 @@ import { PersistGate } from 'redux-persist/integration/react'; import { configureStore } from '~/redux/store'; import { App } from '~/containers/App'; import '~/styles/main.scss'; -import { Store } from '~/store'; +import { getMOBXStore } from '~/store'; import { StoreContextProvider } from '~/utils/context/StoreContextProvider'; const { store, persistor } = configureStore(); -const mobxStore = new Store(); +const mobxStore = getMOBXStore(); render( diff --git a/src/layouts/NodeLayout/index.tsx b/src/layouts/NodeLayout/index.tsx index 1f641a3d..f5645640 100644 --- a/src/layouts/NodeLayout/index.tsx +++ b/src/layouts/NodeLayout/index.tsx @@ -24,7 +24,7 @@ const NodeLayout: FC = () => { const { node, isLoading, update } = useNodeContext(); const { head, block } = useNodeBlocks(node, isLoading); const [canEdit, canLike, canStar] = useNodePermissions(node); - const { onEdit, onLike, onStar, onLock } = useNodeActions(node, update); + const { onLike, onStar, onLock } = useNodeActions(node, update); useNodeCoverImage(node); @@ -50,7 +50,6 @@ const NodeLayout: FC = () => { canEdit={canEdit} canLike={canLike} canStar={canStar} - onEdit={onEdit} onLike={onLike} onStar={onStar} onLock={onLock} diff --git a/src/redux/auth/sagas.ts b/src/redux/auth/sagas.ts index da6d4cd5..8c9bf979 100644 --- a/src/redux/auth/sagas.ts +++ b/src/redux/auth/sagas.ts @@ -40,7 +40,6 @@ import { apiUpdateUser, apiUserLogin, } from '~/redux/auth/api'; -import { modalSetShown, modalShowDialog } from '~/redux/modal/actions'; import { selectAuth, selectAuthProfile, @@ -51,14 +50,16 @@ import { } from './selectors'; import { OAUTH_EVENT_TYPES, Unwrap } from '../types'; import { REHYDRATE, RehydrateAction } from 'redux-persist'; -import { selectModal } from '~/redux/modal/selectors'; -import { DIALOGS } from '~/redux/modal/constants'; import { ERRORS } from '~/constants/errors'; import { messagesSet } from '~/redux/messages/actions'; import { SagaIterator } from 'redux-saga'; import { isEmpty } from 'ramda'; import { AxiosError } from 'axios'; import { labGetUpdates } from '~/redux/lab/actions'; +import { getMOBXStore } from '~/store'; +import { Dialog } from '~/constants/modal'; + +const modalStore = getMOBXStore().modal; function* setTokenSaga({ token }: ReturnType) { localStorage.setItem('token', token); @@ -76,7 +77,8 @@ function* sendLoginRequestSaga({ username, password }: ReturnType) { yield put(authSetToken(token)); yield call(refreshUser); - const { is_shown, dialog }: ReturnType = yield select(selectModal); + const { current } = modalStore; - if (is_shown && dialog === DIALOGS.LOGIN) yield put(modalSetShown(false)); + if (current === Dialog.Login) { + modalStore.hide(); + } } function* logoutSaga() { @@ -143,13 +147,13 @@ function* loadProfile({ username }: ReturnType): SagaIte } function* openProfile({ username, tab = 'profile' }: ReturnType) { - yield put(modalShowDialog(DIALOGS.PROFILE)); + modalStore.setCurrent(Dialog.Profile); yield put(authSetProfile({ tab })); const success: Unwrap = yield call(loadProfile, authLoadProfile(username)); if (!success) { - return yield put(modalSetShown(false)); + modalStore.hide(); } } @@ -159,13 +163,13 @@ function* getUpdates() { if (!user || !user.is_user || user.role === USER_ROLES.GUEST || !user.id) return; - const modal: ReturnType = yield select(selectModal); + const { current } = modalStore; + const profile: ReturnType = yield select(selectAuthProfile); const { last, boris_commented_at }: ReturnType = yield select( selectAuthUpdates ); - const exclude_dialogs = - modal.is_shown && modal.dialog === DIALOGS.PROFILE && profile.user?.id ? profile.user.id : 0; + const exclude_dialogs = current === Dialog.Profile && profile.user?.id ? profile.user.id : 0; const data: Unwrap = yield call(apiAuthGetUpdates, { exclude_dialogs, @@ -248,12 +252,14 @@ function* showRestoreModal({ code }: ReturnType) { const data: Unwrap = yield call(apiCheckRestoreCode, { code }); yield put(authSetRestore({ user: data.user, code, is_loading: false })); - yield put(modalShowDialog(DIALOGS.RESTORE_PASSWORD)); + + modalStore.setCurrent(Dialog.RestoreRequest); } catch (error) { yield put( authSetRestore({ is_loading: false, error: error.message || ERRORS.CODE_IS_INVALID }) ); - yield put(modalShowDialog(DIALOGS.RESTORE_PASSWORD)); + + modalStore.setCurrent(Dialog.RestoreRequest); } } @@ -347,20 +353,20 @@ function* loginWithSocial({ token }: ReturnType) { if (data.token) { yield put(authSetToken(data.token)); yield call(refreshUser); - yield put(modalSetShown(false)); + modalStore.hide(); return; } } catch (error) { - const { dialog }: ReturnType = yield select(selectModal); + const { current } = modalStore; const data = (error as AxiosError<{ needs_register: boolean; errors: Record<'username' | 'password', string>; }>).response?.data; // Backend asks us for account registration - if (dialog !== DIALOGS.LOGIN_SOCIAL_REGISTER && data?.needs_register) { + if (current !== Dialog.LoginSocialRegister && data?.needs_register) { yield put(authSetRegisterSocial({ token })); - yield put(modalShowDialog(DIALOGS.LOGIN_SOCIAL_REGISTER)); + modalStore.setCurrent(Dialog.LoginSocialRegister); return; } @@ -400,7 +406,9 @@ function* authRegisterSocial({ username, password }: ReturnType) => ({ - type: MODAL_ACTIONS.SET, - modal, -}); - -export const modalSetShown = (is_shown: IModalState['is_shown']) => ({ - is_shown, - type: MODAL_ACTIONS.SET_SHOWN, -}); - -export const modalSetDialog = (dialog: IModalState['dialog']) => ({ - dialog, - type: MODAL_ACTIONS.SET_DIALOG, -}); - -export const modalShowDialog = (dialog: IModalState['dialog']) => ({ - dialog, - type: MODAL_ACTIONS.SHOW_DIALOG, -}); - -export const modalShowPhotoswipe = (images: IFile[], index: number) => ({ - type: MODAL_ACTIONS.SHOW_PHOTOSWIPE, - images, - index, -}); diff --git a/src/redux/modal/constants.ts b/src/redux/modal/constants.ts deleted file mode 100644 index 8e300a5c..00000000 --- a/src/redux/modal/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ValueOf } from '~/redux/types'; - -export const DIALOGS = { - EDITOR_IMAGE: 'EDITOR_IMAGE', - EDITOR_TEXT: 'EDITOR_TEXT', - EDITOR_VIDEO: 'EDITOR_VIDEO', - EDITOR_AUDIO: 'EDITOR_AUDIO', - LOGIN: 'LOGIN', - LOGIN_SOCIAL_REGISTER: 'LOGIN_SOCIAL_REGISTER', - LOADING: 'LOADING', - PROFILE: 'PROFILE', - RESTORE_REQUEST: 'RESTORE_REQUEST', - RESTORE_PASSWORD: 'RESTORE_PASSWORD', - TEST: 'TEST', - PHOTOSWIPE: 'PHOTOSWIPE', -}; - -const prefix = 'MODAL.'; - -export const MODAL_ACTIONS = { - SET: `${prefix}SET`, - SET_SHOWN: `${prefix}MODAL.SET_SHOWN`, - SET_DIALOG: `${prefix}SET_DIALOG`, - SHOW_DIALOG: `${prefix}SHOW_DIALOG`, - SHOW_PHOTOSWIPE: `${prefix}SHOW_PHOTOSWIPE`, -}; - -export interface IDialogProps { - onRequestClose: () => void; - onDialogChange?: (dialog: ValueOf) => void; -} diff --git a/src/redux/modal/handlers.ts b/src/redux/modal/handlers.ts deleted file mode 100644 index 04211378..00000000 --- a/src/redux/modal/handlers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MODAL_ACTIONS } from '~/redux/modal/constants'; -import { modalSet } from './actions'; - -const setState = (state, { modal }: ReturnType) => ({ ...state, ...modal }); -const setShown = (state, { is_shown }) => ({ ...state, is_shown }); -const showDialog = (state, { dialog }) => ({ ...state, dialog, is_shown: true }); -const setDialog = (state, { dialog }) => ({ ...state, dialog }); - -export const MODAL_HANDLERS = { - [MODAL_ACTIONS.SET]: setState, - [MODAL_ACTIONS.SET_SHOWN]: setShown, - [MODAL_ACTIONS.SHOW_DIALOG]: showDialog, - [MODAL_ACTIONS.SET_DIALOG]: setDialog, -}; diff --git a/src/redux/modal/index.ts b/src/redux/modal/index.ts deleted file mode 100644 index f8cad6a9..00000000 --- a/src/redux/modal/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MODAL_HANDLERS } from '~/redux/modal/handlers'; -import { createReducer } from '~/utils/reducer'; -import { DIALOGS } from '~/redux/modal/constants'; -import { IFile, ValueOf } from '~/redux/types'; - -export interface IModalState { - is_shown: boolean; - dialog: ValueOf; - photoswipe: { - images: IFile[]; - index: number; - }; -} - -const INITIAL_STATE: IModalState = { - is_shown: false, - dialog: '', - photoswipe: { - images: [], - index: 0, - }, -}; - -export default createReducer(INITIAL_STATE, MODAL_HANDLERS); diff --git a/src/redux/modal/sagas.ts b/src/redux/modal/sagas.ts deleted file mode 100644 index 983a127b..00000000 --- a/src/redux/modal/sagas.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { put, takeEvery } from 'redux-saga/effects'; -import { LOCATION_CHANGE, LocationChangeAction } from 'connected-react-router'; -import { authOpenProfile, authShowRestoreModal } from '../auth/actions'; -import { DIALOGS, MODAL_ACTIONS } from './constants'; -import { modalSet, modalShowPhotoswipe } from './actions'; - -function* onPathChange({ - payload: { - location: { pathname }, - }, -}: LocationChangeAction) { - if (pathname.match(/^\/~([\wа-яА-Я]+)/)) { - const match = pathname.match(/^\/~([\wа-яА-Я]+)/); - - if (!match || !match.length || !match[1]) { - return; - } - - return yield put(authOpenProfile(match[1])); - } - - if (pathname.match(/^\/restore\/([\w\-]+)/)) { - const match = pathname.match(/^\/restore\/([\w\-]+)/); - - if (!match || !match.length || !match[1]) { - return; - } - - return yield put(authShowRestoreModal(match[1])); - } -} - -function* onShowPhotoswipe({ images, index }: ReturnType) { - yield put( - modalSet({ - dialog: DIALOGS.PHOTOSWIPE, - is_shown: true, - photoswipe: { - images, - index, - }, - }) - ); -} - -export function* modalSaga() { - yield takeEvery(LOCATION_CHANGE, onPathChange); - yield takeEvery(MODAL_ACTIONS.SHOW_PHOTOSWIPE, onShowPhotoswipe); -} diff --git a/src/redux/modal/selectors.ts b/src/redux/modal/selectors.ts deleted file mode 100644 index 7c90bc06..00000000 --- a/src/redux/modal/selectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { IState } from '~/redux/store'; - -export const selectModal = (state: IState) => state.modal; diff --git a/src/redux/store.ts b/src/redux/store.ts index d9ee758f..aa963169 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -21,9 +21,6 @@ import uploadSaga from '~/redux/uploads/sagas'; import player, { IPlayerState } from '~/redux/player/reducer'; import playerSaga from '~/redux/player/sagas'; -import modal, { IModalState } from '~/redux/modal'; -import { modalSaga } from './modal/sagas'; - import { authLogout, authOpenProfile, gotAuthPostMessage } from './auth/actions'; import messages, { IMessagesState } from './messages'; @@ -53,7 +50,6 @@ const playerPersistConfig: PersistConfig = { export interface IState { auth: IAuthState; - modal: IModalState; router: RouterState; uploads: IUploadState; player: IPlayerState; @@ -74,7 +70,6 @@ const composeEnhancers = export const store = createStore( combineReducers({ auth: persistReducer(authPersistConfig, auth), - modal, router: connectRouter(history), uploads, player: persistReducer(playerPersistConfig, player), @@ -91,7 +86,6 @@ export function configureStore(): { sagaMiddleware.run(authSaga); sagaMiddleware.run(uploadSaga); sagaMiddleware.run(playerSaga); - sagaMiddleware.run(modalSaga); sagaMiddleware.run(messagesSaga); sagaMiddleware.run(labSaga); diff --git a/src/redux/types.ts b/src/redux/types.ts index eaf490ec..cae6b804 100644 --- a/src/redux/types.ts +++ b/src/redux/types.ts @@ -1,5 +1,4 @@ import { DetailedHTMLProps, InputHTMLAttributes, ReactElement } from 'react'; -import { DIALOGS } from '~/redux/modal/constants'; import { ERRORS } from '~/constants/errors'; import { IUser } from './auth/types'; import { CallEffect } from 'redux-saga/effects'; @@ -31,11 +30,6 @@ export type IIcon = string; export type ValueOf = T[keyof T]; -export interface IDialogProps { - onRequestClose: () => void; - onDialogChange: (dialog: ValueOf) => void; -} - export interface IApiErrorResult { detail?: string; code?: string; diff --git a/src/store/index.ts b/src/store/index.ts index c828c1bd..85009ca3 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,10 +1,18 @@ import { makeAutoObservable } from 'mobx'; import { FlowStore } from '~/store/flow/FlowStore'; +import { ModalStore } from '~/store/modal/ModalStore'; +import { PhotoSwipeStore } from '~/store/photoSwipe/PhotoSwipeStore'; export class Store { flow = new FlowStore(); + modal = new ModalStore(); + photoSwipe = new PhotoSwipeStore(); constructor() { makeAutoObservable(this); } } + +const defaultStore = new Store(); + +export const getMOBXStore = () => defaultStore; diff --git a/src/store/modal/ModalStore.tsx b/src/store/modal/ModalStore.tsx new file mode 100644 index 00000000..33611ac6 --- /dev/null +++ b/src/store/modal/ModalStore.tsx @@ -0,0 +1,14 @@ +import { makeAutoObservable } from 'mobx'; +import { Dialog } from '~/constants/modal'; + +export class ModalStore { + current: Dialog | null = null; + + constructor() { + makeAutoObservable(this); + } + + setCurrent = (current: Dialog | null) => (this.current = current); + + hide = () => (this.current = null); +} diff --git a/src/store/modal/useModalStore.ts b/src/store/modal/useModalStore.ts new file mode 100644 index 00000000..ef35f445 --- /dev/null +++ b/src/store/modal/useModalStore.ts @@ -0,0 +1,3 @@ +import { useStore } from '~/utils/context/StoreContextProvider'; + +export const useModalStore = () => useStore().modal; diff --git a/src/store/photoSwipe/PhotoSwipeStore.tsx b/src/store/photoSwipe/PhotoSwipeStore.tsx new file mode 100644 index 00000000..5b80f66f --- /dev/null +++ b/src/store/photoSwipe/PhotoSwipeStore.tsx @@ -0,0 +1,16 @@ +import { makeAutoObservable } from 'mobx'; +import { IFile } from '~/redux/types'; + +export class PhotoSwipeStore { + images: IFile[] = []; + index: number = 0; + + constructor() { + makeAutoObservable(this); + } + + setData = (images: IFile[], index: number) => { + this.images = images; + this.index = index; + }; +} diff --git a/src/store/photoSwipe/usePhotoSwipeStore.tsx b/src/store/photoSwipe/usePhotoSwipeStore.tsx new file mode 100644 index 00000000..e271dbdc --- /dev/null +++ b/src/store/photoSwipe/usePhotoSwipeStore.tsx @@ -0,0 +1,3 @@ +import { useStore } from '~/utils/context/StoreContextProvider'; + +export const usePhotoSwipeStore = () => useStore().photoSwipe; diff --git a/src/types/modal/index.ts b/src/types/modal/index.ts new file mode 100644 index 00000000..19e3b4e5 --- /dev/null +++ b/src/types/modal/index.ts @@ -0,0 +1,3 @@ +export interface IDialogProps { + onRequestClose: () => void; +}