1- import React from 'react' ;
2- import { cn } from "@/lib/utils" ;
3-
4- interface AudioProps {
5- id ?: string ;
6- src : string ;
7- caption ?: string ;
8- align ?: 'left' | 'center' | 'right' ;
9- styles ?: {
10- bold ?: boolean ;
11- italic ?: boolean ;
12- underline ?: boolean ;
13- } ;
1+ "use client"
2+
3+ import { cn } from '@/lib/utils'
4+ import { Button } from '@/components/ui/button'
5+ import { Music , Pause , Play } from 'lucide-react'
6+ import { useState , useRef , useEffect } from 'react'
7+
8+ interface AudioBlockProps {
9+ content : string
10+ id : string
11+ onUpdate ?: ( content : string ) => void
12+ isEditing ?: boolean
13+ metadata ?: {
14+ caption ?: string
15+ alignment ?: 'left' | 'center' | 'right'
16+ styles ?: {
17+ bold ?: boolean
18+ italic ?: boolean
19+ underline ?: boolean
20+ }
21+ }
1422}
1523
16- export function Audio ( { id, src, caption, align = 'left' , styles } : AudioProps ) {
17- const alignClass = align === 'center' ? 'mx-auto' : align === 'right' ? 'ml-auto' : '' ;
18- return (
19- < div id = { id } className = { `mb-6 py-1 ${ alignClass } ` } >
20- < audio controls className = "w-full bg-background border border-border rounded-md" >
21- < source src = { src } type = "audio/mpeg" />
22- < span className = "text-muted-foreground" > Your browser does not support the audio element.</ span >
23- </ audio >
24- { caption && < p className = { cn (
25- "mt-2 text-sm text-muted-foreground" ,
26- styles ?. bold && 'font-bold' ,
27- styles ?. italic && 'italic' ,
28- styles ?. underline && 'underline'
29- ) } > { caption } </ p > }
30- </ div >
31- ) ;
24+ interface AudioBlockContent {
25+ url : string
26+ caption ?: string
27+ alignment ?: 'left' | 'center' | 'right'
3228}
29+
30+ export function AudioBlock ( { content, id, onUpdate, isEditing, metadata } : AudioBlockProps ) {
31+ const [ isHovered , setIsHovered ] = useState ( false )
32+ const [ isPlaying , setIsPlaying ] = useState ( false )
33+ const audioRef = useRef < HTMLAudioElement > ( null )
34+
35+ const handlePlayPause = ( e : React . MouseEvent ) => {
36+ e . stopPropagation ( )
37+ if ( audioRef . current ) {
38+ if ( isPlaying ) {
39+ audioRef . current . pause ( )
40+ } else {
41+ audioRef . current . play ( )
42+ }
43+ setIsPlaying ( ! isPlaying )
44+ }
45+ }
46+
47+ // Add event listeners to sync the isPlaying state with audio events
48+ useEffect ( ( ) => {
49+ const audio = audioRef . current
50+ if ( ! audio ) return
51+
52+ const handlePlay = ( ) => setIsPlaying ( true )
53+ const handlePause = ( ) => setIsPlaying ( false )
54+ const handleEnded = ( ) => setIsPlaying ( false )
55+
56+ audio . addEventListener ( 'play' , handlePlay )
57+ audio . addEventListener ( 'pause' , handlePause )
58+ audio . addEventListener ( 'ended' , handleEnded )
59+
60+ return ( ) => {
61+ audio . removeEventListener ( 'play' , handlePlay )
62+ audio . removeEventListener ( 'pause' , handlePause )
63+ audio . removeEventListener ( 'ended' , handleEnded )
64+ }
65+ } , [ ] )
66+
67+ const parseAudioContent = ( content : string ) : AudioBlockContent => {
68+ try {
69+ return typeof content === 'string' ? JSON . parse ( content ) : content
70+ } catch {
71+ return {
72+ url : content ,
73+ caption : metadata ?. caption || '' ,
74+ alignment : metadata ?. alignment || 'center'
75+ }
76+ }
77+ }
78+
79+ const UploadButton = ( ) => (
80+ < div className = "text-center" >
81+ < input
82+ type = "file"
83+ id = { `audio-upload-${ id } ` }
84+ className = "hidden"
85+ accept = "audio/*"
86+ />
87+ < div className = "flex flex-col items-center gap-2" >
88+ < Button
89+ variant = "ghost"
90+ className = "w-full justify-start h-auto py-6 px-8 hover:bg-accent flex flex-col items-center gap-2"
91+ onClick = { ( e ) => {
92+ e . stopPropagation ( )
93+ document . getElementById ( `audio-upload-${ id } ` ) ?. click ( )
94+ } }
95+ >
96+ < Music className = "h-8 w-8 text-muted-foreground" />
97+ < span className = "font-medium" > Upload Audio</ span >
98+ < p className = "text-sm text-muted-foreground" >
99+ Click to upload or drag and drop
100+ </ p >
101+ </ Button >
102+ </ div >
103+ </ div >
104+ )
105+
106+ if ( ! content && isEditing ) {
107+ return (
108+ < div className = "flex items-center justify-center border-2 border-dashed border-muted-foreground/25 rounded-lg my-4" >
109+ < UploadButton />
110+ </ div >
111+ )
112+ }
113+
114+ const audioContent = parseAudioContent ( content )
115+ const alignment = audioContent . alignment || metadata ?. alignment || 'center'
116+
117+ return (
118+ < figure
119+ className = { cn (
120+ "py-1 mb-6 relative group" ,
121+ "flex flex-col" ,
122+ alignment === 'left' && "items-start" ,
123+ alignment === 'center' && "items-center" ,
124+ alignment === 'right' && "items-end" ,
125+ ) }
126+ onMouseEnter = { ( ) => setIsHovered ( true ) }
127+ onMouseLeave = { ( ) => setIsHovered ( false ) }
128+ >
129+ < div className = "relative w-full max-w-[500px]" >
130+ < div className = "bg-muted rounded-lg p-4 flex items-center gap-4" >
131+ < div className = "w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center" >
132+ < Music className = "h-6 w-6 text-primary" />
133+ </ div >
134+ < audio
135+ ref = { audioRef }
136+ src = { audioContent . url }
137+ controls
138+ className = "w-full"
139+ />
140+ </ div >
141+ { isEditing && isHovered && (
142+ < div
143+ className = "absolute inset-0 bg-black/50 flex items-center justify-center gap-2 rounded-lg transition-opacity"
144+ onClick = { ( e ) => e . stopPropagation ( ) }
145+ >
146+ < input
147+ type = "file"
148+ id = { `audio-change-${ id } ` }
149+ className = "hidden"
150+ accept = "audio/*"
151+ />
152+ < Button
153+ variant = "secondary"
154+ className = "gap-2"
155+ onClick = { ( e ) => {
156+ e . stopPropagation ( )
157+ document . getElementById ( `audio-change-${ id } ` ) ?. click ( )
158+ } }
159+ >
160+ < Music className = "h-4 w-4" />
161+ Change Audio
162+ </ Button >
163+ < Button
164+ variant = "secondary"
165+ className = "gap-2"
166+ onClick = { handlePlayPause }
167+ >
168+ { isPlaying ? (
169+ < >
170+ < Pause className = "h-4 w-4" />
171+ Pause
172+ </ >
173+ ) : (
174+ < >
175+ < Play className = "h-4 w-4" />
176+ Play
177+ </ >
178+ ) }
179+ </ Button >
180+ </ div >
181+ ) }
182+ </ div >
183+ { audioContent . caption && (
184+ < figcaption className = { cn (
185+ "mt-2 text-sm text-muted-foreground italic" ,
186+ alignment === 'left' && "text-left" ,
187+ alignment === 'center' && "text-center" ,
188+ alignment === 'right' && "text-right" ,
189+ metadata ?. styles ?. bold && "font-bold" ,
190+ metadata ?. styles ?. italic && "italic" ,
191+ metadata ?. styles ?. underline && "underline"
192+ ) } >
193+ { audioContent . caption }
194+ </ figcaption >
195+ ) }
196+ </ figure >
197+ )
198+ }
0 commit comments