From 645ea8e29e0632f89751e52bea2f79c70ebc9de6 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 21 Oct 2019 17:51:52 +0700 Subject: [PATCH] audio editor --- src/components/editors/AudioEditor/index.tsx | 34 +++++++-- .../editors/AudioEditor/styles.scss | 4 + src/components/editors/AudioGrid/index.tsx | 43 +++++++++++ src/components/editors/AudioGrid/styles.scss | 4 + .../editors/SortableAudioGrid/index.tsx | 38 ++++++++++ .../editors/SortableAudioGrid/styles.scss | 13 ++++ .../editors/SortableAudioGridItem/index.tsx | 10 +++ .../editors/SortableAudioGridItem/styles.scss | 4 + .../editors/SortableImageGrid/styles.scss | 2 - src/components/upload/AudioUpload/index.tsx | 43 +++++++++++ src/components/upload/AudioUpload/styles.scss | 75 +++++++++++++++++++ src/constants/errors.ts | 2 + src/redux/uploads/constants.ts | 1 + src/redux/uploads/reducer.ts | 1 + src/redux/uploads/sagas.ts | 8 +- 15 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 src/components/editors/AudioEditor/styles.scss create mode 100644 src/components/editors/AudioGrid/index.tsx create mode 100644 src/components/editors/AudioGrid/styles.scss create mode 100644 src/components/editors/SortableAudioGrid/index.tsx create mode 100644 src/components/editors/SortableAudioGrid/styles.scss create mode 100644 src/components/editors/SortableAudioGridItem/index.tsx create mode 100644 src/components/editors/SortableAudioGridItem/styles.scss create mode 100644 src/components/upload/AudioUpload/index.tsx create mode 100644 src/components/upload/AudioUpload/styles.scss diff --git a/src/components/editors/AudioEditor/index.tsx b/src/components/editors/AudioEditor/index.tsx index a22ea0d1..9826c74c 100644 --- a/src/components/editors/AudioEditor/index.tsx +++ b/src/components/editors/AudioEditor/index.tsx @@ -3,8 +3,10 @@ import { INode } from '~/redux/types'; import { connect } from 'react-redux'; import { UPLOAD_TYPES } from '~/redux/uploads/constants'; import { ImageGrid } from '../ImageGrid'; +import { AudioGrid } from '../AudioGrid'; import * as UPLOAD_ACTIONS from '~/redux/uploads/actions'; import { selectUploads } from '~/redux/uploads/selectors'; +import * as styles from './styles.scss'; const mapStateToProps = selectUploads; const mapDispatchToProps = { @@ -25,23 +27,45 @@ const AudioEditorUnconnected: FC = ({ data, setData, temp, statuses }) = [data.files] ); - const pending_images = useMemo(() => temp.filter(id => !!statuses[id]).map(id => statuses[id]), [ - temp, - statuses, - ]); + const pending_images = useMemo( + () => + temp + .filter(id => !!statuses[id] && statuses[id].type === UPLOAD_TYPES.IMAGE) + .map(id => statuses[id]), + [temp, statuses] + ); const audios = useMemo( () => data.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), [data.files] ); + const pending_audios = useMemo( + () => + temp + .filter(id => !!statuses[id] && statuses[id].type === UPLOAD_TYPES.AUDIO) + .map(id => statuses[id]), + [temp, statuses] + ); + const setImages = useCallback(files => setData({ ...data, files: [...files, ...audios] }), [ setData, data, audios, ]); - return ; + const setAudios = useCallback(files => setData({ ...data, files: [...files, ...images] }), [ + setData, + data, + images, + ]); + + return ( +
+ + +
+ ); }; const AudioEditor = connect( diff --git a/src/components/editors/AudioEditor/styles.scss b/src/components/editors/AudioEditor/styles.scss new file mode 100644 index 00000000..4909ae19 --- /dev/null +++ b/src/components/editors/AudioEditor/styles.scss @@ -0,0 +1,4 @@ +.wrap { + padding-bottom: 64px; + min-height: 200px; +} diff --git a/src/components/editors/AudioGrid/index.tsx b/src/components/editors/AudioGrid/index.tsx new file mode 100644 index 00000000..8dd8556d --- /dev/null +++ b/src/components/editors/AudioGrid/index.tsx @@ -0,0 +1,43 @@ +import React, { FC, useCallback } from 'react'; +import { SortEnd } from 'react-sortable-hoc'; +import * as styles from './styles.scss'; +import { IFile } from '~/redux/types'; +import { IUploadStatus } from '~/redux/uploads/reducer'; +import { moveArrItem } from '~/utils/fn'; +import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid'; + +interface IProps { + files: IFile[]; + setFiles: (val: IFile[]) => void; + locked: IUploadStatus[]; +} + +const AudioGrid: FC = ({ files, setFiles, locked }) => { + const onMove = useCallback( + ({ oldIndex, newIndex }: SortEnd) => { + setFiles(moveArrItem(oldIndex, newIndex, files.filter(file => !!file)) as IFile[]); + }, + [setFiles, files] + ); + + const onDrop = useCallback( + (remove_id: IFile['id']) => { + setFiles(files.filter(file => file && file.id !== remove_id)); + }, + [setFiles, files] + ); + + return ( + + ); +}; + +export { AudioGrid }; diff --git a/src/components/editors/AudioGrid/styles.scss b/src/components/editors/AudioGrid/styles.scss new file mode 100644 index 00000000..7ab40804 --- /dev/null +++ b/src/components/editors/AudioGrid/styles.scss @@ -0,0 +1,4 @@ +.helper { + opacity: 0.5; + z-index: 10 !important; +} diff --git a/src/components/editors/SortableAudioGrid/index.tsx b/src/components/editors/SortableAudioGrid/index.tsx new file mode 100644 index 00000000..5aa4c293 --- /dev/null +++ b/src/components/editors/SortableAudioGrid/index.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { SortableContainer } from 'react-sortable-hoc'; +import { AudioUpload } from '~/components/upload/AudioUpload'; +import * as styles from './styles.scss'; +import { SortableImageGridItem } from '~/components/editors/SortableImageGridItem'; +import { IFile } from '~/redux/types'; +import { IUploadStatus } from '~/redux/uploads/reducer'; +import { AudioPlayer } from '~/components/media/AudioPlayer'; + +const SortableAudioGrid = SortableContainer( + ({ + items, + locked, + onDrop, + }: { + items: IFile[]; + locked: IUploadStatus[]; + onDrop: (file_id: IFile['id']) => void; + }) => ( +
+ {items + .filter(file => file && file.id) + .map((file, index) => ( + + + + ))} + + {locked.map((item, index) => ( + + + + ))} +
+ ) +); + +export { SortableAudioGrid }; diff --git a/src/components/editors/SortableAudioGrid/styles.scss b/src/components/editors/SortableAudioGrid/styles.scss new file mode 100644 index 00000000..7dc895db --- /dev/null +++ b/src/components/editors/SortableAudioGrid/styles.scss @@ -0,0 +1,13 @@ +.grid { + box-sizing: border-box; + + display: grid; + grid-column-gap: $gap; + grid-row-gap: $gap; + grid-template-columns: auto; + grid-template-rows: $comment_height; + + @media (max-width: 600px) { + grid-template-columns: repeat(auto-fill, minmax(30vw, 1fr)); + } +} diff --git a/src/components/editors/SortableAudioGridItem/index.tsx b/src/components/editors/SortableAudioGridItem/index.tsx new file mode 100644 index 00000000..2b7ff3d0 --- /dev/null +++ b/src/components/editors/SortableAudioGridItem/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { SortableElement } from 'react-sortable-hoc'; + +import * as styles from './styles.scss'; + +const SortableAudioGridItem = SortableElement(({ children }) => ( +
{children}
+)); + +export { SortableAudioGridItem }; diff --git a/src/components/editors/SortableAudioGridItem/styles.scss b/src/components/editors/SortableAudioGridItem/styles.scss new file mode 100644 index 00000000..81fd01cb --- /dev/null +++ b/src/components/editors/SortableAudioGridItem/styles.scss @@ -0,0 +1,4 @@ +.item { + z-index: 1; + box-sizing: border-box; +} diff --git a/src/components/editors/SortableImageGrid/styles.scss b/src/components/editors/SortableImageGrid/styles.scss index c525238b..cbd34386 100644 --- a/src/components/editors/SortableImageGrid/styles.scss +++ b/src/components/editors/SortableImageGrid/styles.scss @@ -1,6 +1,4 @@ .grid { - min-height: 200px; - padding-bottom: 62px; box-sizing: border-box; display: grid; diff --git a/src/components/upload/AudioUpload/index.tsx b/src/components/upload/AudioUpload/index.tsx new file mode 100644 index 00000000..fd150593 --- /dev/null +++ b/src/components/upload/AudioUpload/index.tsx @@ -0,0 +1,43 @@ +import React, { FC, useCallback } from 'react'; +import classNames from 'classnames'; +import * as styles from './styles.scss'; +import { ArcProgress } from '~/components/input/ArcProgress'; +import { IFile } from '~/redux/types'; +import { Icon } from '~/components/input/Icon'; + +interface IProps { + id?: IFile['id']; + title?: string; + progress?: number; + onDrop?: (file_id: IFile['id']) => void; + + is_uploading?: boolean; +} + +const AudioUpload: FC = ({ title, progress, is_uploading, id, onDrop }) => { + const onDropFile = useCallback(() => { + if (!id || !onDrop) return; + onDrop(id); + }, [id, onDrop]); + + return ( +
+ {id && onDrop && ( +
+ +
+ )} + +
+ {is_uploading && ( +
+ +
+ )} + {title &&
{title}
} +
+
+ ); +}; + +export { AudioUpload }; diff --git a/src/components/upload/AudioUpload/styles.scss b/src/components/upload/AudioUpload/styles.scss new file mode 100644 index 00000000..17882875 --- /dev/null +++ b/src/components/upload/AudioUpload/styles.scss @@ -0,0 +1,75 @@ +.wrap { + background: lighten($content_bg, 4%); + // padding-bottom: 100%; + border-radius: $radius; + position: relative; + user-select: none; + height: $comment_height; +} + +.thumb_wrap { + // position: absolute; + // width: 100%; + // height: 100%; + z-index: 1; + border-radius: $radius; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; + height: 100%; +} + +.title { + flex: 1; + border-radius: $radius; + display: flex; + align-items: center; + padding: 10px; + box-sizing: border-box; +} + +.progress { + flex: 0 0 $comment_height; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 40px; + height: 40px; + fill: none; + fill: white; + } +} + +.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/constants/errors.ts b/src/constants/errors.ts index 70a8ee47..92aea57b 100644 --- a/src/constants/errors.ts +++ b/src/constants/errors.ts @@ -7,6 +7,7 @@ export const ERRORS = { TEXT_REQUIRED: 'Text_Required', UNKNOWN_NODE_TYPE: 'Unknown_Node_Type', URL_INVALID: 'Url_Invalid', + FILES_AUDIO_REQUIRED: 'Files_Audio_Required', }; export const ERROR_LITERAL = { @@ -18,4 +19,5 @@ export const ERROR_LITERAL = { [ERRORS.TEXT_REQUIRED]: 'Нужно немного текста', [ERRORS.UNKNOWN_NODE_TYPE]: 'Неизвестный тип поста', [ERRORS.URL_INVALID]: 'Неизвестный адрес', + [ERRORS.FILES_AUDIO_REQUIRED]: 'Нужна хотя бы одна песня', }; diff --git a/src/redux/uploads/constants.ts b/src/redux/uploads/constants.ts index 74a1528b..21a03c8a 100644 --- a/src/redux/uploads/constants.ts +++ b/src/redux/uploads/constants.ts @@ -38,6 +38,7 @@ export const EMPTY_UPLOAD_STATUS: IUploadStatus = { thumbnail_url: null, type: null, temp_id: null, + name: null, }; // for targeted cancellation diff --git a/src/redux/uploads/reducer.ts b/src/redux/uploads/reducer.ts index 98915b38..38a5d310 100644 --- a/src/redux/uploads/reducer.ts +++ b/src/redux/uploads/reducer.ts @@ -12,6 +12,7 @@ export interface IUploadStatus { thumbnail_url: string; progress: number; temp_id: UUID; + name: string; } export interface IUploadState { diff --git a/src/redux/uploads/sagas.ts b/src/redux/uploads/sagas.ts index 357852e8..94868079 100644 --- a/src/redux/uploads/sagas.ts +++ b/src/redux/uploads/sagas.ts @@ -78,8 +78,10 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) { { preview, is_uploading: true, - type: file.type, + // type: file.type, temp_id, + type, + name: file.name, } ) ); @@ -106,7 +108,7 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) { if (error) { return yield put( - uploadSetStatus(temp_id, { is_uploading: false, error: data.detail || error }) + uploadSetStatus(temp_id, { is_uploading: false, error: data.detail || error, type }) ); } @@ -116,8 +118,10 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) { error: null, uuid: data.id, url: data.full_path, + type, thumbnail_url: data.full_path, progress: 1, + name: file.name, }) );