diff --git a/public/configStore.js b/public/configStore.js index c321b966..d3fbe951 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -29,6 +29,7 @@ const defaults = { simpleMode: true, outSegTemplate: undefined, keyboardSeekAccFactor: 1.03, + keyboardNormalSeekSpeed: 1, }, }; diff --git a/src/App.jsx b/src/App.jsx index 2afb883a..97c3039b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -33,6 +33,7 @@ import Timeline from './Timeline'; import RightMenu from './RightMenu'; import TimelineControls from './TimelineControls'; import ExportConfirm from './ExportConfirm'; +import ValueTuner from './components/ValueTuner'; import { loadMifiLink } from './mifi'; import { primaryColor, controlsBackground, waveformColor } from './colors'; import { showMergeDialog, showOpenAndMergeDialog } from './merge/merge'; @@ -142,7 +143,7 @@ const App = memo(() => { const isCustomFormatSelected = fileFormat !== detectedFileFormat; const { - captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeShowFrames, setTimecodeShowFrames, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, muted, setMuted, 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, + captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeShowFrames, setTimecodeShowFrames, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, muted, setMuted, 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, } = useUserPreferences(); const outSegTemplateOrDefault = outSegTemplate || defaultOutSegTemplate; @@ -154,7 +155,7 @@ const App = memo(() => { // Global state const [helpVisible, setHelpVisible] = useState(false); const [settingsVisible, setSettingsVisible] = useState(false); - const [wheelTunerVisible, setWheelTunerVisible] = useState(false); + const [tunerVisible, setTunerVisible] = useState(); const [exportConfirmVisible, setExportConfirmVisible] = useState(false); const [mifiLink, setMifiLink] = useState(); @@ -1322,11 +1323,11 @@ const App = memo(() => { const reducePlaybackRate = () => changePlaybackRate(-1); const increasePlaybackRate = () => changePlaybackRate(1); function seekBackwards() { - seekRel(-1 * seekAccelerationRef.current); + seekRel(keyboardNormalSeekSpeed * seekAccelerationRef.current * -1); seekAccelerationRef.current *= keyboardSeekAccFactor; } function seekForwards() { - seekRel(seekAccelerationRef.current); + seekRel(keyboardNormalSeekSpeed * seekAccelerationRef.current); seekAccelerationRef.current *= keyboardSeekAccFactor; } const seekReset = () => { @@ -1397,6 +1398,7 @@ const App = memo(() => { setCutEnd, setCutStart, seekRel, seekRelPercent, shortStep, cleanupFiles, jumpSeg, seekClosestKeyframe, zoomRel, toggleComfortZoom, splitCurrentSegment, exportConfirmVisible, increaseRotation, jumpCutStart, jumpCutEnd, cutSegmentsHistory, keyboardSeekAccFactor, + keyboardNormalSeekSpeed, ]); useEffect(() => { @@ -1868,9 +1870,9 @@ const App = memo(() => { /> ), [autoExportExtraStreams, setAutoExportExtraStreams]); - const onWheelTunerRequested = useCallback(() => { + const onTunerRequested = useCallback((type) => { setSettingsVisible(false); - setWheelTunerVisible(true); + setTunerVisible(type); }, []); const renderSettings = useCallback(() => ( @@ -1908,9 +1910,9 @@ const App = memo(() => { AutoExportToggler={AutoExportToggler} renderCaptureFormatButton={renderCaptureFormatButton} - onWheelTunerRequested={onWheelTunerRequested} + onTunerRequested={onTunerRequested} /> - ), [changeOutDir, customOutDir, autoMerge, setAutoMerge, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, autoSaveProjectFile, setAutoSaveProjectFile, timecodeShowFrames, setTimecodeShowFrames, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, ffmpegExperimental, setFfmpegExperimental, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, AutoExportToggler, renderCaptureFormatButton, onWheelTunerRequested]); + ), [changeOutDir, customOutDir, autoMerge, setAutoMerge, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, autoSaveProjectFile, setAutoSaveProjectFile, timecodeShowFrames, setTimecodeShowFrames, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, ffmpegExperimental, setFfmpegExperimental, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, AutoExportToggler, renderCaptureFormatButton, onTunerRequested]); useEffect(() => { if (!isStoreBuild) loadMifiLink().then(setMifiLink); @@ -1948,6 +1950,33 @@ const App = memo(() => { const { t } = useTranslation(); + function renderTuner(type) { + const types = { + wheelSensitivity: { + title: t('Timeline trackpad/wheel sensitivity'), + value: wheelSensitivity, + setValue: setWheelSensitivity, + }, + keyboardNormalSeekSpeed: { + title: t('Timeline keyboard seek speed'), + value: keyboardNormalSeekSpeed, + setValue: setKeyboardNormalSeekSpeed, + min: 0, + max: 100, + }, + keyboardSeekAccFactor: { + title: t('Timeline keyboard seek acceleration'), + value: keyboardSeekAccFactor, + setValue: setKeyboardSeekAccFactor, + min: 1, + max: 2, + }, + }; + const { title, value, setValue, min, max } = types[type]; + + return setTunerVisible()} max={max} min={min} />; + } + // throw new Error('Test'); return ( @@ -2228,13 +2257,7 @@ const App = memo(() => { renderSettings={renderSettings} /> - {wheelTunerVisible && ( -
- {t('Timeline trackpad/wheel sensitivity')} - setWheelSensitivity(e.target.value / 1000)} /> - -
- )} + {tunerVisible && renderTuner(tunerVisible)} ); }); diff --git a/src/Settings.jsx b/src/Settings.jsx index 0a86439b..e6c00ead 100644 --- a/src/Settings.jsx +++ b/src/Settings.jsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; const Settings = memo(({ changeOutDir, customOutDir, autoMerge, setAutoMerge, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, autoSaveProjectFile, setAutoSaveProjectFile, timecodeShowFrames, setTimecodeShowFrames, askBeforeClose, setAskBeforeClose, - AutoExportToggler, renderCaptureFormatButton, onWheelTunerRequested, language, setLanguage, + AutoExportToggler, renderCaptureFormatButton, onTunerRequested, language, setLanguage, invertTimelineScroll, setInvertTimelineScroll, ffmpegExperimental, setFfmpegExperimental, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, @@ -171,7 +171,21 @@ const Settings = memo(({ {t('Timeline trackpad/wheel sensitivity')} - + + + + + + {t('Timeline keyboard seek speed')} + + + + + + + {t('Timeline keyboard seek acceleration')} + + diff --git a/src/components/ValueTuner.jsx b/src/components/ValueTuner.jsx new file mode 100644 index 00000000..885f243f --- /dev/null +++ b/src/components/ValueTuner.jsx @@ -0,0 +1,27 @@ +import React, { memo } from 'react'; +import { Button } from 'evergreen-ui'; +import { useTranslation } from 'react-i18next'; + +const ValueTuner = memo(({ style, title, value, setValue, onFinished, resolution = 1000, min = 0, max = 1 }) => { + const { t } = useTranslation(); + + function onChange(e) { + e.target.blur(); + setValue(Math.min(Math.max(min, ((e.target.value / resolution) * (max - min)) + min)), max); + } + + return ( +
+
+
{title}
+
{value.toFixed(2)}
+
+
+ + +
+
+ ); +}); + +export default ValueTuner; diff --git a/src/hooks/useUserPreferences.js b/src/hooks/useUserPreferences.js index 80c5c4cd..472a3378 100644 --- a/src/hooks/useUserPreferences.js +++ b/src/hooks/useUserPreferences.js @@ -80,6 +80,8 @@ export default () => { useEffect(() => safeSetConfig('outSegTemplate', outSegTemplate), [outSegTemplate]); const [keyboardSeekAccFactor, setKeyboardSeekAccFactor] = useState(configStore.get('keyboardSeekAccFactor')); useEffect(() => safeSetConfig('keyboardSeekAccFactor', keyboardSeekAccFactor), [keyboardSeekAccFactor]); + const [keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed] = useState(configStore.get('keyboardNormalSeekSpeed')); + useEffect(() => safeSetConfig('keyboardNormalSeekSpeed', keyboardNormalSeekSpeed), [keyboardNormalSeekSpeed]); // NOTE! This useEffect must be placed after all usages of firstUpdateRef.current (safeSetConfig) @@ -144,5 +146,7 @@ export default () => { setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, + keyboardNormalSeekSpeed, + setKeyboardNormalSeekSpeed, }; };