From 770f3cb2aadfd50a3dee014f09652c7fcfadd8ac Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Wed, 16 Oct 2019 12:46:52 +0700 Subject: [PATCH] removing uploaded files --- .../editors/EditorUploadButton/index.tsx | 10 ++++- src/components/editors/ImageGrid/index.tsx | 35 ++++++++++++++--- src/components/upload/ImageUpload/index.tsx | 38 +++++++++++++------ src/components/upload/ImageUpload/styles.scss | 26 +++++++++++++ src/containers/dialogs/EditorDialog/index.tsx | 26 ++++++++++--- src/containers/dialogs/Modal/index.tsx | 8 ++-- .../editors/EditorDialogImage/index.tsx | 10 +++++ src/redux/modal/constants.ts | 4 +- src/redux/node/constants.ts | 7 ++++ src/redux/types.ts | 2 +- src/redux/uploads/reducer.ts | 2 +- 11 files changed, 137 insertions(+), 31 deletions(-) create mode 100644 src/containers/editors/EditorDialogImage/index.tsx diff --git a/src/components/editors/EditorUploadButton/index.tsx b/src/components/editors/EditorUploadButton/index.tsx index e29115d2..eb183634 100644 --- a/src/components/editors/EditorUploadButton/index.tsx +++ b/src/components/editors/EditorUploadButton/index.tsx @@ -9,6 +9,7 @@ import assocPath from 'ramda/es/assocPath'; import append from 'ramda/es/append'; import { selectUploads } from '~/redux/uploads/selectors'; import { connect } from 'react-redux'; +import { MAX_NODE_FILES } from '~/redux/node/constants'; const mapStateToProps = state => { const { statuses, files } = selectUploads(state); @@ -41,6 +42,11 @@ const EditorUploadButtonUnconnected: FC = ({ const onUpload = useCallback( (uploads: File[]) => { + const current = temp.length + data.files.length; + const limit = MAX_NODE_FILES - current; + + if (current >= MAX_NODE_FILES) return; + const items: IFileWithUUID[] = Array.from(uploads).map( (file: File): IFileWithUUID => ({ file, @@ -51,12 +57,12 @@ const EditorUploadButtonUnconnected: FC = ({ }) ); - const temps = items.map(file => file.temp_id); + const temps = items.map(file => file.temp_id).slice(0, limit); setTemp([...temp, ...temps]); uploadUploadFiles(items); }, - [setTemp, uploadUploadFiles, temp] + [setTemp, uploadUploadFiles, temp, data] ); const onFileAdd = useCallback( diff --git a/src/components/editors/ImageGrid/index.tsx b/src/components/editors/ImageGrid/index.tsx index beb7f2b9..2310acd3 100644 --- a/src/components/editors/ImageGrid/index.tsx +++ b/src/components/editors/ImageGrid/index.tsx @@ -7,6 +7,9 @@ import { IUploadStatus } from '~/redux/uploads/reducer'; import { getURL } from '~/utils/dom'; import assocPath from 'ramda/es/assocPath'; import { moveArrItem } from '~/utils/fn'; +import omit from 'ramda/es/omit'; +import remove from 'ramda/es/remove'; +import reject from 'ramda/es/reject'; interface IProps { data: INode; @@ -19,13 +22,23 @@ const SortableItem = SortableElement(({ children }) => ( )); const SortableList = SortableContainer( - ({ items, locked }: { items: IFile[]; locked: IUploadStatus[] }) => ( + ({ + items, + locked, + onDrop, + }: { + items: IFile[]; + locked: IUploadStatus[]; + onDrop: (file_id: IFile['id']) => void; + }) => (
- {items.map((file, index) => ( - - - - ))} + {items + .filter(file => file && file.id) + .map((file, index) => ( + + + + ))} {locked.map((item, index) => ( @@ -44,8 +57,18 @@ const ImageGrid: FC = ({ data, setData, locked }) => { [data, setData] ); + const onDrop = useCallback( + (file_id: IFile['id']) => { + setData( + assocPath(['files'], reject(el => !el || !el.id || el.id === file_id, data.files), data) + ); + }, + [setData, data] + ); + return ( void; is_uploading?: boolean; } -const ImageUpload: FC = ({ thumb, progress, is_uploading }) => ( -
-
- {thumb &&
} - {is_uploading && ( -
- +const ImageUpload: FC = ({ thumb, progress, is_uploading, id, onDrop }) => { + const onDropFile = useCallback(() => { + if (!id || !onDrop) return; + onDrop(id); + }, [id, onDrop]); + + return ( +
+ {id && onDrop && ( +
+
)} + +
+ {thumb &&
} + {is_uploading && ( +
+ +
+ )} +
-
-); + ); +}; export { ImageUpload }; diff --git a/src/components/upload/ImageUpload/styles.scss b/src/components/upload/ImageUpload/styles.scss index 52bd0d22..f8f6baf6 100644 --- a/src/components/upload/ImageUpload/styles.scss +++ b/src/components/upload/ImageUpload/styles.scss @@ -57,3 +57,29 @@ .helper { opacity: 0.3; } + +.drop { + width: 24px; + height: 24px; + background: #222222; + position: absolute; + right: $gap; + top: $gap; + border-radius: 12px; + z-index: 2; + transition: background-color 250ms, opacity 0.25s; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 20px; + height: 20px; + } + + &:hover { + opacity: 1; + background-color: $red; + } +} diff --git a/src/containers/dialogs/EditorDialog/index.tsx b/src/containers/dialogs/EditorDialog/index.tsx index c018aaaa..02d5a53e 100644 --- a/src/containers/dialogs/EditorDialog/index.tsx +++ b/src/containers/dialogs/EditorDialog/index.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState, useCallback, FormEvent, useEffect } from 'react'; +import React, { FC, useState, useCallback, FormEvent, useEffect, createElement } from 'react'; import { connect } from 'react-redux'; import { ScrollDialog } from '../ScrollDialog'; import { IDialogProps } from '~/redux/modal/constants'; @@ -9,11 +9,11 @@ import { Button } from '~/components/input/Button'; import { Padder } from '~/components/containers/Padder'; import * as styles from './styles.scss'; import { selectNode } from '~/redux/node/selectors'; -import { ImageEditor } from '~/components/editors/ImageEditor'; import { EditorPanel } from '~/components/editors/EditorPanel'; import * as NODE_ACTIONS from '~/redux/node/actions'; import { selectUploads } from '~/redux/uploads/selectors'; import { ERROR_LITERAL } from '~/constants/errors'; +import { NODE_EDITORS } from '~/redux/node/constants'; const mapStateToProps = state => { const { editor, errors } = selectNode(state); @@ -27,14 +27,19 @@ const mapDispatchToProps = { nodeSetSaveErrors: NODE_ACTIONS.nodeSetSaveErrors, }; -type IProps = IDialogProps & ReturnType & typeof mapDispatchToProps & {}; +type IProps = IDialogProps & + ReturnType & + typeof mapDispatchToProps & { + type: typeof NODE_EDITORS[keyof typeof NODE_EDITORS]; + }; const EditorDialogUnconnected: FC = ({ - onRequestClose, editor, errors, nodeSave, nodeSetSaveErrors, + onRequestClose, + type, }) => { const [data, setData] = useState(editor); const [temp, setTemp] = useState([]); @@ -54,6 +59,10 @@ const EditorDialogUnconnected: FC = ({ [data, nodeSave] ); + useEffect(() => { + if (!NODE_EDITORS[type] && onRequestClose) onRequestClose(); + }, [type]); + useEffect(() => { if (!Object.keys(errors).length) return; nodeSetSaveErrors({}); @@ -75,6 +84,8 @@ const EditorDialogUnconnected: FC = ({ const error = errors && Object.values(errors)[0]; + if (!NODE_EDITORS[type]) return null; + return (
= ({ onClose={onRequestClose} >
- + {createElement(NODE_EDITORS[type], { + data, + setData, + temp, + setTemp, + })}
diff --git a/src/containers/dialogs/Modal/index.tsx b/src/containers/dialogs/Modal/index.tsx index 13b1a2d6..d3c6362d 100644 --- a/src/containers/dialogs/Modal/index.tsx +++ b/src/containers/dialogs/Modal/index.tsx @@ -29,6 +29,8 @@ const ModalUnconnected: FC = ({ if (!dialog || !DIALOG_CONTENT[dialog] || !is_shown) return null; + console.log({ onRequestClose }); + return ReactDOM.createPortal(
@@ -38,18 +40,18 @@ const ModalUnconnected: FC = ({ {React.createElement(DIALOG_CONTENT[dialog], { onRequestClose, onDialogChange: modalShowDialog, - } as IDialogProps)} + })}
, - document.body, + document.body ); }; const Modal = connect( mapStateToProps, - mapDispatchToProps, + mapDispatchToProps )(ModalUnconnected); export { ModalUnconnected, Modal }; diff --git a/src/containers/editors/EditorDialogImage/index.tsx b/src/containers/editors/EditorDialogImage/index.tsx new file mode 100644 index 00000000..9d2b8f41 --- /dev/null +++ b/src/containers/editors/EditorDialogImage/index.tsx @@ -0,0 +1,10 @@ +import React, { FC } from 'react'; +import { EditorDialog } from '~/containers/dialogs/EditorDialog'; +import { IDialogProps } from '~/redux/types'; +import { NODE_TYPES } from '~/redux/node/constants'; + +type IProps = IDialogProps & {}; + +const EditorDialogImage: FC = props => ; + +export { EditorDialogImage }; diff --git a/src/redux/modal/constants.ts b/src/redux/modal/constants.ts index 08ee7acb..fd43d538 100644 --- a/src/redux/modal/constants.ts +++ b/src/redux/modal/constants.ts @@ -1,5 +1,5 @@ import { ValueOf } from '~/redux/types'; -import { EditorDialog } from '~/containers/dialogs/EditorDialog'; +import { EditorDialogImage } from '~/containers/editors/EditorDialogImage'; import { LoginDialog } from '~/containers/dialogs/LoginDialog'; export const MODAL_ACTIONS = { @@ -14,7 +14,7 @@ export const DIALOGS = { }; export const DIALOG_CONTENT = { - [DIALOGS.EDITOR_IMAGE]: EditorDialog, + [DIALOGS.EDITOR_IMAGE]: EditorDialogImage, [DIALOGS.LOGIN]: LoginDialog, }; diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts index f61dad63..22c1930e 100644 --- a/src/redux/node/constants.ts +++ b/src/redux/node/constants.ts @@ -1,6 +1,7 @@ import { FC } from 'react'; import { IBlock, INode, ValueOf, IComment } from '../types'; import { NodeImageBlock } from '~/components/node/NodeImageBlock'; +import { ImageEditor } from '~/components/editors/ImageEditor'; const prefix = 'NODE.'; export const NODE_ACTIONS = { @@ -74,3 +75,9 @@ export const EMPTY_COMMENT: IComment = { is_private: false, user: null, }; + +export const NODE_EDITORS = { + [NODE_TYPES.IMAGE]: ImageEditor, +}; + +export const MAX_NODE_FILES = 16; diff --git a/src/redux/types.ts b/src/redux/types.ts index a8523790..ff43f361 100644 --- a/src/redux/types.ts +++ b/src/redux/types.ts @@ -58,7 +58,7 @@ export type UUID = string; export type IUploadType = 'image' | 'text' | 'audio' | 'video' | 'other'; export interface IFile { - id?: UUID; + id?: number; temp_id?: UUID; user_id?: UUID; node_id?: UUID; diff --git a/src/redux/uploads/reducer.ts b/src/redux/uploads/reducer.ts index 43f3f205..98915b38 100644 --- a/src/redux/uploads/reducer.ts +++ b/src/redux/uploads/reducer.ts @@ -6,7 +6,7 @@ export interface IUploadStatus { is_uploading: boolean; error: string; preview: string; - uuid: UUID; + uuid: IFile['id']; url: string; type: string; thumbnail_url: string;