1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

audio editor

This commit is contained in:
Fedor Katurov 2019-10-21 17:51:52 +07:00
parent a9d4be064e
commit 645ea8e29e
15 changed files with 273 additions and 9 deletions

View file

@ -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<IProps> = ({ 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 <ImageGrid files={images} setFiles={setImages} locked={pending_images} />;
const setAudios = useCallback(files => setData({ ...data, files: [...files, ...images] }), [
setData,
data,
images,
]);
return (
<div className={styles.wrap}>
<ImageGrid files={images} setFiles={setImages} locked={pending_images} />
<AudioGrid files={audios} setFiles={setAudios} locked={pending_audios} />
</div>
);
};
const AudioEditor = connect(

View file

@ -0,0 +1,4 @@
.wrap {
padding-bottom: 64px;
min-height: 200px;
}

View file

@ -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<IProps> = ({ 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 (
<SortableAudioGrid
onDrop={onDrop}
onSortEnd={onMove}
axis="xy"
items={files}
locked={locked}
pressDelay={window.innerWidth < 768 ? 200 : 0}
helperClass={styles.helper}
/>
);
};
export { AudioGrid };

View file

@ -0,0 +1,4 @@
.helper {
opacity: 0.5;
z-index: 10 !important;
}

View file

@ -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;
}) => (
<div className={styles.grid}>
{items
.filter(file => file && file.id)
.map((file, index) => (
<SortableImageGridItem key={file.id} index={index} collection={0}>
<AudioPlayer file={file} onDrop={onDrop} />
</SortableImageGridItem>
))}
{locked.map((item, index) => (
<SortableImageGridItem key={item.temp_id} index={index} collection={1} disabled>
<AudioUpload title={item.name} progress={item.progress} is_uploading />
</SortableImageGridItem>
))}
</div>
)
);
export { SortableAudioGrid };

View file

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

View file

@ -0,0 +1,10 @@
import React from 'react';
import { SortableElement } from 'react-sortable-hoc';
import * as styles from './styles.scss';
const SortableAudioGridItem = SortableElement(({ children }) => (
<div className={styles.item}>{children}</div>
));
export { SortableAudioGridItem };

View file

@ -0,0 +1,4 @@
.item {
z-index: 1;
box-sizing: border-box;
}

View file

@ -1,6 +1,4 @@
.grid {
min-height: 200px;
padding-bottom: 62px;
box-sizing: border-box;
display: grid;

View file

@ -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<IProps> = ({ title, progress, is_uploading, id, onDrop }) => {
const onDropFile = useCallback(() => {
if (!id || !onDrop) return;
onDrop(id);
}, [id, onDrop]);
return (
<div className={styles.wrap}>
{id && onDrop && (
<div className={styles.drop} onMouseDown={onDropFile}>
<Icon icon="close" />
</div>
)}
<div className={classNames(styles.thumb_wrap, { is_uploading })}>
{is_uploading && (
<div className={styles.progress}>
<ArcProgress size={40} progress={progress} />
</div>
)}
{title && <div className={styles.title}>{title}</div>}
</div>
</div>
);
};
export { AudioUpload };

View file

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

View file

@ -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]: 'Нужна хотя бы одна песня',
};

View file

@ -38,6 +38,7 @@ export const EMPTY_UPLOAD_STATUS: IUploadStatus = {
thumbnail_url: null,
type: null,
temp_id: null,
name: null,
};
// for targeted cancellation

View file

@ -12,6 +12,7 @@ export interface IUploadStatus {
thumbnail_url: string;
progress: number;
temp_id: UUID;
name: string;
}
export interface IUploadState {

View file

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