diff --git a/public/configStore.js b/public/configStore.js index 948aaef..0c01dff 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -62,6 +62,7 @@ const defaultKeyBindings = [ { keys: 'e', action: 'export' }, { keys: 'h', action: 'toggleHelp' }, + { keys: 'shift+/', action: 'toggleKeyboardShortcuts' }, { keys: 'escape', action: 'closeActiveScreen' }, ]; diff --git a/src/App.jsx b/src/App.jsx index f53805f..40b88f4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -197,7 +197,7 @@ const App = memo(() => { const isCustomFormatSelected = fileFormat !== detectedFileFormat; const { - captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly, keyBindings, setKeyBindings, + captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, } = useUserPreferences(); const { @@ -1626,6 +1626,8 @@ const App = memo(() => { setStartTimeOffset(newStartTimeOffset); }, [startTimeOffset]); + const toggleKeyboardShortcuts = useCallback(() => setKeyboardShortcutsVisible((v) => !v), []); + const onKeyPress = useCallback(({ action, keyup }) => { function seekReset() { seekAccelerationRef.current = 1; @@ -1723,6 +1725,11 @@ const App = memo(() => { return false; } + if (action === 'toggleKeyboardShortcuts') { + toggleKeyboardShortcuts(); + return false; + } + if (concatDialogVisible || keyboardShortcutsVisible) { return true; // don't allow any further hotkeys } @@ -1740,7 +1747,7 @@ const App = memo(() => { if (match) return bubble; return true; // bubble the event - }, [addCutSegment, askSetStartTimeOffset, batchFileJump, captureSnapshot, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, currentSegIndexSafe, cutSegmentsHistory, exportConfirmVisible, extractAllStreams, goToTimecode, increaseRotation, invertAllCutSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegmentPress, removeCutSegment, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, setCutEnd, setCutStart, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleHelp, toggleKeyframeCut, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, userHtml5ifyCurrentFile, zoomRel]); + }, [addCutSegment, askSetStartTimeOffset, batchFileJump, captureSnapshot, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, currentSegIndexSafe, cutSegmentsHistory, exportConfirmVisible, extractAllStreams, goToTimecode, increaseRotation, invertAllCutSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegmentPress, removeCutSegment, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, setCutEnd, setCutStart, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleHelp, toggleKeyboardShortcuts, toggleKeyframeCut, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, userHtml5ifyCurrentFile, zoomRel]); useKeyboard({ keyBindings, onKeyPress }); @@ -2481,7 +2488,7 @@ const App = memo(() => { 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} initialPaths={batchFilePaths} onConcat={mergeFiles} segmentsToChapters={segmentsToChapters} setSegmentsToChapters={setSegmentsToChapters} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} preserveMetadataOnMerge={preserveMetadataOnMerge} setPreserveMetadataOnMerge={setPreserveMetadataOnMerge} preserveMovData={preserveMovData} setPreserveMovData={setPreserveMovData} /> - setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} /> + setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> ); diff --git a/src/components/KeyboardShortcuts.jsx b/src/components/KeyboardShortcuts.jsx index b79ad61..3fc707f 100644 --- a/src/components/KeyboardShortcuts.jsx +++ b/src/components/KeyboardShortcuts.jsx @@ -100,7 +100,7 @@ const CreateBinding = memo(({ const rowStyle = { display: 'flex', alignItems: 'center', margin: '6px 0' }; const KeyboardShortcuts = memo(({ - keyBindings, setKeyBindings, currentCutSeg, + keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg, }) => { const { t } = useTranslation(); @@ -136,7 +136,11 @@ const KeyboardShortcuts = memo(({ toggleHelp: { name: t('Show/hide help screen'), }, + toggleKeyboardShortcuts: { + name: t('Keyboard & mouse shortcuts'), + }, + // playbackCategory togglePlayResetSpeed: { name: t('Play/pause'), category: playbackCategory, @@ -162,6 +166,7 @@ const KeyboardShortcuts = memo(({ category: playbackCategory, }, + // seekingCategory seekPreviousFrame: { name: t('Step backward 1 frame'), category: seekingCategory, @@ -209,6 +214,7 @@ const KeyboardShortcuts = memo(({ category: seekingCategory, }, + // segmentsAndCutpointsCategory addSegment: { name: t('Add cut segment'), category: segmentsAndCutpointsCategory, @@ -272,6 +278,7 @@ const KeyboardShortcuts = memo(({ category: segmentsAndCutpointsCategory, }, + // streamsCategory toggleStreamsSelector: { name: t('Edit tracks / metadata tags'), category: streamsCategory, @@ -281,6 +288,7 @@ const KeyboardShortcuts = memo(({ category: streamsCategory, }, + // zoomOperationsCategory timelineZoomIn: { name: t('Zoom in timeline'), category: zoomOperationsCategory, @@ -294,6 +302,7 @@ const KeyboardShortcuts = memo(({ category: zoomOperationsCategory, }, + // outputCategory export: { name: t('Export segment(s)'), category: outputCategory, @@ -315,6 +324,7 @@ const KeyboardShortcuts = memo(({ category: outputCategory, }, + // batchFilesCategory batchPreviousFile: { name: t('Previous file'), category: batchFilesCategory, @@ -332,6 +342,7 @@ const KeyboardShortcuts = memo(({ category: batchFilesCategory, }, + // otherCategory toggleKeyframeCutMode: { name: t('Cut mode'), category: otherCategory, @@ -380,6 +391,14 @@ const KeyboardShortcuts = memo(({ setKeyBindings((existingBindings) => existingBindings.filter((existingBinding) => !(existingBinding.keys === keys && existingBinding.action === action))); }, [setKeyBindings, t]); + + const onResetClick = useCallback(() => { + // eslint-disable-next-line no-alert + if (!window.confirm(t('Are you sure?'))) return; + + resetKeyBindings(); + }, [resetKeyBindings, t]); + const onAddBindingClick = useCallback((action) => { setCreatingBinding(action); }, []); @@ -448,13 +467,15 @@ const KeyboardShortcuts = memo(({ ))} + + ); }); const KeyboardShortcutsDialog = memo(({ - isShown, onHide, keyBindings, setKeyBindings, currentCutSeg, + isShown, onHide, keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg, }) => { const { t } = useTranslation(); @@ -468,7 +489,7 @@ const KeyboardShortcutsDialog = memo(({ onConfirm={onHide} topOffset="3vh" > - {isShown ? :
} + {isShown ? :
} ); }); diff --git a/src/hooks/useUserPreferences.js b/src/hooks/useUserPreferences.js index 720cde2..9baa0ec 100644 --- a/src/hooks/useUserPreferences.js +++ b/src/hooks/useUserPreferences.js @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; import i18n from 'i18next'; import { errorToast } from '../util'; @@ -102,6 +102,10 @@ export default () => { useEffect(() => safeSetConfig('segmentsToChaptersOnly', segmentsToChaptersOnly), [segmentsToChaptersOnly]); const [keyBindings, setKeyBindings] = useState(safeGetConfig('keyBindings')); useEffect(() => safeSetConfig('keyBindings', keyBindings), [keyBindings]); + const resetKeyBindings = useCallback(() => { + configStore.reset('keyBindings'); + setKeyBindings(safeGetConfig('keyBindings')); + }, []); // NOTE! This useEffect must be placed after all usages of firstUpdateRef.current (safeSetConfig) useEffect(() => { @@ -182,5 +186,6 @@ export default () => { setSegmentsToChaptersOnly, keyBindings, setKeyBindings, + resetKeyBindings, }; };