1- import { useRef , useState , useEffect } from 'react' ;
1+ import { useRef , useState } from 'react' ;
22import {
33 Play , Pause , Circle , Square ,
4- Upload , Save , Film , Pencil , Mic
4+ Upload , Save , Film , Pencil , Scissors , ChevronDown , Trash2
55} from 'lucide-react' ;
66import type { RecordedSession } from '../types' ;
77
@@ -23,9 +23,9 @@ interface RecorderControlsProps {
2323 importSession : ( file : File ) => void ;
2424 exportVideo : ( ) => void ;
2525 updateMetadata : ( updates : any ) => void ;
26-
27- isAudioEnabled : boolean ;
28- setIsAudioEnabled : ( enabled : boolean ) => void ;
26+ trimSession : ( startTime : number , endTime : number ) => void ;
27+ deleteEvent : ( index : number ) => void ;
28+ deleteEventsByType : ( type : string , fromTime ?: number , toTime ?: number ) => void ;
2929
3030 isLightMode : boolean ;
3131 cardBg : string ;
@@ -42,51 +42,15 @@ export const RecorderControls = ({
4242 isRecording, isPlaying, recordingTime, playbackTime, session, playbackSpeed,
4343 startRecording, stopRecording, play, pause, seek, setPlaybackSpeed,
4444 exportSession, importSession, exportVideo, updateMetadata,
45- isAudioEnabled , setIsAudioEnabled ,
45+ trimSession , deleteEvent , deleteEventsByType ,
4646 isLightMode, cardBg
4747} : RecorderControlsProps ) => {
4848
4949 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
5050 const [ isEditingTitle , setIsEditingTitle ] = useState ( false ) ;
51- const audioRef = useRef < HTMLAudioElement > ( null ) ;
52-
53- // Audio playback sync
54- useEffect ( ( ) => {
55- if ( ! session ?. metadata . audioData || ! audioRef . current ) return ;
56-
57- // Decode base64 and create audio blob
58- const audioBlob = new Blob (
59- [ Uint8Array . from ( atob ( session . metadata . audioData ) , c => c . charCodeAt ( 0 ) ) ] ,
60- { type : session . metadata . audioMimeType || 'audio/webm' }
61- ) ;
62- const audioUrl = URL . createObjectURL ( audioBlob ) ;
63- audioRef . current . src = audioUrl ;
64-
65- return ( ) => URL . revokeObjectURL ( audioUrl ) ;
66- } , [ session ] ) ;
67-
68- // Sync audio with playback
69- useEffect ( ( ) => {
70- if ( ! audioRef . current || ! session ?. metadata . audioData ) return ;
71-
72- if ( isPlaying ) {
73- audioRef . current . play ( ) . catch ( err => console . error ( 'Audio play failed:' , err ) ) ;
74- } else {
75- audioRef . current . pause ( ) ;
76- }
77- } , [ isPlaying , session ] ) ;
78-
79- // Sync audio seek
80- useEffect ( ( ) => {
81- if ( ! audioRef . current || ! session ?. metadata . audioData ) return ;
82- audioRef . current . currentTime = playbackTime / 1000 ; // Convert ms to seconds
83- } , [ playbackTime , session ] ) ;
84-
85- // Sync playback speed
86- useEffect ( ( ) => {
87- if ( ! audioRef . current || ! session ?. metadata . audioData ) return ;
88- audioRef . current . playbackRate = playbackSpeed ;
89- } , [ playbackSpeed , session ] ) ;
51+ const [ isEditPanelOpen , setIsEditPanelOpen ] = useState ( false ) ;
52+ const [ trimStart , setTrimStart ] = useState ( '00:00' ) ;
53+ const [ trimEnd , setTrimEnd ] = useState ( '00:00' ) ;
9054
9155 // Only show full detailed controls if we have a session or are recording
9256 // Otherwise show a compact "Start Recording" or "Load Session" button
@@ -182,14 +146,6 @@ export const RecorderControls = ({
182146 { /* File Controls (Load/Save) */ }
183147 { ! isRecording && (
184148 < div className = "flex gap-1" >
185- { /* Mic Toggle */ }
186- < button
187- onClick = { ( ) => setIsAudioEnabled ( ! isAudioEnabled ) }
188- className = { `p-1 transition-colors ${ isAudioEnabled ? 'text-red-500' : 'hover:text-blue-500' } ` }
189- title = { isAudioEnabled ? 'Microphone enabled' : 'Enable microphone' }
190- >
191- < Mic className = "w-3.5 h-3.5" />
192- </ button >
193149 < button
194150 onClick = { ( ) => fileInputRef . current ?. click ( ) }
195151 className = "p-1 hover:text-blue-500 transition-colors"
@@ -264,8 +220,98 @@ export const RecorderControls = ({
264220 </ div >
265221 ) }
266222
267- { /* Hidden audio element for narration playback */ }
268- < audio ref = { audioRef } style = { { display : 'none' } } />
223+ { /* Edit Panel (Only if session exists and not recording) */ }
224+ { session && ! isRecording && (
225+ < div className = "space-y-2" >
226+ < button
227+ onClick = { ( ) => setIsEditPanelOpen ( ! isEditPanelOpen ) }
228+ className = { `w-full flex items-center justify-between p-2 rounded ${ styles . button } text-xs` }
229+ >
230+ < div className = "flex items-center gap-2" >
231+ < Scissors className = "w-3.5 h-3.5" />
232+ < span className = { styles . text } > Edit Recording</ span >
233+ </ div >
234+ < ChevronDown className = { `w-3 h-3 transition-transform ${ isEditPanelOpen ? 'rotate-180' : '' } ` } />
235+ </ button >
236+
237+ { isEditPanelOpen && (
238+ < div className = "space-y-3 p-3 bg-black/20 rounded-lg" >
239+ { /* Trim Controls */ }
240+ < div className = "space-y-2" >
241+ < label className = { `${ styles . text } block` } > Trim</ label >
242+ < div className = "flex gap-2 items-center" >
243+ < input
244+ type = "text"
245+ value = { trimStart }
246+ onChange = { ( e ) => setTrimStart ( e . target . value ) }
247+ placeholder = "00:00"
248+ className = "bg-black/30 border border-white/10 rounded px-2 py-1 text-xs w-16 font-mono"
249+ />
250+ < span className = "text-xs opacity-50" > to</ span >
251+ < input
252+ type = "text"
253+ value = { trimEnd }
254+ onChange = { ( e ) => setTrimEnd ( e . target . value ) }
255+ placeholder = { formatTime ( session . metadata . duration ) }
256+ className = "bg-black/30 border border-white/10 rounded px-2 py-1 text-xs w-16 font-mono"
257+ />
258+ < button
259+ onClick = { ( ) => {
260+ const start = parseTimeString ( trimStart ) ;
261+ const end = parseTimeString ( trimEnd ) || session . metadata . duration ;
262+ if ( start < end ) {
263+ trimSession ( start , end ) ;
264+ setIsEditPanelOpen ( false ) ;
265+ }
266+ } }
267+ className = "px-3 py-1 bg-purple-600 hover:bg-purple-700 rounded text-xs transition-colors"
268+ >
269+ Apply
270+ </ button >
271+ </ div >
272+ </ div >
273+
274+ { /* Event Deletion Controls */ }
275+ < div className = "space-y-2" >
276+ < label className = { `${ styles . text } block` } > Delete Events</ label >
277+ < div className = "flex gap-2 flex-wrap" >
278+ < button
279+ onClick = { ( ) => {
280+ const lastIdx = session . events . length - 1 ;
281+ if ( lastIdx >= 0 ) deleteEvent ( lastIdx ) ;
282+ } }
283+ className = "px-2 py-1 bg-red-600/20 hover:bg-red-600/40 border border-red-600/30 rounded text-xs transition-colors flex items-center gap-1"
284+ >
285+ < Trash2 className = "w-3 h-3" />
286+ Undo Last
287+ </ button >
288+ < button
289+ onClick = { ( ) => deleteEventsByType ( 'camera' ) }
290+ className = "px-2 py-1 bg-blue-600/20 hover:bg-blue-600/40 border border-blue-600/30 rounded text-xs transition-colors"
291+ >
292+ Delete Camera
293+ </ button >
294+ < button
295+ onClick = { ( ) => deleteEventsByType ( 'annotation' ) }
296+ className = "px-2 py-1 bg-yellow-600/20 hover:bg-yellow-600/40 border border-yellow-600/30 rounded text-xs transition-colors"
297+ >
298+ Delete Annotations
299+ </ button >
300+ </ div >
301+ </ div >
302+ </ div >
303+ ) }
304+ </ div >
305+ ) }
269306 </ div >
270307 ) ;
271308} ;
309+
310+ // Helper function to parse MM:SS format
311+ const parseTimeString = ( timeStr : string ) : number => {
312+ const parts = timeStr . split ( ':' ) ;
313+ if ( parts . length !== 2 ) return 0 ;
314+ const minutes = parseInt ( parts [ 0 ] , 10 ) || 0 ;
315+ const seconds = parseInt ( parts [ 1 ] , 10 ) || 0 ;
316+ return ( minutes * 60 + seconds ) * 1000 ; // Convert to milliseconds
317+ } ;
0 commit comments