diff --git a/src/components/node/Comment/index.tsx b/src/components/comment/Comment/index.tsx similarity index 88% rename from src/components/node/Comment/index.tsx rename to src/components/comment/Comment/index.tsx index 839748b1..5f194158 100644 --- a/src/components/node/Comment/index.tsx +++ b/src/components/comment/Comment/index.tsx @@ -1,12 +1,12 @@ import React, { FC, HTMLAttributes, memo } from 'react'; import { CommentWrapper } from '~/components/containers/CommentWrapper'; -import { ICommentGroup, IComment } from '~/redux/types'; -import { CommentContent } from '~/components/node/CommentContent'; +import { ICommentGroup } from '~/redux/types'; +import { CommentContent } from '~/components/comment/CommentContent'; import styles from './styles.module.scss'; -import { nodeLockComment, nodeEditComment } from '~/redux/node/actions'; +import { nodeEditComment, nodeLockComment } from '~/redux/node/actions'; import { INodeState } from '~/redux/node/reducer'; import { CommentForm } from '../CommentForm'; -import { CommendDeleted } from '../CommendDeleted'; +import { CommendDeleted } from '../../node/CommendDeleted'; import * as MODAL_ACTIONS from '~/redux/modal/actions'; type IProps = HTMLAttributes & { diff --git a/src/components/node/Comment/styles.module.scss b/src/components/comment/Comment/styles.module.scss similarity index 100% rename from src/components/node/Comment/styles.module.scss rename to src/components/comment/Comment/styles.module.scss diff --git a/src/components/node/CommentContent/index.tsx b/src/components/comment/CommentContent/index.tsx similarity index 100% rename from src/components/node/CommentContent/index.tsx rename to src/components/comment/CommentContent/index.tsx diff --git a/src/components/node/CommentContent/styles.module.scss b/src/components/comment/CommentContent/styles.module.scss similarity index 100% rename from src/components/node/CommentContent/styles.module.scss rename to src/components/comment/CommentContent/styles.module.scss diff --git a/src/components/comment/CommentForm/index.tsx b/src/components/comment/CommentForm/index.tsx new file mode 100644 index 00000000..6481aa7e --- /dev/null +++ b/src/components/comment/CommentForm/index.tsx @@ -0,0 +1,234 @@ +import React, { FC, KeyboardEventHandler, memo, useCallback, useEffect, useMemo } from 'react'; +import { Textarea } from '~/components/input/Textarea'; +import styles from './styles.module.scss'; +import { Filler } from '~/components/containers/Filler'; +import { Button } from '~/components/input/Button'; +import assocPath from 'ramda/es/assocPath'; +import { IComment, IFileWithUUID, InputHandler } from '~/redux/types'; +import { connect } from 'react-redux'; +import * as NODE_ACTIONS from '~/redux/node/actions'; +import { selectNode } from '~/redux/node/selectors'; +import { LoaderCircle } from '~/components/input/LoaderCircle'; +import { Group } from '~/components/containers/Group'; +import { UPLOAD_SUBJECTS, UPLOAD_TARGETS, UPLOAD_TYPES } from '~/redux/uploads/constants'; +import uuid from 'uuid4'; +import * as UPLOAD_ACTIONS from '~/redux/uploads/actions'; +import { selectUploads } from '~/redux/uploads/selectors'; +import { IState } from '~/redux/store'; +import { getFileType } from '~/utils/uploader'; +import { getRandomPhrase } from '~/constants/phrases'; +import { ERROR_LITERAL } from '~/constants/errors'; +import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches'; +import { CommentFormAttachButtons } from '~/components/comment/CommentFormButtons'; +import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone'; + +const mapStateToProps = (state: IState) => ({ + node: selectNode(state), + uploads: selectUploads(state), +}); + +const mapDispatchToProps = { + nodePostComment: NODE_ACTIONS.nodePostComment, + nodeCancelCommentEdit: NODE_ACTIONS.nodeCancelCommentEdit, + nodeSetCommentData: NODE_ACTIONS.nodeSetCommentData, + uploadUploadFiles: UPLOAD_ACTIONS.uploadUploadFiles, +}; + +type IProps = ReturnType & + typeof mapDispatchToProps & { + id: number; + is_before?: boolean; + }; + +const CommentFormUnconnected: FC = memo( + ({ + node: { comment_data, is_sending_comment }, + uploads: { statuses, files }, + id, + is_before = false, + nodePostComment, + nodeSetCommentData, + uploadUploadFiles, + nodeCancelCommentEdit, + }) => { + const comment = useMemo(() => comment_data[id], [comment_data, id]); + + const onUpload = useCallback( + (files: File[]) => { + console.log(files); + + const items: IFileWithUUID[] = files.map( + (file: File): IFileWithUUID => ({ + file, + temp_id: uuid(), + subject: UPLOAD_SUBJECTS.COMMENT, + target: UPLOAD_TARGETS.COMMENTS, + type: getFileType(file), + }) + ); + + const temps = items.map(file => file.temp_id); + + nodeSetCommentData(id, assocPath(['temp_ids'], [...comment.temp_ids, ...temps], comment)); + uploadUploadFiles(items); + }, + [uploadUploadFiles, comment, id, nodeSetCommentData] + ); + + const onInput = useCallback( + text => { + nodeSetCommentData(id, assocPath(['text'], text, comment)); + }, + [nodeSetCommentData, comment, id] + ); + + useEffect(() => { + const temp_ids = (comment && comment.temp_ids) || []; + const added_files = temp_ids + .map(temp_uuid => statuses[temp_uuid] && statuses[temp_uuid].uuid) + .map(el => !!el && files[el]) + .filter(el => !!el && !comment.files.some(file => file && file.id === el.id)); + + const filtered_temps = temp_ids.filter( + temp_id => + statuses[temp_id] && + (!statuses[temp_id].uuid || !added_files.some(file => file.id === statuses[temp_id].uuid)) + ); + + if (added_files.length) { + nodeSetCommentData(id, { + ...comment, + temp_ids: filtered_temps, + files: [...comment.files, ...added_files], + }); + } + }, [statuses, files]); + + const isUploadingNow = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]); + + const onSubmit = useCallback( + event => { + if (event) event.preventDefault(); + if (isUploadingNow || is_sending_comment) return; + + nodePostComment(id, is_before); + }, + [nodePostComment, id, is_before, isUploadingNow, is_sending_comment] + ); + + const onKeyDown = useCallback>( + ({ ctrlKey, key }) => { + if (!!ctrlKey && key === 'Enter') onSubmit(null); + }, + [onSubmit] + ); + + const images = useMemo( + () => comment.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), + [comment.files] + ); + + const locked_images = useMemo( + () => + comment.temp_ids + .filter(temp => statuses[temp] && statuses[temp].type === UPLOAD_TYPES.IMAGE) + .map(temp_id => statuses[temp_id]), + [statuses, comment.temp_ids] + ); + + const audios = useMemo( + () => comment.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), + [comment.files] + ); + + const locked_audios = useMemo( + () => + comment.temp_ids + .filter(temp => statuses[temp] && statuses[temp].type === UPLOAD_TYPES.AUDIO) + .map(temp_id => statuses[temp_id]), + [statuses, comment.temp_ids] + ); + + const onCancelEdit = useCallback(() => { + nodeCancelCommentEdit(id); + }, [nodeCancelCommentEdit, comment.id]); + + const placeholder = getRandomPhrase('SIMPLE'); + + const clearError = useCallback(() => nodeSetCommentData(id, { error: '' }), [ + id, + nodeSetCommentData, + ]); + + useEffect(() => { + if (comment.error) clearError(); + }, [comment.files, comment.text]); + + const setData = useCallback( + (data: Partial) => { + nodeSetCommentData(id, data); + }, + [nodeSetCommentData, id] + ); + + return ( + +
+
+