Files
ClipTrimApp/electron-ui/src/renderer/components/ClipList.tsx
2026-02-21 21:15:06 -05:00

237 lines
7.2 KiB
TypeScript

import React from 'react';
import {
DndContext,
closestCenter,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
arrayMove,
SortableContext,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import AudioTrimmer from './AudioTrimer';
import { ClipMetadata } from '../../redux/types';
import { useAppDispatch, useAppSelector } from '../hooks';
export interface ClipListProps {
collection: string;
}
export default function ClipList({ collection }: ClipListProps) {
const metadata = useAppSelector(
(state) =>
state.collections.find((col) => col.name === collection) || { clips: [] },
);
const dispatch = useAppDispatch();
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
);
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
console.log('Files dropped:', event.dataTransfer.files);
const files = Array.from(event.dataTransfer.files).filter((file) =>
file.type.startsWith('audio/'),
);
if (files.length > 0) {
const formData = new FormData();
files.forEach((file) => formData.append('files', file));
// todo send the file to the backend and add to the collection
// fetch('http://localhost:5010/file/upload', {
// method: 'POST',
// body: formData,
// })
// .then((res) => res.json())
// .catch((err) => console.error('Error uploading files:', err));
// Implement your onDrop logic here
// onDrop(files, selectedCollection);
}
};
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
};
async function handleDragEnd(event: any) {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = metadata.clips.findIndex(
(item) => item.filename === active.id,
);
const newIndex = metadata.clips.findIndex(
(item) => item.filename === over.id,
);
const newMetadata = {
...metadata,
clips: arrayMove(metadata.clips, oldIndex, newIndex),
};
console.log('New order:', newMetadata);
dispatch({
type: 'metadata/setCollections',
payload: { collection, newMetadata },
});
try {
const response = await fetch(
'http://localhost:5010/meta/collection/clips/reorder',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: collection,
clips: newMetadata.clips,
}),
},
);
const data = await response.json();
console.log('handle reorder return:', data.collections);
dispatch({ type: 'metadata/setAllData', payload: data });
} catch (error) {
console.error('Error saving new clip order:', error);
}
// setMetadata(newMetadata);
}
}
async function handleDelete(meta: ClipMetadata) {
dispatch({
type: 'metadata/deleteClip',
payload: { collection, clip: meta },
});
fetch('http://localhost:5010/meta/collection/clips/remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: collection,
clip: meta,
}),
})
.then((res) => res.json())
.catch((err) => console.error('Error deleting clip:', err));
console.log('Deleting clip:', meta);
}
async function handleClipMove(targetCollection: string, meta: ClipMetadata) {
console.log('Moving clip:', meta, 'to collection:', targetCollection);
dispatch({
type: 'metadata/moveClip',
payload: { sourceCollection: collection, targetCollection, clip: meta },
});
fetch('http://localhost:5010/meta/collection/clips/move', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sourceCollection: collection,
targetCollection,
clip: meta,
}),
})
.then((res) => res.json())
.catch((err) => console.error('Error moving clip:', err));
}
async function handleClipSave(meta: ClipMetadata) {
try {
dispatch({
type: 'metadata/editClip',
payload: { collection, clip: meta },
});
const response = await fetch(
'http://localhost:5010/meta/collection/clips/edit',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: collection,
clip: meta,
}),
},
);
await response.json();
// console.log('handle clip save return:', data.collections);
dispatch({
type: 'metadata/editClip',
payload: { collection, clip: meta },
});
} catch (error) {
console.error('Error saving clip metadata:', error);
}
}
return (
<div
className="min-h-full flex flex-col justify-start bg-midnight text-offwhite"
onDrop={handleDrop}
onDragOver={handleDragOver}
>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
// eslint-disable-next-line react/jsx-no-bind
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis]}
>
<SortableContext
items={metadata.clips.map((item) => item.filename)}
strategy={verticalListSortingStrategy}
>
{metadata.clips.map((trimmer, idx) => (
<React.Fragment key={trimmer.filename}>
<AudioTrimmer
metadata={trimmer}
onSave={handleClipSave}
onDelete={handleDelete}
onMove={handleClipMove}
/>
{(idx + 1) % 10 === 0 && idx !== metadata.clips.length - 1 && (
<div className="my-4 border-t border-gray-500">
<p className="text-center text-sm text-gray-400">
-- Page {Math.ceil((idx + 1) / 10) + 1} --
</p>
</div>
)}
</React.Fragment>
))}
{/* {metadata.map((trimmer) => (
<AudioTrimmer
key={trimmer.filename}
filename={trimmer.filename}
onSave={handleClipSave}
/>
))} */}
</SortableContext>
</DndContext>
</div>
// <div className="min-h-screen flex flex-col justify-center bg-midnight text-offwhite">
// <AudioTrimmer
// title="audio_capture_20251206_123108.wav"
// filePath="C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250118_000351.wav"
// // section="Section 1"
// />
// <AudioTrimmer
// title="audio_capture_20251206_123108.wav"
// filePath="C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250118_000351.wav"
// // section="Section 1"
// />
// <AudioTrimmer
// title="audio_capture_20251206_123108.wav"
// filePath="C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250118_000351.wav"
// // section="Section 1"
// />
// </div>
);
}