1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

completely removed modal reducer

This commit is contained in:
Fedor Katurov 2022-01-04 20:30:23 +07:00
parent b1e8bddbaf
commit 8d5afb4f98
34 changed files with 189 additions and 300 deletions

View file

@ -6,7 +6,6 @@ import { Logo } from '~/components/main/Logo';
import { Filler } from '~/components/containers/Filler'; import { Filler } from '~/components/containers/Filler';
import { selectAuthUpdates, selectUser } from '~/redux/auth/selectors'; import { selectAuthUpdates, selectUser } from '~/redux/auth/selectors';
import { DIALOGS } from '~/redux/modal/constants';
import { path, pick } from 'ramda'; import { path, pick } from 'ramda';
import { UserButton } from '../UserButton'; import { UserButton } from '../UserButton';
import { Notifications } from '../Notifications'; import { Notifications } from '../Notifications';
@ -14,7 +13,6 @@ import { URLS } from '~/constants/urls';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import * as MODAL_ACTIONS from '~/redux/modal/actions';
import * as AUTH_ACTIONS from '~/redux/auth/actions'; import * as AUTH_ACTIONS from '~/redux/auth/actions';
import { IState } from '~/redux/store'; import { IState } from '~/redux/store';
import isBefore from 'date-fns/isBefore'; import isBefore from 'date-fns/isBefore';
@ -24,6 +22,8 @@ import { selectLabUpdatesNodes } from '~/redux/lab/selectors';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
import { useFlowStore } from '~/store/flow/useFlowStore'; import { useFlowStore } from '~/store/flow/useFlowStore';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { useShowModal } from '~/hooks/modal/useShowModal';
import { Dialog } from '~/constants/modal';
const mapStateToProps = (state: IState) => ({ const mapStateToProps = (state: IState) => ({
user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)), user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)),
@ -33,7 +33,6 @@ const mapStateToProps = (state: IState) => ({
const mapDispatchToProps = { const mapDispatchToProps = {
push: historyPush, push: historyPush,
showDialog: MODAL_ACTIONS.modalShowDialog,
authLogout: AUTH_ACTIONS.authLogout, authLogout: AUTH_ACTIONS.authLogout,
authOpenProfile: AUTH_ACTIONS.authOpenProfile, authOpenProfile: AUTH_ACTIONS.authOpenProfile,
}; };
@ -44,7 +43,6 @@ const HeaderUnconnected: FC<IProps> = observer(
({ ({
user, user,
user: { is_user, last_seen_boris }, user: { is_user, last_seen_boris },
showDialog,
pathname, pathname,
updates: { boris_commented_at }, updates: { boris_commented_at },
authLogout, authLogout,
@ -53,7 +51,7 @@ const HeaderUnconnected: FC<IProps> = observer(
const [is_scrolled, setIsScrolled] = useState(false); const [is_scrolled, setIsScrolled] = useState(false);
const labUpdates = useShallowSelect(selectLabUpdatesNodes); const labUpdates = useShallowSelect(selectLabUpdatesNodes);
const { updated: flowUpdates } = useFlowStore(); const { updated: flowUpdates } = useFlowStore();
const onLogin = useCallback(() => showDialog(DIALOGS.LOGIN), [showDialog]); const onLogin = useShowModal(Dialog.Login);
const onScroll = useCallback(() => { const onScroll = useCallback(() => {
const active = window.scrollY > 32; const active = window.scrollY > 32;

View file

@ -24,7 +24,6 @@ interface IProps {
isLoading: boolean; isLoading: boolean;
onEdit: () => void;
onLike: () => void; onLike: () => void;
onStar: () => void; onStar: () => void;
onLock: () => void; onLock: () => void;
@ -49,7 +48,6 @@ const NodeTitle: VFC<IProps> = memo(
isLoading, isLoading,
onStar, onStar,
onEdit,
onLike, onLike,
onLock, onLock,
}) => { }) => {
@ -95,7 +93,7 @@ const NodeTitle: VFC<IProps> = memo(
{!!id && ( {!!id && (
<Link to={URLS.NODE_EDIT_URL(id)}> <Link to={URLS.NODE_EDIT_URL(id)}>
<Icon icon="edit" size={24} onClick={onEdit} /> <Icon icon="edit" size={24} />
</Link> </Link>
)} )}
</div> </div>

View file

@ -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<string, FC<IDialogProps>> = {
[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,
};

View file

@ -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, FC<IDialogProps>> = {
[Dialog.Login]: LoginDialog,
[Dialog.LoginSocialRegister]: LoginSocialRegisterDialog,
[Dialog.Loading]: LoadingDialog,
[Dialog.Test]: TestDialog,
[Dialog.Profile]: ProfileDialog,
[Dialog.RestoreRequest]: RestoreRequestDialog,
[Dialog.RestorePassword]: RestorePasswordDialog,
[Dialog.Photoswipe]: PhotoSwipe,
};

View file

@ -1,5 +1,4 @@
import React, { createElement, FC, useCallback, useMemo, useState } from 'react'; import React, { createElement, FC, useCallback, useMemo, useState } from 'react';
import { IDialogProps } from '~/redux/modal/constants';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { NODE_EDITORS } from '~/constants/node'; import { NODE_EDITORS } from '~/constants/node';
import { BetterScrollDialog } from '../BetterScrollDialog'; import { BetterScrollDialog } from '../BetterScrollDialog';
@ -15,6 +14,7 @@ import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
import { useTranslatedError } from '~/hooks/data/useTranslatedError'; import { useTranslatedError } from '~/hooks/data/useTranslatedError';
import { useCloseOnEscape } from '~/hooks'; import { useCloseOnEscape } from '~/hooks';
import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose'; import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose';
import { IDialogProps } from '~/types/modal';
interface Props extends IDialogProps { interface Props extends IDialogProps {
node: INode; node: INode;

View file

@ -1,6 +1,5 @@
import React, { FC, FormEvent, useCallback, useEffect, useState } from 'react'; import React, { FC, FormEvent, useCallback, useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { DIALOGS, IDialogProps } from '~/redux/modal/constants';
import { useCloseOnEscape } from '~/hooks'; import { useCloseOnEscape } from '~/hooks';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import { InputText } from '~/components/input/InputText'; import { InputText } from '~/components/input/InputText';
@ -12,13 +11,15 @@ import { BetterScrollDialog } from '../BetterScrollDialog';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import * as ACTIONS from '~/redux/auth/actions'; import * as ACTIONS from '~/redux/auth/actions';
import * as MODAL_ACTIONS from '~/redux/modal/actions';
import { ISocialProvider } from '~/redux/auth/types'; import { ISocialProvider } from '~/redux/auth/types';
import { pick } from 'ramda'; import { pick } from 'ramda';
import { LoginDialogButtons } from '~/containers/dialogs/LoginDialogButtons'; import { LoginDialogButtons } from '~/containers/dialogs/LoginDialogButtons';
import { OAUTH_EVENT_TYPES } from '~/redux/types'; import { OAUTH_EVENT_TYPES } from '~/redux/types';
import { DialogTitle } from '~/components/dialogs/DialogTitle'; import { DialogTitle } from '~/components/dialogs/DialogTitle';
import { useTranslatedError } from '~/hooks/data/useTranslatedError'; 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 => ({ const mapStateToProps = state => ({
...pick(['error', 'is_registering'], selectAuthLogin(state)), ...pick(['error', 'is_registering'], selectAuthLogin(state)),
@ -28,7 +29,6 @@ const mapDispatchToProps = {
userSendLoginRequest: ACTIONS.userSendLoginRequest, userSendLoginRequest: ACTIONS.userSendLoginRequest,
userSetLoginError: ACTIONS.userSetLoginError, userSetLoginError: ACTIONS.userSetLoginError,
authLoginWithSocial: ACTIONS.authLoginWithSocial, authLoginWithSocial: ACTIONS.authLoginWithSocial,
modalShowDialog: MODAL_ACTIONS.modalShowDialog,
authGotOauthLoginEvent: ACTIONS.authGotOauthLoginEvent, authGotOauthLoginEvent: ACTIONS.authGotOauthLoginEvent,
}; };
@ -40,12 +40,13 @@ const LoginDialogUnconnected: FC<IProps> = ({
onRequestClose, onRequestClose,
userSendLoginRequest, userSendLoginRequest,
userSetLoginError, userSetLoginError,
modalShowDialog,
authGotOauthLoginEvent, authGotOauthLoginEvent,
}) => { }) => {
const [username, setUserName] = useState(''); const [username, setUserName] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const showRestoreDialog = useShowModal(Dialog.RestoreRequest);
const onSubmit = useCallback( const onSubmit = useCallback(
(event: FormEvent) => { (event: FormEvent) => {
event.preventDefault(); event.preventDefault();
@ -57,9 +58,9 @@ const LoginDialogUnconnected: FC<IProps> = ({
const onRestoreRequest = useCallback( const onRestoreRequest = useCallback(
event => { event => {
event.preventDefault(); event.preventDefault();
modalShowDialog(DIALOGS.RESTORE_REQUEST); showRestoreDialog();
}, },
[modalShowDialog] [showRestoreDialog]
); );
const openOauthWindow = useCallback( const openOauthWindow = useCallback(

View file

@ -1,6 +1,5 @@
import React, { FC, FormEvent, useCallback, useEffect, useState } from 'react'; import React, { FC, FormEvent, useCallback, useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { IDialogProps } from '~/redux/modal/constants';
import { BetterScrollDialog } from '~/containers/dialogs/BetterScrollDialog'; import { BetterScrollDialog } from '~/containers/dialogs/BetterScrollDialog';
import { Padder } from '~/components/containers/Padder'; import { Padder } from '~/components/containers/Padder';
import { DialogTitle } from '~/components/dialogs/DialogTitle'; import { DialogTitle } from '~/components/dialogs/DialogTitle';
@ -12,6 +11,7 @@ import * as AUTH_ACTIONS from '~/redux/auth/actions';
import { useCloseOnEscape } from '~/hooks'; import { useCloseOnEscape } from '~/hooks';
import { LoginSocialRegisterButtons } from '~/containers/dialogs/LoginSocialRegisterButtons'; import { LoginSocialRegisterButtons } from '~/containers/dialogs/LoginSocialRegisterButtons';
import { Toggle } from '~/components/input/Toggle'; import { Toggle } from '~/components/input/Toggle';
import { IDialogProps } from '~/types/modal';
const mapStateToProps = selectAuthRegisterSocial; const mapStateToProps = selectAuthRegisterSocial;
const mapDispatchToProps = { const mapDispatchToProps = {

View file

@ -1,34 +1,26 @@
import React, { FC, useCallback } from 'react'; import React, { FC } 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 { ModalWrapper } from '~/components/dialogs/ModalWrapper'; 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 = {}; type IProps = {};
const Modal: FC<IProps> = ({}) => { const Modal: FC<IProps> = observer(() => {
const { is_shown, dialog } = useShallowSelect(selectModal); const { current, hide } = useModalStore();
const dispatch = useDispatch();
const onRequestClose = useCallback(() => { if (!current || !has(current, DIALOG_CONTENT)) {
dispatch(modalSetShown(false)); return null;
dispatch(modalSetDialog('')); }
}, [dispatch]);
const onDialogChange = useCallback((val: string) => dispatch(modalShowDialog(val)), [dispatch]);
if (!dialog || !DIALOG_CONTENT[dialog] || !is_shown) return null;
return ( return (
<ModalWrapper onOverlayClick={onRequestClose}> <ModalWrapper onOverlayClick={hide}>
{React.createElement(DIALOG_CONTENT[dialog], { {React.createElement(DIALOG_CONTENT[current!]!, {
onRequestClose, onRequestClose: hide,
onDialogChange,
})} })}
</ModalWrapper> </ModalWrapper>
); );
}; });
export { Modal }; export { Modal };

View file

@ -1,33 +1,22 @@
import React, { FC, useCallback, useEffect, useRef } from 'react'; import React, { useEffect, useRef, VFC } from 'react';
import { connect } from 'react-redux';
import PhotoSwipeJs from 'photoswipe/dist/photoswipe.js'; import PhotoSwipeJs from 'photoswipe/dist/photoswipe.js';
import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default.js'; import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default.js';
import 'photoswipe/dist/photoswipe.css'; import 'photoswipe/dist/photoswipe.css';
import 'photoswipe/dist/default-skin/default-skin.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 { getURL } from '~/utils/dom';
import { PRESETS } from '~/constants/urls'; import { PRESETS } from '~/constants/urls';
import * as MODAL_ACTIONS from '~/redux/modal/actions';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import classNames from 'classnames'; import classNames from 'classnames';
import { useBlockBackButton } from '~/hooks/navigation/useBlockBackButton'; 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) => ({ const PhotoSwipe: VFC = observer(() => {
photoswipe: selectModal(state).photoswipe,
});
const mapDispatchToProps = {
modalSetShown: MODAL_ACTIONS.modalSetShown,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const PhotoSwipeUnconnected: FC<Props> = ({ photoswipe, modalSetShown }) => {
let ref = useRef<HTMLDivElement>(null); let ref = useRef<HTMLDivElement>(null);
const { hideModal } = useModal();
const closeModal = useCallback(() => modalSetShown(false), [modalSetShown]); const photoswipe = usePhotoSwipeStore();
useEffect(() => { useEffect(() => {
new Promise(async resolve => { new Promise(async resolve => {
@ -63,12 +52,12 @@ const PhotoSwipeUnconnected: FC<Props> = ({ photoswipe, modalSetShown }) => {
}); });
ps.init(); ps.init();
ps.listen('destroy', closeModal); ps.listen('destroy', hideModal);
ps.listen('close', closeModal); ps.listen('close', hideModal);
}); });
}, [closeModal, photoswipe.images, photoswipe.index]); }, [hideModal, photoswipe.images, photoswipe.index]);
useBlockBackButton(closeModal); useBlockBackButton(hideModal);
return ( return (
<div className="pswp" tabIndex={-1} role="dialog" aria-hidden="true" ref={ref}> <div className="pswp" tabIndex={-1} role="dialog" aria-hidden="true" ref={ref}>
@ -112,8 +101,6 @@ const PhotoSwipeUnconnected: FC<Props> = ({ photoswipe, modalSetShown }) => {
</div> </div>
</div> </div>
); );
}; });
const PhotoSwipe = connect(mapStateToProps, mapDispatchToProps)(PhotoSwipeUnconnected);
export { PhotoSwipe }; export { PhotoSwipe };

View file

@ -1,7 +1,6 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { BetterScrollDialog } from '../BetterScrollDialog'; import { BetterScrollDialog } from '../BetterScrollDialog';
import { ProfileInfo } from '~/containers/profile/ProfileInfo'; import { ProfileInfo } from '~/containers/profile/ProfileInfo';
import { IDialogProps } from '~/redux/types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors'; import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors';
import * as AUTH_ACTIONS from '~/redux/auth/actions'; 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 { ProfileMessages } from '~/containers/profile/ProfileMessages';
import { ProfileSettings } from '~/components/profile/ProfileSettings'; import { ProfileSettings } from '~/components/profile/ProfileSettings';
import { ProfileAccounts } from '~/components/profile/ProfileAccounts'; import { ProfileAccounts } from '~/components/profile/ProfileAccounts';
import { IDialogProps } from '~/types/modal';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
profile: selectAuthProfile(state), profile: selectAuthProfile(state),

View file

@ -1,5 +1,4 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { IDialogProps } from '~/redux/types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { BetterScrollDialog } from '../BetterScrollDialog'; import { BetterScrollDialog } from '../BetterScrollDialog';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
@ -13,6 +12,7 @@ import { selectAuthRestore } from '~/redux/auth/selectors';
import { ERROR_LITERAL, ERRORS } from '~/constants/errors'; import { ERROR_LITERAL, ERRORS } from '~/constants/errors';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { useCloseOnEscape } from '~/hooks'; import { useCloseOnEscape } from '~/hooks';
import { IDialogProps } from '~/types/modal';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
restore: selectAuthRestore(state), restore: selectAuthRestore(state),

View file

@ -1,5 +1,4 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { IDialogProps } from '~/redux/types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { BetterScrollDialog } from '../BetterScrollDialog'; import { BetterScrollDialog } from '../BetterScrollDialog';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
@ -13,6 +12,7 @@ import { selectAuthRestore } from '~/redux/auth/selectors';
import { ERROR_LITERAL } from '~/constants/errors'; import { ERROR_LITERAL } from '~/constants/errors';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { useCloseOnEscape } from '~/hooks'; import { useCloseOnEscape } from '~/hooks';
import { IDialogProps } from '~/types/modal';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
restore: selectAuthRestore(state), restore: selectAuthRestore(state),

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useState } from 'react';
import { getNodeDiff } from '~/api/node'; import { getNodeDiff } from '~/api/node';
import { uniq } from 'ramda'; import { uniq } from 'ramda';
import { useFlowStore } from '~/store/flow/useFlowStore'; import { useFlowStore } from '~/store/flow/useFlowStore';

View file

@ -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 };
};

View file

@ -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]);
};

View file

@ -1,13 +1,18 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { IFile } from '~/redux/types'; import { IFile } from '~/redux/types';
import { modalShowPhotoswipe } from '~/redux/modal/actions'; import { usePhotoSwipeStore } from '~/store/photoSwipe/usePhotoSwipeStore';
import { useDispatch } from 'react-redux'; import { useShowModal } from '~/hooks/modal/useShowModal';
import { Dialog } from '~/constants/modal';
export const useImageModal = () => { export const useImageModal = () => {
const dispatch = useDispatch(); const { setData } = usePhotoSwipeStore();
const showModal = useShowModal(Dialog.Photoswipe);
return useCallback( return useCallback(
(images: IFile[], index: number) => dispatch(modalShowPhotoswipe(images, index)), (images: IFile[], index: number) => {
[dispatch] setData(images, index);
showModal();
},
[setData, showModal]
); );
}; };

View file

@ -1,22 +1,9 @@
import { INode } from '~/redux/types'; import { INode } from '~/redux/types';
import { useCallback } from 'react'; 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 { apiLockNode, apiPostNodeHeroic, apiPostNodeLike } from '~/api/node';
import { showErrorToast } from '~/utils/errors/showToast'; import { showErrorToast } from '~/utils/errors/showToast';
export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Promise<unknown>) => { export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Promise<unknown>) => {
const dispatch = useDispatch();
const onEdit = useCallback(() => {
if (!node.type) {
return;
}
dispatch(modalShowDialog(NODE_EDITOR_DIALOGS[node.type]));
}, [dispatch, node]);
const onLike = useCallback(async () => { const onLike = useCallback(async () => {
try { try {
const result = await apiPostNodeLike({ id: node.id }); const result = await apiPostNodeLike({ id: node.id });
@ -50,5 +37,5 @@ export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Pr
} }
}, [node.deleted_at, node.id, update]); }, [node.deleted_at, node.id, update]);
return { onEdit, onLike, onStar, onLock }; return { onLike, onStar, onLock };
}; };

View file

@ -5,11 +5,11 @@ import { PersistGate } from 'redux-persist/integration/react';
import { configureStore } from '~/redux/store'; import { configureStore } from '~/redux/store';
import { App } from '~/containers/App'; import { App } from '~/containers/App';
import '~/styles/main.scss'; import '~/styles/main.scss';
import { Store } from '~/store'; import { getMOBXStore } from '~/store';
import { StoreContextProvider } from '~/utils/context/StoreContextProvider'; import { StoreContextProvider } from '~/utils/context/StoreContextProvider';
const { store, persistor } = configureStore(); const { store, persistor } = configureStore();
const mobxStore = new Store(); const mobxStore = getMOBXStore();
render( render(
<StoreContextProvider store={mobxStore}> <StoreContextProvider store={mobxStore}>

View file

@ -24,7 +24,7 @@ const NodeLayout: FC<IProps> = () => {
const { node, isLoading, update } = useNodeContext(); const { node, isLoading, update } = useNodeContext();
const { head, block } = useNodeBlocks(node, isLoading); const { head, block } = useNodeBlocks(node, isLoading);
const [canEdit, canLike, canStar] = useNodePermissions(node); const [canEdit, canLike, canStar] = useNodePermissions(node);
const { onEdit, onLike, onStar, onLock } = useNodeActions(node, update); const { onLike, onStar, onLock } = useNodeActions(node, update);
useNodeCoverImage(node); useNodeCoverImage(node);
@ -50,7 +50,6 @@ const NodeLayout: FC<IProps> = () => {
canEdit={canEdit} canEdit={canEdit}
canLike={canLike} canLike={canLike}
canStar={canStar} canStar={canStar}
onEdit={onEdit}
onLike={onLike} onLike={onLike}
onStar={onStar} onStar={onStar}
onLock={onLock} onLock={onLock}

View file

@ -40,7 +40,6 @@ import {
apiUpdateUser, apiUpdateUser,
apiUserLogin, apiUserLogin,
} from '~/redux/auth/api'; } from '~/redux/auth/api';
import { modalSetShown, modalShowDialog } from '~/redux/modal/actions';
import { import {
selectAuth, selectAuth,
selectAuthProfile, selectAuthProfile,
@ -51,14 +50,16 @@ import {
} from './selectors'; } from './selectors';
import { OAUTH_EVENT_TYPES, Unwrap } from '../types'; import { OAUTH_EVENT_TYPES, Unwrap } from '../types';
import { REHYDRATE, RehydrateAction } from 'redux-persist'; import { REHYDRATE, RehydrateAction } from 'redux-persist';
import { selectModal } from '~/redux/modal/selectors';
import { DIALOGS } from '~/redux/modal/constants';
import { ERRORS } from '~/constants/errors'; import { ERRORS } from '~/constants/errors';
import { messagesSet } from '~/redux/messages/actions'; import { messagesSet } from '~/redux/messages/actions';
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { isEmpty } from 'ramda'; import { isEmpty } from 'ramda';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { labGetUpdates } from '~/redux/lab/actions'; import { labGetUpdates } from '~/redux/lab/actions';
import { getMOBXStore } from '~/store';
import { Dialog } from '~/constants/modal';
const modalStore = getMOBXStore().modal;
function* setTokenSaga({ token }: ReturnType<typeof authSetToken>) { function* setTokenSaga({ token }: ReturnType<typeof authSetToken>) {
localStorage.setItem('token', token); localStorage.setItem('token', token);
@ -76,7 +77,8 @@ function* sendLoginRequestSaga({ username, password }: ReturnType<typeof userSen
yield put(authSetToken(token)); yield put(authSetToken(token));
yield put(authSetUser({ ...user, is_user: true })); yield put(authSetUser({ ...user, is_user: true }));
yield put(authLoggedIn()); yield put(authLoggedIn());
yield put(modalSetShown(false));
modalStore.hide();
} catch (error) { } catch (error) {
yield put(userSetLoginError(error.message)); yield put(userSetLoginError(error.message));
} }
@ -110,9 +112,11 @@ function* gotPostMessageSaga({ token }: ReturnType<typeof gotAuthPostMessage>) {
yield put(authSetToken(token)); yield put(authSetToken(token));
yield call(refreshUser); yield call(refreshUser);
const { is_shown, dialog }: ReturnType<typeof selectModal> = yield select(selectModal); const { current } = modalStore;
if (is_shown && dialog === DIALOGS.LOGIN) yield put(modalSetShown(false)); if (current === Dialog.Login) {
modalStore.hide();
}
} }
function* logoutSaga() { function* logoutSaga() {
@ -143,13 +147,13 @@ function* loadProfile({ username }: ReturnType<typeof authLoadProfile>): SagaIte
} }
function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenProfile>) { function* openProfile({ username, tab = 'profile' }: ReturnType<typeof authOpenProfile>) {
yield put(modalShowDialog(DIALOGS.PROFILE)); modalStore.setCurrent(Dialog.Profile);
yield put(authSetProfile({ tab })); yield put(authSetProfile({ tab }));
const success: Unwrap<typeof loadProfile> = yield call(loadProfile, authLoadProfile(username)); const success: Unwrap<typeof loadProfile> = yield call(loadProfile, authLoadProfile(username));
if (!success) { 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; if (!user || !user.is_user || user.role === USER_ROLES.GUEST || !user.id) return;
const modal: ReturnType<typeof selectModal> = yield select(selectModal); const { current } = modalStore;
const profile: ReturnType<typeof selectAuthProfile> = yield select(selectAuthProfile); const profile: ReturnType<typeof selectAuthProfile> = yield select(selectAuthProfile);
const { last, boris_commented_at }: ReturnType<typeof selectAuthUpdates> = yield select( const { last, boris_commented_at }: ReturnType<typeof selectAuthUpdates> = yield select(
selectAuthUpdates selectAuthUpdates
); );
const exclude_dialogs = const exclude_dialogs = current === Dialog.Profile && profile.user?.id ? profile.user.id : 0;
modal.is_shown && modal.dialog === DIALOGS.PROFILE && profile.user?.id ? profile.user.id : 0;
const data: Unwrap<typeof apiAuthGetUpdates> = yield call(apiAuthGetUpdates, { const data: Unwrap<typeof apiAuthGetUpdates> = yield call(apiAuthGetUpdates, {
exclude_dialogs, exclude_dialogs,
@ -248,12 +252,14 @@ function* showRestoreModal({ code }: ReturnType<typeof authShowRestoreModal>) {
const data: Unwrap<typeof apiCheckRestoreCode> = yield call(apiCheckRestoreCode, { code }); const data: Unwrap<typeof apiCheckRestoreCode> = yield call(apiCheckRestoreCode, { code });
yield put(authSetRestore({ user: data.user, code, is_loading: false })); yield put(authSetRestore({ user: data.user, code, is_loading: false }));
yield put(modalShowDialog(DIALOGS.RESTORE_PASSWORD));
modalStore.setCurrent(Dialog.RestoreRequest);
} catch (error) { } catch (error) {
yield put( yield put(
authSetRestore({ is_loading: false, error: error.message || ERRORS.CODE_IS_INVALID }) 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<typeof authLoginWithSocial>) {
if (data.token) { if (data.token) {
yield put(authSetToken(data.token)); yield put(authSetToken(data.token));
yield call(refreshUser); yield call(refreshUser);
yield put(modalSetShown(false)); modalStore.hide();
return; return;
} }
} catch (error) { } catch (error) {
const { dialog }: ReturnType<typeof selectModal> = yield select(selectModal); const { current } = modalStore;
const data = (error as AxiosError<{ const data = (error as AxiosError<{
needs_register: boolean; needs_register: boolean;
errors: Record<'username' | 'password', string>; errors: Record<'username' | 'password', string>;
}>).response?.data; }>).response?.data;
// Backend asks us for account registration // 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(authSetRegisterSocial({ token }));
yield put(modalShowDialog(DIALOGS.LOGIN_SOCIAL_REGISTER)); modalStore.setCurrent(Dialog.LoginSocialRegister);
return; return;
} }
@ -400,7 +406,9 @@ function* authRegisterSocial({ username, password }: ReturnType<typeof authSendR
if (data.token) { if (data.token) {
yield put(authSetToken(data.token)); yield put(authSetToken(data.token));
yield call(refreshUser); yield call(refreshUser);
yield put(modalSetShown(false));
modalStore.hide();
return; return;
} }
} catch (error) { } catch (error) {

View file

@ -1,29 +0,0 @@
import { IModalState } from '~/redux/modal/index';
import { MODAL_ACTIONS } from '~/redux/modal/constants';
import { IFile } from '../types';
export const modalSet = (modal: Partial<IModalState>) => ({
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,
});

View file

@ -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<typeof DIALOGS>) => void;
}

View file

@ -1,14 +0,0 @@
import { MODAL_ACTIONS } from '~/redux/modal/constants';
import { modalSet } from './actions';
const setState = (state, { modal }: ReturnType<typeof modalSet>) => ({ ...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,
};

View file

@ -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<typeof DIALOGS>;
photoswipe: {
images: IFile[];
index: number;
};
}
const INITIAL_STATE: IModalState = {
is_shown: false,
dialog: '',
photoswipe: {
images: [],
index: 0,
},
};
export default createReducer(INITIAL_STATE, MODAL_HANDLERS);

View file

@ -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<typeof modalShowPhotoswipe>) {
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);
}

View file

@ -1,3 +0,0 @@
import { IState } from '~/redux/store';
export const selectModal = (state: IState) => state.modal;

View file

@ -21,9 +21,6 @@ import uploadSaga from '~/redux/uploads/sagas';
import player, { IPlayerState } from '~/redux/player/reducer'; import player, { IPlayerState } from '~/redux/player/reducer';
import playerSaga from '~/redux/player/sagas'; 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 { authLogout, authOpenProfile, gotAuthPostMessage } from './auth/actions';
import messages, { IMessagesState } from './messages'; import messages, { IMessagesState } from './messages';
@ -53,7 +50,6 @@ const playerPersistConfig: PersistConfig = {
export interface IState { export interface IState {
auth: IAuthState; auth: IAuthState;
modal: IModalState;
router: RouterState; router: RouterState;
uploads: IUploadState; uploads: IUploadState;
player: IPlayerState; player: IPlayerState;
@ -74,7 +70,6 @@ const composeEnhancers =
export const store = createStore( export const store = createStore(
combineReducers<IState>({ combineReducers<IState>({
auth: persistReducer(authPersistConfig, auth), auth: persistReducer(authPersistConfig, auth),
modal,
router: connectRouter(history), router: connectRouter(history),
uploads, uploads,
player: persistReducer(playerPersistConfig, player), player: persistReducer(playerPersistConfig, player),
@ -91,7 +86,6 @@ export function configureStore(): {
sagaMiddleware.run(authSaga); sagaMiddleware.run(authSaga);
sagaMiddleware.run(uploadSaga); sagaMiddleware.run(uploadSaga);
sagaMiddleware.run(playerSaga); sagaMiddleware.run(playerSaga);
sagaMiddleware.run(modalSaga);
sagaMiddleware.run(messagesSaga); sagaMiddleware.run(messagesSaga);
sagaMiddleware.run(labSaga); sagaMiddleware.run(labSaga);

View file

@ -1,5 +1,4 @@
import { DetailedHTMLProps, InputHTMLAttributes, ReactElement } from 'react'; import { DetailedHTMLProps, InputHTMLAttributes, ReactElement } from 'react';
import { DIALOGS } from '~/redux/modal/constants';
import { ERRORS } from '~/constants/errors'; import { ERRORS } from '~/constants/errors';
import { IUser } from './auth/types'; import { IUser } from './auth/types';
import { CallEffect } from 'redux-saga/effects'; import { CallEffect } from 'redux-saga/effects';
@ -31,11 +30,6 @@ export type IIcon = string;
export type ValueOf<T> = T[keyof T]; export type ValueOf<T> = T[keyof T];
export interface IDialogProps {
onRequestClose: () => void;
onDialogChange: (dialog: ValueOf<typeof DIALOGS>) => void;
}
export interface IApiErrorResult { export interface IApiErrorResult {
detail?: string; detail?: string;
code?: string; code?: string;

View file

@ -1,10 +1,18 @@
import { makeAutoObservable } from 'mobx'; import { makeAutoObservable } from 'mobx';
import { FlowStore } from '~/store/flow/FlowStore'; import { FlowStore } from '~/store/flow/FlowStore';
import { ModalStore } from '~/store/modal/ModalStore';
import { PhotoSwipeStore } from '~/store/photoSwipe/PhotoSwipeStore';
export class Store { export class Store {
flow = new FlowStore(); flow = new FlowStore();
modal = new ModalStore();
photoSwipe = new PhotoSwipeStore();
constructor() { constructor() {
makeAutoObservable(this); makeAutoObservable(this);
} }
} }
const defaultStore = new Store();
export const getMOBXStore = () => defaultStore;

View file

@ -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);
}

View file

@ -0,0 +1,3 @@
import { useStore } from '~/utils/context/StoreContextProvider';
export const useModalStore = () => useStore().modal;

View file

@ -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;
};
}

View file

@ -0,0 +1,3 @@
import { useStore } from '~/utils/context/StoreContextProvider';
export const usePhotoSwipeStore = () => useStore().photoSwipe;

3
src/types/modal/index.ts Normal file
View file

@ -0,0 +1,3 @@
export interface IDialogProps {
onRequestClose: () => void;
}