diff --git a/src/main/Cutting.tsx b/src/main/Cutting.tsx index 8df320b60..c298802f8 100644 --- a/src/main/Cutting.tsx +++ b/src/main/Cutting.tsx @@ -135,6 +135,7 @@ const Cutting: React.FC = () => { selectIsMuted={selectIsMuted} selectVolume={selectVolume} selectIsPlayPreview={selectIsPlayPreview} + setCurrentlyAt={setCurrentlyAt} setIsPlaying={setIsPlaying} setIsMuted={setIsMuted} setVolume={setVolume} diff --git a/src/main/SubtitleVideoArea.tsx b/src/main/SubtitleVideoArea.tsx index cbf03a789..86d05af59 100644 --- a/src/main/SubtitleVideoArea.tsx +++ b/src/main/SubtitleVideoArea.tsx @@ -20,6 +20,7 @@ import VideoControls from "./VideoControls"; import Select from "react-select"; import { selectFieldStyle } from "../cssStyles"; import { ActionCreatorWithPayload, AsyncThunk } from "@reduxjs/toolkit"; +import { setCurrentlyAt } from "../redux/subtitleSlice"; /** * A part of the subtitle editor that displays a video and related controls @@ -163,6 +164,7 @@ const SubtitleVideoArea: React.FC<{ selectIsMuted={selectIsMuted} selectVolume={selectVolume} selectIsPlayPreview={selectIsPlayPreview} + setCurrentlyAt={setCurrentlyAt} setIsPlaying={setIsPlaying} setIsMuted={setIsMuted} setVolume={setVolume} diff --git a/src/main/ThumbnailGeneration.tsx b/src/main/ThumbnailGeneration.tsx index 526d914a9..3a31768c8 100644 --- a/src/main/ThumbnailGeneration.tsx +++ b/src/main/ThumbnailGeneration.tsx @@ -178,6 +178,7 @@ const ThumbnailGeneration: React.FC = () => { selectIsMuted={selectIsMuted} selectVolume={selectVolume} selectIsPlayPreview={selectIsPlayPreview} + setCurrentlyAt={setCurrentlyAt} setIsPlaying={setIsPlaying} setIsMuted={setIsMuted} setVolume={setVolume} diff --git a/src/main/VideoControls.tsx b/src/main/VideoControls.tsx index b0646805e..56414b415 100644 --- a/src/main/VideoControls.tsx +++ b/src/main/VideoControls.tsx @@ -11,7 +11,7 @@ import { } from "../redux/videoSlice"; import { convertMsToReadableString } from "../util/utilityFunctions"; -import { BREAKPOINTS, basicButtonStyle, undisplayContainer } from "../cssStyles"; +import { BREAKPOINTS, basicButtonStyle, undisplay, undisplayContainer } from "../cssStyles"; import { KEYMAP, rewriteKeys } from "../globalKeys"; import { useTranslation } from "react-i18next"; @@ -35,6 +35,7 @@ const VideoControls: React.FC<{ selectIsMuted: (state: RootState) => boolean, selectVolume: (state: RootState) => number, selectIsPlayPreview: (state: RootState) => boolean, + setCurrentlyAt: ActionCreatorWithPayload, setIsPlaying: ActionCreatorWithPayload, setIsMuted: ActionCreatorWithPayload, setVolume: ActionCreatorWithPayload, @@ -47,6 +48,7 @@ const VideoControls: React.FC<{ selectIsMuted, selectVolume, selectIsPlayPreview, + setCurrentlyAt, setIsPlaying, setIsMuted, setVolume, @@ -76,6 +78,8 @@ const VideoControls: React.FC<{
{jumpToPreviousSegment && ( number, + setCurrentlyAt: ActionCreatorWithPayload, + setIsPlaying: ActionCreatorWithPayload, }> = ({ selectCurrentlyAt, + setCurrentlyAt, + setIsPlaying, }) => { const { t } = useTranslation(); + const theme = useTheme(); // Init redux variables - const currentlyAt = useAppSelector(selectCurrentlyAt); const duration = useAppSelector(selectDuration); - const theme = useTheme(); const timeDisplayStyle = css({ display: "flex", flexDirection: "row", gap: "5px", + alignItems: "center", }); const timeTextStyle = (theme: Theme) => css({ @@ -355,23 +363,117 @@ const TimeDisplay: React.FC<{ return (
- - - -
{" / "}
+ +
{" / "}
-
- {new Date((duration ? duration : 0)).toISOString().substr(11, 10)} +
+ {formatMs(duration ? duration : 0)}
); }; +const CurrentTime: React.FC<{ + selectCurrentlyAt: (state: RootState) => number; + setCurrentlyAt: ActionCreatorWithPayload, + setIsPlaying: ActionCreatorWithPayload, +}> = ({ + selectCurrentlyAt, + setCurrentlyAt, + setIsPlaying, +}) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const currentlyAt = useAppSelector(selectCurrentlyAt); + + const [editing, setEditing] = React.useState(false); + const [value, setValue] = React.useState(formatMs(currentlyAt)); + + const parseTime = (value: string) => { + const parts = value.split(":").map(Number); + if (parts.some(isNaN)) { + return null; + } + + const [hh = 0, mm = 0, ss = 0] = parts; + return ((hh * 60 + mm) * 60 + ss) * 1000; + }; + + React.useEffect(() => { + if (!editing) { + setValue(formatMs(currentlyAt)); + } + }, [currentlyAt, editing]); + + const commit = () => { + const parsedTime = parseTime(value); + if (parsedTime) { + dispatch(setCurrentlyAt(parsedTime)); + } + setEditing(false); + }; + + const cancel = () => { + setValue(formatMs(currentlyAt)); + setEditing(false); + }; + + const inputStyle = css({ + maxWidth: "77px", + }); + + return ( + + {editing ? ( + setValue(e.target.value)} + onBlur={commit} + onKeyDown={e => { + if (e.key === "Enter") { commit(); } + if (e.key === "Escape") { cancel(); } + }} + aria-label={t("video.time-aria")} + css={inputStyle} + /> + ) : ( + + )} + + ); +}; + +const formatMs = (ms: number) => { + return new Date(ms).toISOString().substr(11, 10); +}; + const VolumeSlider: React.FC<{ selectIsMuted: (state: RootState) => boolean, setIsMuted: ActionCreatorWithPayload,