1
0
Fork 0
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:
Fedor Katurov 2020-11-09 18:44:36 +07:00
parent 63b9781977
commit 62d4e03206
8 changed files with 142 additions and 93 deletions

View file

@ -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>
);
}
);

View file

@ -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}

View file

@ -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;

View 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 };

View file

@ -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

View file

@ -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 };

View file

@ -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],
];

View file

@ -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]
);
};