From fff3770b1e7f8e1750ce7257e6132c8e77e915d3 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 16 Mar 2020 16:52:16 +0700 Subject: [PATCH] Audio title editing --- src/components/editors/AudioGrid/index.tsx | 12 + .../editors/SortableAudioGrid/index.tsx | 4 +- src/components/media/AudioPlayer/index.tsx | 53 +- src/components/media/AudioPlayer/styles.scss | 6 + src/components/node/CommentForm/index.tsx | 455 +++++++++--------- src/redux/types.ts | 1 + src/redux/uploads/constants.ts | 15 +- 7 files changed, 304 insertions(+), 242 deletions(-) diff --git a/src/components/editors/AudioGrid/index.tsx b/src/components/editors/AudioGrid/index.tsx index c3ca845e..40593006 100644 --- a/src/components/editors/AudioGrid/index.tsx +++ b/src/components/editors/AudioGrid/index.tsx @@ -28,9 +28,21 @@ const AudioGrid: FC = ({ files, setFiles, locked }) => { [setFiles, files] ); + const onTitleChange = useCallback( + (changeId: IFile['id'], title: IFile['metadata']['title']) => { + setFiles( + files.map(file => + file && file.id === changeId ? { ...file, metadata: { ...file.metadata, title } } : file + ) + ); + }, + [setFiles, files] + ); + return ( void; + onTitleChange: (file_id: IFile['id'], title: IFile['metadata']['title']) => void; }) => (
{items .filter(file => file && file.id) .map((file, index) => ( - + ))} diff --git a/src/components/media/AudioPlayer/index.tsx b/src/components/media/AudioPlayer/index.tsx index 9ea11463..00b13c5b 100644 --- a/src/components/media/AudioPlayer/index.tsx +++ b/src/components/media/AudioPlayer/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState, useEffect, memo } from 'react'; +import React, { useCallback, useState, useEffect, memo, useMemo } from 'react'; import { connect } from 'react-redux'; import { selectPlayer } from '~/redux/player/selectors'; import * as PLAYER_ACTIONS from '~/redux/player/actions'; @@ -8,6 +8,7 @@ import { Player, IPlayerProgress } from '~/utils/player'; import classNames from 'classnames'; import * as styles from './styles.scss'; import { Icon } from '~/components/input/Icon'; +import { InputText } from '~/components/input/InputText'; const mapStateToProps = state => ({ player: selectPlayer(state), @@ -23,15 +24,17 @@ const mapDispatchToProps = { type Props = ReturnType & typeof mapDispatchToProps & { file: IFile; - nonInteractive?: boolean; + isEditing?: boolean; onDrop?: (id: IFile['id']) => void; + onTitleChange?: (file_id: IFile['id'], title: IFile['metadata']['title']) => void; }; const AudioPlayerUnconnected = memo( ({ file, onDrop, - nonInteractive, + isEditing, + onTitleChange, player: { file: current, status }, playerSetFileAndPlay, playerPlay, @@ -46,7 +49,7 @@ const AudioPlayerUnconnected = memo( }); const onPlay = useCallback(() => { - if (nonInteractive) return; + if (isEditing) return; if (current && current.id === file.id) { if (status === PLAYER_STATES.PLAYING) return playerPause(); @@ -54,7 +57,7 @@ const AudioPlayerUnconnected = memo( } playerSetFileAndPlay(file); - }, [file, current, status, playerPlay, playerPause, playerSetFileAndPlay, nonInteractive]); + }, [file, current, status, playerPlay, playerPause, playerSetFileAndPlay, isEditing]); const onProgress = useCallback( ({ detail }: { detail: IPlayerProgress }) => { @@ -80,6 +83,21 @@ const AudioPlayerUnconnected = memo( onDrop(file.id); }, [file, onDrop]); + const title = useMemo( + () => + (file.metadata && + (file.metadata.title || + [file.metadata.id3artist, file.metadata.id3title].filter(el => el).join(' - '))) || + file.orig_name || + '', + [file.metadata] + ); + + const onRename = useCallback((val: string) => onTitleChange(file.id, val), [ + onTitleChange, + file.id, + ]); + useEffect(() => { const active = current && current.id === file.id; setPlaying(current && current.id === file.id); @@ -91,11 +109,6 @@ const AudioPlayerUnconnected = memo( }; }, [file, current, setPlaying, onProgress]); - const title = - file.metadata && - (file.metadata.title || - [file.metadata.id3artist, file.metadata.id3title].filter(el => !!el).join(' - ')); - return (
{onDrop && ( @@ -112,13 +125,21 @@ const AudioPlayerUnconnected = memo( )}
-
-
{title || 'Unknown'}
- -
-
+ {isEditing ? ( +
+
-
+ ) : ( +
+
+
{title || 'Unknown'}
+
+ +
+
+
+
+ )}
); } diff --git a/src/components/media/AudioPlayer/styles.scss b/src/components/media/AudioPlayer/styles.scss index dae743ff..ee5d160a 100644 --- a/src/components/media/AudioPlayer/styles.scss +++ b/src/components/media/AudioPlayer/styles.scss @@ -130,3 +130,9 @@ height: 20px; } } + +.input { + flex: 1; + box-sizing: border-box; + padding: 0 48px 0 0; +} diff --git a/src/components/node/CommentForm/index.tsx b/src/components/node/CommentForm/index.tsx index 75c4ce86..3a355a7c 100644 --- a/src/components/node/CommentForm/index.tsx +++ b/src/components/node/CommentForm/index.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, KeyboardEventHandler, useEffect, useMemo } from 'react'; +import React, { FC, useCallback, KeyboardEventHandler, useEffect, useMemo, memo } from 'react'; import { Textarea } from '~/components/input/Textarea'; import * as styles from './styles.scss'; import { Filler } from '~/components/containers/Filler'; @@ -41,244 +41,263 @@ type IProps = ReturnType & is_before?: boolean; }; -const CommentFormUnconnected: FC = ({ - node: { comment_data, is_sending_comment }, - uploads: { statuses, files }, - id, - is_before = false, - nodePostComment, - nodeSetCommentData, - uploadUploadFiles, - nodeCancelCommentEdit, -}) => { - const onInputChange = useCallback( - event => { - event.preventDefault(); +const CommentFormUnconnected: FC = memo( + ({ + node: { comment_data, is_sending_comment }, + uploads: { statuses, files }, + id, + is_before = false, + nodePostComment, + nodeSetCommentData, + uploadUploadFiles, + nodeCancelCommentEdit, + }) => { + const onInputChange = useCallback( + event => { + event.preventDefault(); - if (!event.target.files || !event.target.files.length) return; + if (!event.target.files || !event.target.files.length) return; - const items: IFileWithUUID[] = Array.from(event.target.files).map( - (file: File): IFileWithUUID => ({ - file, - temp_id: uuid(), - subject: UPLOAD_SUBJECTS.COMMENT, - target: UPLOAD_TARGETS.COMMENTS, - type: getFileType(file), - }) - ); + const items: IFileWithUUID[] = Array.from(event.target.files).map( + (file: File): IFileWithUUID => ({ + file, + temp_id: uuid(), + subject: UPLOAD_SUBJECTS.COMMENT, + target: UPLOAD_TARGETS.COMMENTS, + type: getFileType(file), + }) + ); - const temps = items.map(file => file.temp_id); + const temps = items.map(file => file.temp_id); - nodeSetCommentData( - id, - assocPath(['temp_ids'], [...comment_data[id].temp_ids, ...temps], comment_data[id]) - ); - uploadUploadFiles(items); - }, - [uploadUploadFiles, comment_data, id, nodeSetCommentData] - ); - - const onInput = useCallback( - text => { - nodeSetCommentData(id, assocPath(['text'], text, comment_data[id])); - }, - [nodeSetCommentData, comment_data, id] - ); - - useEffect(() => { - const temp_ids = (comment_data && comment_data[id] && comment_data[id].temp_ids) || []; - const added_files = temp_ids - .map(temp_uuid => statuses[temp_uuid] && statuses[temp_uuid].uuid) - .map(el => !!el && files[el]) - .filter(el => !!el && !comment_data[id].files.some(file => file && file.id === el.id)); - - const filtered_temps = temp_ids.filter( - temp_id => - statuses[temp_id] && - (!statuses[temp_id].uuid || !added_files.some(file => file.id === statuses[temp_id].uuid)) + nodeSetCommentData( + id, + assocPath(['temp_ids'], [...comment_data[id].temp_ids, ...temps], comment_data[id]) + ); + uploadUploadFiles(items); + }, + [uploadUploadFiles, comment_data, id, nodeSetCommentData] ); - if (added_files.length) { - nodeSetCommentData(id, { - ...comment_data[id], - temp_ids: filtered_temps, - files: [...comment_data[id].files, ...added_files], - }); - } - }, [statuses, files]); + const onInput = useCallback( + text => { + nodeSetCommentData(id, assocPath(['text'], text, comment_data[id])); + }, + [nodeSetCommentData, comment_data, id] + ); - const comment = comment_data[id]; + useEffect(() => { + const temp_ids = (comment_data && comment_data[id] && comment_data[id].temp_ids) || []; + const added_files = temp_ids + .map(temp_uuid => statuses[temp_uuid] && statuses[temp_uuid].uuid) + .map(el => !!el && files[el]) + .filter(el => !!el && !comment_data[id].files.some(file => file && file.id === el.id)); - const is_uploading_files = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]); - - const onSubmit = useCallback( - event => { - if (event) event.preventDefault(); - if (is_uploading_files || is_sending_comment) return; - - nodePostComment(id, is_before); - }, - [nodePostComment, id, is_before, is_uploading_files, is_sending_comment] - ); - - const onKeyDown = useCallback>( - ({ ctrlKey, key }) => { - if (!!ctrlKey && key === 'Enter') onSubmit(null); - }, - [onSubmit] - ); - - const images = useMemo( - () => comment.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), - [comment.files] - ); - - const locked_images = useMemo( - () => - comment.temp_ids - .filter(temp => statuses[temp] && statuses[temp].type === UPLOAD_TYPES.IMAGE) - .map(temp_id => statuses[temp_id]), - [statuses, comment.temp_ids] - ); - - const audios = useMemo( - () => comment.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), - [comment.files] - ); - - const locked_audios = useMemo( - () => - comment.temp_ids - .filter(temp => statuses[temp] && statuses[temp].type === UPLOAD_TYPES.AUDIO) - .map(temp_id => statuses[temp_id]), - [statuses, comment.temp_ids] - ); - - const onFileDrop = useCallback( - (file_id: IFile['id']) => { - nodeSetCommentData( - id, - assocPath(['files'], comment.files.filter(file => file.id != file_id), comment_data[id]) + const filtered_temps = temp_ids.filter( + temp_id => + statuses[temp_id] && + (!statuses[temp_id].uuid || !added_files.some(file => file.id === statuses[temp_id].uuid)) ); - }, - [comment_data, id, nodeSetCommentData] - ); - const onImageMove = useCallback( - ({ oldIndex, newIndex }: SortEnd) => { - nodeSetCommentData( - id, - assocPath( - ['files'], - [ - ...audios, - ...(moveArrItem(oldIndex, newIndex, images.filter(file => !!file)) as IFile[]), - ], - comment_data[id] - ) - ); - }, - [images, audios] - ); + if (added_files.length) { + nodeSetCommentData(id, { + ...comment_data[id], + temp_ids: filtered_temps, + files: [...comment_data[id].files, ...added_files], + }); + } + }, [statuses, files]); - const onAudioMove = useCallback( - ({ oldIndex, newIndex }: SortEnd) => { - nodeSetCommentData( - id, - assocPath( - ['files'], - [ - ...images, - ...(moveArrItem(oldIndex, newIndex, audios.filter(file => !!file)) as IFile[]), - ], - comment_data[id] - ) - ); - }, - [images, audios] - ); + const comment = comment_data[id]; - const onCancelEdit = useCallback(() => { - nodeCancelCommentEdit(id); - }, [nodeCancelCommentEdit, comment.id]); + const is_uploading_files = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]); - const placeholder = getRandomPhrase('SIMPLE'); + const onSubmit = useCallback( + event => { + if (event) event.preventDefault(); + if (is_uploading_files || is_sending_comment) return; - return ( -
-
-