From 0ff27de81100d74be0db2e3e1e6001f0a2e62b9c Mon Sep 17 00:00:00 2001 From: Stefano Brilli Date: Sat, 23 Jan 2021 15:10:43 +0100 Subject: [PATCH] Add and reorder tracks in convert dialog --- src/components/convert-dialog.tsx | 252 ++++++++++++++++++++++++++---- src/redux/actions.ts | 12 +- 2 files changed, 227 insertions(+), 37 deletions(-) diff --git a/src/components/convert-dialog.tsx b/src/components/convert-dialog.tsx index 144e3be..5a1688c 100644 --- a/src/components/convert-dialog.tsx +++ b/src/components/convert-dialog.tsx @@ -1,6 +1,6 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { useShallowEqualSelector } from '../utils'; +import { belowDesktop, useShallowEqualSelector } from '../utils'; import { actions as convertDialogActions } from '../redux/convert-dialog-feature'; import { convertAndUpload } from '../redux/actions'; @@ -20,6 +20,22 @@ import { Typography } from '@material-ui/core'; import Select from '@material-ui/core/Select'; import Input from '@material-ui/core/Input'; import MenuItem from '@material-ui/core/MenuItem'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ExpandLessIcon from '@material-ui/icons/ExpandLess'; +import AddIcon from '@material-ui/icons/Add'; +import RemoveIcon from '@material-ui/icons/Remove'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import IconButton from '@material-ui/core/IconButton'; +import Toolbar from '@material-ui/core/Toolbar'; +import { lighten } from '@material-ui/core/styles'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import Radio from '@material-ui/core/Radio'; +import { useDropzone } from 'react-dropzone'; +import Backdrop from '@material-ui/core/Backdrop'; const Transition = React.forwardRef(function Transition( props: TransitionProps & { children?: React.ReactElement }, @@ -40,6 +56,11 @@ const useStyles = makeStyles(theme => ({ minWidth: 40, }, dialogContent: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'stretch', + }, + formatAndTitle: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', @@ -51,6 +72,43 @@ const useStyles = makeStyles(theme => ({ titleFormControl: { minWidth: 170, marginTop: 4, + [belowDesktop(theme)]: { + width: 114, + minWidth: 0, + }, + }, + spacer: { + display: 'flex', + flex: '1 1 auto', + }, + showTracksOrderBtn: { + marginLeft: theme.spacing(1), + }, + tracksOrderAccordion: { + '&:before': { + opacity: 0, + }, + }, + tracksOrderAccordionDetail: { + maxHeight: '40vh', + overflow: 'auto', + }, + toolbarHighlight: + theme.palette.type === 'light' + ? { + color: theme.palette.secondary.main, + backgroundColor: lighten(theme.palette.secondary.light, 0.85), + } + : { + color: theme.palette.text.primary, + backgroundColor: theme.palette.secondary.dark, + }, + trackList: { + flex: '1 1 auto', + }, + backdrop: { + zIndex: theme.zIndex.drawer + 1, + color: '#fff', }, })); @@ -60,6 +118,38 @@ export const ConvertDialog = (props: { files: File[] }) => { let { visible, format, titleFormat } = useShallowEqualSelector(state => state.convertDialog); + // Track reodering + const [files, setFiles] = useState(props.files); + const [selectedTrackIndex, setSelectedTrack] = useState(-1); + + const moveFile = useCallback( + (offset: number) => { + const targetIndex = selectedTrackIndex + offset; + if (targetIndex >= files.length || targetIndex < 0) { + return; // This should not be allowed by the UI + } + + const newFileArray = files.slice(); + + // Swap trakcs + let tmp = newFileArray[selectedTrackIndex]; + newFileArray[selectedTrackIndex] = newFileArray[targetIndex]; + newFileArray[targetIndex] = tmp; + + setFiles(newFileArray); + setSelectedTrack(targetIndex); + }, + [files, selectedTrackIndex] + ); + + const moveFileUp = useCallback(() => { + moveFile(-1); + }, [moveFile]); + + const moveFileDown = useCallback(() => { + moveFile(1); + }, [moveFile]); + const handleClose = useCallback(() => { dispatch(convertDialogActions.setVisible(false)); }, [dispatch]); @@ -83,8 +173,71 @@ export const ConvertDialog = (props: { files: File[] }) => { const handleConvert = useCallback(() => { handleClose(); - dispatch(convertAndUpload(props.files, format, titleFormat)); - }, [dispatch, props, format, titleFormat, handleClose]); + dispatch(convertAndUpload(files, format, titleFormat)); + }, [dispatch, files, format, titleFormat, handleClose]); + + const [tracksOrderVisible, setTracksOrderVisible] = useState(false); + const handleToggleTracksOrder = useCallback(() => { + setTracksOrderVisible(!tracksOrderVisible); + }, [tracksOrderVisible, setTracksOrderVisible]); + + // Dialog init on new files + useEffect(() => { + const newFiles = Array.from(props.files); + setFiles(newFiles); + setSelectedTrack(-1); + setTracksOrderVisible(false); + }, [props.files, setSelectedTrack, setTracksOrderVisible]); + + // scroll selected track into view + const selectedTrackRef = useRef(null); + useEffect(() => { + selectedTrackRef.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + }, [selectedTrackRef, selectedTrackIndex]); + + const renderTracks = useCallback(() => { + return files.map((file, i) => { + const isSelected = selectedTrackIndex === i; + const ref = isSelected ? selectedTrackRef : null; + return ( + setSelectedTrack(i)} ref={ref} button> + + + + + + ); + }); + }, [files, selectedTrackIndex, setSelectedTrack, selectedTrackRef]); + + // Add/Remove tracks + const onDrop = useCallback( + (acceptedFiles: File[], rejectedFiles: File[]) => { + const newFileArray = files.slice().concat(acceptedFiles); + setFiles(newFileArray); + }, + [files, setFiles] + ); + const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ + onDrop, + accept: [`audio/*`, `video/mp4`], + noClick: true, + }); + const disableRemove = selectedTrackIndex < 0 || selectedTrackIndex >= files.length; + const handleRemoveSelectedTrack = useCallback(() => { + const newFileArray = files.filter((f, i) => i !== selectedTrackIndex); + setFiles(newFileArray); + if (selectedTrackIndex >= newFileArray.length) { + setSelectedTrack(newFileArray.length - 1); + } + }, [selectedTrackIndex, files, setFiles]); + + const dialogVisible = useShallowEqualSelector(state => state.convertDialog.visible); + useEffect(() => { + if (dialogVisible && files.length === 0) { + handleClose(); + } + }, [files, dialogVisible]); return ( { > Upload Settings - - - Recording Mode - - - - SP - - - LP2 - - - LP4 - - - -
- +
+ - Track title + Recording Mode - - - + + + SP + + + LP2 + + + LP4 + + +
+ + + Track title + + + + + +
+ +
+
+ + + + + + + +
+ + + + + + +
+ + + {renderTracks()} + + + + Drop your Music to add it to the queue + + +
+
+ +
diff --git a/src/redux/actions.ts b/src/redux/actions.ts index c2246c9..0d8753b 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -262,7 +262,13 @@ async function getTrackNameFromMediaTags(file: File, titleFormat: TitleFormatTyp return `${artist} - ${album} - ${title}`; } case 'filename': { - return file.name; + let title = file.name; + // Remove file extension + const extStartIndex = title.lastIndexOf('.'); + if (extStartIndex > 0) { + title = title.substring(0, extStartIndex); + } + return title; } } } @@ -365,10 +371,6 @@ export function convertAndUpload(files: File[], format: UploadFormat, titleForma console.error(err); } - const extStartIndex = title.lastIndexOf('.'); - if (extStartIndex > 0) { - title = title.substring(0, extStartIndex); - } if (maxTitleLength > -1) { title = title.substring(0, maxTitleLength); }