@@ -35,6 +35,7 @@ const VideoControls: React.FC<{
3535 selectIsMuted : ( state : RootState ) => boolean ,
3636 selectVolume : ( state : RootState ) => number ,
3737 selectIsPlayPreview : ( state : RootState ) => boolean ,
38+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
3839 setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
3940 setIsMuted : ActionCreatorWithPayload < boolean , string > ,
4041 setVolume : ActionCreatorWithPayload < number , string > ,
@@ -47,6 +48,7 @@ const VideoControls: React.FC<{
4748 selectIsMuted,
4849 selectVolume,
4950 selectIsPlayPreview,
51+ setCurrentlyAt,
5052 setIsPlaying,
5153 setIsMuted,
5254 setVolume,
@@ -75,6 +77,8 @@ const VideoControls: React.FC<{
7577 < div css = { videoControlsRowStyle } >
7678 < TimeDisplay
7779 selectCurrentlyAt = { selectCurrentlyAt }
80+ setCurrentlyAt = { setCurrentlyAt }
81+ setIsPlaying = { setIsPlaying }
7882 />
7983 { jumpToPreviousSegment && (
8084 < PreviousButton
@@ -331,41 +335,145 @@ const NextButton: React.FC<{
331335 */
332336const TimeDisplay : React . FC < {
333337 selectCurrentlyAt : ( state : RootState ) => number ,
338+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
339+ setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
334340} > = ( {
335341 selectCurrentlyAt,
342+ setCurrentlyAt,
343+ setIsPlaying,
336344} ) => {
337345
338346 const { t } = useTranslation ( ) ;
347+ const theme = useTheme ( ) ;
339348
340349 // Init redux variables
341- const currentlyAt = useAppSelector ( selectCurrentlyAt ) ;
342350 const duration = useAppSelector ( selectDuration ) ;
343- const theme = useTheme ( ) ;
351+
352+ const timeDisplayStyle = css ( {
353+ display : "flex" ,
354+ flexDirection : "row" ,
355+ gap : "5px" ,
356+ alignItems : "center" ,
357+ } ) ;
344358
345359 const timeTextStyle = ( theme : Theme ) => css ( {
346360 display : "inline-block" ,
347361 color : `${ theme . text } ` ,
348362 } ) ;
349363
350364 return (
351- < div css = { { display : "flex" , flexDirection : "row" , gap : "5px" } } >
352- < ThemedTooltip title = { t ( "video.current-time-tooltip" ) } >
353- < time css = { timeTextStyle ( theme ) }
354- tabIndex = { 0 } role = "timer" aria-label = { t ( "video.time-aria" ) + ": " + convertMsToReadableString ( currentlyAt ) } >
355- { new Date ( ( currentlyAt ? currentlyAt : 0 ) ) . toISOString ( ) . substr ( 11 , 10 ) }
356- </ time >
357- </ ThemedTooltip >
365+ < div css = { timeDisplayStyle } >
366+ < CurrentTime
367+ selectCurrentlyAt = { selectCurrentlyAt }
368+ setCurrentlyAt = { setCurrentlyAt }
369+ setIsPlaying = { setIsPlaying }
370+ />
358371 < div css = { undisplay ( BREAKPOINTS . medium ) } > { " / " } </ div >
359372 < ThemedTooltip title = { t ( "video.time-duration-tooltip" ) } >
360373 < div css = { [ timeTextStyle ( theme ) , undisplay ( BREAKPOINTS . medium ) ] }
361- tabIndex = { 0 } aria-label = { t ( "video.duration-aria" ) + ": " + convertMsToReadableString ( duration ) } >
362- { new Date ( ( duration ? duration : 0 ) ) . toISOString ( ) . substr ( 11 , 10 ) }
374+ tabIndex = { 0 }
375+ aria-label = { t ( "video.duration-aria" ) + ": " + convertMsToReadableString ( duration ) }
376+ >
377+ { formatMs ( duration ? duration : 0 ) }
363378 </ div >
364379 </ ThemedTooltip >
365380 </ div >
366381 ) ;
367382} ;
368383
384+ const CurrentTime : React . FC < {
385+ selectCurrentlyAt : ( state : RootState ) => number ;
386+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
387+ setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
388+ } > = ( {
389+ selectCurrentlyAt,
390+ setCurrentlyAt,
391+ setIsPlaying,
392+ } ) => {
393+ const { t } = useTranslation ( ) ;
394+ const dispatch = useAppDispatch ( ) ;
395+
396+ const currentlyAt = useAppSelector ( selectCurrentlyAt ) ;
397+
398+ const [ editing , setEditing ] = React . useState ( false ) ;
399+ const [ value , setValue ] = React . useState ( formatMs ( currentlyAt ) ) ;
400+
401+ const parseTime = ( value : string ) => {
402+ const parts = value . split ( ":" ) . map ( Number ) ;
403+ if ( parts . some ( isNaN ) ) {
404+ return null ;
405+ }
406+
407+ const [ hh = 0 , mm = 0 , ss = 0 ] = parts ;
408+ return ( ( hh * 60 + mm ) * 60 + ss ) * 1000 ;
409+ } ;
410+
411+ React . useEffect ( ( ) => {
412+ if ( ! editing ) {
413+ setValue ( formatMs ( currentlyAt ) ) ;
414+ }
415+ } , [ currentlyAt , editing ] ) ;
416+
417+ const commit = ( ) => {
418+ const parsedTime = parseTime ( value ) ;
419+ if ( parsedTime ) {
420+ dispatch ( setCurrentlyAt ( parsedTime ) ) ;
421+ }
422+ setEditing ( false ) ;
423+ } ;
424+
425+ const cancel = ( ) => {
426+ setValue ( formatMs ( currentlyAt ) ) ;
427+ setEditing ( false ) ;
428+ } ;
429+
430+ const inputStyle = css ( {
431+ maxWidth : "77px" ,
432+ } ) ;
433+
434+ return (
435+ < ThemedTooltip title = { t ( "video.current-time-tooltip" ) } >
436+ { editing ? (
437+ < input
438+ autoFocus
439+ value = { value }
440+ onChange = { e => setValue ( e . target . value ) }
441+ onBlur = { commit }
442+ onKeyDown = { e => {
443+ if ( e . key === "Enter" ) { commit ( ) ; }
444+ if ( e . key === "Escape" ) { cancel ( ) ; }
445+ } }
446+ aria-label = { t ( "video.time-aria" ) }
447+ css = { inputStyle }
448+ />
449+ ) : (
450+ < time
451+ tabIndex = { 0 }
452+ role = "timer"
453+ onClick = { ( ) => {
454+ setEditing ( true ) ;
455+ dispatch ( setIsPlaying ( false ) ) ;
456+ } }
457+ onKeyDown = { e => {
458+ if ( e . key === "Enter" || e . key === " " ) {
459+ e . preventDefault ( ) ;
460+ setEditing ( true ) ;
461+ dispatch ( setIsPlaying ( false ) ) ;
462+ }
463+ } }
464+ aria-label = { t ( "video.time-aria" ) + ": " + convertMsToReadableString ( currentlyAt ) }
465+ >
466+ { formatMs ( currentlyAt ) }
467+ </ time >
468+ ) }
469+ </ ThemedTooltip >
470+ ) ;
471+ } ;
472+
473+ const formatMs = ( ms : number ) => {
474+ return new Date ( ms ) . toISOString ( ) . substr ( 11 , 10 ) ;
475+ } ;
476+
369477const VolumeSlider : React . FC < {
370478 selectIsMuted : ( state : RootState ) => boolean ,
371479 setIsMuted : ActionCreatorWithPayload < boolean , string > ,
0 commit comments