1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46: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 { ERROR_LITERAL } from '~/constants/errors';
import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches'; import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches';
import { CommentFormAttachButtons } from '~/components/comment/CommentFormButtons'; import { CommentFormAttachButtons } from '~/components/comment/CommentFormButtons';
import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone';
const mapStateToProps = (state: IState) => ({ const mapStateToProps = (state: IState) => ({
node: selectNode(state), node: selectNode(state),
@ -50,8 +51,12 @@ const CommentFormUnconnected: FC<IProps> = memo(
uploadUploadFiles, uploadUploadFiles,
nodeCancelCommentEdit, nodeCancelCommentEdit,
}) => { }) => {
const comment = useMemo(() => comment_data[id], [comment_data, id]);
const onUpload = useCallback( const onUpload = useCallback(
(files: File[]) => { (files: File[]) => {
console.log(files);
const items: IFileWithUUID[] = files.map( const items: IFileWithUUID[] = files.map(
(file: File): IFileWithUUID => ({ (file: File): IFileWithUUID => ({
file, file,
@ -64,28 +69,25 @@ const CommentFormUnconnected: FC<IProps> = memo(
const temps = items.map(file => file.temp_id); const temps = items.map(file => file.temp_id);
nodeSetCommentData( nodeSetCommentData(id, assocPath(['temp_ids'], [...comment.temp_ids, ...temps], comment));
id,
assocPath(['temp_ids'], [...comment_data[id].temp_ids, ...temps], comment_data[id])
);
uploadUploadFiles(items); uploadUploadFiles(items);
}, },
[uploadUploadFiles, comment_data, id, nodeSetCommentData] [uploadUploadFiles, comment, id, nodeSetCommentData]
); );
const onInput = useCallback<InputHandler>( const onInput = useCallback<InputHandler>(
text => { text => {
nodeSetCommentData(id, assocPath(['text'], text, comment_data[id])); nodeSetCommentData(id, assocPath(['text'], text, comment));
}, },
[nodeSetCommentData, comment_data, id] [nodeSetCommentData, comment, id]
); );
useEffect(() => { 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 const added_files = temp_ids
.map(temp_uuid => statuses[temp_uuid] && statuses[temp_uuid].uuid) .map(temp_uuid => statuses[temp_uuid] && statuses[temp_uuid].uuid)
.map(el => !!el && files[el]) .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( const filtered_temps = temp_ids.filter(
temp_id => temp_id =>
@ -95,25 +97,23 @@ const CommentFormUnconnected: FC<IProps> = memo(
if (added_files.length) { if (added_files.length) {
nodeSetCommentData(id, { nodeSetCommentData(id, {
...comment_data[id], ...comment,
temp_ids: filtered_temps, temp_ids: filtered_temps,
files: [...comment_data[id].files, ...added_files], files: [...comment.files, ...added_files],
}); });
} }
}, [statuses, files]); }, [statuses, files]);
const comment = useMemo(() => comment_data[id], [comment_data, id]); const isUploadingNow = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]);
const is_uploading_files = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]);
const onSubmit = useCallback( const onSubmit = useCallback(
event => { event => {
if (event) event.preventDefault(); 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);
}, },
[nodePostComment, id, is_before, is_uploading_files, is_sending_comment] [nodePostComment, id, is_before, isUploadingNow, is_sending_comment]
); );
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>( const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
@ -172,6 +172,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
); );
return ( return (
<CommentFormDropzone onUpload={onUpload}>
<form onSubmit={onSubmit} className={styles.wrap}> <form onSubmit={onSubmit} className={styles.wrap}>
<div className={styles.input}> <div className={styles.input}>
<Textarea <Textarea
@ -197,6 +198,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
locked_images={locked_images} locked_images={locked_images}
comment={comment} comment={comment}
setComment={setData} setComment={setData}
onUpload={onUpload}
/> />
<Group horizontal className={styles.buttons}> <Group horizontal className={styles.buttons}>
@ -204,7 +206,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
<Filler /> <Filler />
{(is_sending_comment || is_uploading_files) && <LoaderCircle size={20} />} {(is_sending_comment || isUploadingNow) && <LoaderCircle size={20} />}
{id !== 0 && ( {id !== 0 && (
<Button size="small" color="link" type="button" onClick={onCancelEdit}> <Button size="small" color="link" type="button" onClick={onCancelEdit}>
@ -216,12 +218,13 @@ const CommentFormUnconnected: FC<IProps> = memo(
size="small" size="small"
color="gray" color="gray"
iconRight={id === 0 ? 'enter' : 'check'} iconRight={id === 0 ? 'enter' : 'check'}
disabled={is_sending_comment || is_uploading_files} disabled={is_sending_comment || isUploadingNow}
> >
{id === 0 ? 'Сказать' : 'Сохранить'} {id === 0 ? 'Сказать' : 'Сохранить'}
</Button> </Button>
</Group> </Group>
</form> </form>
</CommentFormDropzone>
); );
} }
); );

View file

@ -7,6 +7,8 @@ import { IUploadStatus } from '~/redux/uploads/reducer';
import { SortEnd } from 'react-sortable-hoc'; import { SortEnd } from 'react-sortable-hoc';
import assocPath from 'ramda/es/assocPath'; import assocPath from 'ramda/es/assocPath';
import { moveArrItem } from '~/utils/fn'; import { moveArrItem } from '~/utils/fn';
import { useDropZone } from '~/utils/hooks';
import { COMMENT_FILE_TYPES } from '~/redux/uploads/constants';
interface IProps { interface IProps {
images: IFile[]; images: IFile[];
@ -15,6 +17,7 @@ interface IProps {
locked_audios: IUploadStatus[]; locked_audios: IUploadStatus[];
comment: IComment; comment: IComment;
setComment: (data: IComment) => void; setComment: (data: IComment) => void;
onUpload: (files: File[]) => void;
} }
const CommentFormAttaches: FC<IProps> = ({ const CommentFormAttaches: FC<IProps> = ({
@ -24,7 +27,10 @@ const CommentFormAttaches: FC<IProps> = ({
locked_audios, locked_audios,
comment, comment,
setComment, setComment,
onUpload,
}) => { }) => {
const onDrop = useDropZone(onUpload, COMMENT_FILE_TYPES);
const hasImageAttaches = images.length > 0 || locked_images.length > 0; const hasImageAttaches = images.length > 0 || locked_images.length > 0;
const hasAudioAttaches = audios.length > 0 || locked_audios.length > 0; const hasAudioAttaches = audios.length > 0 || locked_audios.length > 0;
const hasAttaches = hasImageAttaches || hasAudioAttaches; const hasAttaches = hasImageAttaches || hasAudioAttaches;
@ -99,7 +105,7 @@ const CommentFormAttaches: FC<IProps> = ({
return ( return (
hasAttaches && ( hasAttaches && (
<div className={styles.attaches}> <div className={styles.attaches} onDropCapture={onDrop}>
{hasImageAttaches && ( {hasImageAttaches && (
<SortableImageGrid <SortableImageGrid
onDelete={onFileDelete} onDelete={onFileDelete}

View file

@ -1,21 +1,19 @@
import React, { FC, useCallback } from 'react'; import React, { FC, useCallback } from 'react';
import { ButtonGroup } from '~/components/input/ButtonGroup'; import { ButtonGroup } from '~/components/input/ButtonGroup';
import { Button } from '~/components/input/Button'; 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 { interface IProps {
onUpload: (files: File[]) => void; onUpload: (files: File[]) => void;
} }
const ALLOWED_TYPES = [...FILE_MIMES[UPLOAD_TYPES.IMAGE], ...FILE_MIMES[UPLOAD_TYPES.AUDIO]];
const CommentFormAttachButtons: FC<IProps> = ({ onUpload }) => { const CommentFormAttachButtons: FC<IProps> = ({ onUpload }) => {
const onInputChange = useCallback( const onInputChange = useCallback(
event => { event => {
event.preventDefault(); event.preventDefault();
const files = Array.from(event.target?.files as File[]).filter((file: File) => 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; 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; onDelete: (file_id: IFile['id']) => void;
onTitleChange: (file_id: IFile['id'], title: IFile['metadata']['title']) => void; onTitleChange: (file_id: IFile['id'], title: IFile['metadata']['title']) => void;
}) => { }) => {
console.log(locked);
return ( return (
<div className={styles.grid}> <div className={styles.grid}>
{items {items

View file

@ -1,4 +1,4 @@
import React from 'react'; import React, { useCallback } from 'react';
import { SortableContainer } from 'react-sortable-hoc'; import { SortableContainer } from 'react-sortable-hoc';
import { ImageUpload } from '~/components/upload/ImageUpload'; import { ImageUpload } from '~/components/upload/ImageUpload';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -19,10 +19,14 @@ const SortableImageGrid = SortableContainer(
locked: IUploadStatus[]; locked: IUploadStatus[];
onDelete: (file_id: IFile['id']) => void; onDelete: (file_id: IFile['id']) => void;
size?: number; size?: number;
}) => ( }) => {
const preventEvent = useCallback(event => event.preventDefault(), []);
return (
<div <div
className={styles.grid} className={styles.grid}
style={{ gridTemplateColumns: `repeat(auto-fill, minmax(${size}px, 1fr))` }} style={{ gridTemplateColumns: `repeat(auto-fill, minmax(${size}px, 1fr))` }}
onDropCapture={preventEvent}
> >
{items {items
.filter(file => file && file.id) .filter(file => file && file.id)
@ -38,7 +42,8 @@ const SortableImageGrid = SortableContainer(
</SortableImageGridItem> </SortableImageGridItem>
))} ))}
</div> </div>
) );
}
); );
export { SortableImageGrid }; export { SortableImageGrid };

View file

@ -69,3 +69,8 @@ export const FILE_MIMES = {
[UPLOAD_TYPES.AUDIO]: ['audio/mpeg3', 'audio/mpeg', 'audio/mp3'], [UPLOAD_TYPES.AUDIO]: ['audio/mpeg3', 'audio/mpeg', 'audio/mp3'],
[UPLOAD_TYPES.OTHER]: [], [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 { import { useCallback, useEffect } from 'react';
useCallback, useEffect, useRef, useState
} from 'react';
export const useCloseOnEscape = (onRequestClose: () => void, ignore_inputs = false) => { export const useCloseOnEscape = (onRequestClose: () => void, ignore_inputs = false) => {
const onEscape = useCallback( const onEscape = useCallback(
(event) => { event => {
if (event.key !== 'Escape') return; if (event.key !== 'Escape') return;
if ( if (
ignore_inputs ignore_inputs &&
&& (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA')
) return; )
return;
onRequestClose(); onRequestClose();
}, },
[ignore_inputs, onRequestClose], [ignore_inputs, onRequestClose]
); );
useEffect(() => { useEffect(() => {
@ -34,3 +33,24 @@ export const useDelayedReady = (setReady: (val: boolean) => void, delay: number
}; };
}, [delay, setReady]); }, [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]
);
};