mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added comment form drop zone
This commit is contained in:
parent
63b9781977
commit
62d4e03206
8 changed files with 142 additions and 93 deletions
|
@ -20,6 +20,7 @@ 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),
|
||||
|
@ -50,8 +51,12 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
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,
|
||||
|
@ -64,28 +69,25 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
|
||||
const temps = items.map(file => file.temp_id);
|
||||
|
||||
nodeSetCommentData(
|
||||
id,
|
||||
assocPath(['temp_ids'], [...comment_data[id].temp_ids, ...temps], comment_data[id])
|
||||
);
|
||||
nodeSetCommentData(id, assocPath(['temp_ids'], [...comment.temp_ids, ...temps], comment));
|
||||
uploadUploadFiles(items);
|
||||
},
|
||||
[uploadUploadFiles, comment_data, id, nodeSetCommentData]
|
||||
[uploadUploadFiles, comment, id, nodeSetCommentData]
|
||||
);
|
||||
|
||||
const onInput = useCallback<InputHandler>(
|
||||
text => {
|
||||
nodeSetCommentData(id, assocPath(['text'], text, comment_data[id]));
|
||||
nodeSetCommentData(id, assocPath(['text'], text, comment));
|
||||
},
|
||||
[nodeSetCommentData, comment_data, id]
|
||||
[nodeSetCommentData, comment, id]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const temp_ids = (comment_data && comment_data[id] && comment_data[id].temp_ids) || [];
|
||||
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_data[id].files.some(file => file && file.id === el.id));
|
||||
.filter(el => !!el && !comment.files.some(file => file && file.id === el.id));
|
||||
|
||||
const filtered_temps = temp_ids.filter(
|
||||
temp_id =>
|
||||
|
@ -95,25 +97,23 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
|
||||
if (added_files.length) {
|
||||
nodeSetCommentData(id, {
|
||||
...comment_data[id],
|
||||
...comment,
|
||||
temp_ids: filtered_temps,
|
||||
files: [...comment_data[id].files, ...added_files],
|
||||
files: [...comment.files, ...added_files],
|
||||
});
|
||||
}
|
||||
}, [statuses, files]);
|
||||
|
||||
const comment = useMemo(() => comment_data[id], [comment_data, id]);
|
||||
|
||||
const is_uploading_files = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]);
|
||||
const isUploadingNow = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
event => {
|
||||
if (event) event.preventDefault();
|
||||
if (is_uploading_files || is_sending_comment) return;
|
||||
if (isUploadingNow || is_sending_comment) return;
|
||||
|
||||
nodePostComment(id, is_before);
|
||||
},
|
||||
[nodePostComment, id, is_before, is_uploading_files, is_sending_comment]
|
||||
[nodePostComment, id, is_before, isUploadingNow, is_sending_comment]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
||||
|
@ -172,6 +172,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
);
|
||||
|
||||
return (
|
||||
<CommentFormDropzone onUpload={onUpload}>
|
||||
<form onSubmit={onSubmit} className={styles.wrap}>
|
||||
<div className={styles.input}>
|
||||
<Textarea
|
||||
|
@ -197,6 +198,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
locked_images={locked_images}
|
||||
comment={comment}
|
||||
setComment={setData}
|
||||
onUpload={onUpload}
|
||||
/>
|
||||
|
||||
<Group horizontal className={styles.buttons}>
|
||||
|
@ -204,7 +206,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
|
||||
<Filler />
|
||||
|
||||
{(is_sending_comment || is_uploading_files) && <LoaderCircle size={20} />}
|
||||
{(is_sending_comment || isUploadingNow) && <LoaderCircle size={20} />}
|
||||
|
||||
{id !== 0 && (
|
||||
<Button size="small" color="link" type="button" onClick={onCancelEdit}>
|
||||
|
@ -216,12 +218,13 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
size="small"
|
||||
color="gray"
|
||||
iconRight={id === 0 ? 'enter' : 'check'}
|
||||
disabled={is_sending_comment || is_uploading_files}
|
||||
disabled={is_sending_comment || isUploadingNow}
|
||||
>
|
||||
{id === 0 ? 'Сказать' : 'Сохранить'}
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</CommentFormDropzone>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,6 +7,8 @@ import { IUploadStatus } from '~/redux/uploads/reducer';
|
|||
import { SortEnd } from 'react-sortable-hoc';
|
||||
import assocPath from 'ramda/es/assocPath';
|
||||
import { moveArrItem } from '~/utils/fn';
|
||||
import { useDropZone } from '~/utils/hooks';
|
||||
import { COMMENT_FILE_TYPES } from '~/redux/uploads/constants';
|
||||
|
||||
interface IProps {
|
||||
images: IFile[];
|
||||
|
@ -15,6 +17,7 @@ interface IProps {
|
|||
locked_audios: IUploadStatus[];
|
||||
comment: IComment;
|
||||
setComment: (data: IComment) => void;
|
||||
onUpload: (files: File[]) => void;
|
||||
}
|
||||
|
||||
const CommentFormAttaches: FC<IProps> = ({
|
||||
|
@ -24,7 +27,10 @@ const CommentFormAttaches: FC<IProps> = ({
|
|||
locked_audios,
|
||||
comment,
|
||||
setComment,
|
||||
onUpload,
|
||||
}) => {
|
||||
const onDrop = useDropZone(onUpload, COMMENT_FILE_TYPES);
|
||||
|
||||
const hasImageAttaches = images.length > 0 || locked_images.length > 0;
|
||||
const hasAudioAttaches = audios.length > 0 || locked_audios.length > 0;
|
||||
const hasAttaches = hasImageAttaches || hasAudioAttaches;
|
||||
|
@ -99,7 +105,7 @@ const CommentFormAttaches: FC<IProps> = ({
|
|||
|
||||
return (
|
||||
hasAttaches && (
|
||||
<div className={styles.attaches}>
|
||||
<div className={styles.attaches} onDropCapture={onDrop}>
|
||||
{hasImageAttaches && (
|
||||
<SortableImageGrid
|
||||
onDelete={onFileDelete}
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import { ButtonGroup } from '~/components/input/ButtonGroup';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { FILE_MIMES, UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||
import { COMMENT_FILE_TYPES } from '~/redux/uploads/constants';
|
||||
|
||||
interface IProps {
|
||||
onUpload: (files: File[]) => void;
|
||||
}
|
||||
|
||||
const ALLOWED_TYPES = [...FILE_MIMES[UPLOAD_TYPES.IMAGE], ...FILE_MIMES[UPLOAD_TYPES.AUDIO]];
|
||||
|
||||
const CommentFormAttachButtons: FC<IProps> = ({ onUpload }) => {
|
||||
const onInputChange = useCallback(
|
||||
event => {
|
||||
event.preventDefault();
|
||||
|
||||
const files = Array.from(event.target?.files as File[]).filter((file: File) =>
|
||||
ALLOWED_TYPES.includes(file.type)
|
||||
COMMENT_FILE_TYPES.includes(file.type)
|
||||
);
|
||||
if (!files || !files.length) return;
|
||||
|
||||
|
|
14
src/components/comment/CommentFormDropzone/index.tsx
Normal file
14
src/components/comment/CommentFormDropzone/index.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React, { FC } from 'react';
|
||||
import { COMMENT_FILE_TYPES } from '~/redux/uploads/constants';
|
||||
import { useDropZone } from '~/utils/hooks';
|
||||
|
||||
interface IProps {
|
||||
onUpload: (files: File[]) => void;
|
||||
}
|
||||
|
||||
const CommentFormDropzone: FC<IProps> = ({ children, onUpload }) => {
|
||||
const onDrop = useDropZone(onUpload, COMMENT_FILE_TYPES);
|
||||
return <div onDropCapture={onDrop}>{children}</div>;
|
||||
};
|
||||
|
||||
export { CommentFormDropzone };
|
|
@ -19,8 +19,6 @@ const SortableAudioGrid = SortableContainer(
|
|||
onDelete: (file_id: IFile['id']) => void;
|
||||
onTitleChange: (file_id: IFile['id'], title: IFile['metadata']['title']) => void;
|
||||
}) => {
|
||||
console.log(locked);
|
||||
|
||||
return (
|
||||
<div className={styles.grid}>
|
||||
{items
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { SortableContainer } from 'react-sortable-hoc';
|
||||
import { ImageUpload } from '~/components/upload/ImageUpload';
|
||||
import styles from './styles.module.scss';
|
||||
|
@ -19,10 +19,14 @@ const SortableImageGrid = SortableContainer(
|
|||
locked: IUploadStatus[];
|
||||
onDelete: (file_id: IFile['id']) => void;
|
||||
size?: number;
|
||||
}) => (
|
||||
}) => {
|
||||
const preventEvent = useCallback(event => event.preventDefault(), []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.grid}
|
||||
style={{ gridTemplateColumns: `repeat(auto-fill, minmax(${size}px, 1fr))` }}
|
||||
onDropCapture={preventEvent}
|
||||
>
|
||||
{items
|
||||
.filter(file => file && file.id)
|
||||
|
@ -38,7 +42,8 @@ const SortableImageGrid = SortableContainer(
|
|||
</SortableImageGridItem>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export { SortableImageGrid };
|
||||
|
|
|
@ -69,3 +69,8 @@ export const FILE_MIMES = {
|
|||
[UPLOAD_TYPES.AUDIO]: ['audio/mpeg3', 'audio/mpeg', 'audio/mp3'],
|
||||
[UPLOAD_TYPES.OTHER]: [],
|
||||
};
|
||||
|
||||
export const COMMENT_FILE_TYPES = [
|
||||
...FILE_MIMES[UPLOAD_TYPES.IMAGE],
|
||||
...FILE_MIMES[UPLOAD_TYPES.AUDIO],
|
||||
];
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import {
|
||||
useCallback, useEffect, useRef, useState
|
||||
} from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export const useCloseOnEscape = (onRequestClose: () => void, ignore_inputs = false) => {
|
||||
const onEscape = useCallback(
|
||||
(event) => {
|
||||
event => {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (
|
||||
ignore_inputs
|
||||
&& (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA')
|
||||
) return;
|
||||
ignore_inputs &&
|
||||
(event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA')
|
||||
)
|
||||
return;
|
||||
|
||||
onRequestClose();
|
||||
},
|
||||
[ignore_inputs, onRequestClose],
|
||||
[ignore_inputs, onRequestClose]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -34,3 +33,24 @@ export const useDelayedReady = (setReady: (val: boolean) => void, delay: number
|
|||
};
|
||||
}, [delay, setReady]);
|
||||
};
|
||||
|
||||
/**
|
||||
* useDropZone returns onDrop handler to upload files
|
||||
* @param onUpload -- upload callback
|
||||
* @param allowedTypes -- list of allowed types
|
||||
*/
|
||||
export const useDropZone = (onUpload: (file: File[]) => void, allowedTypes?: string[]) => {
|
||||
return useCallback(
|
||||
event => {
|
||||
event.preventDefault();
|
||||
const files: File[] = Array.from((event.dataTransfer?.files as File[]) || []).filter(
|
||||
(file: File) => file?.type && (!allowedTypes || allowedTypes.includes(file.type))
|
||||
);
|
||||
|
||||
if (!files || !files.length) return;
|
||||
|
||||
onUpload(files);
|
||||
},
|
||||
[onUpload]
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue