trimmer jsx cleanup
This commit is contained in:
@ -164,7 +164,7 @@ app
|
||||
.whenReady()
|
||||
.then(() => {
|
||||
// if (app.isPackaged) {
|
||||
pythonManager.start();
|
||||
// pythonManager.start();
|
||||
// }
|
||||
createWindow();
|
||||
app.on('activate', () => {
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
||||
import AudioTrimmer from './AudioTrimer';
|
||||
import AudioTrimmer from './Trimmer/AudioTrimer';
|
||||
import { ClipMetadata } from '../../redux/types';
|
||||
import { useAppDispatch, useAppSelector } from '../hooks';
|
||||
import { apiFetch } from '../api';
|
||||
|
||||
@ -5,27 +5,22 @@ import React, {
|
||||
useCallback,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import Slider from '@mui/material/Slider';
|
||||
import ToggleButton from '@mui/material/ToggleButton';
|
||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||
import { useWavesurfer } from '@wavesurfer/react';
|
||||
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 { ClipMetadata, PlaybackType } from '../../redux/types';
|
||||
import { useAppSelector } from '../hooks';
|
||||
import PlayStopIcon from './playStopIcon';
|
||||
import PlayOverlapIcon from './playOverlapIcon';
|
||||
import { ClipMetadata, PlaybackType } from '../../../redux/types';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import PlayStopIcon from '../icons/playStopIcon';
|
||||
import PlayOverlapIcon from '../icons/playOverlapIcon';
|
||||
import NameEditDialog from './dialogs/NameEditDialog';
|
||||
import DeleteClipDialog from './dialogs/DeleteClipDialog';
|
||||
import TitleBlock from './TitleBlock';
|
||||
import ClipButtonRow from './ClipButtonRow';
|
||||
|
||||
export interface AudioTrimmerProps {
|
||||
metadata: ClipMetadata;
|
||||
@ -42,35 +37,23 @@ export default function AudioTrimmer({
|
||||
}: AudioTrimmerProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||
useSortable({ id: metadata.filename });
|
||||
|
||||
// Dialog state for editing name
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [nameInput, setNameInput] = useState<string>(metadata.name);
|
||||
const [volumeInput, setVolumeInput] = useState<number>(metadata.volume ?? 1);
|
||||
const collectionNames = useAppSelector((state) =>
|
||||
state.collections.map((col) => col.name),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setNameInput(metadata.name);
|
||||
}, [metadata.name]);
|
||||
|
||||
const openEditDialog = () => setEditDialogOpen(true);
|
||||
const closeEditDialog = () => setEditDialogOpen(false);
|
||||
|
||||
const handleDialogSave = () => {
|
||||
if (nameInput.trim() && nameInput !== metadata.name) {
|
||||
const updated = { ...metadata, name: nameInput.trim() };
|
||||
const handleDialogSave = (newName: string) => {
|
||||
if (newName.trim() && newName !== metadata.name) {
|
||||
const updated = { ...metadata, name: newName.trim() };
|
||||
if (onSave) onSave(updated);
|
||||
}
|
||||
closeEditDialog();
|
||||
setEditDialogOpen(false);
|
||||
};
|
||||
|
||||
const [blobUrl, setBlobUrl] = useState<string | undefined>(undefined);
|
||||
const containerRef = useRef(null);
|
||||
// const [clipStart, setClipStart] = useState<number | undefined>(undefined);
|
||||
// const [clipEnd, setClipEnd] = useState<number | undefined>(undefined);
|
||||
@ -85,16 +68,12 @@ export default function AudioTrimmer({
|
||||
[],
|
||||
);
|
||||
|
||||
const fileBaseName =
|
||||
metadata.filename.split('\\').pop()?.split('/').pop() || 'Unknown';
|
||||
|
||||
const { wavesurfer, isReady, isPlaying } = useWavesurfer({
|
||||
container: containerRef,
|
||||
height: 100,
|
||||
waveColor: '#ccb1ff',
|
||||
progressColor: '#6e44ba',
|
||||
hideScrollbar: true,
|
||||
url: blobUrl,
|
||||
plugins,
|
||||
});
|
||||
|
||||
@ -276,6 +255,21 @@ export default function AudioTrimmer({
|
||||
}}
|
||||
className="shadow-[0_2px_8px_rgba(0,0,0,0.5)] m-2 rounded-lg bg-darkDrop"
|
||||
>
|
||||
<NameEditDialog
|
||||
open={editDialogOpen}
|
||||
onCancel={() => setEditDialogOpen(false)}
|
||||
startValue={metadata.name}
|
||||
onSave={handleDialogSave}
|
||||
/>
|
||||
|
||||
<DeleteClipDialog
|
||||
open={deleteDialogOpen}
|
||||
onCancel={() => setDeleteDialogOpen(false)}
|
||||
onDelete={() => {
|
||||
setDeleteDialogOpen(false);
|
||||
if (onDelete) onDelete(metadata);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...attributes}
|
||||
@ -295,137 +289,22 @@ export default function AudioTrimmer({
|
||||
{/* <div className="flex flex-col"> */}
|
||||
<div className="ml-4 mr-2 p-2">
|
||||
<div className="grid justify-items-stretch grid-cols-2">
|
||||
<div className="mb-5px flex flex-col">
|
||||
<span
|
||||
className="font-bold text-lg text-white mb-1 cursor-pointer"
|
||||
onClick={openEditDialog}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
openEditDialog();
|
||||
}
|
||||
}}
|
||||
title="Click to edit name"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
{metadata.name}
|
||||
</span>
|
||||
<span className="text-sm text-neutral-500">{fileBaseName}</span>
|
||||
</div>
|
||||
<Dialog
|
||||
open={editDialogOpen}
|
||||
onClose={closeEditDialog}
|
||||
slotProps={{
|
||||
paper: { sx: { backgroundColor: '#1a1a1a', color: 'white' } },
|
||||
<TitleBlock
|
||||
name={metadata.name}
|
||||
filename={metadata.filename}
|
||||
onNameClick={() => setEditDialogOpen(true)}
|
||||
/>
|
||||
<ClipButtonRow
|
||||
isPlaying={isPlaying}
|
||||
collectionNames={collectionNames}
|
||||
onPlayPause={onPlayPause}
|
||||
onMove={(collectionName) => {
|
||||
if (onMove !== undefined) {
|
||||
onMove(collectionName, metadata);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle>Edit Clip Name</DialogTitle>
|
||||
<DialogContent>
|
||||
<textarea
|
||||
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 resize-y"
|
||||
value={nameInput}
|
||||
onChange={(e) => setNameInput(e.target.value)}
|
||||
rows={3}
|
||||
onFocus={(event) => event.target.select()}
|
||||
aria-label="Edit clip name"
|
||||
style={{ minHeight: '3em' }}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeEditDialog}
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDialogSave}
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</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"
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-11 w-11 rounded-md ml-1"
|
||||
onClick={onPlayPause}
|
||||
>
|
||||
{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"
|
||||
onClick={() => setDeleteDialogOpen(true)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</button>
|
||||
</div>
|
||||
onDelete={() => setDeleteDialogOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1 wavesurfer-scroll-container">
|
||||
<div ref={containerRef} className="wavesurfer-inner" />
|
||||
@ -450,24 +329,6 @@ export default function AudioTrimmer({
|
||||
color="secondary"
|
||||
className="p-0 m-0"
|
||||
/>
|
||||
{/* <input
|
||||
type="range"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={volumeInput}
|
||||
onChange={(e) => {
|
||||
const newVolume = parseFloat(e.target.value);
|
||||
setVolumeInput(newVolume);
|
||||
}}
|
||||
onDragEnd={(e) => {
|
||||
console.log('Volume change:');
|
||||
// const newVolume = parseFloat(e.target.value);
|
||||
// if (onSave) onSave({ ...metadata, volume: newVolume });
|
||||
}}
|
||||
className="mx-2 w-full accent-plum"
|
||||
aria-label="Volume slider"
|
||||
/> */}
|
||||
</div>
|
||||
<div className="w-1/5 flex justify-end text-sm text-neutral-500">
|
||||
<ToggleButtonGroup value={metadata.playbackType}>
|
||||
@ -0,0 +1,67 @@
|
||||
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 { useState } from 'react';
|
||||
|
||||
export default function ClipButtonRow({
|
||||
isPlaying,
|
||||
collectionNames,
|
||||
onPlayPause,
|
||||
onMove,
|
||||
onDelete,
|
||||
}: {
|
||||
isPlaying: boolean;
|
||||
collectionNames: string[];
|
||||
onPlayPause: () => void;
|
||||
onMove?: (collectionName: string) => void;
|
||||
onDelete?: () => void;
|
||||
}) {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-11 w-11 rounded-md ml-1"
|
||||
onClick={onPlayPause}
|
||||
>
|
||||
{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);
|
||||
}}
|
||||
>
|
||||
{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"
|
||||
onClick={() => onDelete && onDelete()}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
electron-ui/src/renderer/components/Trimmer/TitleBlock.tsx
Normal file
27
electron-ui/src/renderer/components/Trimmer/TitleBlock.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
export default function TitleBlock({
|
||||
name,
|
||||
filename,
|
||||
onNameClick,
|
||||
}: {
|
||||
name: string;
|
||||
filename: string;
|
||||
onNameClick: () => void;
|
||||
}) {
|
||||
const basename = filename.split('\\').pop()?.split('/').pop() || 'Unknown';
|
||||
return (
|
||||
<div className="mb-5px flex flex-col">
|
||||
<span
|
||||
className="font-bold text-lg text-white mb-1 cursor-pointer"
|
||||
onClick={onNameClick}
|
||||
onKeyDown={(e) => {}}
|
||||
title="Click to edit name"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<span className="text-sm text-neutral-500">{basename}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
|
||||
export default function DeleteClipDialog({
|
||||
open,
|
||||
onCancel,
|
||||
onDelete,
|
||||
}: {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onDelete: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onCancel}
|
||||
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={onCancel}
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
|
||||
export default function NameEditDialog({
|
||||
open,
|
||||
startValue,
|
||||
onCancel,
|
||||
onSave,
|
||||
}: {
|
||||
open: boolean;
|
||||
startValue: string;
|
||||
onCancel: () => void;
|
||||
onSave: (newName: string) => void;
|
||||
}) {
|
||||
const [input, setInput] = useState(startValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setInput(startValue);
|
||||
}
|
||||
}, [open, startValue]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onCancel}
|
||||
slotProps={{
|
||||
paper: { sx: { backgroundColor: '#1a1a1a', color: 'white' } },
|
||||
}}
|
||||
>
|
||||
<DialogTitle>Edit Clip Name</DialogTitle>
|
||||
<DialogContent>
|
||||
<textarea
|
||||
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 resize-y"
|
||||
value={input}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
}}
|
||||
rows={3}
|
||||
onFocus={(event) => event.target.select()}
|
||||
aria-label="Edit clip name"
|
||||
style={{ minHeight: '3em' }}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSave(input)}
|
||||
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user