From 402b8290afa72c4ee694f5b8a0b6f5edfedd3abc Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 1 Mar 2022 13:53:44 +0800 Subject: [PATCH] reduce react prop drilling also fix issue with keyframe cut button not working --- src/App.jsx | 638 ++++++++---------- src/BottomBar.jsx | 18 +- src/ExportConfirm.jsx | 30 +- src/NoFileLoaded.jsx | 6 +- src/SegmentList.jsx | 7 +- src/Settings.jsx | 19 +- src/StreamsSelector.jsx | 3 +- src/Timeline.jsx | 5 +- src/TopMenu.jsx | 13 +- src/components/AutoExportToggler.jsx | 18 + src/components/CaptureFormatButton.jsx | 25 + src/components/ConcatDialog.jsx | 5 +- src/components/CustomOutDirButton.jsx | 23 - src/components/ExportButton.jsx | 5 +- src/components/KeyframeCutButton.jsx | 6 +- src/components/MergeExportButton.jsx | 5 +- src/components/MovFastStartButton.jsx | 4 +- src/components/OutSegTemplateEditor.jsx | 5 +- src/components/PreserveMovDataButton.jsx | 4 +- src/components/SimpleModeButton.jsx | 4 +- src/components/ToggleExportConfirm.jsx | 4 +- src/components/ValueTuners.jsx | 44 ++ src/contexts/UserSettingsContext.js | 3 + src/hooks/useUserSettings.js | 5 + ...rPreferences.js => useUserSettingsRoot.js} | 0 25 files changed, 454 insertions(+), 445 deletions(-) create mode 100644 src/components/AutoExportToggler.jsx create mode 100644 src/components/CaptureFormatButton.jsx delete mode 100644 src/components/CustomOutDirButton.jsx create mode 100644 src/components/ValueTuners.jsx create mode 100644 src/contexts/UserSettingsContext.js create mode 100644 src/hooks/useUserSettings.js rename src/hooks/{useUserPreferences.js => useUserSettingsRoot.js} (100%) diff --git a/src/App.jsx b/src/App.jsx index 258791af..48564d30 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,7 +3,7 @@ import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'; import { FaAngleLeft, FaWindowClose } from 'react-icons/fa'; import { MdRotate90DegreesCcw } from 'react-icons/md'; import { AnimatePresence, motion } from 'framer-motion'; -import { Table, SideSheet, Button, Position, ForkIcon, DisableIcon, ThemeProvider } from 'evergreen-ui'; +import { Table, SideSheet, Position, ThemeProvider } from 'evergreen-ui'; import { useStateWithHistory } from 'react-use/lib/useStateWithHistory'; import useDebounceOld from 'react-use/lib/useDebounce'; // Want to phase out this import { useDebounce } from 'use-debounce'; @@ -18,13 +18,15 @@ import isEqual from 'lodash/isEqual'; import theme from './theme'; import useTimelineScroll from './hooks/useTimelineScroll'; -import useUserPreferences from './hooks/useUserPreferences'; +import useUserSettingsRoot from './hooks/useUserSettingsRoot'; import useFfmpegOperations from './hooks/useFfmpegOperations'; import useKeyframes from './hooks/useKeyframes'; import useWaveform from './hooks/useWaveform'; import useKeyboard from './hooks/useKeyboard'; import useFileFormatState from './hooks/useFileFormatState'; +import UserSettingsContext from './contexts/UserSettingsContext'; + import NoFileLoaded from './NoFileLoaded'; import Canvas from './Canvas'; import TopMenu from './TopMenu'; @@ -36,7 +38,7 @@ import Settings from './Settings'; import Timeline from './Timeline'; import BottomBar from './BottomBar'; import ExportConfirm from './ExportConfirm'; -import ValueTuner from './components/ValueTuner'; +import ValueTuners from './components/ValueTuners'; import VolumeControl from './components/VolumeControl'; import SubtitleControl from './components/SubtitleControl'; import BatchFilesList from './components/BatchFilesList'; @@ -59,7 +61,7 @@ import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, defaul import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, askForEdlImport } from './edlStore'; import { formatYouTube, getTimeFromFrameNum as getTimeFromFrameNumRaw, getFrameCountRaw } from './edlFormats'; import { - getOutPath, toast, errorToast, handleError, setFileNameTitle, getOutDir, getFileDir, withBlur, + getOutPath, toast, errorToast, handleError, setFileNameTitle, getOutDir, getFileDir, checkDirWriteAccess, dirExists, openDirToast, isMasBuild, isStoreBuild, dragPreventer, isDurationValid, filenamify, getOutFileExtension, generateSegFileName, defaultOutSegTemplate, havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile, @@ -196,7 +198,7 @@ const App = memo(() => { 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, resetKeyBindings, enableSmartCut, setEnableSmartCut, - } = useUserPreferences(); + } = useUserSettingsRoot(); const { concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration, @@ -214,8 +216,6 @@ const App = memo(() => { const isFileOpened = !!filePath; - const onOutFormatLockedClick = () => setOutFormatLocked((v) => (v ? undefined : fileFormat)); - const onOutputFormatUserChange = useCallback((newFormat) => { setFileFormat(newFormat); if (outFormatLocked) { @@ -740,6 +740,10 @@ const App = memo(() => { return !v; }), [hideAllNotifications, setSimpleMode]); + const userSettingsContext = useMemo(() => ({ + captureFormat, setCaptureFormat, toggleCaptureFormat, customOutDir, setCustomOutDir, changeOutDir, keyframeCut, setKeyframeCut, toggleKeyframeCut, preserveMovData, setPreserveMovData, togglePreserveMovData, movFastStart, setMovFastStart, toggleMovFastStart, 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, toggleExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, toggleSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, togglePreserveMetadataOnMerge, simpleMode, setSimpleMode, toggleSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, toggleSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, setEnableSmartCut, + }), [askBeforeClose, autoDeleteMergedSegments, autoExportExtraStreams, autoLoadTimecode, autoMerge, autoSaveProjectFile, avoidNegativeTs, captureFormat, changeOutDir, customOutDir, enableAskForFileOpenAction, enableAskForImportChapters, enableAutoHtml5ify, enableSmartCut, enableTransferTimestamps, exportConfirmEnabled, ffmpegExperimental, hideNotifications, invertCutSegments, invertTimelineScroll, keyBindings, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyframeCut, language, movFastStart, outFormatLocked, outSegTemplate, playbackVolume, preserveMetadataOnMerge, preserveMovData, resetKeyBindings, safeOutputFileName, segmentsToChapters, segmentsToChaptersOnly, setAskBeforeClose, setAutoDeleteMergedSegments, setAutoExportExtraStreams, setAutoLoadTimecode, setAutoMerge, setAutoSaveProjectFile, setAvoidNegativeTs, setCaptureFormat, setCustomOutDir, setEnableAskForFileOpenAction, setEnableAskForImportChapters, setEnableAutoHtml5ify, setEnableSmartCut, setEnableTransferTimestamps, setExportConfirmEnabled, setFfmpegExperimental, setHideNotifications, setInvertCutSegments, setInvertTimelineScroll, setKeyBindings, setKeyboardNormalSeekSpeed, setKeyboardSeekAccFactor, setKeyframeCut, setLanguage, setMovFastStart, setOutFormatLocked, setOutSegTemplate, setPlaybackVolume, setPreserveMetadataOnMerge, setPreserveMovData, setSafeOutputFileName, setSegmentsToChapters, setSegmentsToChaptersOnly, setSimpleMode, setTimecodeFormat, setWheelSensitivity, simpleMode, timecodeFormat, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode, wheelSensitivity]); + const isCopyingStreamId = useCallback((path, streamId) => ( !!(copyStreamIdsByFile[path] || {})[streamId] ), [copyStreamIdsByFile]); @@ -2109,23 +2113,6 @@ const App = memo(() => { ), [detectedFileFormat, fileFormat, onOutputFormatUserChange]); - const renderCaptureFormatButton = useCallback((props) => ( - - ), [captureFormat, toggleCaptureFormat]); - - const AutoExportToggler = useCallback(() => ( - - ), [autoExportExtraStreams, setAutoExportExtraStreams]); - const onTunerRequested = useCallback((type) => { setSettingsVisible(false); setTunerVisible(type); @@ -2163,39 +2150,6 @@ const App = memo(() => { const { t } = useTranslation(); - function renderTuner(type) { - // NOTE default values are duplicated in public/configStore.js - const types = { - wheelSensitivity: { - title: t('Timeline trackpad/wheel sensitivity'), - value: wheelSensitivity, - setValue: setWheelSensitivity, - default: 0.2, - }, - keyboardNormalSeekSpeed: { - title: t('Timeline keyboard seek speed'), - value: keyboardNormalSeekSpeed, - setValue: setKeyboardNormalSeekSpeed, - min: 0, - max: 100, - default: 1, - }, - keyboardSeekAccFactor: { - title: t('Timeline keyboard seek acceleration'), - value: keyboardSeekAccFactor, - setValue: setKeyboardSeekAccFactor, - min: 1, - max: 2, - default: 1.03, - }, - }; - const { title, value, setValue, min, max, default: defaultValue } = types[type]; - - const resetToDefault = () => setValue(defaultValue); - - return setTunerVisible()} max={max} min={min} resetToDefault={resetToDefault} />; - } - function renderSubtitles() { if (!activeSubtitle) return null; return ; @@ -2204,328 +2158,276 @@ const App = memo(() => { // throw new Error('Test error boundary'); return ( - -
- - -
- - {showLeftBar && ( - - )} - - - {/* Middle part: */} -
- {!isFileOpened && } - -
- {/* eslint-disable-next-line jsx-a11y/media-has-caption */} - - - {canvasPlayerEnabled && } -
- - {isRotationSet && !hideCanvasPreview && ( -
- - {t('Rotation preview')} - {!canvasPlayerRequired && setHideCanvasPreview(true)} />} -
- )} - - {isFileOpened && ( -
- + + +
+ - {subtitleStreams.length > 0 && } +
+ + {showLeftBar && ( + + )} + - {!showRightBar && ( - - )} + {/* Middle part: */} +
+ {!isFileOpened && } + +
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + + + {canvasPlayerEnabled && }
- )} + + {isRotationSet && !hideCanvasPreview && ( +
+ + {t('Rotation preview')} + {!canvasPlayerRequired && setHideCanvasPreview(true)} />} +
+ )} + + {isFileOpened && ( +
+ + + {subtitleStreams.length > 0 && } + + {!showRightBar && ( + + )} +
+ )} + + + {working && } + + + {tunerVisible && setTunerVisible()} />} +
- {working && } + {showRightBar && isFileOpened && ( + + + + )} - - {tunerVisible && renderTuner(tunerVisible)}
- - {showRightBar && isFileOpened && ( - - + + + + + + setStreamsSelectorShown(false)} + > + + + + + + + + + + + {t('Settings')} + {t('Current setting')} + + + - - )} - - + +
+
- - + 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} initialPaths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> - - - - setStreamsSelectorShown(false)} - > - - - - - - - - - - - {t('Settings')} - {t('Current setting')} - - - - -
-
- - 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} initialPaths={batchFilePaths} onConcat={userConcatFiles} segmentsToChapters={segmentsToChapters} setSegmentsToChapters={setSegmentsToChapters} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} preserveMetadataOnMerge={preserveMetadataOnMerge} setPreserveMetadataOnMerge={setPreserveMetadataOnMerge} preserveMovData={preserveMovData} setPreserveMovData={setPreserveMovData} /> - - setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> -
-
+ setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> +
+ + ); }); diff --git a/src/BottomBar.jsx b/src/BottomBar.jsx index ea17cb69..b101fdbf 100644 --- a/src/BottomBar.jsx +++ b/src/BottomBar.jsx @@ -13,11 +13,13 @@ import SegmentCutpointButton from './components/SegmentCutpointButton'; import SetCutpointButton from './components/SetCutpointButton'; import ExportButton from './components/ExportButton'; import ToggleExportConfirm from './components/ToggleExportConfirm'; +import CaptureFormatButton from './components/CaptureFormatButton'; import SimpleModeButton from './components/SimpleModeButton'; import { withBlur, toast, mirrorTransform } from './util'; import { getSegColor } from './util/colors'; import { formatDuration, parseDuration } from './util/duration'; +import useUserSettings from './hooks/useUserSettings'; const isDev = window.require('electron-is-dev'); @@ -27,9 +29,9 @@ const zoomOptions = Array(13).fill().map((unused, z) => 2 ** z); const leftRightWidth = 100; const BottomBar = memo(({ - zoom, setZoom, invertCutSegments, setInvertCutSegments, timelineToggleComfortZoom, simpleMode, toggleSimpleMode, - isRotationSet, rotation, areWeCutting, increaseRotation, cleanupFilesDialog, renderCaptureFormatButton, - captureSnapshot, onExportPress, enabledSegments, hasVideo, autoMerge, exportConfirmEnabled, toggleExportConfirmEnabled, + zoom, setZoom, timelineToggleComfortZoom, + isRotationSet, rotation, areWeCutting, increaseRotation, cleanupFilesDialog, + captureSnapshot, onExportPress, enabledSegments, hasVideo, seekAbs, currentSegIndexSafe, cutSegments, currentCutSeg, setCutStart, setCutEnd, setCurrentSegIndex, cutStartTimeManual, setCutStartTimeManual, cutEndTimeManual, setCutEndTimeManual, jumpTimelineStart, jumpTimelineEnd, jumpCutEnd, jumpCutStart, startTimeOffset, setCutTime, currentApparentCutSeg, @@ -38,6 +40,8 @@ const BottomBar = memo(({ }) => { const { t } = useTranslation(); + const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode } = useUserSettings(); + const onYinYangClick = useCallback(() => { setInvertCutSegments(v => { const newVal = !v; @@ -281,7 +285,7 @@ const BottomBar = memo(({ className="no-user-select" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '3px 4px' }} > - + {simpleMode &&
{t('Toggle advanced view')}
} @@ -344,7 +348,7 @@ const BottomBar = memo(({ {hasVideo && ( <> - {!simpleMode && renderCaptureFormatButton({ height: 20 })} + {!simpleMode && } )} - {!simpleMode && } + {!simpleMode && } - +
); diff --git a/src/ExportConfirm.jsx b/src/ExportConfirm.jsx index 0e811271..17e265ee 100644 --- a/src/ExportConfirm.jsx +++ b/src/ExportConfirm.jsx @@ -16,6 +16,7 @@ import HighlightedText from './components/HighlightedText'; import { withBlur, toast } from './util'; import { isMov as ffmpegIsMov } from './ffmpeg'; +import useUserSettings from './hooks/useUserSettings'; const sheetStyle = { position: 'fixed', @@ -39,16 +40,17 @@ const warningStyle = { color: '#faa', fontSize: '80%' }; const HelpIcon = ({ onClick }) => ; const ExportConfirm = memo(({ - autoMerge, areWeCutting, enabledSegments, willMerge, visible, onClosePress, onExportConfirm, keyframeCut, toggleKeyframeCut, - setAutoMerge, renderOutFmt, preserveMovData, togglePreserveMovData, movFastStart, toggleMovFastStart, avoidNegativeTs, setAvoidNegativeTs, - changeOutDir, outputDir, numStreamsTotal, numStreamsToCopy, setStreamsSelectorShown, - exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, toggleSegmentsToChapters, outFormat, - preserveMetadataOnMerge, togglePreserveMetadataOnMerge, outSegTemplate, setOutSegTemplate, generateOutSegFileNames, - filePath, currentSegIndexSafe, getOutSegError, autoDeleteMergedSegments, setAutoDeleteMergedSegments, - safeOutputFileName, toggleSafeOutputFileName, segmentsToChaptersOnly, setSegmentsToChaptersOnly, enableSmartCut, setEnableSmartCut, + areWeCutting, enabledSegments, willMerge, visible, onClosePress, onExportConfirm, + renderOutFmt, + outputDir, numStreamsTotal, numStreamsToCopy, setStreamsSelectorShown, + outFormat, + outSegTemplate, setOutSegTemplate, generateOutSegFileNames, + filePath, currentSegIndexSafe, getOutSegError, }) => { const { t } = useTranslation(); + const { changeOutDir, keyframeCut, preserveMovData, movFastStart, avoidNegativeTs, setAvoidNegativeTs, autoDeleteMergedSegments, exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, toggleSegmentsToChapters, preserveMetadataOnMerge, togglePreserveMetadataOnMerge, enableSmartCut, setEnableSmartCut } = useUserSettings(); + const isMov = ffmpegIsMov(outFormat); const isIpod = outFormat === 'ipod'; @@ -121,7 +123,7 @@ const ExportConfirm = memo(({

{t('Export options')}

    - {enabledSegments.length >= 2 &&
  • {t('Merge {{segments}} cut segments to one file?', { segments: enabledSegments.length })}
  • } + {enabledSegments.length >= 2 &&
  • {t('Merge {{segments}} cut segments to one file?', { segments: enabledSegments.length })}
  • }
  • {t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })} @@ -135,7 +137,7 @@ const ExportConfirm = memo(({
  • {canEditTemplate && (
  • - +
  • )}
@@ -166,7 +168,7 @@ const ExportConfirm = memo(({ {!enableSmartCut && (
  • - {t('Cut mode:')} toggleKeyframeCut(false))} /> + {t('Cut mode:')} {!keyframeCut && {t('Note: Keyframe cut is recommended for most common files')}}
  • )} @@ -176,11 +178,11 @@ const ExportConfirm = memo(({ {isMov && ( <>
  • - {t('Enable MOV Faststart?')} + {t('Enable MOV Faststart?')} {isIpod && !movFastStart && {t('For the ipod format, it is recommended to activate this option')}}
  • - {t('Preserve all MP4/MOV metadata?')} + {t('Preserve all MP4/MOV metadata?')} {isIpod && preserveMovData && {t('For the ipod format, it is recommended to deactivate this option')}}
  • @@ -211,7 +213,7 @@ const ExportConfirm = memo(({ transition={{ duration: 0.4, easings: ['easeOut'] }} style={{ display: 'flex', alignItems: 'flex-end' }} > - +
    {t('Show this page before exporting?')}
    @@ -222,7 +224,7 @@ const ExportConfirm = memo(({ exit={{ scale: 0.7, opacity: 0 }} transition={{ duration: 0.4, easings: ['easeOut'] }} > - onExportConfirm()} size={1.7} /> + onExportConfirm()} size={1.7} />
    diff --git a/src/NoFileLoaded.jsx b/src/NoFileLoaded.jsx index d756f457..82abba80 100644 --- a/src/NoFileLoaded.jsx +++ b/src/NoFileLoaded.jsx @@ -5,11 +5,13 @@ import { useTranslation, Trans } from 'react-i18next'; import SetCutpointButton from './components/SetCutpointButton'; import SimpleModeButton from './components/SimpleModeButton'; +import useUserSettings from './hooks/useUserSettings'; const electron = window.require('electron'); -const NoFileLoaded = memo(({ mifiLink, toggleHelp, currentCutSeg, simpleMode, toggleSimpleMode }) => { +const NoFileLoaded = memo(({ mifiLink, toggleHelp, currentCutSeg }) => { const { t } = useTranslation(); + const { simpleMode, toggleSimpleMode } = useUserSettings(); return (
    @@ -24,7 +26,7 @@ const NoFileLoaded = memo(({ mifiLink, toggleHelp, currentCutSeg, simpleMode, to
    - {simpleMode ? i18n.t('to show advanced view') : i18n.t('to show simple view')} + {simpleMode ? i18n.t('to show advanced view') : i18n.t('to show simple view')}
    diff --git a/src/SegmentList.jsx b/src/SegmentList.jsx index 0dff18a4..801f74d1 100644 --- a/src/SegmentList.jsx +++ b/src/SegmentList.jsx @@ -10,6 +10,7 @@ import useDebounce from 'react-use/lib/useDebounce'; import scrollIntoView from 'scroll-into-view-if-needed'; import useContextMenu from './hooks/useContextMenu'; +import useUserSettings from './hooks/useUserSettings'; import { saveColor } from './colors'; import { getSegColor } from './util/colors'; @@ -124,14 +125,16 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou const SegmentList = memo(({ formatTimecode, apparentCutSegments, inverseCutSegments, getFrameCount, onSegClick, - currentSegIndex, invertCutSegments, + currentSegIndex, updateSegOrder, updateSegOrders, addCutSegment, removeCutSegment, onLabelSegmentPress, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment, enabledSegments, enabledSegmentsRaw, onExportSingleSegmentClick, onExportSegmentEnabledToggle, onExportSegmentDisableAll, onExportSegmentEnableAll, - jumpSegStart, jumpSegEnd, simpleMode, onViewSegmentTagsPress, + jumpSegStart, jumpSegEnd, onViewSegmentTagsPress, }) => { const { t } = useTranslation(); + const { invertCutSegments, simpleMode } = useUserSettings(); + const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments; const sortableList = segments.map((seg) => ({ id: seg.segId, seg })); diff --git a/src/Settings.jsx b/src/Settings.jsx index 361ea2c0..38513d25 100644 --- a/src/Settings.jsx +++ b/src/Settings.jsx @@ -2,6 +2,9 @@ import React, { memo, useCallback, useMemo } from 'react'; import { FaYinYang, FaKeyboard } from 'react-icons/fa'; import { Button, Table, NumericalIcon, KeyIcon, FolderCloseIcon, DocumentIcon, TimeIcon, Checkbox, Select } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; +import CaptureFormatButton from './components/CaptureFormatButton'; +import AutoExportToggler from './components/AutoExportToggler'; +import useUserSettings from './hooks/useUserSettings'; // https://www.electronjs.org/docs/api/locales @@ -36,17 +39,13 @@ const Row = (props) => ; const KeyCell = (props) => ; const Settings = memo(({ - changeOutDir, customOutDir, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, - autoSaveProjectFile, setAutoSaveProjectFile, timecodeFormat, setTimecodeFormat, askBeforeClose, setAskBeforeClose, - AutoExportToggler, renderCaptureFormatButton, onTunerRequested, language, setLanguage, - invertTimelineScroll, setInvertTimelineScroll, ffmpegExperimental, setFfmpegExperimental, - enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, - hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, - enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, + onTunerRequested, onKeyboardShortcutsDialogRequested, }) => { const { t } = useTranslation(); + const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify } = useUserSettings(); + const onLangChange = useCallback((e) => { const { value } = e.target; const l = value !== '' ? value : undefined; @@ -115,8 +114,8 @@ const Settings = memo(({ {t('Normal cut')}: {t('Accurate time but could leave an empty portion at the beginning of the video. Equiv to')} ffmpeg -i -ss ...
    - @@ -174,7 +173,7 @@ const Settings = memo(({ {t('Snapshot capture format')} - {renderCaptureFormatButton()} + diff --git a/src/StreamsSelector.jsx b/src/StreamsSelector.jsx index c04f5e73..7d8a9654 100644 --- a/src/StreamsSelector.jsx +++ b/src/StreamsSelector.jsx @@ -7,6 +7,7 @@ import { MdSubtitles } from 'react-icons/md'; import { BookIcon, Paragraph, TextInput, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Select, Heading, SortAscIcon, SortDescIcon, Dialog, Button, PlusIcon, Pane, ForkIcon, Alert } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; +import AutoExportToggler from './components/AutoExportToggler'; import { askForMetadataKey, showJson5Dialog } from './dialogs'; import { formatDuration } from './util/duration'; import { getStreamFps } from './ffmpeg'; @@ -325,7 +326,7 @@ const StreamsSelector = memo(({ mainFilePath, mainFileFormatData, streams: mainFileStreams, mainFileChapters, isCopyingStreamId, toggleCopyStreamId, setCopyStreamIdsForPath, onExtractStreamPress, onExtractAllStreamsPress, allFilesMeta, externalFilesMeta, setExternalFilesMeta, showAddStreamSourceDialog, shortestFlag, setShortestFlag, nonCopiedExtraStreams, - AutoExportToggler, customTagsByFile, setCustomTagsByFile, customTagsByStreamId, setCustomTagsByStreamId, + customTagsByFile, setCustomTagsByFile, customTagsByStreamId, setCustomTagsByStreamId, dispositionByStreamId, setDispositionByStreamId, }) => { const [editingFile, setEditingFile] = useState(); diff --git a/src/Timeline.jsx b/src/Timeline.jsx index a01dc638..f73cb30f 100644 --- a/src/Timeline.jsx +++ b/src/Timeline.jsx @@ -8,6 +8,7 @@ import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; import TimelineSeg from './TimelineSeg'; import BetweenSegments from './BetweenSegments'; import useContextMenu from './hooks/useContextMenu'; +import useUserSettings from './hooks/useUserSettings'; import { timelineBackground } from './colors'; @@ -59,13 +60,15 @@ const CommandedTime = memo(({ commandedTimePercent }) => { const Timeline = memo(({ durationSafe, getCurrentTime, startTimeOffset, playerTime, commandedTime, zoom, neighbouringKeyFrames, seekAbs, apparentCutSegments, - setCurrentSegIndex, currentSegIndexSafe, invertCutSegments, inverseCutSegments, formatTimecode, + setCurrentSegIndex, currentSegIndexSafe, inverseCutSegments, formatTimecode, waveforms, shouldShowWaveform, shouldShowKeyframes, timelineHeight = 36, thumbnails, onZoomWindowStartTimeChange, waveformEnabled, thumbnailsEnabled, playing, isFileOpened, onWheel, commandedTimeRef, goToTimecode, }) => { const { t } = useTranslation(); + const { invertCutSegments } = useUserSettings(); + const timelineScrollerRef = useRef(); const timelineScrollerSkipEventRef = useRef(); const timelineScrollerSkipEventDebounce = useRef(); diff --git a/src/TopMenu.jsx b/src/TopMenu.jsx index a2042c45..619a21fa 100644 --- a/src/TopMenu.jsx +++ b/src/TopMenu.jsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; +import React, { memo, useCallback } from 'react'; import { IoIosHelpCircle, IoIosSettings } from 'react-icons/io'; import { FaLock, FaUnlock } from 'react-icons/fa'; import { IconButton, Button, CrossIcon, ListIcon, VolumeUpIcon, VolumeOffIcon } from 'evergreen-ui'; @@ -8,15 +8,18 @@ import MergeExportButton from './components/MergeExportButton'; import { withBlur, isMasBuild } from './util'; import { primaryTextColor, controlsBackground } from './colors'; +import useUserSettings from './hooks/useUserSettings'; const TopMenu = memo(({ - filePath, copyAnyAudioTrack, toggleStripAudio, customOutDir, changeOutDir, + filePath, fileFormat, copyAnyAudioTrack, toggleStripAudio, renderOutFmt, toggleHelp, numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings, - enabledSegments, autoMerge, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, isCustomFormatSelected, onOutFormatLockedClick, simpleMode, outFormatLocked, clearOutDir, - segmentsToChaptersOnly, setSegmentsToChaptersOnly, + enabledSegments, isCustomFormatSelected, clearOutDir, }) => { const { t } = useTranslation(); + const { customOutDir, changeOutDir, simpleMode, outFormatLocked, setOutFormatLocked } = useUserSettings(); + + const onOutFormatLockedClick = useCallback(() => setOutFormatLocked((v) => (v ? undefined : fileFormat)), [fileFormat, setOutFormatLocked]); // We cannot allow exporting to a directory which has not yet been confirmed by an open dialog because of sandox restrictions const showClearWorkingDirButton = customOutDir && !isMasBuild; @@ -75,7 +78,7 @@ const TopMenu = memo(({ {!simpleMode && (isCustomFormatSelected || outFormatLocked) && renderFormatLock()} - + )} diff --git a/src/components/AutoExportToggler.jsx b/src/components/AutoExportToggler.jsx new file mode 100644 index 00000000..3bc3a6b6 --- /dev/null +++ b/src/components/AutoExportToggler.jsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, ForkIcon, DisableIcon } from 'evergreen-ui'; + +import useUserSettings from '../hooks/useUserSettings'; + +const AutoExportToggler = memo(() => { + const { t } = useTranslation(); + const { autoExportExtraStreams, setAutoExportExtraStreams } = useUserSettings(); + + return ( + + ); +}); + +export default AutoExportToggler; diff --git a/src/components/CaptureFormatButton.jsx b/src/components/CaptureFormatButton.jsx new file mode 100644 index 00000000..5d563e7f --- /dev/null +++ b/src/components/CaptureFormatButton.jsx @@ -0,0 +1,25 @@ +import React, { memo } from 'react'; +import { Button } from 'evergreen-ui'; +import { useTranslation } from 'react-i18next'; +import { FaImage } from 'react-icons/fa'; + +import useUserSettings from '../hooks/useUserSettings'; +import { withBlur } from '../util'; + +const CaptureFormatButton = memo(({ showIcon = false, ...props }) => { + const { t } = useTranslation(); + const { captureFormat, toggleCaptureFormat } = useUserSettings(); + return ( + + ); +}); + +export default CaptureFormatButton; diff --git a/src/components/ConcatDialog.jsx b/src/components/ConcatDialog.jsx index e828869c..9693cdd1 100644 --- a/src/components/ConcatDialog.jsx +++ b/src/components/ConcatDialog.jsx @@ -6,6 +6,7 @@ import { AiOutlineMergeCells } from 'react-icons/ai'; import { readFileMeta, getSmarterOutFormat } from '../ffmpeg'; import useFileFormatState from '../hooks/useFileFormatState'; import OutputFormatSelect from './OutputFormatSelect'; +import useUserSettings from '../hooks/useUserSettings'; const { basename } = window.require('path'); @@ -17,12 +18,10 @@ const rowStyle = { const ConcatDialog = memo(({ isShown, onHide, initialPaths, onConcat, - segmentsToChapters, setSegmentsToChapters, alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles, - preserveMetadataOnMerge, setPreserveMetadataOnMerge, - preserveMovData, setPreserveMovData, }) => { const { t } = useTranslation(); + const { preserveMovData, setPreserveMovData, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge } = useUserSettings(); const [paths, setPaths] = useState(initialPaths); const [includeAllStreams, setIncludeAllStreams] = useState(false); diff --git a/src/components/CustomOutDirButton.jsx b/src/components/CustomOutDirButton.jsx deleted file mode 100644 index 7db07b59..00000000 --- a/src/components/CustomOutDirButton.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { memo } from 'react'; -import { Button, FolderOpenIcon } from 'evergreen-ui'; -import { useTranslation } from 'react-i18next'; - -import { withBlur } from '../util'; - - -const CustomOutDirButton = memo(({ customOutDir, changeOutDir }) => { - const { t } = useTranslation(); - - return ( - - ); -}); - -export default CustomOutDirButton; diff --git a/src/components/ExportButton.jsx b/src/components/ExportButton.jsx index c2bc2f46..0974b355 100644 --- a/src/components/ExportButton.jsx +++ b/src/components/ExportButton.jsx @@ -4,13 +4,16 @@ import { FaFileExport } from 'react-icons/fa'; import { useTranslation } from 'react-i18next'; import { primaryColor } from '../colors'; +import useUserSettings from '../hooks/useUserSettings'; -const ExportButton = memo(({ enabledSegments, areWeCutting, autoMerge, onClick, size = 1 }) => { +const ExportButton = memo(({ enabledSegments, areWeCutting, onClick, size = 1 }) => { const CutIcon = areWeCutting ? FiScissors : FaFileExport; const { t } = useTranslation(); + const { autoMerge } = useUserSettings(); + let exportButtonTitle = t('Export'); if (enabledSegments.length === 1) { exportButtonTitle = t('Export selection'); diff --git a/src/components/KeyframeCutButton.jsx b/src/components/KeyframeCutButton.jsx index b8d45231..60dfb233 100644 --- a/src/components/KeyframeCutButton.jsx +++ b/src/components/KeyframeCutButton.jsx @@ -3,17 +3,19 @@ import { Button, KeyIcon } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; import { withBlur } from '../util'; +import useUserSettings from '../hooks/useUserSettings'; -const KeyframeCutButton = memo(({ keyframeCut, onClick }) => { +const KeyframeCutButton = memo(() => { const { t } = useTranslation(); + const { keyframeCut, toggleKeyframeCut } = useUserSettings(); return ( diff --git a/src/components/MergeExportButton.jsx b/src/components/MergeExportButton.jsx index d8a1bbab..9681f4ee 100644 --- a/src/components/MergeExportButton.jsx +++ b/src/components/MergeExportButton.jsx @@ -4,11 +4,14 @@ import { useTranslation } from 'react-i18next'; import { MdCallSplit, MdCallMerge } from 'react-icons/md'; import { withBlur } from '../util'; +import useUserSettings from '../hooks/useUserSettings'; -const MergeExportButton = memo(({ autoMerge, enabledSegments, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly }) => { +const MergeExportButton = memo(({ enabledSegments }) => { const { t } = useTranslation(); + const { autoMerge, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly } = useUserSettings(); + let AutoMergeIcon; let effectiveMode; diff --git a/src/components/MovFastStartButton.jsx b/src/components/MovFastStartButton.jsx index df20896c..40699e90 100644 --- a/src/components/MovFastStartButton.jsx +++ b/src/components/MovFastStartButton.jsx @@ -3,10 +3,12 @@ import { Button } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; import { withBlur } from '../util'; +import useUserSettings from '../hooks/useUserSettings'; -const MovFastStartButton = memo(({ movFastStart, toggleMovFastStart }) => { +const MovFastStartButton = memo(() => { const { t } = useTranslation(); + const { movFastStart, toggleMovFastStart } = useUserSettings(); return (