Merge pull request #53 from cybercase/fix/various_fixes_and_improvements
Fix/various fixes and improvements
This commit is contained in:
commit
3453528afe
|
@ -121,9 +121,11 @@ const App = () => {
|
|||
</Typography>
|
||||
</main>
|
||||
|
||||
{loading ? (
|
||||
<Backdrop className={classes.backdrop} open={loading}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
) : null}
|
||||
</ThemeProvider>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { EncodingName } from '../utils';
|
||||
|
||||
import { formatTimeFromFrames, Track, Group } from 'netmd-js';
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import TableCell from '@material-ui/core/TableCell';
|
||||
import TableRow from '@material-ui/core/TableRow';
|
||||
import * as BadgeImpl from '@material-ui/core/Badge/Badge';
|
||||
|
||||
import DragIndicator from '@material-ui/icons/DragIndicator';
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
||||
import PauseIcon from '@material-ui/icons/Pause';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import FolderIcon from '@material-ui/icons/Folder';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
|
||||
import { DraggableProvided } from 'react-beautiful-dnd';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
currentTrackRow: {
|
||||
color: theme.palette.primary.main,
|
||||
'& > td': {
|
||||
color: 'inherit',
|
||||
},
|
||||
},
|
||||
inGroupTrackRow: {
|
||||
'& > $indexCell': {
|
||||
transform: `translateX(${theme.spacing(3)}px)`,
|
||||
},
|
||||
'& > $titleCell': {
|
||||
transform: `translateX(${theme.spacing(3)}px)`,
|
||||
},
|
||||
},
|
||||
playButtonInTrackList: {
|
||||
display: 'none',
|
||||
},
|
||||
trackRow: {
|
||||
'&:hover': {
|
||||
'& $playButtonInTrackList': {
|
||||
display: 'inline-flex',
|
||||
},
|
||||
'& $trackIndex': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
controlButtonInTrackCommon: {
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
verticalAlign: 'middle',
|
||||
marginLeft: theme.spacing(-0.5),
|
||||
},
|
||||
formatBadge: {
|
||||
...(BadgeImpl as any).styles(theme).badge,
|
||||
...(BadgeImpl as any).styles(theme).colorPrimary,
|
||||
position: 'static',
|
||||
display: 'inline-flex',
|
||||
border: `2px solid ${theme.palette.background.paper}`,
|
||||
padding: '0 4px',
|
||||
verticalAlign: 'middle',
|
||||
width: theme.spacing(4.5),
|
||||
marginRight: theme.spacing(0.5),
|
||||
},
|
||||
durationCell: {
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
durationCellSecondary: {
|
||||
whiteSpace: 'nowrap',
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
durationCellTime: {
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
titleCell: {
|
||||
overflow: 'hidden',
|
||||
maxWidth: '40ch',
|
||||
textOverflow: 'ellipsis',
|
||||
// whiteSpace: 'nowrap',
|
||||
},
|
||||
deleteGroupButton: {
|
||||
display: 'none',
|
||||
},
|
||||
indexCell: {
|
||||
whiteSpace: 'nowrap',
|
||||
paddingRight: 0,
|
||||
width: theme.spacing(4),
|
||||
},
|
||||
trackIndex: {
|
||||
display: 'inline-block',
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
},
|
||||
dragHandle: {
|
||||
width: 20,
|
||||
padding: `${theme.spacing(0.5)}px 0 0 0`,
|
||||
},
|
||||
dragHandleEmpty: {
|
||||
width: 20,
|
||||
padding: `${theme.spacing(0.5)}px 0 0 0`,
|
||||
},
|
||||
groupFolderIcon: {},
|
||||
groupHeadRow: {
|
||||
'&:hover': {
|
||||
'& $deleteGroupButton': {
|
||||
display: 'inline-flex',
|
||||
},
|
||||
'& $groupFolderIcon': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
interface TrackRowProps {
|
||||
track: Track;
|
||||
inGroup: boolean;
|
||||
isSelected: boolean;
|
||||
trackStatus: 'playing' | 'paused' | 'none';
|
||||
draggableProvided: DraggableProvided;
|
||||
onSelect: (event: React.MouseEvent, trackIdx: number) => void;
|
||||
onRename: (event: React.MouseEvent, trackIdx: number) => void;
|
||||
onTogglePlayPause: (event: React.MouseEvent, trackIdx: number) => void;
|
||||
}
|
||||
|
||||
export function TrackRow({
|
||||
track,
|
||||
inGroup,
|
||||
isSelected,
|
||||
draggableProvided,
|
||||
trackStatus,
|
||||
onSelect,
|
||||
onRename,
|
||||
onTogglePlayPause,
|
||||
}: TrackRowProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const handleRename = useCallback(event => onRename(event, track.index), [track.index, onRename]);
|
||||
const handleSelect = useCallback(event => onSelect(event, track.index), [track.index, onSelect]);
|
||||
const handlePlayPause: React.MouseEventHandler = useCallback(
|
||||
event => {
|
||||
event.stopPropagation();
|
||||
onTogglePlayPause(event, track.index);
|
||||
},
|
||||
[track.index, onTogglePlayPause]
|
||||
);
|
||||
const handleDoubleClickOnPlayButton: React.MouseEventHandler = useCallback(event => event.stopPropagation(), []);
|
||||
const isPlayingOrPaused = trackStatus === 'playing' || trackStatus === 'paused';
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
{...draggableProvided.draggableProps}
|
||||
ref={draggableProvided.innerRef}
|
||||
hover
|
||||
selected={isSelected}
|
||||
onDoubleClick={handleRename}
|
||||
onClick={handleSelect}
|
||||
color="inherit"
|
||||
className={clsx(classes.trackRow, { [classes.inGroupTrackRow]: inGroup, [classes.currentTrackRow]: isPlayingOrPaused })}
|
||||
>
|
||||
<TableCell className={classes.dragHandle} {...draggableProvided.dragHandleProps} onClick={event => event.stopPropagation()}>
|
||||
<DragIndicator fontSize="small" color="disabled" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.indexCell}>
|
||||
<span className={classes.trackIndex}>{track.index + 1}</span>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
className={clsx(classes.controlButtonInTrackCommon, classes.playButtonInTrackList)}
|
||||
size="small"
|
||||
onClick={handlePlayPause}
|
||||
onDoubleClick={handleDoubleClickOnPlayButton}
|
||||
>
|
||||
{trackStatus === 'paused' || trackStatus === 'none' ? (
|
||||
<PlayArrowIcon fontSize="inherit" />
|
||||
) : (
|
||||
<PauseIcon fontSize="inherit" />
|
||||
)}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell className={classes.titleCell} title={track.title ?? ''}>
|
||||
{track.fullWidthTitle ? `${track.fullWidthTitle} / ` : ``}
|
||||
{track.title || `No Title`}
|
||||
</TableCell>
|
||||
<TableCell align="right" className={classes.durationCell}>
|
||||
<span className={classes.formatBadge}>{EncodingName[track.encoding]}</span>
|
||||
<span className={classes.durationCellTime}>{formatTimeFromFrames(track.duration, false)}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
interface GroupRowProps {
|
||||
group: Group;
|
||||
onRename: (event: React.MouseEvent, groupIdx: number) => void;
|
||||
onDelete: (event: React.MouseEvent, groupIdx: number) => void;
|
||||
}
|
||||
|
||||
export function GroupRow({ group, onRename, onDelete }: GroupRowProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const handleDelete = useCallback((event: React.MouseEvent) => onDelete(event, group.index), [onDelete, group]);
|
||||
const handleRename = useCallback((event: React.MouseEvent) => onRename(event, group.index), [onRename, group]);
|
||||
return (
|
||||
<TableRow hover className={classes.groupHeadRow} onDoubleClick={handleRename}>
|
||||
<TableCell className={classes.dragHandleEmpty}></TableCell>
|
||||
<TableCell className={classes.indexCell}>
|
||||
<FolderIcon className={clsx(classes.controlButtonInTrackCommon, classes.groupFolderIcon)} />
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
className={clsx(classes.controlButtonInTrackCommon, classes.deleteGroupButton)}
|
||||
size="small"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<DeleteIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell className={classes.titleCell} title={group.title!}>
|
||||
{group.fullWidthTitle ? `${group.fullWidthTitle} / ` : ``}
|
||||
{group.title || `No Name`}
|
||||
</TableCell>
|
||||
<TableCell align="right" className={classes.durationCellSecondary}>
|
||||
<span className={classes.durationCellTime}>
|
||||
{formatTimeFromFrames(
|
||||
group.tracks.map(n => n.duration).reduce((a, b) => a + b),
|
||||
false
|
||||
)}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
|
@ -17,18 +17,10 @@ import { actions as renameDialogActions } from '../redux/rename-dialog-feature';
|
|||
import { actions as convertDialogActions } from '../redux/convert-dialog-feature';
|
||||
import { actions as dumpDialogActions } from '../redux/dump-dialog-feature';
|
||||
|
||||
import { formatTimeFromFrames, Track } from 'netmd-js';
|
||||
import { DeviceStatus, formatTimeFromFrames, Track } from 'netmd-js';
|
||||
import { control } from '../redux/actions';
|
||||
|
||||
import {
|
||||
belowDesktop,
|
||||
forAnyDesktop,
|
||||
getGroupedTracks,
|
||||
getSortedTracks,
|
||||
isSequential,
|
||||
useShallowEqualSelector,
|
||||
EncodingName,
|
||||
} from '../utils';
|
||||
import { belowDesktop, forAnyDesktop, getGroupedTracks, getSortedTracks, isSequential, useShallowEqualSelector } from '../utils';
|
||||
|
||||
import { lighten, makeStyles } from '@material-ui/core/styles';
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
|
@ -39,9 +31,6 @@ import AddIcon from '@material-ui/icons/Add';
|
|||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import Backdrop from '@material-ui/core/Backdrop';
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
||||
import PauseIcon from '@material-ui/icons/Pause';
|
||||
import FolderIcon from '@material-ui/icons/Folder';
|
||||
import CreateNewFolderIcon from '@material-ui/icons/CreateNewFolder';
|
||||
|
||||
import Table from '@material-ui/core/Table';
|
||||
|
@ -55,6 +44,7 @@ import Toolbar from '@material-ui/core/Toolbar';
|
|||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
|
||||
import { GroupRow, TrackRow } from './main-rows';
|
||||
import { RenameDialog } from './rename-dialog';
|
||||
import { UploadDialog } from './upload-dialog';
|
||||
import { RecordDialog } from './record-dialog';
|
||||
|
@ -65,7 +55,6 @@ import { AboutDialog } from './about-dialog';
|
|||
import { DumpDialog } from './dump-dialog';
|
||||
import { TopMenu } from './topmenu';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import * as BadgeImpl from '@material-ui/core/Badge/Badge';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { W95Main } from './win95/main';
|
||||
import { useMemo } from 'react';
|
||||
|
@ -120,33 +109,10 @@ const useStyles = makeStyles(theme => ({
|
|||
spacing: {
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
formatBadge: {
|
||||
...(BadgeImpl as any).styles(theme).badge,
|
||||
...(BadgeImpl as any).styles(theme).colorPrimary,
|
||||
position: 'static',
|
||||
display: 'inline-flex',
|
||||
border: `2px solid ${theme.palette.background.paper}`,
|
||||
padding: '0 4px',
|
||||
verticalAlign: 'middle',
|
||||
width: theme.spacing(4.5),
|
||||
marginRight: theme.spacing(0.5),
|
||||
},
|
||||
titleCell: {
|
||||
overflow: 'hidden',
|
||||
maxWidth: '40ch',
|
||||
textOverflow: 'ellipsis',
|
||||
// whiteSpace: 'nowrap',
|
||||
},
|
||||
durationCell: {
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
durationCellTime: {
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
indexCell: {
|
||||
whiteSpace: 'nowrap',
|
||||
paddingRight: 0,
|
||||
width: `16px`,
|
||||
width: theme.spacing(4),
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
|
@ -156,91 +122,41 @@ const useStyles = makeStyles(theme => ({
|
|||
textDecoration: 'underline',
|
||||
textDecorationStyle: 'dotted',
|
||||
},
|
||||
controlButtonInTrackCommon: {
|
||||
width: `16px`,
|
||||
height: `16px`,
|
||||
verticalAlign: 'middle',
|
||||
cursor: 'pointer',
|
||||
marginLeft: theme.spacing(-0.5),
|
||||
},
|
||||
playButtonInTrackListPlaying: {
|
||||
color: theme.palette.primary.main,
|
||||
display: 'none',
|
||||
},
|
||||
pauseButtonInTrackListPlaying: {
|
||||
color: theme.palette.primary.main,
|
||||
display: 'none',
|
||||
},
|
||||
currentControlButton: {
|
||||
display: 'inline-block',
|
||||
},
|
||||
playButtonInTrackListNotPlaying: {
|
||||
width: `0px`,
|
||||
},
|
||||
trackRow: {
|
||||
userSelect: 'none',
|
||||
'&:hover': {
|
||||
/* For the tracks that aren't currently playing */
|
||||
'& $playButtonInTrackListNotPlaying': {
|
||||
width: `16px`,
|
||||
},
|
||||
'& $trackIndex': {
|
||||
width: `0px`,
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
/* For the current track */
|
||||
'& svg:not($currentControlButton)': {
|
||||
display: 'inline-block',
|
||||
},
|
||||
'& $currentControlButton': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
inGroupTrackRow: {
|
||||
'& > $indexCell': {
|
||||
transform: `translateX(${theme.spacing(3)}px)`,
|
||||
},
|
||||
'& > $titleCell': {
|
||||
transform: `translateX(${theme.spacing(3)}px)`,
|
||||
},
|
||||
},
|
||||
deleteGroupButton: {
|
||||
display: 'none',
|
||||
},
|
||||
groupHeadRow: {
|
||||
'&:hover': {
|
||||
'& svg:not($deleteGroupButton)': {
|
||||
display: 'none',
|
||||
},
|
||||
'& $deleteGroupButton': {
|
||||
display: 'inline-block',
|
||||
},
|
||||
},
|
||||
},
|
||||
hoveringOverGroup: {
|
||||
backgroundColor: `${fade(theme.palette.secondary.dark, 0.4)}`,
|
||||
},
|
||||
trackIndex: {
|
||||
display: 'inline-block',
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
dragHandleEmpty: {
|
||||
width: 20,
|
||||
padding: `${theme.spacing(0.5)}px 0 0 0`,
|
||||
},
|
||||
}));
|
||||
|
||||
function getTrackStatus(track: Track, deviceStatus: DeviceStatus | null): 'playing' | 'paused' | 'none' {
|
||||
if (!deviceStatus || track.index !== deviceStatus.track) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
if (deviceStatus.state === 'playing') {
|
||||
return 'playing';
|
||||
} else if (deviceStatus.state === 'paused') {
|
||||
return 'paused';
|
||||
} else {
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
|
||||
export const Main = (props: {}) => {
|
||||
let dispatch = useDispatch();
|
||||
let disc = useShallowEqualSelector(state => state.main.disc);
|
||||
let deviceName = useShallowEqualSelector(state => state.main.deviceName);
|
||||
const disc = useShallowEqualSelector(state => state.main.disc);
|
||||
const deviceName = useShallowEqualSelector(state => state.main.deviceName);
|
||||
const deviceStatus = useShallowEqualSelector(state => state.main.deviceStatus);
|
||||
const { vintageMode } = useShallowEqualSelector(state => state.appState);
|
||||
|
||||
const [selected, setSelected] = React.useState<number[]>([]);
|
||||
const [uploadedFiles, setUploadedFiles] = React.useState<File[]>([]);
|
||||
const [lastClicked, setLastClicked] = useState(-1);
|
||||
const selectedCount = selected.length;
|
||||
|
||||
const [moveMenuAnchorEl, setMoveMenuAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
|
||||
const handleShowMoveMenu = useCallback(
|
||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setMoveMenuAnchorEl(event.currentTarget);
|
||||
|
@ -250,6 +166,7 @@ export const Main = (props: {}) => {
|
|||
const handleCloseMoveMenu = useCallback(() => {
|
||||
setMoveMenuAnchorEl(null);
|
||||
}, [setMoveMenuAnchorEl]);
|
||||
|
||||
const handleMoveSelectedTrack = useCallback(
|
||||
(destIndex: number) => {
|
||||
dispatch(moveTrack(selected[0], destIndex));
|
||||
|
@ -282,7 +199,6 @@ export const Main = (props: {}) => {
|
|||
setSelected([]); // Reset selection if disc changes
|
||||
}, [disc]);
|
||||
|
||||
let [uploadedFiles, setUploadedFiles] = React.useState<File[]>([]);
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[], rejectedFiles: File[]) => {
|
||||
setUploadedFiles(acceptedFiles);
|
||||
|
@ -290,6 +206,7 @@ export const Main = (props: {}) => {
|
|||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
|
||||
onDrop,
|
||||
accept: [`audio/*`, `video/mp4`],
|
||||
|
@ -301,7 +218,8 @@ export const Main = (props: {}) => {
|
|||
const groupedTracks = useMemo(() => getGroupedTracks(disc), [disc]);
|
||||
|
||||
// Action Handlers
|
||||
const handleSelectClick = (event: React.MouseEvent, item: number) => {
|
||||
const handleSelectTrackClick = useCallback(
|
||||
(event: React.MouseEvent, item: number) => {
|
||||
if (event.shiftKey && selected.length && lastClicked !== -1) {
|
||||
let rangeBegin = Math.min(lastClicked + 1, item),
|
||||
rangeEnd = Math.max(lastClicked - 1, item);
|
||||
|
@ -319,70 +237,106 @@ export const Main = (props: {}) => {
|
|||
setSelected([...selected, item]);
|
||||
}
|
||||
setLastClicked(item);
|
||||
};
|
||||
},
|
||||
[selected, setSelected, lastClicked, setLastClicked]
|
||||
);
|
||||
|
||||
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleSelectAllClick = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (selected.length < tracks.length) {
|
||||
setSelected(tracks.map(t => t.index));
|
||||
} else {
|
||||
setSelected([]);
|
||||
}
|
||||
};
|
||||
},
|
||||
[selected, tracks]
|
||||
);
|
||||
|
||||
const handleRenameDoubleClick = (event: React.MouseEvent, index: number, renameGroup?: boolean) => {
|
||||
let group, track;
|
||||
if (renameGroup) {
|
||||
group = groupedTracks.find(n => n.tracks[0]?.index === index);
|
||||
if (!group) return;
|
||||
} else {
|
||||
track = tracks.find(n => n.index === index);
|
||||
if (!track) return;
|
||||
const handleRenameTrack = useCallback(
|
||||
(event: React.MouseEvent, index: number) => {
|
||||
let track = tracks.find(t => t.index === index);
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentName = (track ? track.title : group?.title) ?? '';
|
||||
let currentFullWidthName = (track ? track.fullWidthTitle : group?.fullWidthTitle) ?? '';
|
||||
dispatch(
|
||||
batchActions([
|
||||
renameDialogActions.setVisible(true),
|
||||
renameDialogActions.setGroupIndex(group ? index : null),
|
||||
renameDialogActions.setCurrentName(currentName),
|
||||
renameDialogActions.setCurrentFullWidthName(currentFullWidthName),
|
||||
renameDialogActions.setIndex(track?.index ?? -1),
|
||||
renameDialogActions.setGroupIndex(null),
|
||||
renameDialogActions.setCurrentName(track.title),
|
||||
renameDialogActions.setCurrentFullWidthName(track.fullWidthTitle),
|
||||
renameDialogActions.setIndex(track.index),
|
||||
])
|
||||
);
|
||||
};
|
||||
},
|
||||
[dispatch, tracks]
|
||||
);
|
||||
|
||||
const handleRenameActionClick = (event: React.MouseEvent) => {
|
||||
if(event.detail !== 1) return; //Event retriggering when hitting enter in the dialog
|
||||
handleRenameDoubleClick(event, selected[0]);
|
||||
};
|
||||
|
||||
const handleDeleteSelected = (event: React.MouseEvent) => {
|
||||
dispatch(deleteTracks(selected));
|
||||
};
|
||||
|
||||
const handleGroupTracks = (event: React.MouseEvent) => {
|
||||
dispatch(groupTracks(selected));
|
||||
};
|
||||
|
||||
const handleGroupRemoval = (event: React.MouseEvent, groupBegin: number) => {
|
||||
dispatch(deleteGroup(groupBegin));
|
||||
};
|
||||
|
||||
const handlePlayTrack = async (event: React.MouseEvent, track: number) => {
|
||||
if (deviceStatus?.track !== track) {
|
||||
dispatch(control('goto', track));
|
||||
const handleRenameGroup = useCallback(
|
||||
(event: React.MouseEvent, index: number) => {
|
||||
let group = groupedTracks.find(g => g.index === index);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
if (deviceStatus?.state !== 'playing') {
|
||||
|
||||
dispatch(
|
||||
batchActions([
|
||||
renameDialogActions.setVisible(true),
|
||||
renameDialogActions.setGroupIndex(index),
|
||||
renameDialogActions.setCurrentName(group.title ?? ''),
|
||||
renameDialogActions.setCurrentFullWidthName(group.fullWidthTitle ?? ''),
|
||||
renameDialogActions.setIndex(-1),
|
||||
])
|
||||
);
|
||||
},
|
||||
[dispatch, groupedTracks]
|
||||
);
|
||||
|
||||
const handleRenameActionClick = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
if (event.detail !== 1) return; //Event retriggering when hitting enter in the dialog
|
||||
handleRenameTrack(event, selected[0]);
|
||||
},
|
||||
[handleRenameTrack, selected]
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
dispatch(deleteTracks(selected));
|
||||
},
|
||||
[dispatch, selected]
|
||||
);
|
||||
|
||||
const handleGroupTracks = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
dispatch(groupTracks(selected));
|
||||
},
|
||||
[dispatch, selected]
|
||||
);
|
||||
|
||||
const handleDeleteGroup = useCallback(
|
||||
(event: React.MouseEvent, index: number) => {
|
||||
dispatch(deleteGroup(index));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleTogglePlayPauseTrack = useCallback(
|
||||
(event: React.MouseEvent, track: number) => {
|
||||
if (!deviceStatus) {
|
||||
return;
|
||||
}
|
||||
if (deviceStatus.track !== track) {
|
||||
dispatch(control('goto', track));
|
||||
if (deviceStatus.state !== 'playing') {
|
||||
dispatch(control('play'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCurrentClick = async (event: React.MouseEvent) => {
|
||||
if (deviceStatus?.state === 'playing') {
|
||||
} else if (deviceStatus.state === 'playing') {
|
||||
dispatch(control('pause'));
|
||||
} else dispatch(control('play'));
|
||||
};
|
||||
}
|
||||
},
|
||||
[dispatch, deviceStatus]
|
||||
);
|
||||
|
||||
const canGroup = useMemo(() => {
|
||||
return (
|
||||
|
@ -390,6 +344,7 @@ export const Main = (props: {}) => {
|
|||
isSequential(selected.sort((a, b) => a - b))
|
||||
);
|
||||
}, [tracks, selected]);
|
||||
const selectedCount = selected.length;
|
||||
|
||||
if (vintageMode) {
|
||||
const p = {
|
||||
|
@ -419,71 +374,13 @@ export const Main = (props: {}) => {
|
|||
handleShowDumpDialog,
|
||||
handleDeleteSelected,
|
||||
handleRenameActionClick,
|
||||
handleRenameDoubleClick,
|
||||
handleRenameTrack,
|
||||
handleSelectAllClick,
|
||||
handleSelectClick,
|
||||
handleSelectTrackClick,
|
||||
};
|
||||
return <W95Main {...p} />;
|
||||
}
|
||||
|
||||
const getTrackRow = (track: Track, inGroup: boolean, additional: {}) => {
|
||||
const child = (
|
||||
<TableRow
|
||||
{...additional}
|
||||
hover
|
||||
selected={selected.includes(track.index)}
|
||||
onDoubleClick={event => handleRenameDoubleClick(event, track.index)}
|
||||
onClick={event => handleSelectClick(event, track.index)}
|
||||
className={clsx(classes.trackRow, { [classes.inGroupTrackRow]: inGroup })}
|
||||
>
|
||||
<TableCell className={classes.indexCell}>
|
||||
{track.index === deviceStatus?.track && ['playing', 'paused'].includes(deviceStatus?.state) ? (
|
||||
<span>
|
||||
<PlayArrowIcon
|
||||
className={clsx(classes.controlButtonInTrackCommon, classes.playButtonInTrackListPlaying, {
|
||||
[classes.currentControlButton]: deviceStatus?.state === 'playing',
|
||||
})}
|
||||
onClick={event => {
|
||||
handleCurrentClick(event);
|
||||
event.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
<PauseIcon
|
||||
className={clsx(classes.controlButtonInTrackCommon, classes.pauseButtonInTrackListPlaying, {
|
||||
[classes.currentControlButton]: deviceStatus?.state === 'paused',
|
||||
})}
|
||||
onClick={event => {
|
||||
handleCurrentClick(event);
|
||||
event.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<span className={classes.trackIndex}>{track.index + 1}</span>
|
||||
<PlayArrowIcon
|
||||
className={clsx(classes.controlButtonInTrackCommon, classes.playButtonInTrackListNotPlaying)}
|
||||
onClick={event => {
|
||||
handlePlayTrack(event, track.index);
|
||||
event.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.titleCell} title={track.title ?? ''}>
|
||||
{track.fullWidthTitle ? `${track.fullWidthTitle} / ` : ``}
|
||||
{track.title || `No Title`}
|
||||
</TableCell>
|
||||
<TableCell align="right" className={classes.durationCell}>
|
||||
<span className={classes.formatBadge}>{EncodingName[track.encoding]}</span>
|
||||
<span className={classes.durationCellTime}>{formatTimeFromFrames(track.duration, false)}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return child;
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box className={classes.headBox}>
|
||||
|
@ -575,6 +472,7 @@ export const Main = (props: {}) => {
|
|||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.dragHandleEmpty}></TableCell>
|
||||
<TableCell className={classes.indexCell}>#</TableCell>
|
||||
<TableCell>Title</TableCell>
|
||||
<TableCell align="right">Duration</TableCell>
|
||||
|
@ -584,7 +482,7 @@ export const Main = (props: {}) => {
|
|||
<TableBody>
|
||||
{groupedTracks.map((group, index) => (
|
||||
<TableRow key={`${index}`}>
|
||||
<TableCell colSpan={3} style={{ padding: '0' }}>
|
||||
<TableCell colSpan={4} style={{ padding: '0' }}>
|
||||
<Table size="small">
|
||||
<Droppable droppableId={`${index}`} key={`${index}`}>
|
||||
{(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => (
|
||||
|
@ -594,56 +492,33 @@ export const Main = (props: {}) => {
|
|||
className={clsx({ [classes.hoveringOverGroup]: snapshot.isDraggingOver })}
|
||||
>
|
||||
{group.title !== null && (
|
||||
<TableRow
|
||||
hover
|
||||
className={classes.groupHeadRow}
|
||||
key={`${index}-head`}
|
||||
onDoubleClick={event =>
|
||||
handleRenameDoubleClick(event, group.tracks[0].index, true)
|
||||
}
|
||||
>
|
||||
<TableCell className={classes.indexCell}>
|
||||
<FolderIcon className={classes.controlButtonInTrackCommon} />
|
||||
<DeleteIcon
|
||||
className={clsx(
|
||||
classes.controlButtonInTrackCommon,
|
||||
classes.deleteGroupButton
|
||||
)}
|
||||
onClick={event => {
|
||||
handleGroupRemoval(event, group.tracks[0].index);
|
||||
}}
|
||||
<GroupRow
|
||||
group={group}
|
||||
onRename={handleRenameGroup}
|
||||
onDelete={handleDeleteGroup}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.titleCell} title={group.title!}>
|
||||
{group.fullWidthTitle ? `${group.fullWidthTitle} / ` : ``}
|
||||
{group.title || `No Name`}
|
||||
</TableCell>
|
||||
<TableCell align="right" className={classes.durationCell}>
|
||||
<span className={classes.durationCellTime}>
|
||||
{formatTimeFromFrames(
|
||||
group.tracks.map(n => n.duration).reduce((a, b) => a + b),
|
||||
false
|
||||
)}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{group.title === null && group.tracks.length === 0 && (
|
||||
<TableRow style={{ height: '1px' }} />
|
||||
)}
|
||||
{group.tracks.map(n => (
|
||||
{group.tracks.map((t, tidx) => (
|
||||
<Draggable
|
||||
draggableId={`${group.index}-${n.index}`}
|
||||
key={`${n.index - group.tracks[0].index}`}
|
||||
index={n.index - group.tracks[0].index}
|
||||
draggableId={`${group.index}-${t.index}`}
|
||||
key={`t-${t.index}`}
|
||||
index={tidx}
|
||||
>
|
||||
{(providedInGroup: DraggableProvided) =>
|
||||
getTrackRow(n, group.title !== null, {
|
||||
ref: providedInGroup.innerRef,
|
||||
...providedInGroup.draggableProps,
|
||||
...providedInGroup.dragHandleProps,
|
||||
})
|
||||
}
|
||||
{(provided: DraggableProvided) => (
|
||||
<TrackRow
|
||||
track={t}
|
||||
draggableProvided={provided}
|
||||
inGroup={group.title !== null}
|
||||
isSelected={selected.includes(t.index)}
|
||||
trackStatus={getTrackStatus(t, deviceStatus)}
|
||||
onSelect={handleSelectTrackClick}
|
||||
onRename={handleRenameTrack}
|
||||
onTogglePlayPause={handleTogglePlayPauseTrack}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
|
@ -657,9 +532,11 @@ export const Main = (props: {}) => {
|
|||
</TableBody>
|
||||
</DragDropContext>
|
||||
</Table>
|
||||
{isDragActive ? (
|
||||
<Backdrop className={classes.backdrop} open={isDragActive}>
|
||||
Drop your Music to Upload
|
||||
</Backdrop>
|
||||
) : null}
|
||||
</Box>
|
||||
<Fab color="primary" aria-label="add" className={classes.add} onClick={open}>
|
||||
<AddIcon />
|
||||
|
|
|
@ -42,11 +42,12 @@ export const RenameDialog = (props: {}) => {
|
|||
|
||||
const what = renameDialogGroupIndex !== null ? `Group` : renameDialogIndex < 0 ? `Disc` : `Track`;
|
||||
|
||||
const handleCancelRename = () => {
|
||||
const handleCancelRename = useCallback(() => {
|
||||
dispatch(renameDialogActions.setVisible(false));
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleDoRename = () => {
|
||||
const handleDoRename = useCallback(() => {
|
||||
console.log(renameDialogGroupIndex, renameDialogIndex);
|
||||
if (renameDialogGroupIndex !== null) {
|
||||
// Just rename the group with this range
|
||||
dispatch(
|
||||
|
@ -73,7 +74,7 @@ export const RenameDialog = (props: {}) => {
|
|||
);
|
||||
}
|
||||
handleCancelRename(); // Close the dialog
|
||||
};
|
||||
}, [dispatch, handleCancelRename, renameDialogFullWidthTitle, renameDialogGroupIndex, renameDialogIndex, renameDialogTitle]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||
|
@ -89,6 +90,17 @@ export const RenameDialog = (props: {}) => {
|
|||
[dispatch]
|
||||
);
|
||||
|
||||
const handleEnterKeyEvent = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === `Enter`) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
handleDoRename();
|
||||
}
|
||||
},
|
||||
[handleDoRename]
|
||||
);
|
||||
|
||||
const { vintageMode } = useShallowEqualSelector(state => state.appState);
|
||||
if (vintageMode) {
|
||||
const p = {
|
||||
|
@ -121,9 +133,7 @@ export const RenameDialog = (props: {}) => {
|
|||
type="text"
|
||||
fullWidth
|
||||
value={renameDialogTitle}
|
||||
onKeyDown={event => {
|
||||
event.key === `Enter` && handleDoRename();
|
||||
}}
|
||||
onKeyDown={handleEnterKeyEvent}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{allowFullWidth && (
|
||||
|
@ -134,9 +144,7 @@ export const RenameDialog = (props: {}) => {
|
|||
fullWidth
|
||||
className={classes.marginUpDown}
|
||||
value={renameDialogFullWidthTitle}
|
||||
onKeyDown={event => {
|
||||
event.key === `Enter` && handleDoRename();
|
||||
}}
|
||||
onKeyDown={handleEnterKeyEvent}
|
||||
onChange={handleFullWidthChange}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -170,7 +170,7 @@ export const TopMenu = function(props: { onClick?: () => void }) {
|
|||
);
|
||||
}
|
||||
if (mainView === 'MAIN') {
|
||||
menuItems.push(<Divider />);
|
||||
menuItems.push(<Divider key="action-divider" />);
|
||||
menuItems.push(
|
||||
<MenuItem key="allowFullWidth" onClick={handleAllowFullWidth}>
|
||||
<ListItemIcon className={classes.listItemIcon}>
|
||||
|
@ -208,7 +208,7 @@ export const TopMenu = function(props: { onClick?: () => void }) {
|
|||
);
|
||||
}
|
||||
if (mainView === 'MAIN') {
|
||||
menuItems.push(<Divider />);
|
||||
menuItems.push(<Divider key="feature-divider" />);
|
||||
}
|
||||
menuItems.push(
|
||||
<MenuItem key="about" onClick={handleShowAbout}>
|
||||
|
|
|
@ -106,9 +106,9 @@ export const W95Main = (props: {
|
|||
handleShowDumpDialog: () => void;
|
||||
handleDeleteSelected: (event: React.MouseEvent) => void;
|
||||
handleRenameActionClick: (event: React.MouseEvent) => void;
|
||||
handleRenameDoubleClick: (event: React.MouseEvent, item: number) => void;
|
||||
handleRenameTrack: (event: React.MouseEvent, item: number) => void;
|
||||
handleSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
handleSelectClick: (event: React.MouseEvent, item: number) => void;
|
||||
handleSelectTrackClick: (event: React.MouseEvent, item: number) => void;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const themeContext = useContext(ThemeContext);
|
||||
|
@ -199,12 +199,15 @@ export const W95Main = (props: {
|
|||
<CustomTableRow
|
||||
style={props.selected.includes(track.index) ? themeContext.selectedTableRow : {}}
|
||||
key={track.index}
|
||||
onDoubleClick={(event: React.MouseEvent) => props.handleRenameDoubleClick(event, track.index)}
|
||||
onClick={(event: React.MouseEvent) => props.handleSelectClick(event, track.index)}
|
||||
onDoubleClick={(event: React.MouseEvent) => props.handleRenameTrack(event, track.index)}
|
||||
onClick={(event: React.MouseEvent) => props.handleSelectTrackClick(event, track.index)}
|
||||
>
|
||||
<TableDataCell style={{ textAlign: 'center', width: '2ch' }}>{track.index + 1}</TableDataCell>
|
||||
<TableDataCell style={{ width: '80%' }}>
|
||||
<div>{track.fullWidthTitle && `${track.fullWidthTitle} / `}{track.title || `No Title`}</div>
|
||||
<div>
|
||||
{track.fullWidthTitle && `${track.fullWidthTitle} / `}
|
||||
{track.title || `No Title`}
|
||||
</div>
|
||||
</TableDataCell>
|
||||
<TableDataCell style={{ textAlign: 'right', width: '20%' }}>
|
||||
<span>{track.encoding}</span>
|
||||
|
|
|
@ -76,10 +76,10 @@ export function groupTracks(indexes: number[]) {
|
|||
};
|
||||
}
|
||||
|
||||
export function deleteGroup(groupBegin: number) {
|
||||
export function deleteGroup(index: number) {
|
||||
return async function(dispatch: AppDispatch) {
|
||||
const { netmdService } = serviceRegistry;
|
||||
netmdService!.deleteGroup(groupBegin);
|
||||
netmdService!.deleteGroup(index);
|
||||
listContent()(dispatch);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -55,21 +55,26 @@ class NetMDMockService implements NetMDService {
|
|||
channel: Channels.stereo,
|
||||
protected: TrackFlag.unprotected,
|
||||
title: 'Mock Track 5',
|
||||
fullWidthTitle: '',
|
||||
fullWidthTitle: 'スコット と リバース',
|
||||
},
|
||||
];
|
||||
public _groups: Group[] = [
|
||||
public _groupsDef: {
|
||||
index: number;
|
||||
title: string | null;
|
||||
fullWidthTitle: string | null;
|
||||
tracksIdx: number[];
|
||||
}[] = [
|
||||
{
|
||||
title: null,
|
||||
index: 0,
|
||||
tracks: this._tracks.slice(2),
|
||||
fullWidthTitle: '',
|
||||
index: 0,
|
||||
tracksIdx: [2, 3, 4],
|
||||
},
|
||||
{
|
||||
title: 'Test',
|
||||
fullWidthTitle: '',
|
||||
index: 0,
|
||||
tracks: [this._tracks[0], this._tracks[1]],
|
||||
index: 1,
|
||||
tracksIdx: [0, 1],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -80,6 +85,15 @@ class NetMDMockService implements NetMDService {
|
|||
state: 'ready',
|
||||
};
|
||||
|
||||
public _getGroups(): Group[] {
|
||||
return this._groupsDef.map(g => ({
|
||||
title: g.title,
|
||||
index: g.index,
|
||||
tracks: this._tracks.filter(t => g.tracksIdx.includes(t.index)),
|
||||
fullWidthTitle: g.fullWidthTitle,
|
||||
}));
|
||||
}
|
||||
|
||||
_updateTrackIndexes() {
|
||||
for (let i = 0; i < this._tracks.length; i++) {
|
||||
this._tracks[i].index = i;
|
||||
|
@ -108,7 +122,7 @@ class NetMDMockService implements NetMDService {
|
|||
used: this._getUsed(),
|
||||
total: this._discCapacity,
|
||||
trackCount: this._tracks.length,
|
||||
groups: this._groups,
|
||||
groups: this._getGroups(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -126,55 +140,72 @@ class NetMDMockService implements NetMDService {
|
|||
return JSON.parse(JSON.stringify(this._getDisc()));
|
||||
}
|
||||
|
||||
async renameGroup(groupBegin: number, newName: string, newFullWidth?: string) {
|
||||
let group = this._groups.slice(1).find(n => n.index === groupBegin);
|
||||
if (!group) return;
|
||||
async renameGroup(gropuIndex: number, newName: string, newFullWidth?: string) {
|
||||
let group = this._groupsDef.find(n => n.index === gropuIndex);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
group.title = newName;
|
||||
if (newFullWidth !== undefined) group.fullWidthTitle = newFullWidth;
|
||||
if (newFullWidth !== undefined) {
|
||||
group.fullWidthTitle = newFullWidth;
|
||||
}
|
||||
}
|
||||
|
||||
async addGroup(groupBegin: number, groupLength: number, newName: string) {
|
||||
let ungrouped = this._groups.find(n => n.title === null);
|
||||
if (!ungrouped) return; // You can only group tracks that aren't already in a different group, if there's no such tracks, there's no point to continue
|
||||
let ungroupedLengthBeforeGroup = ungrouped.tracks.length;
|
||||
|
||||
let thisGroupTracks = ungrouped.tracks.filter(n => n.index >= groupBegin && n.index < groupBegin + groupLength);
|
||||
ungrouped.tracks = ungrouped.tracks.filter(n => !thisGroupTracks.includes(n));
|
||||
|
||||
if (ungroupedLengthBeforeGroup - ungrouped.tracks.length !== groupLength) {
|
||||
throw new Error('A track cannot be in 2 groups!');
|
||||
let ungroupedDefs = this._groupsDef.find(g => g.title === null);
|
||||
if (!ungroupedDefs) {
|
||||
return; // You can only group tracks that aren't already in a different group, if there's no such tracks, there's no point to continue
|
||||
}
|
||||
let ungroupedLengthBeforeGroup = ungroupedDefs.tracksIdx.length;
|
||||
|
||||
if (!isSequential(thisGroupTracks.map(n => n.index))) {
|
||||
const newGroupTracks = ungroupedDefs.tracksIdx.filter(idx => idx >= groupBegin && idx < groupBegin + groupLength);
|
||||
if (!isSequential(newGroupTracks)) {
|
||||
throw new Error('Invalid sequence of tracks!');
|
||||
}
|
||||
this._groups.push({
|
||||
|
||||
const newGroupDef = {
|
||||
title: newName,
|
||||
fullWidthTitle: '',
|
||||
index: groupBegin,
|
||||
tracks: thisGroupTracks,
|
||||
});
|
||||
tracksIdx: newGroupTracks,
|
||||
};
|
||||
this._groupsDef.push(newGroupDef);
|
||||
|
||||
this._groupsDef = this._groupsDef.filter(g => g.tracksIdx.length !== 0).sort((a, b) => a.tracksIdx[0] - b.tracksIdx[0]);
|
||||
|
||||
ungroupedDefs.tracksIdx = ungroupedDefs.tracksIdx.filter(idx => !newGroupTracks.includes(idx));
|
||||
if (ungroupedLengthBeforeGroup - ungroupedDefs.tracksIdx.length !== groupLength) {
|
||||
throw new Error('A track cannot be in 2 groups!');
|
||||
}
|
||||
}
|
||||
|
||||
async deleteGroup(groupBegin: number) {
|
||||
const thisGroup = this._groups.slice(1).find(n => n.tracks[0].index === groupBegin);
|
||||
if (!thisGroup) return;
|
||||
let ungroupedGroup = this._groups.find(n => n.title === null);
|
||||
async deleteGroup(index: number) {
|
||||
const groups = this._getGroups();
|
||||
const group = groups.find(g => g.index === index);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
let ungroupedGroup = this._groupsDef.find(n => n.title === null);
|
||||
if (!ungroupedGroup) {
|
||||
ungroupedGroup = {
|
||||
title: null,
|
||||
fullWidthTitle: null,
|
||||
tracks: [],
|
||||
tracksIdx: [],
|
||||
index: 0,
|
||||
};
|
||||
this._groups.unshift(ungroupedGroup);
|
||||
this._groupsDef.unshift(ungroupedGroup);
|
||||
}
|
||||
ungroupedGroup.tracks = ungroupedGroup.tracks.concat(thisGroup.tracks).sort((a, b) => a.index - b.index);
|
||||
this._groups.splice(this._groups.indexOf(thisGroup), 1);
|
||||
ungroupedGroup.tracksIdx = ungroupedGroup.tracksIdx.concat(group.tracks.map(t => t.index)).sort();
|
||||
this._groupsDef.splice(groups.indexOf(group), 1);
|
||||
}
|
||||
|
||||
async rewriteGroups(groups: Group[]) {
|
||||
this._groups = [...groups];
|
||||
this._groupsDef = groups.map(g => ({
|
||||
title: g.title,
|
||||
fullWidthTitle: g.fullWidthTitle,
|
||||
index: g.index,
|
||||
tracksIdx: g.tracks.map(t => t.index),
|
||||
}));
|
||||
}
|
||||
|
||||
async getDeviceStatus() {
|
||||
|
@ -207,9 +238,16 @@ class NetMDMockService implements NetMDService {
|
|||
indexes = indexes.sort();
|
||||
indexes.reverse();
|
||||
for (let index of indexes) {
|
||||
this._groups = recomputeGroupsAfterTrackMove(this._getDisc(), index, -1).groups;
|
||||
this._groupsDef = recomputeGroupsAfterTrackMove(this._getDisc(), index, -1).groups.map(g => ({
|
||||
title: g.title,
|
||||
fullWidthTitle: g.fullWidthTitle,
|
||||
index: g.index,
|
||||
tracksIdx: g.tracks.map(t => t.index),
|
||||
}));
|
||||
this._tracks.splice(index, 1);
|
||||
this._groups.forEach(n => (n.tracks = n.tracks.filter(n => this._tracks.includes(n))));
|
||||
this._groupsDef.forEach(
|
||||
g => (g.tracksIdx = g.tracksIdx.filter(tidx => this._tracks.find(t => t.index === tidx) !== undefined))
|
||||
);
|
||||
}
|
||||
this._updateTrackIndexes();
|
||||
}
|
||||
|
@ -219,7 +257,14 @@ class NetMDMockService implements NetMDService {
|
|||
assert(t.length === 1);
|
||||
this._tracks.splice(dst, 0, t[0]);
|
||||
this._updateTrackIndexes();
|
||||
if (updateGroups || updateGroups === undefined) this._groups = recomputeGroupsAfterTrackMove(this._getDisc(), src, dst).groups;
|
||||
if (updateGroups || updateGroups === undefined) {
|
||||
this._groupsDef = recomputeGroupsAfterTrackMove(this._getDisc(), src, dst).groups.map(g => ({
|
||||
title: g.title,
|
||||
fullWidthTitle: g.fullWidthTitle,
|
||||
index: g.index,
|
||||
tracksIdx: g.tracks.map(t => t.index),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async wipeDisc() {
|
||||
|
@ -227,12 +272,12 @@ class NetMDMockService implements NetMDService {
|
|||
}
|
||||
|
||||
async wipeDiscTitleInfo() {
|
||||
this._groups = [
|
||||
this._groupsDef = [
|
||||
{
|
||||
index: 0,
|
||||
title: null,
|
||||
fullWidthTitle: null,
|
||||
tracks: this._tracks,
|
||||
tracksIdx: this._tracks.map(t => t.index),
|
||||
},
|
||||
];
|
||||
this._discTitle = '';
|
||||
|
@ -262,7 +307,7 @@ class NetMDMockService implements NetMDService {
|
|||
await sleep(1000);
|
||||
}
|
||||
|
||||
this._tracks.push({
|
||||
const newTrack = {
|
||||
title: halfWidthTitle,
|
||||
duration: 5 * 60 * 512,
|
||||
encoding: Encoding.sp,
|
||||
|
@ -270,7 +315,9 @@ class NetMDMockService implements NetMDService {
|
|||
protected: TrackFlag.unprotected,
|
||||
channel: 0,
|
||||
fullWidthTitle: fullWidthTitle,
|
||||
});
|
||||
};
|
||||
this._tracks.push(newTrack);
|
||||
this._groupsDef[0].tracksIdx.push(newTrack.index);
|
||||
|
||||
await sleep(1000);
|
||||
progressCallback({ written: 100, encrypted: 100, total: 100 });
|
||||
|
@ -283,7 +330,7 @@ class NetMDMockService implements NetMDService {
|
|||
|
||||
@asyncMutex
|
||||
async pause() {
|
||||
console.log('pause');
|
||||
this._status.state = 'paused';
|
||||
}
|
||||
|
||||
@asyncMutex
|
||||
|
|
|
@ -36,7 +36,7 @@ export interface NetMDService {
|
|||
finalize(): Promise<void>;
|
||||
renameTrack(index: number, newTitle: string, newFullWidthTitle?: string): Promise<void>;
|
||||
renameDisc(newName: string, newFullWidthName?: string): Promise<void>;
|
||||
renameGroup(groupBegin: number, newTitle: string, newFullWidthTitle?: string): Promise<void>;
|
||||
renameGroup(groupIndex: number, newTitle: string, newFullWidthTitle?: string): Promise<void>;
|
||||
addGroup(groupBegin: number, groupLength: number, name: string): Promise<void>;
|
||||
deleteGroup(groupIndex: number): Promise<void>;
|
||||
rewriteGroups(groups: Group[]): Promise<void>;
|
||||
|
@ -96,7 +96,6 @@ export class NetMDUSBService implements NetMDService {
|
|||
}
|
||||
|
||||
private async listContentUsingCache() {
|
||||
// listContent takes a long time to execute (>3000ms), so I think caching it should speed up the app
|
||||
if (!this.cachedContentList) {
|
||||
console.log("There's no cached version of the TOC, caching");
|
||||
this.cachedContentList = await listContent(this.netmdInterface!);
|
||||
|
@ -173,13 +172,17 @@ export class NetMDUSBService implements NetMDService {
|
|||
}
|
||||
|
||||
@asyncMutex
|
||||
async renameGroup(groupBegin: number, newName: string, newFullWidthName?: string) {
|
||||
async renameGroup(groupIndex: number, newName: string, newFullWidthName?: string) {
|
||||
const disc = await this.listContentUsingCache();
|
||||
let thisGroup = disc.groups.find(n => n.tracks[0].index === groupBegin);
|
||||
if (!thisGroup) return;
|
||||
let thisGroup = disc.groups.find(g => g.index === groupIndex);
|
||||
if (!thisGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
thisGroup.title = newName;
|
||||
if (newFullWidthName !== undefined) thisGroup.fullWidthTitle = newFullWidthName;
|
||||
if (newFullWidthName !== undefined) {
|
||||
thisGroup.fullWidthTitle = newFullWidthName;
|
||||
}
|
||||
await this.writeRawTitles(compileDiscTitles(disc));
|
||||
}
|
||||
|
||||
|
@ -187,7 +190,10 @@ export class NetMDUSBService implements NetMDService {
|
|||
async addGroup(groupBegin: number, groupLength: number, title: string) {
|
||||
const disc = await this.listContentUsingCache();
|
||||
let ungrouped = disc.groups.find(n => n.title === null);
|
||||
if (!ungrouped) return; // You can only group tracks that aren't already in a different group, if there's no such tracks, there's no point to continue
|
||||
if (!ungrouped) {
|
||||
return; // You can only group tracks that aren't already in a different group, if there's no such tracks, there's no point to continue
|
||||
}
|
||||
|
||||
let ungroupedLengthBeforeGroup = ungrouped.tracks.length;
|
||||
|
||||
let thisGroupTracks = ungrouped.tracks.filter(n => n.index >= groupBegin && n.index < groupBegin + groupLength);
|
||||
|
@ -200,21 +206,25 @@ export class NetMDUSBService implements NetMDService {
|
|||
if (!isSequential(thisGroupTracks.map(n => n.index))) {
|
||||
throw new Error('Invalid sequence of tracks!');
|
||||
}
|
||||
|
||||
disc.groups.push({
|
||||
title,
|
||||
fullWidthTitle: '',
|
||||
index: groupBegin,
|
||||
index: disc.groups.length,
|
||||
tracks: thisGroupTracks,
|
||||
});
|
||||
disc.groups = disc.groups.filter(g => g.tracks.length !== 0).sort((a, b) => a.tracks[0].index - b.tracks[0].index);
|
||||
await this.writeRawTitles(compileDiscTitles(disc));
|
||||
}
|
||||
|
||||
@asyncMutex
|
||||
async deleteGroup(groupBegin: number) {
|
||||
async deleteGroup(index: number) {
|
||||
const disc = await this.listContentUsingCache();
|
||||
|
||||
let thisGroup = disc.groups.find(n => n.tracks[0].index === groupBegin);
|
||||
if (thisGroup) disc.groups.splice(disc.groups.indexOf(thisGroup), 1);
|
||||
let groupIndex = disc.groups.findIndex(g => g.index === index);
|
||||
if (groupIndex >= 0) {
|
||||
disc.groups.splice(groupIndex, 1);
|
||||
}
|
||||
|
||||
await this.writeRawTitles(compileDiscTitles(disc));
|
||||
}
|
||||
|
|
|
@ -171,14 +171,18 @@ export function getSortedTracks(disc: Disc | null) {
|
|||
}
|
||||
|
||||
export function getGroupedTracks(disc: Disc | null) {
|
||||
if (!disc) return [];
|
||||
if (!disc) {
|
||||
return [];
|
||||
}
|
||||
let groupedList: Group[] = [];
|
||||
let ungroupedTracks = [...(disc.groups.find(n => n.title === null)?.tracks ?? [])];
|
||||
|
||||
let lastIndex = 0;
|
||||
|
||||
for (let group of disc.groups) {
|
||||
if (group.title === null) continue; // Ungrouped tracks
|
||||
if (group.title === null) {
|
||||
continue; // Ungrouped tracks
|
||||
}
|
||||
let toCopy = group.tracks[0].index - lastIndex;
|
||||
groupedList.push({
|
||||
index: -1,
|
||||
|
|
Loading…
Reference in New Issue