mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
#58 fixed dialog routers
This commit is contained in:
parent
3e8c2d4b6e
commit
124719c243
15 changed files with 93 additions and 39 deletions
|
@ -16,22 +16,29 @@ const SubmitBar: FC<Props> = ({ isLab }) => {
|
||||||
const onFocus = useCallback(() => setFocused(true), [setFocused]);
|
const onFocus = useCallback(() => setFocused(true), [setFocused]);
|
||||||
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
||||||
|
|
||||||
|
const createUrl = useCallback(
|
||||||
|
(type: string) => {
|
||||||
|
return [url.replace(/\/$/, ''), 'create', type].join('/');
|
||||||
|
},
|
||||||
|
[url]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.wrap, { [styles.lab]: isLab })}>
|
<div className={classNames(styles.wrap, { [styles.lab]: isLab })}>
|
||||||
<div className={classNames(styles.panel, { [styles.active]: focused })}>
|
<div className={classNames(styles.panel, { [styles.active]: focused })}>
|
||||||
<Link to={`${url}/create/image`} className={styles.link}>
|
<Link to={createUrl('image')} className={styles.link}>
|
||||||
<Icon icon="image" size={32} />
|
<Icon icon="image" size={32} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to={`${url}/create/text`} className={styles.link}>
|
<Link to={createUrl('text')} className={styles.link}>
|
||||||
<Icon icon="text" size={32} />
|
<Icon icon="text" size={32} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to={`${url}/create/video`} className={styles.link}>
|
<Link to={createUrl('video')} className={styles.link}>
|
||||||
<Icon icon="video" size={32} />
|
<Icon icon="video" size={32} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to={`${url}/create/audio`} className={styles.link}>
|
<Link to={createUrl('audio')} className={styles.link}>
|
||||||
<Icon icon="audio" size={32} />
|
<Icon icon="audio" size={32} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Padder } from '~/components/containers/Padder';
|
||||||
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
||||||
|
|
||||||
const EditorButtons: FC = () => {
|
const EditorButtons: FC = () => {
|
||||||
const { values, handleChange } = useNodeFormContext();
|
const { values, handleChange, isSubmitting } = useNodeFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Padder style={{ position: 'relative' }}>
|
<Padder style={{ position: 'relative' }}>
|
||||||
|
@ -20,12 +20,14 @@ const EditorButtons: FC = () => {
|
||||||
handler={handleChange('title')}
|
handler={handleChange('title')}
|
||||||
autoFocus
|
autoFocus
|
||||||
maxLength={256}
|
maxLength={256}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
title="Сохранить"
|
title="Сохранить"
|
||||||
iconRight="check"
|
iconRight="check"
|
||||||
color={values.is_promoted ? 'primary' : 'lab'}
|
color={values.is_promoted ? 'primary' : 'lab'}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
</Padder>
|
</Padder>
|
||||||
|
|
|
@ -40,8 +40,6 @@ const BetterScrollDialog: FC<IProps> = ({
|
||||||
return () => clearAllBodyScrollLocks();
|
return () => clearAllBodyScrollLocks();
|
||||||
}, [ref]);
|
}, [ref]);
|
||||||
|
|
||||||
useCloseOnEscape(onClose);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap} ref={ref}>
|
<div className={styles.wrap} ref={ref}>
|
||||||
{backdrop && <div className={styles.backdrop}>{backdrop}</div>}
|
{backdrop && <div className={styles.backdrop}>{backdrop}</div>}
|
||||||
|
|
|
@ -104,6 +104,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 0 0 $radius $radius;
|
border-radius: 0 0 $radius $radius;
|
||||||
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop {
|
.backdrop {
|
||||||
|
|
|
@ -12,7 +12,7 @@ const EditorCreateDialog: FC = () => {
|
||||||
} = useRouteMatch<{ type: string }>();
|
} = useRouteMatch<{ type: string }>();
|
||||||
|
|
||||||
const backUrl = useMemo(() => {
|
const backUrl = useMemo(() => {
|
||||||
return url.replace(/\/create\/(.*)$/, '');
|
return (url && url.replace(/\/create\/(.*)$/, '')) || '/';
|
||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
const goBack = useCallback(() => {
|
const goBack = useCallback(() => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { createElement, FC, useCallback, useMemo } from 'react';
|
import React, { createElement, FC, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { IDialogProps } from '~/redux/modal/constants';
|
import { IDialogProps } from '~/redux/modal/constants';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { NODE_EDITORS } from '~/redux/node/constants';
|
import { NODE_EDITORS } from '~/redux/node/constants';
|
||||||
|
@ -12,26 +12,40 @@ import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants';
|
||||||
import { FormikProvider } from 'formik';
|
import { FormikProvider } from 'formik';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
||||||
|
import { useTranslatedError } from '~/utils/hooks/useTranslatedError';
|
||||||
|
import { useCloseOnEscape } from '~/utils/hooks';
|
||||||
|
|
||||||
interface Props extends IDialogProps {
|
interface Props extends IDialogProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorDialog: FC<Props> = ({ node, onRequestClose }) => {
|
const EditorDialog: FC<Props> = ({ node, onRequestClose }) => {
|
||||||
const uploader = useFileUploader(UPLOAD_SUBJECTS.EDITOR, UPLOAD_TARGETS.NODES, []);
|
const uploader = useFileUploader(UPLOAD_SUBJECTS.EDITOR, UPLOAD_TARGETS.NODES, node.files);
|
||||||
const formik = useNodeFormFormik(node, uploader, onRequestClose);
|
const formik = useNodeFormFormik(node, uploader, onRequestClose);
|
||||||
const { values, handleSubmit, dirty } = formik;
|
const { values, handleSubmit, dirty, status, setStatus } = formik;
|
||||||
|
|
||||||
const component = useMemo(() => node.type && prop(node.type, NODE_EDITORS), [node.type]);
|
const component = useMemo(() => node.type && prop(node.type, NODE_EDITORS), [node.type]);
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
if (!window.confirm('Точно выйти?')) {
|
if (!window.confirm('Точно выйти?')) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
onRequestClose();
|
onRequestClose();
|
||||||
}, [onRequestClose, dirty]);
|
}, [onRequestClose, dirty]);
|
||||||
|
|
||||||
|
const error = useTranslatedError(status);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('');
|
||||||
|
}, [values]);
|
||||||
|
|
||||||
|
useCloseOnEscape(onClose);
|
||||||
|
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +59,7 @@ const EditorDialog: FC<Props> = ({ node, onRequestClose }) => {
|
||||||
footer={<EditorButtons />}
|
footer={<EditorButtons />}
|
||||||
backdrop={<CoverBackdrop cover={values.cover} />}
|
backdrop={<CoverBackdrop cover={values.cover} />}
|
||||||
width={860}
|
width={860}
|
||||||
error=""
|
error={error}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<div className={styles.editor}>{createElement(component)}</div>
|
<div className={styles.editor}>{createElement(component)}</div>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import styles from './styles.module.scss';
|
||||||
|
|
||||||
const EditorEditDialog: FC = () => {
|
const EditorEditDialog: FC = () => {
|
||||||
const [data, setData] = useState(EMPTY_NODE);
|
const [data, setData] = useState(EMPTY_NODE);
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(true);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -18,11 +18,11 @@ const MainRouter: FC<IProps> = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch location={location}>
|
<Switch location={location}>
|
||||||
<Route exact path={URLS.BASE} component={FlowLayout} />
|
|
||||||
<Route path={URLS.NODE_URL(':id')} component={NodeLayout} />
|
<Route path={URLS.NODE_URL(':id')} component={NodeLayout} />
|
||||||
<Route path={URLS.BORIS} component={BorisLayout} />
|
<Route path={URLS.BORIS} component={BorisLayout} />
|
||||||
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
|
<Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
|
||||||
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
|
<Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
|
||||||
|
<Route path={URLS.BASE} component={FlowLayout} />
|
||||||
|
|
||||||
{is_user && (
|
{is_user && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -87,7 +87,7 @@ const FlowLayoutUnconnected: FC<IProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SidebarRouter prefix="/" />
|
<SidebarRouter prefix="" />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,11 +7,6 @@ export const nodeSet = (node: Partial<INodeState>) => ({
|
||||||
type: NODE_ACTIONS.SET,
|
type: NODE_ACTIONS.SET,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const nodeSave = (node: INode) => ({
|
|
||||||
node,
|
|
||||||
type: NODE_ACTIONS.SAVE,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const nodeSetSaveErrors = (errors: IValidationErrors) => ({
|
export const nodeSetSaveErrors = (errors: IValidationErrors) => ({
|
||||||
errors,
|
errors,
|
||||||
type: NODE_ACTIONS.SET_SAVE_ERRORS,
|
type: NODE_ACTIONS.SET_SAVE_ERRORS,
|
||||||
|
@ -55,6 +50,15 @@ export const nodePostLocalComment = (
|
||||||
type: NODE_ACTIONS.POST_COMMENT,
|
type: NODE_ACTIONS.POST_COMMENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const nodeSubmitLocal = (
|
||||||
|
node: INode,
|
||||||
|
callback: (e?: string, errors?: Record<string, string>) => void
|
||||||
|
) => ({
|
||||||
|
node,
|
||||||
|
callback,
|
||||||
|
type: NODE_ACTIONS.SUBMIT_LOCAL,
|
||||||
|
});
|
||||||
|
|
||||||
export const nodeSetSendingComment = (is_sending_comment: boolean) => ({
|
export const nodeSetSendingComment = (is_sending_comment: boolean) => ({
|
||||||
is_sending_comment,
|
is_sending_comment,
|
||||||
type: NODE_ACTIONS.SET_SENDING_COMMENT,
|
type: NODE_ACTIONS.SET_SENDING_COMMENT,
|
||||||
|
|
|
@ -40,6 +40,9 @@ export type ApiGetNodeCommentsResponse = { comments: IComment[]; comment_count:
|
||||||
export const apiPostNode = ({ node }: ApiPostNodeRequest) =>
|
export const apiPostNode = ({ node }: ApiPostNodeRequest) =>
|
||||||
api.post<ApiPostNodeResult>(API.NODE.SAVE, node).then(cleanResult);
|
api.post<ApiPostNodeResult>(API.NODE.SAVE, node).then(cleanResult);
|
||||||
|
|
||||||
|
export const apiPostNodeLocal = ({ node }: ApiPostNodeRequest) =>
|
||||||
|
api.post<ApiPostNodeResult>(API.NODE.SAVE, node).then(cleanResult);
|
||||||
|
|
||||||
export const getNodeDiff = ({
|
export const getNodeDiff = ({
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -18,12 +18,11 @@ import { NodeImageSwiperBlock } from '~/components/node/NodeImageSwiperBlock';
|
||||||
import { LabNodeTitle } from '~/components/lab/LabNodeTitle';
|
import { LabNodeTitle } from '~/components/lab/LabNodeTitle';
|
||||||
import { LabText } from '~/components/lab/LabText';
|
import { LabText } from '~/components/lab/LabText';
|
||||||
import { LabImage } from '~/components/lab/LabImage';
|
import { LabImage } from '~/components/lab/LabImage';
|
||||||
import { LabBottomPanel } from '~/components/lab/LabBottomPanel';
|
|
||||||
import { LabPad } from '~/components/lab/LabPad';
|
import { LabPad } from '~/components/lab/LabPad';
|
||||||
|
|
||||||
const prefix = 'NODE.';
|
const prefix = 'NODE.';
|
||||||
export const NODE_ACTIONS = {
|
export const NODE_ACTIONS = {
|
||||||
SAVE: `${prefix}SAVE`,
|
SUBMIT_LOCAL: `${prefix}SUBMIT_LOCAL`,
|
||||||
LOAD_NODE: `${prefix}LOAD_NODE`,
|
LOAD_NODE: `${prefix}LOAD_NODE`,
|
||||||
GOTO_NODE: `${prefix}GOTO_NODE`,
|
GOTO_NODE: `${prefix}GOTO_NODE`,
|
||||||
SET: `${prefix}SET`,
|
SET: `${prefix}SET`,
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
nodeLock,
|
nodeLock,
|
||||||
nodeLockComment,
|
nodeLockComment,
|
||||||
nodePostLocalComment,
|
nodePostLocalComment,
|
||||||
nodeSave,
|
|
||||||
nodeSet,
|
nodeSet,
|
||||||
nodeSetCommentData,
|
nodeSetCommentData,
|
||||||
nodeSetComments,
|
nodeSetComments,
|
||||||
|
@ -26,8 +25,8 @@ import {
|
||||||
nodeSetLoading,
|
nodeSetLoading,
|
||||||
nodeSetLoadingComments,
|
nodeSetLoadingComments,
|
||||||
nodeSetRelated,
|
nodeSetRelated,
|
||||||
nodeSetSaveErrors,
|
|
||||||
nodeSetTags,
|
nodeSetTags,
|
||||||
|
nodeSubmitLocal,
|
||||||
nodeUpdateTags,
|
nodeUpdateTags,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import {
|
import {
|
||||||
|
@ -43,7 +42,6 @@ import {
|
||||||
apiPostNodeTags,
|
apiPostNodeTags,
|
||||||
} from './api';
|
} from './api';
|
||||||
import { flowSetNodes, flowSetUpdated } from '../flow/actions';
|
import { flowSetNodes, flowSetUpdated } from '../flow/actions';
|
||||||
import { ERRORS } from '~/constants/errors';
|
|
||||||
import { modalSetShown, modalShowDialog } from '../modal/actions';
|
import { modalSetShown, modalShowDialog } from '../modal/actions';
|
||||||
import { selectFlow, selectFlowNodes } from '../flow/selectors';
|
import { selectFlow, selectFlowNodes } from '../flow/selectors';
|
||||||
import { URLS } from '~/constants/urls';
|
import { URLS } from '~/constants/urls';
|
||||||
|
@ -73,14 +71,12 @@ export function* updateNodeEverywhere(node) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function* onNodeSave({ node }: ReturnType<typeof nodeSave>) {
|
function* onNodeSubmitLocal({ node, callback }: ReturnType<typeof nodeSubmitLocal>) {
|
||||||
try {
|
try {
|
||||||
yield put(nodeSetSaveErrors({}));
|
|
||||||
|
|
||||||
const { errors, node: result }: Unwrap<typeof apiPostNode> = yield call(apiPostNode, { node });
|
const { errors, node: result }: Unwrap<typeof apiPostNode> = yield call(apiPostNode, { node });
|
||||||
|
|
||||||
if (errors && Object.values(errors).length > 0) {
|
if (errors && Object.values(errors).length > 0) {
|
||||||
yield put(nodeSetSaveErrors(errors));
|
callback('', errors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +93,10 @@ function* onNodeSave({ node }: ReturnType<typeof nodeSave>) {
|
||||||
yield put(nodeSetCurrent(result));
|
yield put(nodeSetCurrent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
return yield put(modalSetShown(false));
|
callback();
|
||||||
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(nodeSetSaveErrors({ error: error.message || ERRORS.CANT_SAVE_NODE }));
|
callback(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +358,7 @@ function* onLockCommentSaga({ id, is_locked }: ReturnType<typeof nodeLockComment
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function* nodeSaga() {
|
export default function* nodeSaga() {
|
||||||
yield takeLatest(NODE_ACTIONS.SAVE, onNodeSave);
|
yield takeLatest(NODE_ACTIONS.SUBMIT_LOCAL, onNodeSubmitLocal);
|
||||||
yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto);
|
yield takeLatest(NODE_ACTIONS.GOTO_NODE, onNodeGoto);
|
||||||
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
||||||
yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment);
|
yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment);
|
||||||
|
|
|
@ -16,10 +16,10 @@ export const useCloseOnEscape = (onRequestClose?: () => void, ignore_inputs = fa
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('keyup', onEscape);
|
document.addEventListener('keyup', onEscape);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keyup', onEscape);
|
document.removeEventListener('keyup', onEscape);
|
||||||
};
|
};
|
||||||
}, [onEscape]);
|
}, [onEscape]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,46 @@
|
||||||
import { INode } from '~/redux/types';
|
import { IComment, INode } from '~/redux/types';
|
||||||
import { FileUploader } from '~/utils/hooks/fileUploader';
|
import { FileUploader } from '~/utils/hooks/fileUploader';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { useFormik, useFormikContext } from 'formik';
|
import { FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||||
import { object } from 'yup';
|
import { object, string } from 'yup';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { nodeSubmitLocal } from '~/redux/node/actions';
|
||||||
|
import { keys } from 'ramda';
|
||||||
|
|
||||||
const validationSchema = object().shape({});
|
const validationSchema = object().shape({});
|
||||||
|
|
||||||
|
const onSuccess = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHelpers<INode>) => (
|
||||||
|
e?: string,
|
||||||
|
errors?: Record<string, string>
|
||||||
|
) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
if (e) {
|
||||||
|
setStatus(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors && keys(errors).length) {
|
||||||
|
setErrors(errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetForm) {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const useNodeFormFormik = (
|
export const useNodeFormFormik = (
|
||||||
values: INode,
|
values: INode,
|
||||||
uploader: FileUploader,
|
uploader: FileUploader,
|
||||||
stopEditing: () => void
|
stopEditing: () => void
|
||||||
) => {
|
) => {
|
||||||
const onSubmit = useCallback(console.log, []);
|
const dispatch = useDispatch();
|
||||||
|
const onSubmit = useCallback((values: INode, helpers: FormikHelpers<INode>) => {
|
||||||
|
helpers.setSubmitting(true);
|
||||||
|
dispatch(nodeSubmitLocal(values, onSuccess(helpers)));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { current: initialValues } = useRef(values);
|
const { current: initialValues } = useRef(values);
|
||||||
|
|
||||||
const onReset = useCallback(() => {
|
const onReset = useCallback(() => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue