mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
added dropzones for comments and node editors
This commit is contained in:
parent
fb8ad315c0
commit
f10a1fa2d8
23 changed files with 247 additions and 41 deletions
|
@ -11,7 +11,7 @@ import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches';
|
|||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||
import { IComment, INode } from '~/redux/types';
|
||||
import { EMPTY_COMMENT } from '~/redux/node/constants';
|
||||
import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone';
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
import styles from './styles.module.scss';
|
||||
import { ERROR_LITERAL } from '~/constants/errors';
|
||||
import { useInputPasteUpload } from '~/utils/hooks/useInputPasteUpload';
|
||||
|
@ -50,7 +50,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, onCancelEdit }) => {
|
|||
useInputPasteUpload(textarea, uploader.uploadFiles);
|
||||
|
||||
return (
|
||||
<CommentFormDropzone onUpload={uploader.uploadFiles}>
|
||||
<UploadDropzone onUpload={uploader.uploadFiles}>
|
||||
<form onSubmit={formik.handleSubmit} className={styles.wrap}>
|
||||
<FormikProvider value={formik}>
|
||||
<FileUploaderProvider value={uploader}>
|
||||
|
@ -103,7 +103,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, onCancelEdit }) => {
|
|||
</FileUploaderProvider>
|
||||
</FormikProvider>
|
||||
</form>
|
||||
</CommentFormDropzone>
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid';
|
|||
import { IFile } from '~/redux/types';
|
||||
import { SortEnd } from 'react-sortable-hoc';
|
||||
import { moveArrItem } from '~/utils/fn';
|
||||
import { useDropZone } from '~/utils/hooks';
|
||||
import { useFileDropZone } from '~/utils/hooks';
|
||||
import { COMMENT_FILE_TYPES, UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||
|
||||
|
@ -29,7 +29,7 @@ const CommentFormAttaches: FC = () => {
|
|||
pending,
|
||||
]);
|
||||
|
||||
const onDrop = useDropZone(uploadFiles, COMMENT_FILE_TYPES);
|
||||
const onDrop = useFileDropZone(uploadFiles, COMMENT_FILE_TYPES);
|
||||
|
||||
const hasImageAttaches = images.length > 0 || pendingImages.length > 0;
|
||||
const hasAudioAttaches = audios.length > 0 || pendingAudios.length > 0;
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
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 };
|
|
@ -12,12 +12,13 @@ import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
|
|||
import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios';
|
||||
import { useNodeFormContext } from '~/utils/hooks/useNodeFormFormik';
|
||||
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
|
||||
type IProps = NodeEditorProps;
|
||||
|
||||
const AudioEditor: FC<IProps> = () => {
|
||||
const { values } = useNodeFormContext();
|
||||
const { pending, setFiles } = useFileUploaderContext()!;
|
||||
const { pending, setFiles, uploadFiles } = useFileUploaderContext()!;
|
||||
|
||||
const images = useNodeImages(values);
|
||||
const audios = useNodeAudios(values);
|
||||
|
@ -35,10 +36,12 @@ const AudioEditor: FC<IProps> = () => {
|
|||
const setAudios = useCallback(values => setFiles([...values, ...images]), [setFiles, images]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid files={images} setFiles={setImages} locked={pendingImages} />
|
||||
<AudioGrid files={audios} setFiles={setAudios} locked={pendingAudios} />
|
||||
</div>
|
||||
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid files={images} setFiles={setImages} locked={pendingImages} />
|
||||
<AudioGrid files={audios} setFiles={setAudios} locked={pendingAudios} />
|
||||
</div>
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -11,9 +11,13 @@
|
|||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
|
||||
& > * {
|
||||
margin: 0 $gap / 2;
|
||||
pointer-events: all;
|
||||
touch-action: auto;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { IEditorComponentProps } from '~/redux/node/types';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
type IProps = IEditorComponentProps & {};
|
||||
|
||||
const EditorFiller: FC<IProps> = () => <Filler />;
|
||||
const EditorFiller: FC<IProps> = () => <Filler className={styles.filler} />;
|
||||
|
||||
export { EditorFiller };
|
||||
|
|
4
src/components/editors/EditorFiller/styles.module.scss
Normal file
4
src/components/editors/EditorFiller/styles.module.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.filler {
|
||||
touch-action: none;
|
||||
pointer-events: none;
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
import React, { FC, useMemo, useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { INode, IFile } from '~/redux/types';
|
||||
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
|
||||
import { selectUploads } from '~/redux/uploads/selectors';
|
||||
import React, { FC } from 'react';
|
||||
import { ImageGrid } from '~/components/editors/ImageGrid';
|
||||
import styles from './styles.module.scss';
|
||||
import { NodeEditorProps } from '~/redux/node/types';
|
||||
import { useFileUploaderContext } from '~/utils/hooks/useFileUploader';
|
||||
import { UploadDropzone } from '~/components/upload/UploadDropzone';
|
||||
|
||||
type IProps = NodeEditorProps;
|
||||
|
||||
const ImageEditor: FC<IProps> = () => {
|
||||
const { pending, files, setFiles } = useFileUploaderContext()!;
|
||||
const { pending, files, setFiles, uploadFiles } = useFileUploaderContext()!;
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid files={files} setFiles={setFiles} locked={pending} />
|
||||
</div>
|
||||
<UploadDropzone onUpload={uploadFiles} helperClassName={styles.dropzone}>
|
||||
<div className={styles.wrap}>
|
||||
<ImageGrid files={files} setFiles={setFiles} locked={pending} />
|
||||
</div>
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
@import "src/styles/variables";
|
||||
@import 'src/styles/variables';
|
||||
|
||||
.wrap {
|
||||
min-height: 200px;
|
||||
padding-bottom: $upload_button_height + $gap;
|
||||
}
|
||||
|
||||
div.dropzone {
|
||||
}
|
||||
|
|
18
src/components/input/DropHereIcon/index.tsx
Normal file
18
src/components/input/DropHereIcon/index.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React, { FC } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { SVGProps } from '~/utils/types';
|
||||
|
||||
interface Props extends SVGProps {}
|
||||
|
||||
const DropHereIcon: FC<Props> = ({ ...rest }) => (
|
||||
<svg viewBox="0 0 24 24" stroke="none" {...rest}>
|
||||
<path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z" />
|
||||
|
||||
<path
|
||||
d="M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"
|
||||
className={styles.arrow}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export { DropHereIcon };
|
8
src/components/input/DropHereIcon/styles.module.scss
Normal file
8
src/components/input/DropHereIcon/styles.module.scss
Normal file
|
@ -0,0 +1,8 @@
|
|||
@keyframes bounce {
|
||||
0% { transform: translate(0, -5%); }
|
||||
100% { transform: translate(0, 5%); }
|
||||
}
|
||||
|
||||
.arrow {
|
||||
animation: bounce alternate infinite 0.25s;
|
||||
}
|
48
src/components/upload/UploadDropzone/index.tsx
Normal file
48
src/components/upload/UploadDropzone/index.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import styles from './styles.module.scss';
|
||||
import { DivProps } from '~/utils/types';
|
||||
import { DropHereIcon } from '~/components/input/DropHereIcon';
|
||||
import { useDragDetector } from '~/utils/hooks/useDragDetector';
|
||||
|
||||
interface IProps extends DivProps {
|
||||
onUpload: (files: File[]) => void;
|
||||
helperClassName?: string;
|
||||
}
|
||||
|
||||
const UploadDropzone: FC<IProps> = ({ children, onUpload, helperClassName, ...rest }) => {
|
||||
const { isDragging: isDraggingOnBody, onStopDragging } = useDragDetector();
|
||||
const onDrop = useCallback(
|
||||
(files: File[]) => {
|
||||
onStopDragging();
|
||||
onUpload(files);
|
||||
},
|
||||
[onUpload]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropzone onDrop={onDrop}>
|
||||
{({ getRootProps, isDragActive }) => (
|
||||
<div
|
||||
{...getRootProps({
|
||||
...rest,
|
||||
className: classnames(styles.zone),
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
<div
|
||||
className={classNames(styles.helper, helperClassName, {
|
||||
[styles.active]: isDragActive || isDraggingOnBody,
|
||||
})}
|
||||
>
|
||||
<DropHereIcon className={styles.icon} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
);
|
||||
};
|
||||
|
||||
export { UploadDropzone };
|
34
src/components/upload/UploadDropzone/styles.module.scss
Normal file
34
src/components/upload/UploadDropzone/styles.module.scss
Normal file
|
@ -0,0 +1,34 @@
|
|||
@import '~/styles/variables';
|
||||
|
||||
.zone {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.helper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: transparentize($wisegreen, 0.7);
|
||||
border-radius: $radius;
|
||||
z-index: 10;
|
||||
box-shadow: inset $wisegreen 0 0 0 2px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
|
||||
&.active, :global(.dragging) & {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
svg.icon {
|
||||
width: auto;
|
||||
height: 72px;
|
||||
fill: $wisegreen;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue