From 9b027bc762fe534a2645f735b2e5a63e56e4d239 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Fri, 10 Mar 2023 12:12:48 +0800 Subject: [PATCH] implement dark/ight mode still not 100%, but closes #733 --- package.json | 2 + public/configStore.js | 1 + public/electron.js | 7 +- src/App.jsx | 46 ++- src/BetweenSegments.jsx | 6 +- src/BottomBar.jsx | 44 +-- src/LastCommandsSheet.jsx | 4 +- src/NoFileLoaded.jsx | 10 +- src/SegmentList.jsx | 33 +- src/Settings.jsx | 408 ----------------------- src/StreamsSelector.jsx | 5 +- src/Timeline.jsx | 16 +- src/TimelineSeg.jsx | 3 +- src/TopMenu.jsx | 4 +- src/colors.js | 12 +- src/components/BatchFile.jsx | 4 +- src/components/BatchFilesList.jsx | 12 +- src/components/ConcatDialog.jsx | 8 +- src/components/ExportButton.jsx | 2 +- src/{ => components}/ExportConfirm.jsx | 57 ++-- src/components/ExportConfirm.module.css | 26 ++ src/components/ExportModeButton.jsx | 5 +- src/components/HighlightedText.jsx | 6 +- src/components/OutSegTemplateEditor.jsx | 8 +- src/components/OutputFormatSelect.jsx | 2 +- src/components/SegmentCutpointButton.jsx | 4 +- src/components/Select.jsx | 10 + src/components/Select.module.css | 18 + src/components/Settings.jsx | 387 +++++++++++++++++++++ src/components/Settings.module.css | 22 ++ src/{ => components}/Sheet.jsx | 18 +- src/components/Sheet.module.css | 16 + src/components/SimpleModeButton.jsx | 2 +- src/components/SubtitleControl.jsx | 5 +- src/components/Switch.jsx | 12 + src/components/Switch.module.css | 37 ++ src/components/ToggleExportConfirm.jsx | 2 +- src/components/ValueTuner.jsx | 4 +- src/components/VolumeControl.jsx | 2 +- src/hooks/useUserSettingsRoot.js | 4 + src/main.css | 22 +- src/theme.js | 41 ++- src/util/colors.js | 6 +- yarn.lock | 150 +++++++++ 44 files changed, 906 insertions(+), 587 deletions(-) delete mode 100644 src/Settings.jsx rename src/{ => components}/ExportConfirm.jsx (89%) create mode 100644 src/components/ExportConfirm.module.css create mode 100644 src/components/Select.jsx create mode 100644 src/components/Select.module.css create mode 100644 src/components/Settings.jsx create mode 100644 src/components/Settings.module.css rename src/{ => components}/Sheet.jsx (74%) create mode 100644 src/components/Sheet.module.css create mode 100644 src/components/Switch.jsx create mode 100644 src/components/Switch.module.css diff --git a/package.json b/package.json index b72a66d4..f0637241 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "license": "GPL-2.0-only", "devDependencies": { "@fontsource/open-sans": "^4.5.14", + "@radix-ui/react-switch": "^1.0.1", "@types/sortablejs": "^1.15.0", "@vitejs/plugin-react": "^3.1.0", "color": "^3.1.0", @@ -96,6 +97,7 @@ }, "dependencies": { "@electron/remote": "^2.0.9", + "@radix-ui/colors": "^0.1.8", "cue-parser": "^0.3.0", "data-uri-to-buffer": "^4.0.0", "electron-is-dev": "^2.0.0", diff --git a/public/configStore.js b/public/configStore.js index 990fc392..1f905701 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -122,6 +122,7 @@ const defaults = { trashTmpFiles: true, askForCleanup: true, }, allowMultipleInstances: false, + darkMode: true, }; // For portable app: https://github.com/mifi/lossless-cut/issues/645 diff --git a/public/electron.js b/public/electron.js index 50feea68..e2f5430a 100644 --- a/public/electron.js +++ b/public/electron.js @@ -20,7 +20,7 @@ const { checkNewVersion } = require('./update-checker'); require('./i18n'); -const { app, ipcMain, shell, BrowserWindow } = electron; +const { app, ipcMain, shell, BrowserWindow, nativeTheme } = electron; remote.initialize(); @@ -71,6 +71,10 @@ function getSizeOptions() { } function createWindow() { + const darkMode = configStore.get('darkMode'); + // todo follow darkMode setting when user switches + if (darkMode) nativeTheme.themeSource = 'dark'; + mainWindow = new BrowserWindow({ ...getSizeOptions(), darkTheme: true, @@ -81,6 +85,7 @@ function createWindow() { // https://github.com/electron/electron/issues/5107 webSecurity: !isDev, }, + backgroundColor: darkMode ? '#333' : '#fff', }); remote.enable(mainWindow.webContents); diff --git a/src/App.jsx b/src/App.jsx index 1a55b582..71b883b1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,7 +2,7 @@ import React, { memo, useEffect, useState, useCallback, useRef, useMemo } from ' import { FaAngleLeft, FaWindowClose } from 'react-icons/fa'; import { MdRotate90DegreesCcw } from 'react-icons/md'; import { AnimatePresence } from 'framer-motion'; -import { Heading, InlineAlert, Table, SideSheet, Position, ThemeProvider } from 'evergreen-ui'; +import { SideSheet, Position, ThemeProvider } from 'evergreen-ui'; import useDebounceOld from 'react-use/lib/useDebounce'; // Want to phase out this import { useDebounce } from 'use-debounce'; import i18n from 'i18next'; @@ -31,14 +31,14 @@ import UserSettingsContext from './contexts/UserSettingsContext'; import NoFileLoaded from './NoFileLoaded'; import Canvas from './Canvas'; import TopMenu from './TopMenu'; -import Sheet from './Sheet'; +import Sheet from './components/Sheet'; import LastCommandsSheet from './LastCommandsSheet'; import StreamsSelector from './StreamsSelector'; import SegmentList from './SegmentList'; -import Settings from './Settings'; +import Settings from './components/Settings'; import Timeline from './Timeline'; import BottomBar from './BottomBar'; -import ExportConfirm from './ExportConfirm'; +import ExportConfirm from './components/ExportConfirm'; import ValueTuners from './components/ValueTuners'; import VolumeControl from './components/VolumeControl'; import SubtitleControl from './components/SubtitleControl'; @@ -49,7 +49,7 @@ import Working from './components/Working'; import OutputFormatSelect from './components/OutputFormatSelect'; import { loadMifiLink, runStartupCheck } from './mifi'; -import { controlsBackground } from './colors'; +import { controlsBackground, darkModeTransition } from './colors'; import { getStreamFps, isCuttingStart, isCuttingEnd, readFileMeta, getSmarterOutFormat, renderThumbnails as ffmpegRenderThumbnails, @@ -96,7 +96,7 @@ const calcShouldShowKeyframes = (zoomedDuration) => (zoomedDuration != null && z const videoStyle = { width: '100%', height: '100%', objectFit: 'contain' }; -const bottomStyle = { background: controlsBackground }; +const bottomStyle = { background: controlsBackground, transition: darkModeTransition }; let lastOpenedPath; const hevcPlaybackSupportedPromise = doesPlayerSupportHevcPlayback(); @@ -174,7 +174,7 @@ const App = memo(() => { const allUserSettings = useUserSettingsRoot(); const { - captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, + captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode, } = allUserSettings; useEffect(() => { @@ -2160,7 +2160,7 @@ const App = memo(() => { return ( -
+
{ )} {isFileOpened && ( -
+
{subtitleStreams.length > 0 && } @@ -2236,7 +2236,7 @@ const App = memo(() => { title={t('Show sidebar')} size={30} role="button" - style={{ marginRight: 10 }} + style={{ marginRight: 10, color: 'var(--gray12)', opacity: 0.7 }} onClick={toggleSegmentsList} /> )} @@ -2359,6 +2359,8 @@ const App = memo(() => { detectedFps={detectedFps} toggleLoopSelectedSegments={toggleLoopSelectedSegments} isFileOpened={isFileOpened} + darkMode={darkMode} + setDarkMode={setDarkMode} />
@@ -2406,23 +2408,13 @@ const App = memo(() => { ffmpegCommandLog={ffmpegCommandLog} /> - - {t('Keyboard & mouse shortcuts')} - {t('Hover mouse over buttons in the main interface to see which function they have')} - - - {t('Settings')} - {t('Current setting')} - - - - -
+ + 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> diff --git a/src/BetweenSegments.jsx b/src/BetweenSegments.jsx index e287f6f4..f549d744 100644 --- a/src/BetweenSegments.jsx +++ b/src/BetweenSegments.jsx @@ -29,13 +29,13 @@ const BetweenSegments = memo(({ start, end, duration, invertCutSegments }) => { layout transition={mySpring} > -
+
{invertCutSegments ? ( ) : ( - + )} -
+
); }); diff --git a/src/BottomBar.jsx b/src/BottomBar.jsx index 205dbdeb..3861753a 100644 --- a/src/BottomBar.jsx +++ b/src/BottomBar.jsx @@ -1,19 +1,19 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { Select } from 'evergreen-ui'; import { motion } from 'framer-motion'; import { MdRotate90DegreesCcw } from 'react-icons/md'; import { useTranslation } from 'react-i18next'; import { IoIosCamera, IoMdKey } from 'react-icons/io'; -import { FaYinYang, FaTrashAlt, FaStepBackward, FaStepForward, FaCaretLeft, FaCaretRight, FaPause, FaPlay, FaImages, FaKey } from 'react-icons/fa'; +import { FaYinYang, FaTrashAlt, FaStepBackward, FaStepForward, FaCaretLeft, FaCaretRight, FaPause, FaPlay, FaImages, FaKey, FaSun } from 'react-icons/fa'; import { GiSoundWaves } from 'react-icons/gi'; // import useTraceUpdate from 'use-trace-update'; -import { primaryTextColor, primaryColor } from './colors'; +import { primaryTextColor, primaryColor, darkModeTransition } from './colors'; 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 Select from './components/Select'; import SimpleModeButton from './components/SimpleModeButton'; import { withBlur, mirrorTransform, checkAppPath } from './util'; @@ -29,7 +29,7 @@ const zoomOptions = Array(13).fill().map((unused, z) => 2 ** z); const leftRightWidth = 100; -const CutTimeInput = memo(({ cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => { +const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => { const { t } = useTranslation(); const [cutTimeManual, setCutTimeManual] = useState(); @@ -41,10 +41,10 @@ const CutTimeInput = memo(({ cutTime, setCutTime, startTimeOffset, seekAbs, curr const isCutTimeManualSet = () => cutTimeManual !== undefined; - const border = `1px solid ${getSegColor(currentCutSeg).alpha(0.8).string()}`; + const border = `.15em solid ${getSegColor(currentCutSeg, darkMode).alpha(0.8).string()}`; const cutTimeInputStyle = { - background: 'white', border, borderRadius: 5, color: 'rgba(0, 0, 0, 0.7)', fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none', + border, borderRadius: 5, backgroundColor: 'var(--gray5)', transition: darkModeTransition, fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none', }; const trySetTime = useCallback((timeWithOffset) => { @@ -113,7 +113,7 @@ const CutTimeInput = memo(({ cutTime, setCutTime, startTimeOffset, seekAbs, curr return (
handleCutTimeInput(e.target.value)} @@ -137,6 +137,7 @@ const BottomBar = memo(({ jumpTimelineStart, jumpTimelineEnd, jumpCutEnd, jumpCutStart, startTimeOffset, setCutTime, currentApparentCutSeg, playing, shortStep, togglePlay, toggleLoopSelectedSegments, toggleTimelineMode, hasAudio, timelineMode, keyframesEnabled, toggleKeyframesEnabled, seekClosestKeyframe, detectedFps, isFileOpened, selectedSegments, + darkMode, setDarkMode, }) => { const { t } = useTranslation(); @@ -146,7 +147,7 @@ const BottomBar = memo(({ const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10); const gradientColors = selectedSegmentsSafe.map((seg, i) => { - const segColor = getSegColor(seg); + const segColor = getSegColor(seg, darkMode); // make colors stronger, the more segments return `${segColor.alpha(Math.max(0.4, Math.min(0.8, selectedSegmentsSafe.length / 3))).string()} ${((i / (selectedSegmentsSafe.length - 1)) * 100).toFixed(1)}%`; }).join(', '); @@ -155,7 +156,8 @@ const BottomBar = memo(({ paddingLeft: 2, backgroundOffset: 30, background: `linear-gradient(90deg, ${gradientColors})`, - border: '1px solid rgb(200,200,200)', + border: '1px solid var(--gray8)', + color: 'white', margin: '2px 4px 0 0px', display: 'flex', alignItems: 'center', @@ -164,7 +166,7 @@ const BottomBar = memo(({ height: 24, borderRadius: 4, }; - }, [selectedSegments]); + }, [darkMode, selectedSegments]); const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode, exportConfirmEnabled } = useUserSettings(); @@ -187,8 +189,8 @@ const BottomBar = memo(({ const newIndex = currentSegIndexSafe + direction; const seg = cutSegments[newIndex]; - const backgroundColor = seg && getSegColor(seg).alpha(0.5).string(); - const opacity = seg ? undefined : 0.3; + const backgroundColor = seg && getSegColor(seg, darkMode).alpha(0.5).string(); + const opacity = seg ? undefined : 0.5; const text = seg ? `${newIndex + 1}` : '-'; const wide = text.length > 1; const segButtonStyle = { @@ -215,10 +217,12 @@ const BottomBar = memo(({
{!simpleMode && ( <> + setDarkMode((v) => !v)} style={{ padding: '0 .2em 0 .3em' }} /> + {hasAudio && ( toggleTimelineMode('waveform')} @@ -228,7 +232,7 @@ const BottomBar = memo(({ <> toggleTimelineMode('thumbnails')} @@ -236,7 +240,7 @@ const BottomBar = memo(({ - {!simpleMode && } + {!simpleMode && } )} -
togglePlay()} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17 }}> +
togglePlay()} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17, color: 'white' }}> seekClosestKeyframe(1)} /> - {!simpleMode && } + {!simpleMode && } @@ -371,14 +375,14 @@ const BottomBar = memo(({
{Math.floor(zoom)}x
- setZoom(parseInt(e.target.value, 10)))}> {zoomOptions.map(val => ( ))} - {detectedFps != null &&
{detectedFps.toFixed(3)}
} + {detectedFps != null &&
{detectedFps.toFixed(3)}
} )} diff --git a/src/LastCommandsSheet.jsx b/src/LastCommandsSheet.jsx index 20cbceca..a822231a 100644 --- a/src/LastCommandsSheet.jsx +++ b/src/LastCommandsSheet.jsx @@ -3,13 +3,13 @@ import { useTranslation } from 'react-i18next'; import { Heading } from 'evergreen-ui'; import CopyClipboardButton from './components/CopyClipboardButton'; -import Sheet from './Sheet'; +import Sheet from './components/Sheet'; const LastCommandsSheet = memo(({ visible, onTogglePress, ffmpegCommandLog }) => { const { t } = useTranslation(); return ( - + {t('Last ffmpeg commands')} {ffmpegCommandLog.length > 0 ? ( diff --git a/src/NoFileLoaded.jsx b/src/NoFileLoaded.jsx index 98af726b..504cc4d5 100644 --- a/src/NoFileLoaded.jsx +++ b/src/NoFileLoaded.jsx @@ -11,21 +11,21 @@ const electron = window.require('electron'); const NoFileLoaded = memo(({ mifiLink, currentCutSeg }) => { const { t } = useTranslation(); - const { simpleMode, toggleSimpleMode } = useUserSettings(); + const { simpleMode } = useUserSettings(); return ( -
+
{t('DROP FILE(S)')}
-
+
See Help menu for help
-
+
or I O to set cutpoints
-
+
{simpleMode ? i18n.t('to show advanced view') : i18n.t('to show simple view')}
diff --git a/src/SegmentList.jsx b/src/SegmentList.jsx index a3076aa7..0b288d19 100644 --- a/src/SegmentList.jsx +++ b/src/SegmentList.jsx @@ -11,7 +11,7 @@ import scrollIntoView from 'scroll-into-view-if-needed'; import Swal from './swal'; import useContextMenu from './hooks/useContextMenu'; import useUserSettings from './hooks/useUserSettings'; -import { saveColor, controlsBackground, primaryTextColor } from './colors'; +import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } from './colors'; import { getSegColor } from './util/colors'; import { mySpring } from './animations'; @@ -19,10 +19,10 @@ const buttonBaseStyle = { margin: '0 3px', borderRadius: 3, color: 'white', cursor: 'pointer', }; -const neutralButtonColor = 'rgba(255, 255, 255, 0.2)'; +const neutralButtonColor = 'var(--gray8)'; -const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments }) => { +const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments }) => { const { t } = useTranslation(); const ref = useRef(); @@ -79,7 +79,7 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou function renderNumber() { if (invertCutSegments) return ; - const segColor = getSegColor(seg); + const segColor = getSegColor(seg, darkMode); return {index + 1}; } @@ -114,18 +114,18 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou onClick={() => !invertCutSegments && onClick(index)} onDoubleClick={onDoubleClick} layout - style={{ originY: 0, margin: '5px 0', background: 'rgba(0,0,0,0.1)', border: `1px solid rgba(255,255,255,${isActive ? 1 : 0.3})`, padding: 5, borderRadius: 5, position: 'relative' }} + style={{ originY: 0, margin: '5px 0', background: 'var(--gray2)', border: isActive ? '1px solid var(--gray10)' : '1px solid transparent', padding: 5, borderRadius: 5, position: 'relative' }} initial={{ scaleY: 0 }} animate={{ scaleY: 1, opacity: !selected && !invertCutSegments ? 0.5 : undefined }} exit={{ scaleY: 0 }} className="segment-list-entry" > -
+
{renderNumber()} {timeStr}
-
{seg.name}
+
{seg.name}
{t('Duration')} {formatTimecode({ seconds: duration, shorten: true })}
@@ -135,7 +135,7 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou {!invertCutSegments && (
- +
)} @@ -152,7 +152,7 @@ const SegmentList = memo(({ }) => { const { t } = useTranslation(); - const { invertCutSegments, simpleMode } = useUserSettings(); + const { invertCutSegments, simpleMode, darkMode } = useUserSettings(); const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments; @@ -195,14 +195,14 @@ const SegmentList = memo(({ } function renderFooter() { - const currentSegColor = getSegColor(currentCutSeg).alpha(0.5).string(); - const segAtCursorColor = getSegColor(segmentAtCursor).alpha(0.5).string(); + const currentSegColor = getSegColor(currentCutSeg, darkMode).alpha(0.5).string(); + const segAtCursorColor = getSegColor(segmentAtCursor, darkMode).alpha(0.5).string(); const segmentsTotal = selectedSegments.reduce((acc, { start, end }) => (end - start) + acc, 0); return ( <> -
+
-
+
{t('Segments total:')}
{formatTimecode({ seconds: segmentsTotal })}
@@ -258,17 +258,17 @@ const SegmentList = memo(({ return ( -
+
@@ -283,6 +283,7 @@ const SegmentList = memo(({ return ( ; -// eslint-disable-next-line react/jsx-props-no-spreading -const KeyCell = (props) => ; - -const Header = ({ title }) => ( - - {title} - - -); - -const Settings = memo(({ - onTunerRequested, - onKeyboardShortcutsDialogRequested, - askForCleanupChoices, - toggleStoreProjectInWorkingDir, -}) => { - 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, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances } = useUserSettings(); - - const onLangChange = useCallback((e) => { - const { value } = e.target; - const l = value !== '' ? value : undefined; - setLanguage(l); - }, [setLanguage]); - - const timecodeFormatOptions = useMemo(() => ({ - frameCount: t('Frame counts'), - timecodeWithDecimalFraction: t('Millisecond fractions'), - timecodeWithFramesFraction: t('Frame fractions'), - }), [t]); - - const onTimecodeFormatClick = useCallback(() => { - const keys = Object.keys(timecodeFormatOptions); - let index = keys.indexOf(timecodeFormat); - if (index === -1 || index >= keys.length - 1) index = 0; - else index += 1; - setTimecodeFormat(keys[index]); - }, [setTimecodeFormat, timecodeFormat, timecodeFormatOptions]); - - const changeCustomFfPath = useCallback(async () => { - const newCustomFfPath = await askForFfPath(customFfPath); - setCustomFfPath(newCustomFfPath); - }, [customFfPath, setCustomFfPath]); - - return ( - <> - - App language - - - - - - - - {t('Choose cutting mode: Remove or keep selected segments from video when exporting?')}
- {t('Keep')}: {t('The video inside segments will be kept, while the video outside will be discarded.')}
- {t('Remove')}: {t('The video inside segments will be discarded, while the video surrounding them will be kept.')} -
- - - -
- - - - {t('Working directory')}
- {t('This is where working files and exported files are stored.')} -
- - -
{customOutDir}
-
-
- - - - {t('Auto save project file?')}
-
- - setAutoSaveProjectFile(e.target.checked)} - /> - -
- - - {t('Store project file (.llc) in the working directory or next to loaded media file?')} - - - - - -
- - - {t('Keyboard & mouse shortcuts')} - - - - - - - {t('Mouse wheel zoom modifier key')} - - - - - - - {t('Timeline trackpad/wheel sensitivity')} - - - - - - - {t('Timeline keyboard seek speed')} - - - - - - - {t('Timeline keyboard seek acceleration')} - - - - - - - {t('Invert timeline trackpad/wheel direction?')} - - setInvertTimelineScroll(e.target.checked)} - /> - - - -
- - - {t('Set file modification date/time of output files to:')} - - - - - - - - {t('Keyframe cut mode')}
- {t('Keyframe cut')}: {t('Cut at the nearest keyframe (not accurate time.) Equiv to')} ffmpeg -ss -i ...
- {t('Normal cut')}: {t('Accurate time but could leave an empty portion at the beginning of the video. Equiv to')} ffmpeg -i -ss ...
-
- - - -
- - - {t('Overwrite files when exporting, if a file with the same name as the output file name exists?')} - - setEnableOverwriteOutput(e.target.checked)} - /> - - - - - {t('Cleanup files after export?')} - - - - - -
- - - - {t('Snapshot capture format')} - - - - - - - - {t('Snapshot capture method')} - - - {captureFrameMethod === 'ffmpeg' &&
{t('FFmpeg capture method might sometimes capture more correct colors, but the captured snapshot might be off by one or more frames, relative to the preview.')}
} -
-
- - - {t('Snapshot capture quality')} - - setCaptureFrameQuality(Math.max(Math.min(1, parseInt(e.target.value, 10) / 1000)), 0)} />
- {Math.round(captureFrameQuality * 100)}% -
-
- - - {t('File names of extracted video frames')} - - - - - - - {t('In timecode show')} - - - - - -
- - - {t('Hide informational notifications?')} - - setHideNotifications(e.target.checked ? 'all' : undefined)} - /> - - - - - {t('Ask about what to do when opening a new file when another file is already already open?')} - - setEnableAskForFileOpenAction(e.target.checked)} - /> - - - - - {t('Ask for confirmation when closing app or file?')} - - setAskBeforeClose(e.target.checked)} - /> - - - - - {t('Ask about importing chapters from opened file?')} - - setEnableAskForImportChapters(e.target.checked)} - /> - - - -
- - {!isMasBuild && ( - - - {t('Custom FFmpeg directory (experimental)')}
- {t('This allows you to specify custom FFmpeg and FFprobe binaries to use. Make sure the "ffmpeg" and "ffprobe" executables exist in the same directory, and then select the directory.')} -
- - -
{customFfPath}
-
-
- )} - - {!isStoreBuild && ( - - {t('Check for updates on startup?')} - - setEnableUpdateCheck(e.target.checked)} - /> - - - )} - - - {t('Allow multiple instances of LosslessCut to run concurrently? (experimental)')} - - setAllowMultipleInstances(e.target.checked)} - /> - - - - - {t('Enable HEVC / H265 hardware decoding (you may need to turn this off if you have problems with HEVC files)')} - - setEnableNativeHevc(e.target.checked)} - /> - - - - - {t('Enable experimental ffmpeg features flag?')} - - setFfmpegExperimental(e.target.checked)} - /> - - - - - {t('Auto load timecode from file as an offset in the timeline?')} - - setAutoLoadTimecode(e.target.checked)} - /> - - - - - {t('Try to automatically convert to supported format when opening unsupported file?')} - - setEnableAutoHtml5ify(e.target.checked)} - /> - - - - - - {t('Extract unprocessable tracks to separate files or discard them?')}
- {t('(data tracks such as GoPro GPS, telemetry etc. are not copied over by default because ffmpeg cannot cut them, thus they will cause the media duration to stay the same after cutting video/audio)')} -
- - - -
- - ); -}); - -export default Settings; diff --git a/src/StreamsSelector.jsx b/src/StreamsSelector.jsx index e3d46ff0..80bd2d15 100644 --- a/src/StreamsSelector.jsx +++ b/src/StreamsSelector.jsx @@ -4,11 +4,12 @@ import { FaImage, FaCheckCircle, FaPaperclip, FaVideo, FaVideoSlash, FaFileImpor import { GoFileBinary } from 'react-icons/go'; import { FiEdit, FiCheck, FiTrash } from 'react-icons/fi'; 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 { BookIcon, Paragraph, TextInput, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Heading, SortAscIcon, SortDescIcon, Dialog, Button, PlusIcon, Pane, ForkIcon, Alert } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; import prettyBytes from 'pretty-bytes'; import AutoExportToggler from './components/AutoExportToggler'; +import Select from './components/Select'; import { askForMetadataKey, showJson5Dialog } from './dialogs'; import { formatDuration } from './util/duration'; import { getStreamFps } from './ffmpeg'; @@ -254,7 +255,7 @@ const Stream = memo(({ dispositionByStreamId, setDispositionByStreamId, filePath {language} {stream.width && stream.height && `${stream.width}x${stream.height}`} {stream.channels && `${stream.channels}c`} {stream.channel_layout} {streamFps && `${streamFps.toFixed(2)}fps`} - diff --git a/src/Timeline.jsx b/src/Timeline.jsx index 70ba164e..f60a1a2d 100644 --- a/src/Timeline.jsx +++ b/src/Timeline.jsx @@ -10,7 +10,7 @@ import useContextMenu from './hooks/useContextMenu'; import useUserSettings from './hooks/useUserSettings'; -import { timelineBackground } from './colors'; +import { timelineBackground, darkModeTransition } from './colors'; import { getSegColor } from './util/colors'; @@ -43,7 +43,7 @@ const Waveforms = memo(({ calculateTimelinePercent, durationSafe, waveforms, zoo )); const CommandedTime = memo(({ commandedTimePercent }) => { - const color = 'white'; + const color = 'var(--gray12)'; const commonStyle = { left: commandedTimePercent, position: 'absolute', zIndex: 4, pointerEvents: 'none' }; return ( <> @@ -64,7 +64,7 @@ const Timeline = memo(({ }) => { const { t } = useTranslation(); - const { invertCutSegments } = useUserSettings(); + const { invertCutSegments, darkMode } = useUserSettings(); const timelineScrollerRef = useRef(); const timelineScrollerSkipEventRef = useRef(); @@ -276,18 +276,18 @@ const Timeline = memo(({ )}
{currentTimePercent !== undefined && ( - + )} {commandedTimePercent !== undefined && ( )} {apparentCutSegments.map((seg, i) => { - const segColor = getSegColor(seg); + const segColor = getSegColor(seg, darkMode); if (seg.start === 0 && seg.end === 0) return null; // No video loaded @@ -322,13 +322,13 @@ const Timeline = memo(({ ))} {shouldShowKeyframes && !areKeyframesTooClose && neighbouringKeyFrames.map((f) => ( -
+
))}
{(waveformEnabled && !thumbnailsEnabled && !shouldShowWaveform) && ( -
+
{t('Zoom in more to view waveform')}
)} diff --git a/src/TimelineSeg.jsx b/src/TimelineSeg.jsx index fbe2fd3e..44018d5c 100644 --- a/src/TimelineSeg.jsx +++ b/src/TimelineSeg.jsx @@ -33,6 +33,7 @@ const TimelineSeg = memo(({ justifyContent: 'space-between', originX: 0, boxSizing: 'border-box', + color: 'white', borderLeft: markerBorder, borderTopLeftRadius: markerBorderRadius, @@ -72,7 +73,7 @@ const TimelineSeg = memo(({ style={{ width: 16, height: 16, flexShrink: 1 }} > diff --git a/src/TopMenu.jsx b/src/TopMenu.jsx index 2cc8285d..c5220b42 100644 --- a/src/TopMenu.jsx +++ b/src/TopMenu.jsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import ExportModeButton from './components/ExportModeButton'; import { withBlur } from './util'; -import { primaryTextColor, controlsBackground } from './colors'; +import { primaryTextColor, controlsBackground, darkModeTransition } from './colors'; import useUserSettings from './hooks/useUserSettings'; @@ -31,7 +31,7 @@ const TopMenu = memo(({ return (
{filePath && ( <> diff --git a/src/colors.js b/src/colors.js index 5877c478..872920c2 100644 --- a/src/colors.js +++ b/src/colors.js @@ -1,6 +1,8 @@ -export const saveColor = 'hsl(158, 100%, 43%)'; -export const primaryColor = 'hsl(194, 78%, 47%)'; -export const primaryTextColor = 'hsla(194, 100%, 66%, 1)'; +export const saveColor = 'var(--green11)'; +export const primaryColor = 'var(--cyan9)'; +export const primaryTextColor = 'var(--cyan11)'; +// todo darkMode: export const waveformColor = '#ffffff'; // Must be hex because used by ffmpeg -export const controlsBackground = '#6b6b6b'; -export const timelineBackground = '#444'; +export const controlsBackground = 'var(--gray4)'; +export const timelineBackground = 'var(--gray2)'; +export const darkModeTransition = 'background .5s'; diff --git a/src/components/BatchFile.jsx b/src/components/BatchFile.jsx index ff628786..e810f8c1 100644 --- a/src/components/BatchFile.jsx +++ b/src/components/BatchFile.jsx @@ -16,12 +16,12 @@ const BatchFile = memo(({ path, isOpen, isSelected, name, onSelect, onDelete }) useContextMenu(ref, contextMenuTemplate); return ( -
onSelect(path)}> +
onSelect(path)}>
{name}
- {isOpen && } + {isOpen && }
); }); diff --git a/src/components/BatchFilesList.jsx b/src/components/BatchFilesList.jsx index 2f03ef2e..196e9e79 100644 --- a/src/components/BatchFilesList.jsx +++ b/src/components/BatchFilesList.jsx @@ -7,13 +7,13 @@ import { ReactSortable } from 'react-sortablejs'; import { SortAlphabeticalIcon, SortAlphabeticalDescIcon } from 'evergreen-ui'; import BatchFile from './BatchFile'; -import { timelineBackground, controlsBackground } from '../colors'; +import { controlsBackground, darkModeTransition } from '../colors'; import { mySpring } from '../animations'; const iconStyle = { flexShrink: 0, - color: 'white', + color: 'var(--gray12)', cursor: 'pointer', paddingTop: 3, paddingBottom: 3, @@ -46,19 +46,19 @@ const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles, return ( -
- {t('Batch file list')} +
+
{t('Batch file list')}
- +
diff --git a/src/components/ConcatDialog.jsx b/src/components/ConcatDialog.jsx index aeaef53f..c4e4f4ce 100644 --- a/src/components/ConcatDialog.jsx +++ b/src/components/ConcatDialog.jsx @@ -1,8 +1,8 @@ import React, { memo, useState, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { TextInput, IconButton, Alert, Checkbox, Dialog, Button, Paragraph } from 'evergreen-ui'; +import { TextInput, IconButton, Alert, Checkbox, Dialog, Button, Paragraph, CogIcon } from 'evergreen-ui'; import { AiOutlineMergeCells } from 'react-icons/ai'; -import { FaQuestionCircle, FaCheckCircle, FaExclamationTriangle } from 'react-icons/fa'; +import { FaQuestionCircle, FaExclamationTriangle } from 'react-icons/fa'; import i18n from 'i18next'; import withReactContent from 'sweetalert2-react-content'; @@ -170,9 +170,9 @@ const ConcatDialog = memo(({ <>
setEnableReadFileMeta(e.target.checked)} label={t('Check compatibility')} marginLeft={10} marginRight={10} /> - + {fileFormat && detectedFileFormat ? ( - + ) : ( )} diff --git a/src/components/ExportButton.jsx b/src/components/ExportButton.jsx index 4b466dc3..7422b4e1 100644 --- a/src/components/ExportButton.jsx +++ b/src/components/ExportButton.jsx @@ -25,7 +25,7 @@ const ExportButton = memo(({ segmentsToExport, areWeCutting, onClick, size = 1 } return (
; +const HelpIcon = ({ onClick, style }) => ; const ExportConfirm = memo(({ areWeCutting, selectedSegments, segmentsToExport, willMerge, visible, onClosePress, onExportConfirm, @@ -128,11 +119,11 @@ const ExportConfirm = memo(({ initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - style={sheetStyle} + className={styles.sheet} transition={{ duration: 0.3, easings: ['easeOut'] }} >
-
+

{t('Export options')}

@@ -213,7 +204,7 @@ const ExportConfirm = memo(({ {!needSmartCut && (
  • "avoid_negative_ts" - setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}> @@ -237,7 +228,7 @@ const ExportConfirm = memo(({ style={{ display: 'flex', alignItems: 'flex-end' }} > -
    {t('Show this page before exporting?')}
    +
    {t('Show this page before exporting?')}
    { const { t } = useTranslation(); @@ -50,8 +50,7 @@ const ExportModeButton = memo(({ selectedSegments, style }) => { return ( // eslint-disable-next-line react/jsx-props-no-spreading +)); + +export default Select; diff --git a/src/components/Select.module.css b/src/components/Select.module.css new file mode 100644 index 00000000..d01c19a9 --- /dev/null +++ b/src/components/Select.module.css @@ -0,0 +1,18 @@ +.select { + appearance: none; + font: inherit; + line-height: 120%; + font-size: .8em; + background-color: var(--gray3); + color: var(--gray12); + border-radius: .3em; + padding: 0 .3em; + outline: .05em solid var(--gray8); + border: .05em solid var(--gray7); + + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position-x: 100%; + background-position-y: 0; + background-size: auto 100%; +} \ No newline at end of file diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx new file mode 100644 index 00000000..1c5e95e9 --- /dev/null +++ b/src/components/Settings.jsx @@ -0,0 +1,387 @@ +import React, { memo, useCallback, useMemo } from 'react'; +import { FaYinYang, FaKeyboard } from 'react-icons/fa'; +import { GlobeIcon, CleanIcon, CogIcon, Button, NumericalIcon, KeyIcon, FolderCloseIcon, DocumentIcon, TimeIcon } from 'evergreen-ui'; +import { useTranslation } from 'react-i18next'; + +import CaptureFormatButton from './CaptureFormatButton'; +import AutoExportToggler from './AutoExportToggler'; +import Switch from './Switch'; +import useUserSettings from '../hooks/useUserSettings'; +import { askForFfPath } from '../dialogs'; +import { isMasBuild, isStoreBuild } from '../util'; +import { langNames } from '../util/constants'; +import styles from './Settings.module.css'; +import Select from './Select'; + +import { getModifierKeyNames } from '../hooks/useTimelineScroll'; + + +// eslint-disable-next-line react/jsx-props-no-spreading +const Row = (props) => ; +// eslint-disable-next-line react/jsx-props-no-spreading +const KeyCell = (props) => ; + +const Header = ({ title }) => ( + + {title} + + +); + +const detailsStyle = { opacity: 0.7, fontSize: '.9em', marginTop: '.3em' }; + +const Settings = memo(({ + onTunerRequested, + onKeyboardShortcutsDialogRequested, + askForCleanupChoices, + toggleStoreProjectInWorkingDir, +}) => { + 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, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances } = useUserSettings(); + + const onLangChange = useCallback((e) => { + const { value } = e.target; + const l = value !== '' ? value : undefined; + setLanguage(l); + }, [setLanguage]); + + const timecodeFormatOptions = useMemo(() => ({ + frameCount: t('Frame counts'), + timecodeWithDecimalFraction: t('Millisecond fractions'), + timecodeWithFramesFraction: t('Frame fractions'), + }), [t]); + + const onTimecodeFormatClick = useCallback(() => { + const keys = Object.keys(timecodeFormatOptions); + let index = keys.indexOf(timecodeFormat); + if (index === -1 || index >= keys.length - 1) index = 0; + else index += 1; + setTimecodeFormat(keys[index]); + }, [setTimecodeFormat, timecodeFormat, timecodeFormatOptions]); + + const changeCustomFfPath = useCallback(async () => { + const newCustomFfPath = await askForFfPath(customFfPath); + setCustomFfPath(newCustomFfPath); + }, [customFfPath, setCustomFfPath]); + + return ( + <> +
    +
    {t('Hover mouse over buttons in the main interface to see which function they have')}
    +
    + + + + + + + + + + + App language + + + + + + {t('Choose cutting mode: Remove or keep selected segments from video when exporting?')}
    +
    + {t('Keep')}: {t('The video inside segments will be kept, while the video outside will be discarded.')}
    + {t('Remove')}: {t('The video inside segments will be discarded, while the video surrounding them will be kept.')} +
    +
    +
    + + + + + {t('Working directory')}
    +
    + {t('This is where working files and exported files are stored.')} +
    +
    +
    + + + + + {t('Auto save project file?')}
    +
    +
    + + + + {t('Store project file (.llc) in the working directory or next to loaded media file?')} + + + +
    + + + {t('Keyboard & mouse shortcuts')} +
    + + + + {t('Mouse wheel zoom modifier key')} + + + + + {t('Timeline trackpad/wheel sensitivity')} + + + + + {t('Timeline keyboard seek speed')} + + + + + {t('Timeline keyboard seek acceleration')} + + + + + {t('Invert timeline trackpad/wheel direction?')} + + + +
    + + + {t('Set file modification date/time of output files to:')} +
    + + + + + {t('Keyframe cut mode')}
    +
    + {t('Keyframe cut')}: {t('Cut at the nearest keyframe (not accurate time.) Equiv to')} ffmpeg -ss -i ...
    + {t('Normal cut')}: {t('Accurate time but could leave an empty portion at the beginning of the video. Equiv to')} ffmpeg -i -ss ...
    +
    +
    +
    + + + + {t('Overwrite files when exporting, if a file with the same name as the output file name exists?')} + + + + + {t('Cleanup files after export?')} + + + +
    + + + + {t('Snapshot capture format')} + +
    + + + + + {t('Snapshot capture method')} +
    {t('FFmpeg capture method might sometimes capture more correct colors, but the captured snapshot might be off by one or more frames, relative to the preview.')}
    +
    +
    + + + + {t('Snapshot capture quality')} + + + + + {t('File names of extracted video frames')} + + + + + {t('In timecode show')} + + + +
    + + + {t('Show informational notifications')} +
    + + + + {t('Ask about what to do when opening a new file when another file is already already open?')} + + + + + {t('Ask for confirmation when closing app or file?')} + + + + + {t('Ask about importing chapters from opened file?')} + + + +
    + + {!isMasBuild && ( + + + {t('Custom FFmpeg directory (experimental)')}
    +
    + {t('This allows you to specify custom FFmpeg and FFprobe binaries to use. Make sure the "ffmpeg" and "ffprobe" executables exist in the same directory, and then select the directory.')} +
    +
    +
    + + )} + + {!isStoreBuild && ( + + {t('Check for updates on startup?')} + + + )} + + + {t('Allow multiple instances of LosslessCut to run concurrently? (experimental)')} + + + + + {t('Enable HEVC / H265 hardware decoding (you may need to turn this off if you have problems with HEVC files)')} + + + + + {t('Enable experimental ffmpeg features flag?')} + + + + + {t('Auto load timecode from file as an offset in the timeline?')} + + + + + {t('Try to automatically convert to supported format when opening unsupported file?')} + + + + + + {t('Extract unprocessable tracks to separate files or discard them?')}
    +
    + {t('(data tracks such as GoPro GPS, telemetry etc. are not copied over by default because ffmpeg cannot cut them, thus they will cause the media duration to stay the same after cutting video/audio)')} +
    +
    +
    + + +
    {t('Settings')}{t('Current setting')}
    + + + + + +
    {customOutDir}
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setCaptureFrameQuality(Math.max(Math.min(1, parseInt(e.target.value, 10) / 1000)), 0)} />
    + {Math.round(captureFrameQuality * 100)}% +
    + + + + + setHideNotifications(v ? 'all' : undefined)} /> + + + + + + + + +
    {customFfPath}
    +
    + + + + + + + + + + + + + +
    + + ); +}); + +export default Settings; diff --git a/src/components/Settings.module.css b/src/components/Settings.module.css new file mode 100644 index 00000000..a3138c09 --- /dev/null +++ b/src/components/Settings.module.css @@ -0,0 +1,22 @@ +.settings td:first-child, .settings th:first-child { + padding: 1em 2em 1em 2em; +} +.settings td:nth-child(2), .settings th:nth-child(2) { + padding: 1em 2em 1em 0em; +} + +.settings th { + text-align: left; +} + +.settings tr.header { + background-color: var(--blackA3); +} + +:global(.dark-theme) .settings tr.header { + background-color: var(--whiteA6); +} + +.settings { + border-collapse: collapse; +} diff --git a/src/Sheet.jsx b/src/components/Sheet.jsx similarity index 74% rename from src/Sheet.jsx rename to src/components/Sheet.jsx index ab54e2e3..721a43d7 100644 --- a/src/Sheet.jsx +++ b/src/components/Sheet.jsx @@ -2,16 +2,7 @@ import React, { memo } from 'react'; import { IoIosCloseCircleOutline } from 'react-icons/io'; import { motion, AnimatePresence } from 'framer-motion'; -const sheetStyle = { - padding: '1em 2em', - position: 'fixed', - left: 0, - right: 0, - top: 0, - bottom: 0, - zIndex: 10, - overflowY: 'scroll', -}; +import styles from './Sheet.module.css'; const Sheet = memo(({ visible, onClosePress, style, children }) => ( @@ -20,11 +11,14 @@ const Sheet = memo(({ visible, onClosePress, style, children }) => ( initial={{ scale: 0, opacity: 0.5 }} animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0, opacity: 0 }} - style={{ ...sheetStyle, ...style }} + style={style} + className={styles.sheet} > - {children} +
    + {children} +
    )} diff --git a/src/components/Sheet.module.css b/src/components/Sheet.module.css new file mode 100644 index 00000000..700627c3 --- /dev/null +++ b/src/components/Sheet.module.css @@ -0,0 +1,16 @@ +.sheet { + padding: 1em 2em; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 10; + background: var(--whiteA10); + color: var(--gray12); + backdrop-filter: blur(30px); +} + +:global(.dark-theme) .sheet { + background: var(--blackA11); +} diff --git a/src/components/SimpleModeButton.jsx b/src/components/SimpleModeButton.jsx index c643e6ba..8e18ecde 100644 --- a/src/components/SimpleModeButton.jsx +++ b/src/components/SimpleModeButton.jsx @@ -14,7 +14,7 @@ const SimpleModeButton = memo(({ size = 20, style }) => { ); diff --git a/src/components/SubtitleControl.jsx b/src/components/SubtitleControl.jsx index 8f688606..b195264d 100644 --- a/src/components/SubtitleControl.jsx +++ b/src/components/SubtitleControl.jsx @@ -1,7 +1,7 @@ import React, { memo, useState, useCallback, useRef, useEffect } from 'react'; import { MdSubtitles } from 'react-icons/md'; import { useTranslation } from 'react-i18next'; -import { Select } from 'evergreen-ui'; +import Select from './Select'; const SubtitleControl = memo(({ subtitleStreams, activeSubtitleStreamIndex, onActiveSubtitleChange }) => { const [controlVisible, setControlVisible] = useState(false); @@ -32,7 +32,6 @@ const SubtitleControl = memo(({ subtitleStreams, activeSubtitleStreamIndex, onAc <> {controlVisible && (