From 5516ce92120b57c9d55c312c4f527a3587b9465c Mon Sep 17 00:00:00 2001 From: michalcourson Date: Wed, 11 Feb 2026 19:42:52 -0500 Subject: [PATCH] draggables, ipc stuff --- electron-ui/electron-ui/src/old/renderer.js | 0 electron-ui/electron-ui/src/renderer/App.tsx | 121 -------------- .../src/renderer/components/AudioTrimmer.tsx | 138 ---------------- .../electron-ui/src/renderer/types/index.ts | 16 -- electron-ui/package-lock.json | 71 ++++++++- electron-ui/package.json | 3 + electron-ui/src/ipc/audio/channels.ts | 12 ++ electron-ui/src/ipc/audio/main.ts | 18 +++ electron-ui/src/ipc/audio/types.ts | 32 ++++ electron-ui/src/ipc/settings/channels.ts | 8 + electron-ui/src/main/main.ts | 13 +- electron-ui/src/main/preload.ts | 24 ++- electron-ui/src/old/renderer.js | 2 +- electron-ui/src/renderer/App.tsx | 56 +------ .../src/renderer/components/AudioTrimer.tsx | 93 +++++++---- .../src/renderer/components/ClipList.tsx | 147 ++++++++++++++++++ electron-ui/src/renderer/preload.d.ts | 3 +- 17 files changed, 380 insertions(+), 377 deletions(-) delete mode 100644 electron-ui/electron-ui/src/old/renderer.js delete mode 100644 electron-ui/electron-ui/src/renderer/App.tsx delete mode 100644 electron-ui/electron-ui/src/renderer/components/AudioTrimmer.tsx delete mode 100644 electron-ui/electron-ui/src/renderer/types/index.ts create mode 100644 electron-ui/src/ipc/audio/channels.ts create mode 100644 electron-ui/src/ipc/audio/main.ts create mode 100644 electron-ui/src/ipc/audio/types.ts create mode 100644 electron-ui/src/ipc/settings/channels.ts create mode 100644 electron-ui/src/renderer/components/ClipList.tsx diff --git a/electron-ui/electron-ui/src/old/renderer.js b/electron-ui/electron-ui/src/old/renderer.js deleted file mode 100644 index e69de29..0000000 diff --git a/electron-ui/electron-ui/src/renderer/App.tsx b/electron-ui/electron-ui/src/renderer/App.tsx deleted file mode 100644 index 3966307..0000000 --- a/electron-ui/electron-ui/src/renderer/App.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import WaveSurfer from 'wavesurfer.js'; -import RegionsPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.regions.js'; -import { ipcRenderer } from 'electron'; -import path from 'path'; - -interface AudioTrimmerProps { - filePath: string; - section: string; -} - -const AudioTrimmer: React.FC = ({ filePath, section }) => { - const [trimStart, setTrimStart] = useState(0); - const [trimEnd, setTrimEnd] = useState(0); - const [isPlaying, setIsPlaying] = useState(false); - const waveformRef = useRef(null); - const wavesurferRef = useRef(null); - - useEffect(() => { - const loadTrimInfo = async () => { - const savedTrimInfo = await ipcRenderer.invoke('get-trim-info', section, path.basename(filePath)); - setTrimStart(savedTrimInfo.trimStart || 0); - setTrimEnd(savedTrimInfo.trimEnd || 0); - }; - - loadTrimInfo(); - - wavesurferRef.current = WaveSurfer.create({ - container: waveformRef.current!, - waveColor: '#ccb1ff', - progressColor: '#6e44ba', - responsive: true, - height: 100, - hideScrollbar: true, - plugins: [ - RegionsPlugin.create({ - color: 'rgba(132, 81, 224, 0.3)', - drag: false, - resize: true, - }), - ], - }); - - wavesurferRef.current.load(`file://${filePath}`); - - wavesurferRef.current.on('ready', () => { - wavesurferRef.current.addRegion({ - start: trimStart, - end: trimEnd, - color: 'rgba(132, 81, 224, 0.3)', - }); - }); - - wavesurferRef.current.on('region-update-end', (region: any) => { - setTrimStart(region.start); - setTrimEnd(region.end); - }); - - return () => { - wavesurferRef.current.destroy(); - }; - }, [filePath, section, trimStart, trimEnd]); - - const handlePlayPause = () => { - if (isPlaying) { - wavesurferRef.current.pause(); - } else { - wavesurferRef.current.play(trimStart, trimEnd); - } - setIsPlaying(!isPlaying); - }; - - const handleSaveTrim = async () => { - const newTitle = prompt('Enter a title for the trimmed audio:'); - if (newTitle) { - await ipcRenderer.invoke('save-trimmed-file', { - originalFilePath: filePath, - trimStart, - trimEnd, - title: newTitle, - }); - alert('Trimmed audio saved successfully!'); - } - }; - - const handleDelete = async () => { - const confirmDelete = confirm('Are you sure you want to delete this audio file?'); - if (confirmDelete) { - await ipcRenderer.invoke('delete-file', filePath); - alert('File deleted successfully!'); - } - }; - - return ( -
-
-
{path.basename(filePath)}
-
- - - -
-
-
-
-
Start: {formatTime(trimStart)}
-
End: {formatTime(trimEnd)}
-
-
- ); -}; - -const formatTime = (seconds: number) => { - const minutes = Math.floor(seconds / 60); - const remainingSeconds = Math.floor(seconds % 60); - return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; -}; - -export default AudioTrimmer; \ No newline at end of file diff --git a/electron-ui/electron-ui/src/renderer/components/AudioTrimmer.tsx b/electron-ui/electron-ui/src/renderer/components/AudioTrimmer.tsx deleted file mode 100644 index 2a22dbf..0000000 --- a/electron-ui/electron-ui/src/renderer/components/AudioTrimmer.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import WaveSurfer from 'wavesurfer.js'; -import RegionsPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.regions.js'; -import { ipcRenderer } from 'electron'; -import path from 'path'; - -interface AudioTrimmerProps { - filePath: string; - section: string; -} - -const AudioTrimmer: React.FC = ({ filePath, section }) => { - const [trimStart, setTrimStart] = useState(0); - const [trimEnd, setTrimEnd] = useState(0); - const [title, setTitle] = useState(''); - const waveformRef = useRef(null); - const wavesurferRef = useRef(null); - - useEffect(() => { - const loadTrimInfo = async () => { - const savedTrimInfo = await ipcRenderer.invoke('get-trim-info', section, path.basename(filePath)); - setTrimStart(savedTrimInfo.trimStart || 0); - setTrimEnd(savedTrimInfo.trimEnd || 0); - setTitle(savedTrimInfo.title || path.basename(filePath)); - }; - - loadTrimInfo(); - }, [filePath, section]); - - useEffect(() => { - if (waveformRef.current) { - wavesurferRef.current = WaveSurfer.create({ - container: waveformRef.current, - waveColor: '#ccb1ff', - progressColor: '#6e44ba', - responsive: true, - height: 100, - hideScrollbar: true, - plugins: [ - RegionsPlugin.create({ - color: 'rgba(132, 81, 224, 0.3)', - drag: false, - resize: true, - }), - ], - }); - - wavesurferRef.current.load(`file://${filePath}`); - - wavesurferRef.current.on('ready', () => { - wavesurferRef.current?.addRegion({ - start: trimStart, - end: trimEnd, - color: 'rgba(132, 81, 224, 0.3)', - drag: false, - resize: true, - }); - }); - - wavesurferRef.current.on('region-update-end', (region) => { - setTrimStart(region.start); - setTrimEnd(region.end); - }); - - return () => { - wavesurferRef.current?.destroy(); - }; - } - }, [filePath, trimStart, trimEnd]); - - const handlePlayPause = () => { - if (wavesurferRef.current) { - if (wavesurferRef.current.isPlaying()) { - wavesurferRef.current.pause(); - } else { - wavesurferRef.current.play(trimStart, trimEnd); - } - } - }; - - const handleSaveTrim = async () => { - const newTitle = title.trim(); - await ipcRenderer.invoke('save-trimmed-file', { - originalFilePath: filePath, - trimStart, - trimEnd, - title: newTitle, - }); - }; - - const handleDelete = async () => { - const confirmDelete = window.confirm('Are you sure you want to delete this audio file?'); - if (confirmDelete) { - await ipcRenderer.invoke('delete-file', filePath); - } - }; - - return ( -
-
-
-
{title}
-
{path.basename(filePath)}
-
-
- - - -
-
-
-
-
- Start: - {formatTime(trimStart)} -
-
- End: - {formatTime(trimEnd)} -
-
-
- ); -}; - -const formatTime = (seconds: number) => { - const minutes = Math.floor(seconds / 60); - const remainingSeconds = Math.floor(seconds % 60); - return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; -}; - -export default AudioTrimmer; \ No newline at end of file diff --git a/electron-ui/electron-ui/src/renderer/types/index.ts b/electron-ui/electron-ui/src/renderer/types/index.ts deleted file mode 100644 index 518d6ab..0000000 --- a/electron-ui/electron-ui/src/renderer/types/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file is intended for defining TypeScript types and interfaces that can be used throughout the application. - -export interface TrimInfo { - title?: string; - trimStart: number; - trimEnd: number; - originalPath: string; -} - -export interface AudioTrimmerProps { - filePath: string; - section: string; - savedTrimInfo: TrimInfo; - onSave: (trimInfo: TrimInfo) => void; - onDelete: () => void; -} \ No newline at end of file diff --git a/electron-ui/package-lock.json b/electron-ui/package-lock.json index 9d85f77..25031aa 100644 --- a/electron-ui/package-lock.json +++ b/electron-ui/package-lock.json @@ -8,6 +8,9 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@electron/notarize": "^3.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", @@ -2104,6 +2107,73 @@ "node": ">=10.0.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", @@ -22969,7 +23039,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "devOptional": true, "license": "0BSD" }, "node_modules/tsutils": { diff --git a/electron-ui/package.json b/electron-ui/package.json index 7de0737..b71b05e 100644 --- a/electron-ui/package.json +++ b/electron-ui/package.json @@ -101,6 +101,9 @@ } }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@electron/notarize": "^3.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", diff --git a/electron-ui/src/ipc/audio/channels.ts b/electron-ui/src/ipc/audio/channels.ts new file mode 100644 index 0000000..cf5d859 --- /dev/null +++ b/electron-ui/src/ipc/audio/channels.ts @@ -0,0 +1,12 @@ +const AudioChannels = { + LOAD_AUDIO_BUFFER: 'audio:loadAudioBuffer', + TRIM_FILE: 'audio:trimFile', + DELETE_FILE: 'audio:deleteFile', + GET_COLLECTION_METADATA: 'audio:getCollectionMetadata', + SET_COLLECTION_METADATA: 'audio:setCollectionMetadata', + ADD_COLLECTION: 'audio:addCollection', + REMOVE_COLLECTION: 'audio:removeCollection', + GET_COLLECTIONS: 'audio:getCollections', +} as const; + +export default AudioChannels; diff --git a/electron-ui/src/ipc/audio/main.ts b/electron-ui/src/ipc/audio/main.ts new file mode 100644 index 0000000..caecc76 --- /dev/null +++ b/electron-ui/src/ipc/audio/main.ts @@ -0,0 +1,18 @@ +import { ipcMain } from 'electron'; +import fs from 'fs'; +import AudioChannels from './channels'; +import { LoadAudioBufferArgs, LoadAudioBufferResult } from './types'; + +export default function registerAudioIpcHandlers() { + ipcMain.handle( + AudioChannels.LOAD_AUDIO_BUFFER, + async (_, args: LoadAudioBufferArgs): Promise => { + try { + const buffer = await fs.promises.readFile(args.filePath); + return { buffer }; + } catch (err: any) { + return { error: err.message }; + } + }, + ); +} diff --git a/electron-ui/src/ipc/audio/types.ts b/electron-ui/src/ipc/audio/types.ts new file mode 100644 index 0000000..509f387 --- /dev/null +++ b/electron-ui/src/ipc/audio/types.ts @@ -0,0 +1,32 @@ +export interface LoadAudioBufferArgs { + filePath: string; +} + +export interface LoadAudioBufferResult { + buffer?: Buffer; + error?: string; +} + +export enum PlaybackType { + PlayStop = 'playStop', + PlayOverlap = 'playOverlap', +} + +export interface ClipMetadata { + name: string; + filePath: string; + volume: number; + startTime: number | undefined; + endTime: number | undefined; + playbackType: PlaybackType; +} + +export interface CollectionMetadata { + name: string; + clips: ClipMetadata[]; +} + +export interface MetadataFile { + uncategorizedClips: ClipMetadata[]; + collections: CollectionMetadata[]; +} diff --git a/electron-ui/src/ipc/settings/channels.ts b/electron-ui/src/ipc/settings/channels.ts new file mode 100644 index 0000000..6ff9c0b --- /dev/null +++ b/electron-ui/src/ipc/settings/channels.ts @@ -0,0 +1,8 @@ +const SettingsChannels = { + GET_DEFAULTS: 'settings:get-defaults', + GET_SETTINGS: 'settings:get-settings', + SET_SETTINGS: 'settings:set-settings', + GET_INPUT_DEVICES: 'settings:get-input-devices', +} as const; + +export default SettingsChannels; diff --git a/electron-ui/src/main/main.ts b/electron-ui/src/main/main.ts index b07f91a..badfdee 100644 --- a/electron-ui/src/main/main.ts +++ b/electron-ui/src/main/main.ts @@ -15,6 +15,7 @@ import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; +import registerFileIpcHandlers from '../ipc/audio/main'; class AppUpdater { constructor() { @@ -108,17 +109,7 @@ const createWindow = async () => { return { action: 'deny' }; }); - ipcMain.handle('load-audio-buffer', async (event, filePath) => { - try { - // console.log(`Loading audio file: ${filePath}`); - const buffer = fs.readFileSync(filePath); - // console.log(buffer); - return buffer; - } catch (err) { - return { error: err.message }; - } - }); - + registerFileIpcHandlers(); // Remove this if your app does not use auto updates // eslint-disable-next-line new AppUpdater(); diff --git a/electron-ui/src/main/preload.ts b/electron-ui/src/main/preload.ts index 790fd00..0b15825 100644 --- a/electron-ui/src/main/preload.ts +++ b/electron-ui/src/main/preload.ts @@ -1,6 +1,10 @@ // Disable no-unused-vars, broken for spread args /* eslint no-unused-vars: off */ import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; +import FileChannels from '../ipc/audio/channels'; +import { LoadAudioBufferArgs, ReadTextArgs } from '../ipc/audio/types'; +import AudioChannels from '../ipc/audio/channels'; +// import '../ipc/file/preload'; // Import file API preload to ensure it runs and exposes the API export type Channels = 'ipc-example'; @@ -22,11 +26,27 @@ const electronHandler = { ipcRenderer.once(channel, (_event, ...args) => func(...args)); }, - loadAudioBuffer: (filePath: string) => - ipcRenderer.invoke('load-audio-buffer', filePath), + invoke: (event: string, ...args: unknown[]) => + ipcRenderer.invoke(event, ...args), }, }; contextBridge.exposeInMainWorld('electron', electronHandler); export type ElectronHandler = typeof electronHandler; + +const audioHandler = { + loadAudioBuffer: (filePath: string) => + ipcRenderer.invoke(AudioChannels.LOAD_AUDIO_BUFFER, { + filePath, + } satisfies LoadAudioBufferArgs), + + readText: (filePath: string) => + ipcRenderer.invoke(AudioChannels.READ_TEXT, { + filePath, + } satisfies ReadTextArgs), +}; + +contextBridge.exposeInMainWorld('audio', audioHandler); + +export type AudioHandler = typeof audioHandler; diff --git a/electron-ui/src/old/renderer.js b/electron-ui/src/old/renderer.js index 461de7b..420768b 100644 --- a/electron-ui/src/old/renderer.js +++ b/electron-ui/src/old/renderer.js @@ -548,7 +548,7 @@ document.addEventListener('DOMContentLoaded', async () => { transform: translate(-50%, -50%); padding: 10px; border-radius: 8px; - box-shadow: 0 4px 6px rgba(0,0,0,0.1); + box-shadow: 0 4px 2px rgba(0,0,0,0.1); z-index: 1000; ">
diff --git a/electron-ui/src/renderer/App.tsx b/electron-ui/src/renderer/App.tsx index ffc1959..e3abd5a 100644 --- a/electron-ui/src/renderer/App.tsx +++ b/electron-ui/src/renderer/App.tsx @@ -2,65 +2,17 @@ import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; // import 'tailwindcss/tailwind.css'; import icon from '../../assets/icon.svg'; import './App.css'; -import AudioTrimmer from './components/AudioTrimer'; +import ClipList from './components/ClipList'; -function Hello() { - return ( -
- {/*
- icon -
-

electron-react-boilerplate

- */} - - - -
- ); +function MainPage() { + return ; } export default function App() { return ( - } /> + } /> ); diff --git a/electron-ui/src/renderer/components/AudioTrimer.tsx b/electron-ui/src/renderer/components/AudioTrimer.tsx index 465043e..892c40f 100644 --- a/electron-ui/src/renderer/components/AudioTrimer.tsx +++ b/electron-ui/src/renderer/components/AudioTrimer.tsx @@ -10,28 +10,26 @@ import Regions 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 SaveIcon from '@mui/icons-material/Save'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import DeleteIcon from '@mui/icons-material/Delete'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { ClipMetadata } from '../../ipc/audio/types'; export interface AudioTrimmerProps { - filePath: string; - section: string; - title?: string; - trimStart?: number; - trimEnd?: number; + metadata: ClipMetadata; onSave?: (trimStart: number, trimEnd: number, title?: string) => void; onDelete?: () => void; } export default function AudioTrimmer({ - filePath, - section, - title, - trimStart, - trimEnd, + metadata, onSave, onDelete, }: AudioTrimmerProps) { + const { attributes, listeners, setNodeRef, transform, transition } = + useSortable({ id: metadata.filePath }); + const [blobUrl, setBlobUrl] = useState(undefined); const containerRef = useRef(null); const [clipStart, setClipStart] = useState(0); @@ -47,6 +45,9 @@ export default function AudioTrimmer({ [], ); + const fileBaseName = + metadata.filePath.split('\\').pop()?.split('/').pop() || 'Unknown'; + const { wavesurfer, isReady, isPlaying } = useWavesurfer({ container: containerRef, height: 100, @@ -74,12 +75,12 @@ export default function AudioTrimmer({ useEffect(() => { console.log('ready, setting up regions plugin', wavesurfer); - if (trimStart !== undefined && trimEnd !== undefined) { - setClipStart(trimStart); - setClipEnd(trimEnd); + if (metadata.startTime !== undefined && metadata.endTime !== undefined) { + setClipStart(metadata.startTime); + setClipEnd(metadata.endTime); plugins[0].addRegion({ - start: trimStart, - end: trimEnd, + start: metadata.startTime, + end: metadata.endTime, color: 'rgba(132, 81, 224, 0.3)', drag: false, resize: true, @@ -93,17 +94,20 @@ export default function AudioTrimmer({ color: 'rgba(132, 81, 224, 0.3)', }); plugins[0].on('region-created', onRegionCreated); - }, [isReady, plugins, wavesurfer, onRegionCreated, trimStart, trimEnd]); + }, [isReady, plugins, wavesurfer, onRegionCreated, metadata]); useEffect(() => { let url: string | null = null; async function fetchAudio() { // console.log('Loading audio buffer for file:', filePath); - const buffer = - await window.electron.ipcRenderer.loadAudioBuffer(filePath); - if (buffer && !buffer.error) { - const audioData = buffer.data ? new Uint8Array(buffer.data) : buffer; + const buffer = await window.audio.loadAudioBuffer(metadata.filePath); + console.log('Received buffer:', buffer.buffer); + if (buffer.buffer && !buffer.error) { + const audioData = buffer.buffer + ? new Uint8Array(buffer.buffer) + : buffer; url = URL.createObjectURL(new Blob([audioData])); + console.log('Created blob URL:', url); setBlobUrl(url); } } @@ -111,7 +115,7 @@ export default function AudioTrimmer({ return () => { if (url) URL.revokeObjectURL(url); }; - }, [filePath]); + }, [metadata.filePath]); const onPlayPause = () => { if (wavesurfer === null) return; @@ -134,13 +138,36 @@ export default function AudioTrimmer({ }; return ( -
+
+
{/*
*/} -
+
-
- {title} - {filePath} +
+ {metadata.name} + {fileBaseName}
-
- Start: {formatTime(clipStart)} -
-
- End: {formatTime(clipEnd)} +
+ + Clip: {formatTime(clipStart)} - {formatTime(clipEnd)} +
diff --git a/electron-ui/src/renderer/components/ClipList.tsx b/electron-ui/src/renderer/components/ClipList.tsx new file mode 100644 index 0000000..6c7a8ea --- /dev/null +++ b/electron-ui/src/renderer/components/ClipList.tsx @@ -0,0 +1,147 @@ +import { use, useEffect, useState } from 'react'; +import AudioTrimmer from './AudioTrimer'; +import { ClipMetadata, PlaybackType } from '../../ipc/audio/types'; +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + verticalListSortingStrategy, + useSortable, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; + +export interface ClipListProps { + collection: string; +} + +const testData: ClipMetadata[] = [ + { + name: 'test 1', + filePath: + 'C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250118_000351.wav', + volume: 1, + startTime: undefined, + endTime: undefined, + playbackType: PlaybackType.PlayStop, + }, + { + name: 'test 2', + filePath: + 'C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250105_131700.wav', + volume: 1, + startTime: undefined, + endTime: undefined, + playbackType: PlaybackType.PlayStop, + }, + { + name: 'test 3', + filePath: + 'C:\\Users\\mickl\\Music\\clips\\original\\audio_capture_20250117_194006.wav', + volume: 1, + startTime: undefined, + endTime: undefined, + playbackType: PlaybackType.PlayStop, + }, +]; + +function SortableTrimmer({ trimmer, id }) { + const { attributes, listeners, setNodeRef, transform, transition } = + useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + marginBottom: '1rem', + }; + + return ( +
+ {/* Only this div is draggable */} +
+ Drag +
+ {/* Wavesurfer and rest of AudioTrimmer are NOT draggable */} + +
+ ); +} + +export default function ClipList({ collection }: ClipListProps) { + const [metadata, setMetadata] = useState(testData); + useEffect(() => { + setMetadata(testData); + }, [collection]); + + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), + ); + + function handleDragEnd(event) { + const { active, over } = event; + if (active.id !== over?.id) { + const oldIndex = metadata.findIndex( + (item) => item.filePath === active.id, + ); + const newIndex = metadata.findIndex((item) => item.filePath === over.id); + const newMetadata = arrayMove(metadata, oldIndex, newIndex); + console.log('New order:', newMetadata); + setMetadata(newMetadata); + } + } + + return ( +
+ + item.filePath)} + strategy={verticalListSortingStrategy} + > + {metadata.map((trimmer) => ( + + ))} + + +
+ //
+ // + // + // + //
+ ); +} diff --git a/electron-ui/src/renderer/preload.d.ts b/electron-ui/src/renderer/preload.d.ts index 53cc2d7..792d060 100644 --- a/electron-ui/src/renderer/preload.d.ts +++ b/electron-ui/src/renderer/preload.d.ts @@ -1,9 +1,10 @@ -import { ElectronHandler } from '../main/preload'; +import { ElectronHandler, FileHandler } from '../main/preload'; declare global { // eslint-disable-next-line no-unused-vars interface Window { electron: ElectronHandler; + audio: FileHandler; } }