Skip to content

Commit d3ffef4

Browse files
authored
Merge pull request #1675 from Arnei/time-display-as-input-field
Allow typing time in current time field
2 parents 289944c + 0c43779 commit d3ffef4

4 files changed

Lines changed: 119 additions & 13 deletions

File tree

src/main/Cutting.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ const Cutting: React.FC = () => {
135135
selectIsMuted={selectIsMuted}
136136
selectVolume={selectVolume}
137137
selectIsPlayPreview={selectIsPlayPreview}
138+
setCurrentlyAt={setCurrentlyAt}
138139
setIsPlaying={setIsPlaying}
139140
setIsMuted={setIsMuted}
140141
setVolume={setVolume}

src/main/SubtitleVideoArea.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import VideoControls from "./VideoControls";
2020
import Select from "react-select";
2121
import { selectFieldStyle } from "../cssStyles";
2222
import { ActionCreatorWithPayload, AsyncThunk } from "@reduxjs/toolkit";
23+
import { setCurrentlyAt } from "../redux/subtitleSlice";
2324

2425
/**
2526
* A part of the subtitle editor that displays a video and related controls
@@ -163,6 +164,7 @@ const SubtitleVideoArea: React.FC<{
163164
selectIsMuted={selectIsMuted}
164165
selectVolume={selectVolume}
165166
selectIsPlayPreview={selectIsPlayPreview}
167+
setCurrentlyAt={setCurrentlyAt}
166168
setIsPlaying={setIsPlaying}
167169
setIsMuted={setIsMuted}
168170
setVolume={setVolume}

src/main/ThumbnailGeneration.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ const ThumbnailGeneration: React.FC = () => {
178178
selectIsMuted={selectIsMuted}
179179
selectVolume={selectVolume}
180180
selectIsPlayPreview={selectIsPlayPreview}
181+
setCurrentlyAt={setCurrentlyAt}
181182
setIsPlaying={setIsPlaying}
182183
setIsMuted={setIsMuted}
183184
setVolume={setVolume}

src/main/VideoControls.tsx

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "../redux/videoSlice";
1212

1313
import { convertMsToReadableString } from "../util/utilityFunctions";
14-
import { BREAKPOINTS, basicButtonStyle, undisplayContainer } from "../cssStyles";
14+
import { BREAKPOINTS, basicButtonStyle, undisplay, undisplayContainer } from "../cssStyles";
1515

1616
import { KEYMAP, rewriteKeys } from "../globalKeys";
1717
import { 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
*/
345349
const 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+
388490
const VolumeSlider: React.FC<{
389491
selectIsMuted: (state: RootState) => boolean,
390492
setIsMuted: ActionCreatorWithPayload<boolean, string>,

0 commit comments

Comments
 (0)