diff --git a/package.json b/package.json index b72a66d..f063724 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 990fc39..1f90570 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 50feea6..e2f5430 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 1a55b58..71b883b 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 e287f6f..f549d74 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 205dbde..3861753 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 20cbcec..a822231 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 98af726..504cc4d 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 a3076aa..0b288d1 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 e3d46ff..80bd2d1 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 70ba164..f60a1a2 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 fbe2fd3..44018d5 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 2cc8285..c5220b4 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 5877c47..872920c 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 ff62878..e810f8c 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 2f03ef2..196e9e7 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 aeaef53..c4e4f4c 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 4b466dc..7422b4e 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 0000000..d01c19a --- /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 0000000..1c5e95e --- /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 0000000..a3138c0 --- /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 ab54e2e..721a43d 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 0000000..700627c --- /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 c643e6b..8e18ecd 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 8f68860..b195264 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 && (