@@ -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
@@ -332,21 +336,25 @@ const NextButton: React.FC<{
332336 */
333337const TimeDisplay : React . FC < {
334338 selectCurrentlyAt : ( state : RootState ) => number ,
339+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
340+ setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
335341} > = ( {
336342 selectCurrentlyAt,
343+ setCurrentlyAt,
344+ setIsPlaying,
337345} ) => {
338346
339347 const { t } = useTranslation ( ) ;
348+ const theme = useTheme ( ) ;
340349
341350 // Init redux variables
342- const currentlyAt = useAppSelector ( selectCurrentlyAt ) ;
343351 const duration = useAppSelector ( selectDuration ) ;
344- const theme = useTheme ( ) ;
345352
346353 const timeDisplayStyle = css ( {
347354 display : "flex" ,
348355 flexDirection : "row" ,
349356 gap : "5px" ,
357+ alignItems : "center" ,
350358 } ) ;
351359
352360 const timeTextStyle = ( theme : Theme ) => css ( {
@@ -356,23 +364,117 @@ const TimeDisplay: React.FC<{
356364
357365 return (
358366 < div css = { timeDisplayStyle } >
359- < ThemedTooltip title = { t ( "video.current-time-tooltip" ) } >
360- < time css = { timeTextStyle ( theme ) }
361- tabIndex = { 0 } role = "timer" aria-label = { t ( "video.time-aria" ) + ": " + convertMsToReadableString ( currentlyAt ) } >
362- { new Date ( ( currentlyAt ? currentlyAt : 0 ) ) . toISOString ( ) . substr ( 11 , 10 ) }
363- </ time >
364- </ ThemedTooltip >
365- < div css = { undisplayContainer ( BREAKPOINTS . medium ) } > { " / " } </ div >
367+ < CurrentTime
368+ selectCurrentlyAt = { selectCurrentlyAt }
369+ setCurrentlyAt = { setCurrentlyAt }
370+ setIsPlaying = { setIsPlaying }
371+ />
372+ < div css = { undisplay ( BREAKPOINTS . medium ) } > { " / " } </ div >
366373 < ThemedTooltip title = { t ( "video.time-duration-tooltip" ) } >
367- < div css = { [ timeTextStyle ( theme ) , undisplayContainer ( BREAKPOINTS . medium ) ] }
368- tabIndex = { 0 } aria-label = { t ( "video.duration-aria" ) + ": " + convertMsToReadableString ( duration ) } >
369- { new Date ( ( duration ? duration : 0 ) ) . toISOString ( ) . substr ( 11 , 10 ) }
374+ < div css = { [ timeTextStyle ( theme ) , undisplay ( BREAKPOINTS . medium ) ] }
375+ tabIndex = { 0 }
376+ aria-label = { t ( "video.duration-aria" ) + ": " + convertMsToReadableString ( duration ) }
377+ >
378+ { formatMs ( duration ? duration : 0 ) }
370379 </ div >
371380 </ ThemedTooltip >
372381 </ div >
373382 ) ;
374383} ;
375384
385+ const CurrentTime : React . FC < {
386+ selectCurrentlyAt : ( state : RootState ) => number ;
387+ setCurrentlyAt : ActionCreatorWithPayload < number , string > ,
388+ setIsPlaying : ActionCreatorWithPayload < boolean , string > ,
389+ } > = ( {
390+ selectCurrentlyAt,
391+ setCurrentlyAt,
392+ setIsPlaying,
393+ } ) => {
394+ const { t } = useTranslation ( ) ;
395+ const dispatch = useAppDispatch ( ) ;
396+
397+ const currentlyAt = useAppSelector ( selectCurrentlyAt ) ;
398+
399+ const [ editing , setEditing ] = React . useState ( false ) ;
400+ const [ value , setValue ] = React . useState ( formatMs ( currentlyAt ) ) ;
401+
402+ const parseTime = ( value : string ) => {
403+ const parts = value . split ( ":" ) . map ( Number ) ;
404+ if ( parts . some ( isNaN ) ) {
405+ return null ;
406+ }
407+
408+ const [ hh = 0 , mm = 0 , ss = 0 ] = parts ;
409+ return ( ( hh * 60 + mm ) * 60 + ss ) * 1000 ;
410+ } ;
411+
412+ React . useEffect ( ( ) => {
413+ if ( ! editing ) {
414+ setValue ( formatMs ( currentlyAt ) ) ;
415+ }
416+ } , [ currentlyAt , editing ] ) ;
417+
418+ const commit = ( ) => {
419+ const parsedTime = parseTime ( value ) ;
420+ if ( parsedTime ) {
421+ dispatch ( setCurrentlyAt ( parsedTime ) ) ;
422+ }
423+ setEditing ( false ) ;
424+ } ;
425+
426+ const cancel = ( ) => {
427+ setValue ( formatMs ( currentlyAt ) ) ;
428+ setEditing ( false ) ;
429+ } ;
430+
431+ const inputStyle = css ( {
432+ maxWidth : "77px" ,
433+ } ) ;
434+
435+ return (
436+ < ThemedTooltip title = { t ( "video.current-time-tooltip" ) } >
437+ { editing ? (
438+ < input
439+ autoFocus
440+ value = { value }
441+ onChange = { e => setValue ( e . target . value ) }
442+ onBlur = { commit }
443+ onKeyDown = { e => {
444+ if ( e . key === "Enter" ) { commit ( ) ; }
445+ if ( e . key === "Escape" ) { cancel ( ) ; }
446+ } }
447+ aria-label = { t ( "video.time-aria" ) }
448+ css = { inputStyle }
449+ />
450+ ) : (
451+ < time
452+ tabIndex = { 0 }
453+ role = "timer"
454+ onClick = { ( ) => {
455+ setEditing ( true ) ;
456+ dispatch ( setIsPlaying ( false ) ) ;
457+ } }
458+ onKeyDown = { e => {
459+ if ( e . key === "Enter" || e . key === " " ) {
460+ e . preventDefault ( ) ;
461+ setEditing ( true ) ;
462+ dispatch ( setIsPlaying ( false ) ) ;
463+ }
464+ } }
465+ aria-label = { t ( "video.time-aria" ) + ": " + convertMsToReadableString ( currentlyAt ) }
466+ >
467+ { formatMs ( currentlyAt ) }
468+ </ time >
469+ ) }
470+ </ ThemedTooltip >
471+ ) ;
472+ } ;
473+
474+ const formatMs = ( ms : number ) => {
475+ return new Date ( ms ) . toISOString ( ) . substr ( 11 , 10 ) ;
476+ } ;
477+
376478const VolumeSlider : React . FC < {
377479 selectIsMuted : ( state : RootState ) => boolean ,
378480 setIsMuted : ActionCreatorWithPayload < boolean , string > ,
0 commit comments