1- import { mdiClose , mdiCog } from '@mdi/js' ;
1+ import { mdiClose , mdiCog , mdiMagnifyMinus , mdiMagnifyPlus } from '@mdi/js' ;
22import Icon from '@mdi/react' ;
33import { memo , useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
44import { useTranslation } from 'react-i18next' ;
@@ -16,6 +16,7 @@ interface ISpectrogram {
1616 readonly data : [ number , number ] [ ] ;
1717 readonly fftExecutor ?: FFTExecutor ;
1818 readonly renderFPS ?: number ;
19+ readonly zoomStep ?: number ;
1920 readonly onSpectrogramUpdate ?: ( minDB : number , maxDB : number ) => void ;
2021}
2122
@@ -32,13 +33,15 @@ export const Spectrogram = memo(
3233 data,
3334 fftExecutor,
3435 renderFPS = 2 ,
36+ zoomStep = 0.2 ,
3537 onSpectrogramUpdate
3638 } : ISpectrogram ) => {
3739 const { t } = useTranslation ( ) ;
3840
3941 const [ showSettings , setShowSettings ] = useState ( false ) ;
4042 const [ minDBState , setMinDBState ] = useState ( minDB ) ;
4143 const [ maxDBState , setMaxDBState ] = useState ( maxDB ) ;
44+ const [ zoomDuration , setZoomDuration ] = useState ( duration ) ;
4245
4346 const [ initialized , setInitialized ] = useState ( false ) ;
4447 const spectrogramRef = useRef < SpectrogramCore | null > ( null ) ;
@@ -116,7 +119,7 @@ export const Spectrogram = memo(
116119 }
117120
118121 const total = dataDuration ?? 0 ;
119- const window = duration ;
122+ const window = zoomDuration ;
120123 const maxStart = Math . max ( 0 , total - window ) ;
121124 const start = maxStart * timePercent ;
122125 const end = start + window ;
@@ -130,7 +133,7 @@ export const Spectrogram = memo(
130133 } ;
131134 rafId = requestAnimationFrame ( renderLoop ) ;
132135 return ( ) => cancelAnimationFrame ( rafId ) ;
133- } , [ dataDuration , duration , freqRange , initialized , renderFPS , timePercent ] ) ;
136+ } , [ dataDuration , duration , freqRange , initialized , renderFPS , timePercent , zoomDuration ] ) ;
134137
135138 useEffect ( ( ) => {
136139 if ( initialized ) {
@@ -170,25 +173,57 @@ export const Spectrogram = memo(
170173 [ minDBState , onSpectrogramUpdate , spectrogramRef ]
171174 ) ;
172175
176+ const handleZoomIn = useCallback ( ( ) => {
177+ if ( dataDuration ) {
178+ setZoomDuration ( ( prev ) => {
179+ const next = prev * ( 1 - zoomStep ) ;
180+ return Math . max ( next , dataDuration * 0.01 ) ;
181+ } ) ;
182+ }
183+ } , [ dataDuration , zoomStep ] ) ;
184+
185+ const handleZoomOut = useCallback ( ( ) => {
186+ if ( dataDuration ) {
187+ setZoomDuration ( ( prev ) => {
188+ const next = prev * ( 1 + zoomStep ) ;
189+ return Math . min ( next , dataDuration ) ;
190+ } ) ;
191+ }
192+ } , [ dataDuration , zoomStep ] ) ;
193+
173194 return (
174195 < div className = "relative flex h-full w-full flex-col" >
175196 < canvas className = "block" ref = { canvasRef } />
176197
177- < div className = "absolute top-5 left-15 flex items-center space-x -1" >
198+ < div className = "absolute top-5 left-15 mr-15 flex flex-wrap items-center gap -1" >
178199 { title && (
179200 < div className = "flex h-6 items-center rounded bg-black/50 px-3 text-sm font-bold text-white select-none" >
180201 { title }
181202 </ div >
182203 ) }
183204
184205 < button
185- className = "flex size-6 cursor-pointer items-center justify-center rounded bg-black/50 text-white opacity-50 transition-all hover:opacity-100"
206+ className = "flex size-6 flex-shrink-0 cursor-pointer items-center justify-center rounded bg-black/50 text-white opacity-50 transition-all hover:opacity-100"
186207 onClick = { ( ) => setShowSettings ( ( v ) => ! v ) }
187208 >
188- < Icon path = { mdiCog } size = { 0.6 } />
209+ < Icon path = { mdiCog } size = { 0.7 } />
210+ </ button >
211+
212+ < button
213+ className = "flex size-6 flex-shrink-0 cursor-pointer items-center justify-center rounded bg-black/50 text-white opacity-50 transition-all hover:opacity-100"
214+ onClick = { handleZoomIn }
215+ >
216+ < Icon path = { mdiMagnifyPlus } size = { 0.7 } />
217+ </ button >
218+
219+ < button
220+ className = "flex size-6 flex-shrink-0 cursor-pointer items-center justify-center rounded bg-black/50 text-white opacity-50 transition-all hover:opacity-100"
221+ onClick = { handleZoomOut }
222+ >
223+ < Icon path = { mdiMagnifyMinus } size = { 0.7 } />
189224 </ button >
190225
191- < div className = "flex h-6 items-center rounded bg-black/50 opacity-50 transition-all hover:opacity-100" >
226+ < div className = "flex h-6 max-w-36 items-center rounded bg-black/50 opacity-50 transition-all hover:opacity-100" >
192227 < input
193228 type = "range"
194229 min = { 0 }
0 commit comments