From 4dc8bea04072b8daea78455601dba635053fbf3d Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 29 Mar 2021 14:11:39 +0700 Subject: [PATCH] #58 made dialogs as routes --- src/components/bars/SubmitBar/index.tsx | 60 +++++++--------- .../bars/SubmitBar/styles.module.scss | 54 ++++++++------- src/components/dialogs/ModalWrapper/index.tsx | 19 +++++ .../dialogs/ModalWrapper}/styles.module.scss | 0 src/components/node/NodePanelInner/index.tsx | 8 ++- src/constants/dialogs.ts | 8 --- src/constants/urls.ts | 2 + .../dialogs/EditorCreateDialog/index.tsx | 38 ++++++++++ src/containers/dialogs/EditorDialog/index.tsx | 12 ++-- .../dialogs/EditorEditDialog/index.tsx | 57 +++++++++++++++ .../EditorEditDialog/styles.module.scss | 4 ++ src/containers/dialogs/Modal/index.tsx | 69 +++++++------------ .../editors/EditorDialogAudio/index.tsx | 10 --- .../editors/EditorDialogImage/index.tsx | 10 --- .../editors/EditorDialogText/index.tsx | 10 --- .../editors/EditorDialogVideo/index.tsx | 10 --- .../EditorDialogVideo/styles.module.scss | 0 src/containers/main/SidebarRouter/index.tsx | 2 + src/layouts/NodeLayout/index.tsx | 6 +- src/redux/modal/constants.ts | 2 +- src/redux/node/api.ts | 21 ++++-- 21 files changed, 230 insertions(+), 172 deletions(-) create mode 100644 src/components/dialogs/ModalWrapper/index.tsx rename src/{containers/dialogs/Modal => components/dialogs/ModalWrapper}/styles.module.scss (100%) create mode 100644 src/containers/dialogs/EditorCreateDialog/index.tsx create mode 100644 src/containers/dialogs/EditorEditDialog/index.tsx create mode 100644 src/containers/dialogs/EditorEditDialog/styles.module.scss delete mode 100644 src/containers/editors/EditorDialogAudio/index.tsx delete mode 100644 src/containers/editors/EditorDialogImage/index.tsx delete mode 100644 src/containers/editors/EditorDialogText/index.tsx delete mode 100644 src/containers/editors/EditorDialogVideo/index.tsx delete mode 100644 src/containers/editors/EditorDialogVideo/styles.module.scss diff --git a/src/components/bars/SubmitBar/index.tsx b/src/components/bars/SubmitBar/index.tsx index 9a6df0e7..e96b3695 100644 --- a/src/components/bars/SubmitBar/index.tsx +++ b/src/components/bars/SubmitBar/index.tsx @@ -1,58 +1,44 @@ -import React, { FC, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { FC, useCallback, useState } from 'react'; import { Icon } from '~/components/input/Icon'; -import { nodeCreate } from '~/redux/node/actions'; - -import styles from './styles.module.scss'; -import { NODE_TYPES } from '~/redux/node/constants'; +import { Link } from 'react-router-dom'; import classNames from 'classnames'; +import { useRouteMatch } from 'react-router'; +import styles from './styles.module.scss'; interface Props { isLab?: boolean; } const SubmitBar: FC = ({ isLab }) => { - const dispatch = useDispatch(); + const { url } = useRouteMatch(); + const [focused, setFocused] = useState(false); - const onOpenImageEditor = useCallback(() => dispatch(nodeCreate(NODE_TYPES.IMAGE, isLab)), [ - dispatch, - ]); - - const onOpenTextEditor = useCallback(() => dispatch(nodeCreate(NODE_TYPES.TEXT, isLab)), [ - dispatch, - ]); - - const onOpenVideoEditor = useCallback(() => dispatch(nodeCreate(NODE_TYPES.VIDEO, isLab)), [ - dispatch, - ]); - - const onOpenAudioEditor = useCallback(() => dispatch(nodeCreate(NODE_TYPES.AUDIO, isLab)), [ - dispatch, - ]); + const onFocus = useCallback(() => setFocused(true), [setFocused]); + const onBlur = useCallback(() => setFocused(false), [setFocused]); return (
-
-
- -
+
+ + + -
- -
+ + + -
- -
+ + + -
- -
+ + +
-
+
+
); }; diff --git a/src/components/bars/SubmitBar/styles.module.scss b/src/components/bars/SubmitBar/styles.module.scss index bd67f847..da4233da 100644 --- a/src/components/bars/SubmitBar/styles.module.scss +++ b/src/components/bars/SubmitBar/styles.module.scss @@ -5,13 +5,7 @@ bottom: 0; left: 50%; transform: translate($content_width / 2 + $gap, 0); - z-index: 4; - - &:hover { - .panel { - transform: translate(0, 0); - } - } + z-index: 14; @media (max-width: $content_width + ($bar_height + $gap) * 2) { left: 100%; @@ -31,6 +25,8 @@ cursor: pointer; position: relative; z-index: 2; + border: none; + outline: none; svg { width: 32px; @@ -50,24 +46,32 @@ padding-bottom: $bar_height; border-radius: $radius $radius 0 0; transform: translate(0, 100%); - transition: transform 250ms; + transition: transform 250ms 250ms; - div { - @include outer_shadow; - height: $bar_height; - width: $bar_height; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - - svg { - width: 32px; - height: 32px; - } - - &:first-child { - border-radius: $radius $radius 0 0; - } + &.active { + transform: translate(0, 0); + transition: transform 250ms; + } +} + +.link { + @include outer_shadow; + + height: $bar_height; + width: $bar_height; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + fill: white; + color: white; + + svg { + width: 32px; + height: 32px; + } + + &:first-child { + border-radius: $radius $radius 0 0; } } diff --git a/src/components/dialogs/ModalWrapper/index.tsx b/src/components/dialogs/ModalWrapper/index.tsx new file mode 100644 index 00000000..a765f69d --- /dev/null +++ b/src/components/dialogs/ModalWrapper/index.tsx @@ -0,0 +1,19 @@ +import React, { FC, MouseEventHandler } from 'react'; +import ReactDOM from 'react-dom'; +import styles from './styles.module.scss'; + +type IProps = { + onOverlayClick: MouseEventHandler; +}; + +const ModalWrapper: FC = ({ children, onOverlayClick }) => { + return ReactDOM.createPortal( +
+
+
{children}
+
, + document.body + ); +}; + +export { ModalWrapper }; diff --git a/src/containers/dialogs/Modal/styles.module.scss b/src/components/dialogs/ModalWrapper/styles.module.scss similarity index 100% rename from src/containers/dialogs/Modal/styles.module.scss rename to src/components/dialogs/ModalWrapper/styles.module.scss diff --git a/src/components/node/NodePanelInner/index.tsx b/src/components/node/NodePanelInner/index.tsx index 0a10c1b2..ea717ccc 100644 --- a/src/components/node/NodePanelInner/index.tsx +++ b/src/components/node/NodePanelInner/index.tsx @@ -5,6 +5,8 @@ import { INode } from '~/redux/types'; import classNames from 'classnames'; import { Placeholder } from '~/components/placeholders/Placeholder'; import { getPrettyDate } from '~/utils/dom'; +import { URLS } from '~/constants/urls'; +import { Link } from 'react-router-dom'; interface IProps { node: Partial; @@ -24,7 +26,7 @@ interface IProps { const NodePanelInner: FC = memo( ({ - node: { title, user, is_liked, is_heroic, deleted_at, created_at, like_count }, + node: { id, title, user, is_liked, is_heroic, deleted_at, created_at, like_count }, stack, canStar, @@ -78,9 +80,9 @@ const NodePanelInner: FC = memo(
-
+ -
+
)} diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts index 0badb898..9df6f50a 100644 --- a/src/constants/dialogs.ts +++ b/src/constants/dialogs.ts @@ -1,8 +1,4 @@ import { NODE_TYPES } from '~/redux/node/constants'; -import { EditorDialogImage } from '~/containers/editors/EditorDialogImage'; -import { EditorDialogText } from '~/containers/editors/EditorDialogText'; -import { EditorDialogVideo } from '~/containers/editors/EditorDialogVideo'; -import { EditorDialogAudio } from '~/containers/editors/EditorDialogAudio'; import { LoginDialog } from '~/containers/dialogs/LoginDialog'; import { LoadingDialog } from '~/containers/dialogs/LoadingDialog'; import { TestDialog } from '~/containers/dialogs/TestDialog'; @@ -16,10 +12,6 @@ import { IDialogProps } from '~/redux/types'; import { FC } from 'react'; export const DIALOG_CONTENT: Record> = { - [DIALOGS.EDITOR_IMAGE]: EditorDialogImage, - [DIALOGS.EDITOR_TEXT]: EditorDialogText, - [DIALOGS.EDITOR_VIDEO]: EditorDialogVideo, - [DIALOGS.EDITOR_AUDIO]: EditorDialogAudio, [DIALOGS.LOGIN]: LoginDialog, [DIALOGS.LOGIN_SOCIAL_REGISTER]: LoginSocialRegisterDialog, [DIALOGS.LOADING]: LoadingDialog, diff --git a/src/constants/urls.ts b/src/constants/urls.ts index 527e072e..588cf64f 100644 --- a/src/constants/urls.ts +++ b/src/constants/urls.ts @@ -16,6 +16,8 @@ export const URLS = { BACKEND_DOWN: '/oopsie', }, NODE_URL: (id: INode['id'] | string) => `/post${id}`, + NODE_EDIT_URL: (id: INode['id'] | string) => `/post${id}/edit`, + NODE_CREATE_URL: (type: string) => `/`, NODE_TAG_URL: (id: number, tagName: string) => `/post${id}/tag/${tagName}`, PROFILE: (username: string) => `/~${username}`, PROFILE_PAGE: (username: string) => `/profile/${username}`, diff --git a/src/containers/dialogs/EditorCreateDialog/index.tsx b/src/containers/dialogs/EditorCreateDialog/index.tsx new file mode 100644 index 00000000..215f0172 --- /dev/null +++ b/src/containers/dialogs/EditorCreateDialog/index.tsx @@ -0,0 +1,38 @@ +import React, { FC, useCallback, useMemo, useRef } from 'react'; +import { EMPTY_NODE, NODE_TYPES } from '~/redux/node/constants'; +import { EditorDialog } from '~/containers/dialogs/EditorDialog'; +import { useHistory, useRouteMatch } from 'react-router'; +import { values } from 'ramda'; +import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; + +const EditorCreateDialog: FC = () => { + const history = useHistory(); + const { + params: { type }, + url, + } = useRouteMatch<{ type: string }>(); + + const backUrl = useMemo(() => { + return url.replace(/\/create\/(.*)$/, ''); + }, [url]); + + const goBack = useCallback(() => { + history.replace(backUrl); + }, [backUrl, history]); + + const isExist = useMemo(() => values(NODE_TYPES).some(el => el === type), [type]); + + const data = useRef({ ...EMPTY_NODE, type }); + + if (!type || !isExist) { + return null; + } + + return ( + + + + ); +}; + +export { EditorCreateDialog }; diff --git a/src/containers/dialogs/EditorDialog/index.tsx b/src/containers/dialogs/EditorDialog/index.tsx index bf01d869..0eb26972 100644 --- a/src/containers/dialogs/EditorDialog/index.tsx +++ b/src/containers/dialogs/EditorDialog/index.tsx @@ -11,22 +11,20 @@ import { EditorButtons } from '~/components/editors/EditorButtons'; import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/fileUploader'; import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants'; import { FormikProvider } from 'formik'; -import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; -import { selectNodeEditor } from '~/redux/node/selectors'; +import { INode } from '~/redux/types'; interface Props extends IDialogProps { - type: string; + node: INode; } -const EditorDialog: FC = ({ type, onRequestClose }) => { - const editor = useShallowSelect(selectNodeEditor); +const EditorDialog: FC = ({ node, onRequestClose }) => { const uploader = useFileUploader(UPLOAD_SUBJECTS.EDITOR, UPLOAD_TARGETS.NODES, []); - const formik = useNodeFormFormik({ ...editor, type }, uploader, onRequestClose); + const formik = useNodeFormFormik(node, uploader, onRequestClose); const { values, handleSubmit } = formik; useCloseOnEscape(onRequestClose); - const component = useMemo(() => prop(type, NODE_EDITORS), [type]); + const component = useMemo(() => node.type && prop(node.type, NODE_EDITORS), [node.type]); if (!component) { return null; diff --git a/src/containers/dialogs/EditorEditDialog/index.tsx b/src/containers/dialogs/EditorEditDialog/index.tsx new file mode 100644 index 00000000..ebdd98b2 --- /dev/null +++ b/src/containers/dialogs/EditorEditDialog/index.tsx @@ -0,0 +1,57 @@ +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { EMPTY_NODE } from '~/redux/node/constants'; +import { EditorDialog } from '~/containers/dialogs/EditorDialog'; +import { useHistory, useRouteMatch } from 'react-router'; +import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; +import { apiGetNodeWithCancel } from '~/redux/node/api'; +import { LoaderCircle } from '~/components/input/LoaderCircle'; +import styles from './styles.module.scss'; + +const EditorEditDialog: FC = () => { + const [data, setData] = useState(EMPTY_NODE); + const [isLoading, setLoading] = useState(false); + const history = useHistory(); + + const { + params: { id }, + url, + } = useRouteMatch<{ id: string }>(); + + const backUrl = useMemo(() => { + return url.replace(/\/edit$/, ''); + }, [url]); + + const goBack = useCallback(() => { + history.replace(backUrl); + }, [backUrl, history]); + + useEffect(() => { + if (!id) { + return; + } + + const { request, cancel } = apiGetNodeWithCancel({ id }); + + setLoading(true); + request + .then(data => setData(data.node)) + .then(() => setLoading(false)) + .catch(console.log); + + return () => cancel(); + }, [id]); + + return ( + + {isLoading ? ( +
+ +
+ ) : ( + + )} +
+ ); +}; + +export { EditorEditDialog }; diff --git a/src/containers/dialogs/EditorEditDialog/styles.module.scss b/src/containers/dialogs/EditorEditDialog/styles.module.scss new file mode 100644 index 00000000..a8737337 --- /dev/null +++ b/src/containers/dialogs/EditorEditDialog/styles.module.scss @@ -0,0 +1,4 @@ +.loader { + fill: white; + color: white; +} diff --git a/src/containers/dialogs/Modal/index.tsx b/src/containers/dialogs/Modal/index.tsx index 918e7786..12184b9a 100644 --- a/src/containers/dialogs/Modal/index.tsx +++ b/src/containers/dialogs/Modal/index.tsx @@ -1,57 +1,34 @@ -import React, { Attributes, FC, useCallback } from 'react'; -import { connect } from 'react-redux'; -import ReactDOM from 'react-dom'; -import styles from './styles.module.scss'; -import { IState } from '~/redux/store'; -import * as ACTIONS from '~/redux/modal/actions'; +import React, { FC, useCallback } from 'react'; +import { useDispatch } from 'react-redux'; import { DIALOG_CONTENT } from '~/constants/dialogs'; +import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; +import { selectModal } from '~/redux/modal/selectors'; +import { modalSetDialog, modalSetShown, modalShowDialog } from '~/redux/modal/actions'; +import { ModalWrapper } from '~/components/dialogs/ModalWrapper'; -const mapStateToProps = ({ modal }: IState) => ({ ...modal }); -const mapDispatchToProps = { - modalSetShown: ACTIONS.modalSetShown, - modalSetDialog: ACTIONS.modalSetDialog, - modalShowDialog: ACTIONS.modalShowDialog, -}; +type IProps = {}; -type IProps = typeof mapDispatchToProps & ReturnType & {}; +const Modal: FC = ({}) => { + const { is_shown, dialog } = useShallowSelect(selectModal); + const dispatch = useDispatch(); -const ModalUnconnected: FC = ({ - modalSetShown, - modalSetDialog, - modalShowDialog, - is_shown, - dialog, -}) => { const onRequestClose = useCallback(() => { - modalSetShown(false); - modalSetDialog(''); - }, [modalSetShown, modalSetDialog]); + dispatch(modalSetShown(false)); + dispatch(modalSetDialog('')); + }, [dispatch]); + + const onDialogChange = useCallback((val: string) => dispatch(modalShowDialog(val)), [dispatch]); if (!dialog || !DIALOG_CONTENT[dialog] || !is_shown) return null; - return ReactDOM.createPortal( -
-
-
- {React.createElement(DIALOG_CONTENT[dialog], { - onRequestClose, - onDialogChange: modalShowDialog, - })} -
-
, - document.body + return ( + + {React.createElement(DIALOG_CONTENT[dialog], { + onRequestClose, + onDialogChange, + })} + ); }; -const Modal = connect(mapStateToProps, mapDispatchToProps)(ModalUnconnected); - -export { ModalUnconnected, Modal }; - -/* - -
-
-
-
- -*/ +export { Modal }; diff --git a/src/containers/editors/EditorDialogAudio/index.tsx b/src/containers/editors/EditorDialogAudio/index.tsx deleted file mode 100644 index 27b306f9..00000000 --- a/src/containers/editors/EditorDialogAudio/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { FC } from 'react'; -import { EditorDialog } from '~/containers/dialogs/EditorDialog'; -import { IDialogProps } from '~/redux/types'; -import { NODE_TYPES } from '~/redux/node/constants'; - -type IProps = IDialogProps & {}; - -const EditorDialogAudio: FC = props => ; - -export { EditorDialogAudio }; diff --git a/src/containers/editors/EditorDialogImage/index.tsx b/src/containers/editors/EditorDialogImage/index.tsx deleted file mode 100644 index 9d2b8f41..00000000 --- a/src/containers/editors/EditorDialogImage/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { FC } from 'react'; -import { EditorDialog } from '~/containers/dialogs/EditorDialog'; -import { IDialogProps } from '~/redux/types'; -import { NODE_TYPES } from '~/redux/node/constants'; - -type IProps = IDialogProps & {}; - -const EditorDialogImage: FC = props => ; - -export { EditorDialogImage }; diff --git a/src/containers/editors/EditorDialogText/index.tsx b/src/containers/editors/EditorDialogText/index.tsx deleted file mode 100644 index 311df8ed..00000000 --- a/src/containers/editors/EditorDialogText/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { FC } from 'react'; -import { EditorDialog } from '~/containers/dialogs/EditorDialog'; -import { IDialogProps } from '~/redux/types'; -import { NODE_TYPES } from '~/redux/node/constants'; - -type IProps = IDialogProps & {}; - -const EditorDialogText: FC = props => ; - -export { EditorDialogText }; diff --git a/src/containers/editors/EditorDialogVideo/index.tsx b/src/containers/editors/EditorDialogVideo/index.tsx deleted file mode 100644 index 092c688f..00000000 --- a/src/containers/editors/EditorDialogVideo/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { FC } from 'react'; -import { EditorDialog } from '~/containers/dialogs/EditorDialog'; -import { IDialogProps } from '~/redux/types'; -import { NODE_TYPES } from '~/redux/node/constants'; - -type IProps = IDialogProps & {}; - -const EditorDialogVideo: FC = props => ; - -export { EditorDialogVideo }; diff --git a/src/containers/editors/EditorDialogVideo/styles.module.scss b/src/containers/editors/EditorDialogVideo/styles.module.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/containers/main/SidebarRouter/index.tsx b/src/containers/main/SidebarRouter/index.tsx index 47cc641e..e9fc33b9 100644 --- a/src/containers/main/SidebarRouter/index.tsx +++ b/src/containers/main/SidebarRouter/index.tsx @@ -5,6 +5,7 @@ import { TagSidebar } from '~/containers/sidebars/TagSidebar'; import { ProfileSidebar } from '~/containers/sidebars/ProfileSidebar'; import { Authorized } from '~/components/containers/Authorized'; import { SubmitBar } from '~/components/bars/SubmitBar'; +import { EditorCreateDialog } from '~/containers/dialogs/EditorCreateDialog'; interface IProps { prefix?: string; @@ -15,6 +16,7 @@ const SidebarRouter: FC = ({ prefix = '', isLab }) => { return createPortal( <> + diff --git a/src/layouts/NodeLayout/index.tsx b/src/layouts/NodeLayout/index.tsx index 35dd2cf2..5e43d5ca 100644 --- a/src/layouts/NodeLayout/index.tsx +++ b/src/layouts/NodeLayout/index.tsx @@ -1,5 +1,5 @@ import React, { FC, memo } from 'react'; -import { RouteComponentProps } from 'react-router'; +import { Route, RouteComponentProps } from 'react-router'; import { selectNode } from '~/redux/node/selectors'; import { Card } from '~/components/containers/Card'; @@ -15,6 +15,8 @@ import { NodeBottomBlock } from '~/components/node/NodeBottomBlock'; import { useNodeCoverImage } from '~/utils/hooks/node/useNodeCoverImage'; import { useScrollToTop } from '~/utils/hooks/useScrollToTop'; import { useLoadNode } from '~/utils/hooks/node/useLoadNode'; +import { URLS } from '~/constants/urls'; +import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog'; type IProps = RouteComponentProps<{ id: string }> & {}; @@ -64,6 +66,8 @@ const NodeLayout: FC = memo( + +
); } diff --git a/src/redux/modal/constants.ts b/src/redux/modal/constants.ts index f1f6e6c6..8e300a5c 100644 --- a/src/redux/modal/constants.ts +++ b/src/redux/modal/constants.ts @@ -27,5 +27,5 @@ export const MODAL_ACTIONS = { export interface IDialogProps { onRequestClose: () => void; - onDialogChange: (dialog: ValueOf) => void; + onDialogChange?: (dialog: ValueOf) => void; } diff --git a/src/redux/node/api.ts b/src/redux/node/api.ts index 0ae8cb4b..55a09474 100644 --- a/src/redux/node/api.ts +++ b/src/redux/node/api.ts @@ -1,5 +1,5 @@ -import { api, cleanResult, configWithToken, errorMiddleware, resultMiddleware } from '~/utils/api'; -import { IComment, INode, IResultWithStatus } from '../types'; +import { api, cleanResult } from '~/utils/api'; +import { IComment, INode } from '../types'; import { API } from '~/constants/api'; import { COMMENTS_DISPLAY } from './constants'; import { @@ -22,6 +22,7 @@ import { GetNodeDiffRequest, GetNodeDiffResult, } from '~/redux/node/types'; +import axios, { AxiosRequestConfig } from 'axios'; export type ApiPostNodeRequest = { node: INode }; export type ApiPostNodeResult = { @@ -62,8 +63,20 @@ export const getNodeDiff = ({ }) .then(cleanResult); -export const apiGetNode = ({ id }: ApiGetNodeRequest) => - api.get(API.NODE.GET_NODE(id)).then(cleanResult); +export const apiGetNode = ({ id }: ApiGetNodeRequest, config?: AxiosRequestConfig) => + api.get(API.NODE.GET_NODE(id), config).then(cleanResult); + +export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => { + const cancelToken = axios.CancelToken.source(); + return { + request: api + .get(API.NODE.GET_NODE(id), { + cancelToken: cancelToken.token, + }) + .then(cleanResult), + cancel: cancelToken.cancel, + }; +}; export const apiPostComment = ({ id, data }: ApiPostCommentRequest) => api.post(API.NODE.COMMENT(id), data).then(cleanResult);