collection list, move, delete

This commit is contained in:
michalcourson
2026-02-20 20:21:08 -05:00
parent d6f4d4166b
commit 60355d176c
18 changed files with 437 additions and 2064 deletions

View File

@ -10,41 +10,42 @@ import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import { useWavesurfer } from '@wavesurfer/react';
import Regions from 'wavesurfer.js/dist/plugins/regions.esm.js';
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js';
import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom.esm.js';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import PauseIcon from '@mui/icons-material/Pause';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import DeleteIcon from '@mui/icons-material/Delete';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js';
import { ClipMetadata } from '../../redux/types';
import { useAppSelector } from '../hooks';
export interface AudioTrimmerProps {
filename: string;
metadata: ClipMetadata;
onSave?: (metadata: ClipMetadata) => void;
onDelete?: () => void;
onDelete?: (metadata: ClipMetadata) => void;
onMove?: (newCollection: string, metadata: ClipMetadata) => void;
}
export default function AudioTrimmer({
filename,
metadata,
onSave,
onDelete,
onMove,
}: AudioTrimmerProps) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: filename });
useSortable({ id: metadata.filename });
const metadata = useAppSelector((state) => {
const clip = Object.values(state.collections)
.flat()
.find((c) => c.filename === filename);
return clip ?? ({ filename, name: 'Unknown Clip' } as ClipMetadata);
});
// Dialog state for editing name
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [dropdownOpen, setDropdownOpen] = useState(false);
const [nameInput, setNameInput] = useState<string>(metadata.name);
const collectionNames = useAppSelector((state) =>
state.collections.map((col) => col.name),
);
useEffect(() => {
setNameInput(metadata.name);
@ -68,7 +69,7 @@ export default function AudioTrimmer({
const plugins = useMemo(
() => [
Regions.create(),
RegionsPlugin.create(),
ZoomPlugin.create({
scale: 0.25,
}),
@ -282,7 +283,7 @@ export default function AudioTrimmer({
>
{metadata.name}
</span>
<text className="text-sm text-neutral-500">{fileBaseName}</text>
<span className="text-sm text-neutral-500">{fileBaseName}</span>
</div>
<Dialog
open={editDialogOpen}
@ -294,6 +295,7 @@ export default function AudioTrimmer({
<DialogTitle>Edit Clip Name</DialogTitle>
<DialogContent>
<input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
className="font-bold text-lg bg-transparent outline-none border-b border-plum focus:border-plumDark text-white mb-1 w-full text-center"
type="text"
@ -302,6 +304,7 @@ export default function AudioTrimmer({
onKeyDown={(e) => {
if (e.key === 'Enter') handleDialogSave();
}}
onFocus={(event) => event.target.select()}
aria-label="Edit clip name"
/>
</DialogContent>
@ -323,6 +326,38 @@ export default function AudioTrimmer({
</DialogActions>
</Dialog>
<Dialog
open={deleteDialogOpen}
onClose={() => setDeleteDialogOpen(false)}
slotProps={{
paper: { sx: { backgroundColor: '#1a1a1a', color: 'white' } },
}}
>
<DialogTitle>Confirm Delete</DialogTitle>
<DialogContent>
Are you sure you want to delete this clip?
</DialogContent>
<DialogActions>
<button
type="button"
onClick={() => setDeleteDialogOpen(false)}
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
>
Cancel
</button>
<button
type="button"
onClick={() => {
setDeleteDialogOpen(false);
if (onDelete) onDelete(metadataRef.current);
}}
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
>
Delete
</button>
</DialogActions>
</Dialog>
<div className="flex justify-end">
<button
type="button"
@ -331,16 +366,36 @@ export default function AudioTrimmer({
>
{isPlaying ? <PauseIcon /> : <PlayArrowIcon />}
</button>
<div className="relative inline-block">
<button
type="button"
className="bg-plum hover:bg-plumDark text-white font-bold h-11 w-11 rounded-md ml-1"
onClick={() => setDropdownOpen((prev) => !prev)}
>
{dropdownOpen ? <ArrowDownwardIcon /> : <ArrowForwardIcon />}
</button>
{dropdownOpen && (
<div className="absolute z-10 mt-2 w-40 bg-midnight rounded-md shadow-lg">
{collectionNames.map((name) => (
<button
key={name}
type="button"
className="block w-full text-left px-4 py-2 text-white hover:bg-plumDark"
onClick={() => {
setDropdownOpen(false);
if (onMove) onMove(name, metadata);
}}
>
{name}
</button>
))}
</div>
)}
</div>
<button
type="button"
className="bg-plum hover:bg-plumDark text-white font-bold h-11 w-11 rounded-md ml-1"
>
<ArrowForwardIcon />
</button>
<button
type="button"
className="bg-plum hover:bg-plumDark text-white font-bold h-11 w-11 rounded-md ml-1"
onClick={onDelete}
onClick={() => setDeleteDialogOpen(true)}
>
<DeleteIcon />
</button>