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:
parent
265b075ddc
commit
1b6a81d27c
11 changed files with 119 additions and 22 deletions
|
@ -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}>
|
||||
|
|
27
src/components/editors/TextEditor/index.tsx
Normal file
27
src/components/editors/TextEditor/index.tsx
Normal 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 };
|
3
src/components/editors/TextEditor/styles.scss
Normal file
3
src/components/editors/TextEditor/styles.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.wrap {
|
||||
padding-bottom: 60px;
|
||||
}
|
10
src/containers/editors/EditorDialogText/index.tsx
Normal file
10
src/containers/editors/EditorDialogText/index.tsx
Normal 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 };
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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' }],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue