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

sagas for node creation dialog

This commit is contained in:
Fedor Katurov 2019-10-16 16:22:01 +07:00
parent 265b075ddc
commit 1b6a81d27c
11 changed files with 119 additions and 22 deletions

View file

@ -1,20 +1,22 @@
import React, { FC, useCallback } from 'react';
import { connect } from 'react-redux';
import { Icon } from '~/components/input/Icon';
import * as MODAL_ACTIONS from '~/redux/modal/actions';
import * as NODE_ACTIONS from '~/redux/node/actions';
import { DIALOGS } from '~/redux/modal/constants';
import * as styles from './styles.scss';
import { NODE_TYPES } from '~/redux/node/constants';
const mapStateToProps = null;
const mapDispatchToProps = {
showDialog: MODAL_ACTIONS.modalShowDialog,
nodeCreate: NODE_ACTIONS.nodeCreate,
// showDialog: MODAL_ACTIONS.modalShowDialog,
};
type IProps = typeof mapDispatchToProps & {};
const SubmitBarUnconnected: FC<IProps> = ({ showDialog }) => {
const onOpenImageEditor = useCallback(() => showDialog(DIALOGS.EDITOR_IMAGE), [showDialog]);
const SubmitBarUnconnected: FC<IProps> = ({ nodeCreate }) => {
const onOpenImageEditor = useCallback(() => nodeCreate(NODE_TYPES.IMAGE), [nodeCreate]);
return (
<div className={styles.wrap}>

View file

@ -0,0 +1,27 @@
import React, { FC, useCallback } from 'react';
import { INode } from '~/redux/types';
import * as styles from './styles.scss';
import { Textarea } from '~/components/input/Textarea';
import path from 'ramda/es/path';
interface IProps {
data: INode;
setData: (val: INode) => void;
}
const TextEditor: FC<IProps> = ({ data, setData }) => {
const setText = useCallback(
(text: string) => setData({ ...data, blocks: [{ type: 'text', text }] }),
[data, setData]
);
const text = (path(['blocks', 0, 'text'], data) as string) || '';
return (
<div className={styles.wrap}>
<Textarea value={text} handler={setText} minRows={6} />
</div>
);
};
export { TextEditor };

View file

@ -0,0 +1,3 @@
.wrap {
padding-bottom: 60px;
}

View file

@ -0,0 +1,10 @@
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<IProps> = props => <EditorDialog type={NODE_TYPES.TEXT} {...props} />;
export { EditorDialogText };

View file

@ -1,6 +1,8 @@
import { ValueOf } from '~/redux/types';
import { EditorDialogImage } from '~/containers/editors/EditorDialogImage';
import { LoginDialog } from '~/containers/dialogs/LoginDialog';
import { EditorDialogImage } from '~/containers/editors/EditorDialogImage';
import { EditorDialogText } from '~/containers/editors/EditorDialogText';
import { NODE_TYPES } from '../node/constants';
export const MODAL_ACTIONS = {
SET_SHOWN: 'MODAL.SET_SHOWN',
@ -10,14 +12,21 @@ export const MODAL_ACTIONS = {
export const DIALOGS = {
EDITOR_IMAGE: 'EDITOR_IMAGE',
EDITOR_TEXT: 'EDITOR_TEXT',
LOGIN: 'LOGIN',
};
export const DIALOG_CONTENT = {
[DIALOGS.EDITOR_IMAGE]: EditorDialogImage,
[DIALOGS.EDITOR_TEXT]: EditorDialogText,
[DIALOGS.LOGIN]: LoginDialog,
};
export const NODE_EDITOR_DIALOGS = {
[NODE_TYPES.IMAGE]: DIALOGS.EDITOR_IMAGE,
[NODE_TYPES.TEXT]: DIALOGS.EDITOR_TEXT,
};
export interface IDialogProps {
onRequestClose: () => void;
onDialogChange: (dialog: ValueOf<typeof DIALOGS>) => void;

View file

@ -9,8 +9,8 @@ export interface IModalState {
}
const INITIAL_STATE: IModalState = {
is_shown: true,
dialog: DIALOGS.EDITOR_IMAGE,
is_shown: false,
dialog: DIALOGS.EDITOR_TEXT,
};
export default createReducer(INITIAL_STATE, MODAL_HANDLERS);

View file

@ -1,5 +1,5 @@
import { INode, IValidationErrors, IComment, ITag } from '../types';
import { NODE_ACTIONS } from './constants';
import { NODE_ACTIONS, NODE_TYPES } from './constants';
import { INodeState } from './reducer';
export const nodeSave = (node: INode) => ({
@ -64,3 +64,18 @@ export const nodeSetTags = (tags: ITag[]) => ({
type: NODE_ACTIONS.SET_TAGS,
tags,
});
export const nodeCreate = (node_type: INode['type']) => ({
type: NODE_ACTIONS.CREATE,
node_type,
});
export const nodeEdit = (id: INode['id']) => ({
type: NODE_ACTIONS.CREATE,
id,
});
export const nodeSetEditor = (editor: INode) => ({
type: NODE_ACTIONS.SET_EDITOR,
editor,
});

View file

@ -2,18 +2,24 @@ import { FC } from 'react';
import { IBlock, INode, ValueOf, IComment } from '../types';
import { NodeImageBlock } from '~/components/node/NodeImageBlock';
import { ImageEditor } from '~/components/editors/ImageEditor';
import { TextEditor } from '~/components/editors/TextEditor';
import { DIALOGS } from '../modal/constants';
const prefix = 'NODE.';
export const NODE_ACTIONS = {
SAVE: `${prefix}SAVE`,
LOAD_NODE: `${prefix}LOAD_NODE`,
EDIT: `${prefix}EDIT`,
CREATE: `${prefix}CREATE`,
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,
SET_LOADING: `${prefix}SET_LOADING`,
SET_LOADING_COMMENTS: `${prefix}SET_LOADING_COMMENTS`,
SET_SENDING_COMMENT: `${prefix}SET_SENDING_COMMENT`,
SET_CURRENT: `${prefix}SET_CURRENT`,
SET_COMMENT_DATA: `${prefix}SET_COMMENT_DATA`,
SET_EDITOR: `${prefix}SET_EDITOR`,
POST_COMMENT: `${prefix}POST_COMMENT`,
SET_COMMENTS: `${prefix}SET_COMMENTS`,
@ -22,13 +28,6 @@ export const NODE_ACTIONS = {
SET_TAGS: `${prefix}SET_TAGS`,
};
export const EMPTY_BLOCK: IBlock = {
type: null,
files: [],
content: null,
embeds: [],
};
export const EMPTY_NODE: INode = {
id: null,
@ -78,6 +77,16 @@ export const EMPTY_COMMENT: IComment = {
export const NODE_EDITORS = {
[NODE_TYPES.IMAGE]: ImageEditor,
[NODE_TYPES.TEXT]: TextEditor,
};
export const MAX_NODE_FILES = 16;
export const NODE_EDITOR_DATA: Record<
typeof NODE_TYPES[keyof typeof NODE_TYPES],
Partial<INode>
> = {
[NODE_TYPES.TEXT]: {
blocks: [{ text: '', type: 'text' }],
},
};

View file

@ -9,6 +9,7 @@ import {
nodeSetComments,
nodeSetCommentData,
nodeSetTags,
nodeSetEditor,
} from './actions';
import { INodeState } from './reducer';
@ -42,6 +43,9 @@ const setCommentData = (
const setTags = (state: INodeState, { tags }: ReturnType<typeof nodeSetTags>) =>
assocPath(['current', 'tags'], tags, state);
const setEditor = (state: INodeState, { editor }: ReturnType<typeof nodeSetEditor>) =>
assocPath(['current', 'editor'], editor, state);
export const NODE_HANDLERS = {
[NODE_ACTIONS.SET_SAVE_ERRORS]: setSaveErrors,
[NODE_ACTIONS.SET_LOADING]: setLoading,
@ -51,4 +55,5 @@ export const NODE_HANDLERS = {
[NODE_ACTIONS.SET_COMMENTS]: setComments,
[NODE_ACTIONS.SET_COMMENT_DATA]: setCommentData,
[NODE_ACTIONS.SET_TAGS]: setTags,
[NODE_ACTIONS.SET_EDITOR]: setEditor,
};

View file

@ -1,7 +1,7 @@
import { takeLatest, call, put, select, delay } from 'redux-saga/effects';
import { push } from 'connected-react-router';
import { NODE_ACTIONS, EMPTY_NODE, EMPTY_COMMENT } from './constants';
import { NODE_ACTIONS, EMPTY_NODE, EMPTY_COMMENT, NODE_EDITOR_DATA } from './constants';
import {
nodeSave,
nodeSetSaveErrors,
@ -15,16 +15,19 @@ import {
nodeSetCommentData,
nodeUpdateTags,
nodeSetTags,
nodeCreate,
nodeSetEditor,
} from './actions';
import { postNode, getNode, postNodeComment, getNodeComments, updateNodeTags } from './api';
import { reqWrapper } from '../auth/sagas';
import { flowSetNodes } from '../flow/actions';
import { ERRORS } from '~/constants/errors';
import { modalSetShown } from '../modal/actions';
import { modalSetShown, modalShowDialog } from '../modal/actions';
import { selectFlowNodes } from '../flow/selectors';
import { URLS } from '~/constants/urls';
import { selectNode } from './selectors';
import { IResultWithStatus, INode } from '../types';
import { NODE_EDITOR_DIALOGS } from '../modal/constants';
function* onNodeSave({ node }: ReturnType<typeof nodeSave>) {
yield put(nodeSetSaveErrors({}));
@ -118,9 +121,17 @@ function* onUpdateTags({ id, tags }: ReturnType<typeof nodeUpdateTags>) {
yield put(nodeSetTags(node.tags));
}
function* onCreateSaga({ node_type: type }: ReturnType<typeof nodeCreate>) {
if (!NODE_EDITOR_DIALOGS[type]) return;
yield put(nodeSetEditor({ ...EMPTY_NODE, ...(NODE_EDITOR_DATA[type] || {}), type }));
yield put(modalShowDialog(NODE_EDITOR_DIALOGS[type]));
}
export default function* nodeSaga() {
yield takeLatest(NODE_ACTIONS.SAVE, onNodeSave);
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment);
yield takeLatest(NODE_ACTIONS.UPDATE_TAGS, onUpdateTags);
yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga);
}

View file

@ -2,6 +2,7 @@ import { DetailedHTMLProps, InputHTMLAttributes } from 'react';
import { DIALOGS } from '~/redux/modal/constants';
import { ERRORS } from '~/constants/errors';
import { IUser } from './auth/types';
import { string } from 'prop-types';
export interface ITag {
id: number;
@ -93,13 +94,18 @@ export interface IFileWithUUID {
type: string;
}
export interface IBlock {
type: 'image' | 'text' | 'media' | 'youtube' | 'video';
files: UUID[];
content: string;
embeds: string[];
export interface IBlockText {
type: 'text';
text: string;
}
export interface IBlockEmbed {
type: 'embed';
url: string;
}
export type IBlock = IBlockText | IBlockEmbed;
export interface INode {
id?: number;
user: Partial<IUser>;