@@ -11,7 +11,7 @@ import {
1111} from "../redux/videoSlice" ;
1212
1313import { convertMsToReadableString } from "../util/utilityFunctions" ;
14- import { BREAKPOINTS , basicButtonStyle , undisplayContainer } from "../cssStyles" ;
14+ import { BREAKPOINTS , basicButtonStyle , undisplay , undisplayContainer } from "../cssStyles" ;
1515
1616import { KEYMAP , rewriteKeys } from "../globalKeys" ;
1717import { useTranslation } from "react-i18next" ;
@@ -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,
@@ -76,6 +78,8 @@ const VideoControls: React.FC<{
7678 < div css = { videoControlsRowStyle } >
7779 < TimeDisplay
7880 selectCurrentlyAt = { selectCurrentlyAt }
81+ setCurrentlyAt = { setCurrentlyAt }
82+ setIsPlaying = { setIsPlaying }
7983 />
8084 { jumpToPreviousSegment && (
8185 < PreviousButton
@@ -344,21 +348,25 @@ const NextButton: React.FC<{
344348 */
345349const TimeDisplay : React . FC < {
346350 selectCurrentlyAt : ( state : RootState ) => number ,
351+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
352+ setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
347353} > = ( {
348354 selectCurrentlyAt,
355+ setCurrentlyAt,
356+ setIsPlaying,
349357} ) => {
350358
351359 const { t } = useTranslation ( ) ;
360+ const theme = useTheme ( ) ;
352361
353362 // Init redux variables
354- const currentlyAt = useAppSelector ( selectCurrentlyAt ) ;
355363 const duration = useAppSelector ( selectDuration ) ;
356- const theme = useTheme ( ) ;
357364
358365 const timeDisplayStyle = css ( {
359366 display : "flex" ,
360367 flexDirection : "row" ,
361368 gap : "5px" ,
369+ alignItems : "center" ,
362370 } ) ;
363371
364372 const timeTextStyle = ( theme : Theme ) => css ( {
@@ -368,23 +376,117 @@ const TimeDisplay: React.FC<{
368376
369377 return (
370378 < div css = { timeDisplayStyle } >
371- < ThemedTooltip title = { t ( "video.current-time-tooltip" ) } >
372- < time css = { timeTextStyle ( theme ) }
373- tabIndex = { 0 } role = "timer" aria-label = { t ( "video.time-aria" ) + ": " + convertMsToReadableString ( currentlyAt ) } >
374- { new Date ( ( currentlyAt ? currentlyAt : 0 ) ) . toISOString ( ) . substr ( 11 , 10 ) }
375- </ time >
376- </ ThemedTooltip >
377- < div css = { undisplayContainer ( BREAKPOINTS . medium ) } > { " / " } </ div >
379+ < CurrentTime
380+ selectCurrentlyAt = { selectCurrentlyAt }
381+ setCurrentlyAt = { setCurrentlyAt }
382+ setIsPlaying = { setIsPlaying }
383+ />
384+ < div css = { undisplay ( BREAKPOINTS . medium ) } > { " / " } </ div >
378385 < ThemedTooltip title = { t ( "video.time-duration-tooltip" ) } >
379- < div css = { [ timeTextStyle ( theme ) , undisplayContainer ( BREAKPOINTS . medium ) ] }
380- tabIndex = { 0 } aria-label = { t ( "video.duration-aria" ) + ": " + convertMsToReadableString ( duration ) } >
381- { new Date ( ( duration ? duration : 0 ) ) . toISOString ( ) . substr ( 11 , 10 ) }
386+ < div css = { [ timeTextStyle ( theme ) , undisplay ( BREAKPOINTS . medium ) ] }
387+ tabIndex = { 0 }
388+ aria-label = { t ( "video.duration-aria" ) + ": " + convertMsToReadableString ( duration ) }
389+ >
390+ { formatMs ( duration ? duration : 0 ) }
382391 </ div >
383392 </ ThemedTooltip >
384393 </ div >
385394 ) ;
386395} ;
387396
397+ const CurrentTime : React . FC < {
398+ selectCurrentlyAt : ( state : RootState ) => number ;
399+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
400+ setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
401+ } > = ( {
402+ selectCurrentlyAt,
403+ setCurrentlyAt,
404+ setIsPlaying,
405+ } ) => {
406+ const { t } = useTranslation ( ) ;
407+ const dispatch = useAppDispatch ( ) ;
408+
409+ const currentlyAt = useAppSelector ( selectCurrentlyAt ) ;
410+
411+ const [ editing , setEditing ] = React . useState ( false ) ;
412+ const [ value , setValue ] = React . useState ( formatMs ( currentlyAt ) ) ;
413+
414+ const parseTime = ( value : string ) => {
415+ const parts = value . split ( ":" ) . map ( Number ) ;
416+ if ( parts . some ( isNaN ) ) {
417+ return null ;
418+ }
419+
420+ const [ hh = 0 , mm = 0 , ss = 0 ] = parts ;
421+ return ( ( hh * 60 + mm ) * 60 + ss ) * 1000 ;
422+ } ;
423+
424+ React . useEffect ( ( ) => {
425+ if ( ! editing ) {
426+ setValue ( formatMs ( currentlyAt ) ) ;
427+ }
428+ } , [ currentlyAt , editing ] ) ;
429+
430+ const commit = ( ) => {
431+ const parsedTime = parseTime ( value ) ;
432+ if ( parsedTime ) {
433+ dispatch ( setCurrentlyAt ( parsedTime ) ) ;
434+ }
435+ setEditing ( false ) ;
436+ } ;
437+
438+ const cancel = ( ) => {
439+ setValue ( formatMs ( currentlyAt ) ) ;
440+ setEditing ( false ) ;
441+ } ;
442+
443+ const inputStyle = css ( {
444+ maxWidth : "77px" ,
445+ } ) ;
446+
447+ return (
448+ < ThemedTooltip title = { t ( "video.current-time-tooltip" ) } >
449+ { editing ? (
450+ < input
451+ autoFocus
452+ value = { value }
453+ onChange = { e => setValue ( e . target . value ) }
454+ onBlur = { commit }
455+ onKeyDown = { e => {
456+ if ( e . key === "Enter" ) { commit ( ) ; }
457+ if ( e . key === "Escape" ) { cancel ( ) ; }
458+ } }
459+ aria-label = { t ( "video.time-aria" ) }
460+ css = { inputStyle }
461+ />
462+ ) : (
463+ < time
464+ tabIndex = { 0 }
465+ role = "timer"
466+ onClick = { ( ) => {
467+ setEditing ( true ) ;
468+ dispatch ( setIsPlaying ( false ) ) ;
469+ } }
470+ onKeyDown = { e => {
471+ if ( e . key === "Enter" || e . key === " " ) {
472+ e . preventDefault ( ) ;
473+ setEditing ( true ) ;
474+ dispatch ( setIsPlaying ( false ) ) ;
475+ }
476+ } }
477+ aria-label = { t ( "video.time-aria" ) + ": " + convertMsToReadableString ( currentlyAt ) }
478+ >
479+ { formatMs ( currentlyAt ) }
480+ </ time >
481+ ) }
482+ </ ThemedTooltip >
483+ ) ;
484+ } ;
485+
486+ const formatMs = ( ms : number ) => {
487+ return new Date ( ms ) . toISOString ( ) . substr ( 11 , 10 ) ;
488+ } ;
489+
388490const VolumeSlider : React . FC < {
389491 selectIsMuted : ( state : RootState ) => boolean ,
390492 setIsMuted : ActionCreatorWithPayload < boolean , string > ,
0 commit comments