From 1606fae5f041235372c8e1c5bbc234bd7e294eea Mon Sep 17 00:00:00 2001 From: Arnei Date: Thu, 30 May 2024 16:06:28 +0200 Subject: [PATCH] Add cue at current time for subtitles Adds a hotkey (Shift+Alt+e) that allows the user to add a new subtitle segment at the current point on the timeline. --- src/globalKeys.ts | 4 ++++ src/i18n/locales/en-US.json | 3 ++- src/main/SubtitleTimeline.tsx | 27 +++++++++++++++++++++++++++ src/redux/subtitleSlice.ts | 3 +-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/globalKeys.ts b/src/globalKeys.ts index 661b909cc..e850b98cc 100644 --- a/src/globalKeys.ts +++ b/src/globalKeys.ts @@ -127,5 +127,9 @@ export const KEYMAP: IKeyMap = { name: "subtitleList.deleteSegment", key: "Shift+Alt+d", }, + addCue: { + name: "subtitleList.addCue", + key: "Shift+Alt+e", + }, }, }; diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 1ed004ca4..cbfa5a4ff 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -272,7 +272,8 @@ "addSegmentBelow": "Add segment below", "jumpToSegmentAbove": "Jump to segment above", "jumpToSegmentBelow": "Jump to segment below", - "deleteSegment": "Delete segment" + "deleteSegment": "Delete segment", + "addCue": "Add segment at current time" }, "subtitleVideoArea": { diff --git a/src/main/SubtitleTimeline.tsx b/src/main/SubtitleTimeline.tsx index c7846a3f5..0b29ddf96 100644 --- a/src/main/SubtitleTimeline.tsx +++ b/src/main/SubtitleTimeline.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import { css } from "@emotion/react"; import { SegmentsList as CuttingSegmentsList, Waveforms } from "./Timeline"; import { + addCueAtIndex, selectCurrentlyAt, selectSelectedSubtitleById, selectSelectedSubtitleId, @@ -25,6 +26,7 @@ import { ThemedTooltip } from "./Tooltip"; import { useTranslation } from "react-i18next"; import { useHotkeys } from "react-hotkeys-hook"; import { KEYMAP } from "../globalKeys"; +import { shallowEqual } from "react-redux"; /** * Copy-paste of the timeline in Video.tsx, so that we can make some small adjustments, @@ -39,6 +41,7 @@ const SubtitleTimeline: React.FC = () => { const dispatch = useAppDispatch(); const duration = useAppSelector(selectDuration); const currentlyAt = useAppSelector(selectCurrentlyAt); + const subtitleId = useAppSelector(selectSelectedSubtitleId, shallowEqual); const { ref, width = 1 } = useResizeObserver(); const refTop = useRef(null); @@ -71,6 +74,17 @@ const SubtitleTimeline: React.FC = () => { const [keyboardJumpDelta, setKeyboardJumpDelta] = useState(1000); // In milliseconds. For keyboard navigation + // Callback for adding subtitle segment by hotkey + const addCue = (time: number) => { + dispatch(addCueAtIndex({ + identifier: subtitleId, + cueIndex: -1, + text: "", + startTime: time, + endTime: time + 5000, + })); + }; + // Callbacks for keyboard controls // TODO: Better increases and decreases than ten intervals // TODO: Additional helpful controls (e.g. jump to start/end of segment/next segment) @@ -94,6 +108,11 @@ const SubtitleTimeline: React.FC = () => { () => setKeyboardJumpDelta(keyboardJumpDelta => Math.max(keyboardJumpDelta / 10, 1)), {}, [keyboardJumpDelta] ); + useHotkeys( + KEYMAP.subtitleList.addCue.key, + () => addCue(currentlyAt), + {}, [currentlyAt] + ); // Callback for the scroll container const onEndScroll = (e: ScrollEvent) => { @@ -102,6 +121,14 @@ const SubtitleTimeline: React.FC = () => { const offsetX = refTop.current.scrollLeft; const scrollLeftMax = (refTop.current.scrollWidth - refTop.current.clientWidth); dispatch(setCurrentlyAt((offsetX / scrollLeftMax) * (duration))); + + // Blur active element after scrolling, to ensure hotkeys are working + // This is a little hack to work around focus getting stuck in textarea elements from the subtitle list + try { + (document.activeElement as HTMLElement).blur(); + } catch (_e) { + console.error("Tried to blur active element, but active element cannot be blurred."); + } } }; diff --git a/src/redux/subtitleSlice.ts b/src/redux/subtitleSlice.ts index 3fff70fc6..7319b904d 100644 --- a/src/redux/subtitleSlice.ts +++ b/src/redux/subtitleSlice.ts @@ -1,7 +1,6 @@ import { Segment, SubtitleCue, SubtitlesInEditor } from "./../types"; import { createAsyncThunk, createSlice, nanoid, PayloadAction } from "@reduxjs/toolkit"; import { roundToDecimalPlace } from "../util/utilityFunctions"; -import type { RootState } from "../redux/store"; import { video } from "./videoSlice"; export interface subtitle { @@ -126,7 +125,7 @@ export const subtitleSlice = createSlice({ state.subtitles[action.payload.identifier].cues.splice(0, 0, cue); } - if (action.payload.cueIndex >= 0 || + if (action.payload.cueIndex >= 0 && action.payload.cueIndex < state.subtitles[action.payload.identifier].cues.length) { state.subtitles[action.payload.identifier].cues.splice(action.payload.cueIndex, 0, cue); }