diff --git a/package.json b/package.json index ce892e8b..faf3fe80 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "devDependencies": { "@fontsource/open-sans": "^4.5.14", "@radix-ui/colors": "^0.1.8", + "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-switch": "^1.0.1", "@tsconfig/node18": "^18.2.2", "@tsconfig/strictest": "^2.0.2", @@ -101,6 +102,7 @@ "react-syntax-highlighter": "^15.4.3", "react-use": "^17.4.0", "rimraf": "^5.0.5", + "sass": "^1.77.2", "screenfull": "^6.0.2", "scroll-into-view-if-needed": "^2.2.28", "sharp": "^0.32.6", diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 4d69b34e..d8252531 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1338,6 +1338,7 @@ function App() { if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.')); const revealPath = willMerge ? mergedOutFilePath : outFiles[0]; + invariant(revealPath != null); if (!hideAllNotifications) openExportFinishedToast({ filePath: revealPath, warnings, notices }); if (cleanupChoices.cleanupAfterExport) await cleanupFilesWithDialog(); @@ -2260,6 +2261,8 @@ function App() { setLastCommandsVisible(false); setSettingsVisible(false); setStreamsSelectorShown(false); + setConcatDialogVisible(false); + setKeyboardShortcutsVisible(false); return false; } @@ -2506,300 +2509,304 @@ function App() { // throw new Error('Test error boundary'); return ( - - - -
- - -
- - {showLeftBar && ( - - )} - - - {/* Middle part (also shown in fullscreen): */} -
- {!isFileOpened && } - -
- {/* eslint-disable-next-line jsx-a11y/media-has-caption */} - - - {filePath != null && compatPlayerEnabled && } -
- - {bigWaveformEnabled && } - - {compatPlayerEnabled && ( -
- {isRotationSet ? ( - <> - - {t('Rotation preview')} - - ) : ( - <> - {t('FFmpeg-assisted playback')} - - )} - - {!compatPlayerRequired && setHideMediaSourcePlayer(true)} />} -
- )} - - {isFileOpened && ( -
- - - {shouldShowPlaybackStreamSelector && } - - {compatPlayerEnabled &&
incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}
} - - {!showRightBar && ( - - )} -
- )} + <> + + + +
+ +
- {working && } + {showLeftBar && ( + + )} - {tunerVisible && setTunerVisible(undefined)} />} + {/* Middle part (also shown in fullscreen): */} +
+ {!isFileOpened && } + +
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + + + {filePath != null && compatPlayerEnabled && } +
+ + {bigWaveformEnabled && } + + {compatPlayerEnabled && ( +
+ {isRotationSet ? ( + <> + + {t('Rotation preview')} + + ) : ( + <> + {t('FFmpeg-assisted playback')} + + )} + + {!compatPlayerRequired && setHideMediaSourcePlayer(true)} />} +
+ )} + + {isFileOpened && ( +
+ + + {shouldShowPlaybackStreamSelector && } + + {compatPlayerEnabled &&
incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}
} + + {!showRightBar && ( + + )} +
+ )} + + + {working && } + + + {tunerVisible && setTunerVisible(undefined)} />} +
+ + + {showRightBar && isFileOpened && filePath != null && ( + + )} +
- - {showRightBar && isFileOpened && filePath != null && ( - + + + +
+ + + + setStreamsSelectorShown(false)} maxWidth={1000}> + {mainStreams && filePath != null && ( + )} - -
+ -
- - -
- - - - setStreamsSelectorShown(false)} maxWidth={1000}> - {mainStreams && filePath != null && ( - + - )} - + - + 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> - - - + setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> +
+ + + - 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> - - setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> -
-
-
-
+
+ ); } diff --git a/src/renderer/src/StreamsSelector.tsx b/src/renderer/src/StreamsSelector.tsx index b5575bef..d15d838a 100644 --- a/src/renderer/src/StreamsSelector.tsx +++ b/src/renderer/src/StreamsSelector.tsx @@ -16,6 +16,7 @@ import { getActiveDisposition, attachedPicDisposition } from './util/streams'; import TagEditor from './components/TagEditor'; import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe'; import { CustomTagsByFile, FilesMeta, FormatTimecode, ParamsByStreamId, StreamParams } from './types'; +import useUserSettings from './hooks/useUserSettings'; const dispositionOptions = ['default', 'dub', 'original', 'comment', 'lyrics', 'karaoke', 'forced', 'hearing_impaired', 'visual_impaired', 'clean_effects', 'attached_pic', 'captions', 'descriptions', 'dependent', 'metadata']; @@ -147,13 +148,9 @@ const EditStreamDialog = memo(({ editingStream: { streamId: editingStreamId, pat ); }); -function onInfoClick(json: unknown, title: string) { - showJson5Dialog({ title, json }); -} - // eslint-disable-next-line react/display-name -const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copyStream, fileDuration, setEditingStream, onExtractStreamPress, paramsByStreamId, updateStreamParams, formatTimecode, loadSubtitleTrackToSegments }: { - filePath: string, stream: FFprobeStream, onToggle: (a: number) => void, batchSetCopyStreamIds: (filter: (a: FFprobeStream) => boolean, enabled: boolean) => void, copyStream: boolean, fileDuration: number | undefined, setEditingStream: (a: EditingStream) => void, onExtractStreamPress?: () => void, paramsByStreamId: ParamsByStreamId, updateStreamParams: UpdateStreamParams, formatTimecode: FormatTimecode, loadSubtitleTrackToSegments?: (index: number) => void, +const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copyStream, fileDuration, setEditingStream, onExtractStreamPress, paramsByStreamId, updateStreamParams, formatTimecode, loadSubtitleTrackToSegments, onInfoClick }: { + filePath: string, stream: FFprobeStream, onToggle: (a: number) => void, batchSetCopyStreamIds: (filter: (a: FFprobeStream) => boolean, enabled: boolean) => void, copyStream: boolean, fileDuration: number | undefined, setEditingStream: (a: EditingStream) => void, onExtractStreamPress?: () => void, paramsByStreamId: ParamsByStreamId, updateStreamParams: UpdateStreamParams, formatTimecode: FormatTimecode, loadSubtitleTrackToSegments?: (index: number) => void, onInfoClick: (json: unknown, title: string) => void, }) => { const { t } = useTranslation(); @@ -283,8 +280,8 @@ const Stream = memo(({ filePath, stream, onToggle, batchSetCopyStreamIds, copySt ); }); -function FileHeading({ path, formatData, chapters, onTrashClick, onEditClick, setCopyAllStreams, onExtractAllStreamsPress }: { - path: string, formatData: FFprobeFormat | undefined, chapters?: FFprobeChapter[] | undefined, onTrashClick?: (() => void) | undefined, onEditClick?: (() => void) | undefined, setCopyAllStreams: (a: boolean) => void, onExtractAllStreamsPress?: () => Promise, +function FileHeading({ path, formatData, chapters, onTrashClick, onEditClick, setCopyAllStreams, onExtractAllStreamsPress, onInfoClick }: { + path: string, formatData: FFprobeFormat | undefined, chapters?: FFprobeChapter[] | undefined, onTrashClick?: (() => void) | undefined, onEditClick?: (() => void) | undefined, setCopyAllStreams: (a: boolean) => void, onExtractAllStreamsPress?: () => Promise, onInfoClick: (json: unknown, title: string) => void, }) { const { t } = useTranslation(); @@ -360,6 +357,7 @@ function StreamsSelector({ const [editingStream, setEditingStream] = useState(); const [editingTag, setEditingTag] = useState(); const { t } = useTranslation(); + const { darkMode } = useUserSettings(); function getFormatDuration(formatData: FFprobeFormat | undefined) { if (!formatData || !formatData.duration) return undefined; @@ -394,13 +392,17 @@ function StreamsSelector({ const externalFilesEntries = Object.entries(externalFilesMeta); + const onInfoClick = useCallback((json: unknown, title: string) => { + showJson5Dialog({ title, json, darkMode }); + }, [darkMode]); + return ( <>

{t('Click to select which tracks to keep when exporting:')}

{/* We only support editing main file metadata for now */} - setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} /> + setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} /> @@ -420,6 +422,7 @@ function StreamsSelector({ updateStreamParams={updateStreamParams} formatTimecode={formatTimecode} loadSubtitleTrackToSegments={loadSubtitleTrackToSegments} + onInfoClick={onInfoClick} /> ))} @@ -428,7 +431,7 @@ function StreamsSelector({ {externalFilesEntries.map(([path, { streams: externalFileStreams, formatData }]) => (
- removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} /> + removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} onInfoClick={onInfoClick} />
@@ -446,6 +449,7 @@ function StreamsSelector({ paramsByStreamId={paramsByStreamId} updateStreamParams={updateStreamParams} formatTimecode={formatTimecode} + onInfoClick={onInfoClick} /> ))} diff --git a/src/renderer/src/components/Button.module.css b/src/renderer/src/components/Button.module.css index f473ced1..977251c1 100644 --- a/src/renderer/src/components/Button.module.css +++ b/src/renderer/src/components/Button.module.css @@ -9,4 +9,5 @@ padding: 0 .5em 0 .3em; outline: .05em solid var(--gray8); border: .05em solid var(--gray7); + cursor: pointer; } diff --git a/src/renderer/src/components/Checkbox.module.css b/src/renderer/src/components/Checkbox.module.css new file mode 100644 index 00000000..9279fced --- /dev/null +++ b/src/renderer/src/components/Checkbox.module.css @@ -0,0 +1,33 @@ +.CheckboxRoot { + all: unset +} + +.CheckboxRoot { + background-color: var(--gray8); + width: 1em; + height: 1em; + border-radius: .2em; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 10px var(--gray1); +} +.CheckboxRoot:hover { + background-color: var(--gray9); +} +.CheckboxRoot:focus { + box-shadow: 0 0 0 2px var(--gray1); +} + +.CheckboxIndicator { + color: var(--gray12); +} + +.CheckboxRoot[data-disabled]{ + opacity: .5; +} + +.Label { + padding-left: .5em; + line-height: 1.2; +} \ No newline at end of file diff --git a/src/renderer/src/components/Checkbox.tsx b/src/renderer/src/components/Checkbox.tsx new file mode 100644 index 00000000..60a4ab6f --- /dev/null +++ b/src/renderer/src/components/Checkbox.tsx @@ -0,0 +1,25 @@ +import { useId } from 'react'; +import { Root, Indicator, CheckboxProps } from '@radix-ui/react-checkbox'; +import { FaCheck } from 'react-icons/fa'; + +import classes from './Checkbox.module.css'; + + +export default function Checkbox({ label, disabled, style, ...props }: CheckboxProps & { label?: string | undefined }) { + const id = useId(); + return ( +
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + + + + + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + +
+ ); +} diff --git a/src/renderer/src/components/ConcatDialog.tsx b/src/renderer/src/components/ConcatDialog.tsx index d98b33ab..ce831e5f 100644 --- a/src/renderer/src/components/ConcatDialog.tsx +++ b/src/renderer/src/components/ConcatDialog.tsx @@ -1,13 +1,13 @@ import { memo, useState, useCallback, useEffect, useMemo, CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; -import { TextInput, IconButton, Alert, Checkbox, Dialog, Button, Paragraph, CogIcon } from 'evergreen-ui'; +import { IconButton, Checkbox as EvergreenCheckbox, Dialog, Paragraph } from 'evergreen-ui'; import { AiOutlineMergeCells } from 'react-icons/ai'; -import { FaQuestionCircle, FaExclamationTriangle } from 'react-icons/fa'; +import { FaQuestionCircle, FaExclamationTriangle, FaCog } from 'react-icons/fa'; import i18n from 'i18next'; -import withReactContent from 'sweetalert2-react-content'; import invariant from 'tiny-invariant'; +import Checkbox from './Checkbox'; -import Swal from '../swal'; +import { ReactSwal } from '../swal'; import { readFileMeta, getSmarterOutFormat } from '../ffmpeg'; import useFileFormatState from '../hooks/useFileFormatState'; import OutputFormatSelect from './OutputFormatSelect'; @@ -15,17 +15,23 @@ import useUserSettings from '../hooks/useUserSettings'; import { isMov } from '../util/streams'; import { getOutFileExtension, getSuffixedFileName } from '../util'; import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../../ffprobe'; +import Sheet from './Sheet'; +import TextInput from './TextInput'; +import Button from './Button'; const { basename } = window.require('path'); -const ReactSwal = withReactContent(Swal); - -const containerStyle: CSSProperties = { color: 'black' }; const rowStyle: CSSProperties = { - color: 'black', fontSize: 14, margin: '4px 0px', overflowY: 'auto', whiteSpace: 'nowrap', + fontSize: '1em', margin: '4px 0px', overflowY: 'auto', whiteSpace: 'nowrap', }; +function Alert({ text }: { text: string }) { + return ( +
{text}
+ ); +} + function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles }: { isShown: boolean, onHide: () => void, paths: string[], onConcat: (a: { paths: string[], includeAllStreams: boolean, streams: FFprobeStream[], outFileName: string, fileFormat: string, clearBatchFilesAfterConcat: boolean }) => Promise, alwaysConcatMultipleFiles: boolean, setAlwaysConcatMultipleFiles: (a: boolean) => void, }) { @@ -167,72 +173,65 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi return ( <> - -
- setEnableReadFileMeta(e.target.checked)} label={t('Check compatibility')} marginLeft={10} marginRight={10} /> - - {fileFormat && detectedFileFormat ? ( - - ) : ( - - )} - -
-
- {t('Output file name')}: - setOutFileName(e.target.value)} /> -
- - )} - > -
-
+ +

{t('Merge/concatenate files')}

+ +
+
{t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).')}
-
+
{paths.map((path, index) => (
{index + 1} {'. '} - {basename(path)} - {!allFilesMetaCache[path] && } - {problemsByFile[path] && onProblemsByFileClick(path)} title={i18n.t('Mismatches detected')} color="#996A13" style={{ marginLeft: 10 }} />} + {basename(path)} + {!allFilesMetaCache[path] && } + {problemsByFile[path] && onProblemsByFileClick(path)} title={i18n.t('Mismatches detected')} style={{ color: 'var(--orange8)', marginLeft: '1em' }} />}
))}
+
+ setEnableReadFileMeta(!!checked)} label={t('Check compatibility')} /> + + + + {fileFormat && detectedFileFormat && ( + + )} +
+ +
+
{t('Output file name')}:
+ setOutFileName(e.target.value)} /> + +
+ {enableReadFileMeta && (!allFilesMeta || Object.values(problemsByFile).length > 0) && ( - {t('A mismatch was detected in at least one file. You may proceed, but the resulting file might not be playable.')} + )} {!enableReadFileMeta && ( - {t('File compatibility check is not enabled, so the merge operation might not produce a valid output. Enable "Check compatibility" below to check file compatibility before merging.')} + )} -
+ setSettingsVisible(false)} title={t('Merge options')} hasCancel={false} confirmLabel={t('Close')}> - setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} /> + setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} /> - setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} /> + setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} /> - {fileFormat != null && isMov(fileFormat) && setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />} + {fileFormat != null && isMov(fileFormat) && setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />} - setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} /> + setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} /> - setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} /> + setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} /> - setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} /> + setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} /> {t('Note that also other settings from the normal export dialog apply to this merge function. For more information about all options, see the export dialog.')} diff --git a/src/renderer/src/components/ExportConfirm.tsx b/src/renderer/src/components/ExportConfirm.tsx index a5422660..145b58b3 100644 --- a/src/renderer/src/components/ExportConfirm.tsx +++ b/src/renderer/src/components/ExportConfirm.tsx @@ -5,7 +5,7 @@ import { FaRegCheckCircle } from 'react-icons/fa'; import i18n from 'i18next'; import { useTranslation, Trans } from 'react-i18next'; import { IoIosHelpCircle } from 'react-icons/io'; -import { SweetAlertIcon } from 'sweetalert2'; +import type { SweetAlertIcon } from 'sweetalert2'; import ExportButton from './ExportButton'; import ExportModeButton from './ExportModeButton'; diff --git a/src/renderer/src/components/KeyboardShortcuts.tsx b/src/renderer/src/components/KeyboardShortcuts.tsx index daaf4880..0f52d9f2 100644 --- a/src/renderer/src/components/KeyboardShortcuts.tsx +++ b/src/renderer/src/components/KeyboardShortcuts.tsx @@ -1,6 +1,6 @@ import { memo, Fragment, useEffect, useMemo, useCallback, useState, ReactNode, SetStateAction, Dispatch, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { SearchInput, PlusIcon, InlineAlert, UndoIcon, Paragraph, TakeActionIcon, IconButton, Button, DeleteIcon, AddIcon, Heading, Text, Dialog } from 'evergreen-ui'; +import { SearchInput, PlusIcon, InlineAlert, UndoIcon, Paragraph, TakeActionIcon, IconButton, Button, DeleteIcon, AddIcon, Dialog } from 'evergreen-ui'; import { FaMouse, FaPlus, FaStepForward, FaStepBackward } from 'react-icons/fa'; import Mousetrap from 'mousetrap'; import groupBy from 'lodash/groupBy'; @@ -14,6 +14,7 @@ import SegmentCutpointButton from './SegmentCutpointButton'; import { getModifier } from '../hooks/useTimelineScroll'; import { KeyBinding, KeyboardAction } from '../../../../types'; import { StateSegment } from '../types'; +import Sheet from './Sheet'; type Category = string; @@ -22,7 +23,7 @@ type ActionsMap = Record keys.map((key, i) => ( - {i > 0 && } + {i > 0 && } {key.toUpperCase()} )); @@ -116,7 +117,7 @@ const CreateBinding = memo(({ ); }); -const rowStyle = { display: 'flex', alignItems: 'center', margin: '.2em 0', borderBottom: '1px solid rgba(0,0,0,0.1)', paddingBottom: '.5em' }; +const rowStyle = { display: 'flex', alignItems: 'center', borderBottom: '1px solid rgba(0,0,0,0.1)', paddingBottom: '.2em' }; // eslint-disable-next-line react/display-name const KeyboardShortcuts = memo(({ @@ -626,18 +627,18 @@ const KeyboardShortcuts = memo(({ const extraLinesPerCategory: Record = { [zoomOperationsCategory]: [
- {t('Zoom in/out timeline')} + {t('Zoom in/out timeline')}
- - {t('Mouse scroll/wheel up/down')} + + {t('Mouse scroll/wheel up/down')}
,
- {t('Pan timeline')} + {t('Pan timeline')}
{getModifier(mouseWheelZoomModifierKey).map((v) => {v})} - - {t('Mouse scroll/wheel up/down')} + + {t('Mouse scroll/wheel up/down')}
, ], }; @@ -723,14 +724,14 @@ const KeyboardShortcuts = memo(({ return ( <> -
-
+
+
setSearchQuery(e.target.value)} placeholder="Search" width="100%" />
{categoriesWithActions.map(([category, actionsInCategory]) => (
- {category !== 'undefined' && {category}} + {category !== 'undefined' &&
{category}
} {actionsInCategory.map(([action, actionObj]) => { const actionName = (actionObj && actionObj.name) || action; @@ -742,8 +743,8 @@ const KeyboardShortcuts = memo(({
{beforeContent} - {actionName} -
{action}
+ {actionName} +
{action}
@@ -757,7 +758,7 @@ const KeyboardShortcuts = memo(({
))} - {bindingsForThisAction.length === 0 && {t('No binding')}} + {bindingsForThisAction.length === 0 && {t('No binding')}}
onAddBindingClick(action)} /> @@ -785,17 +786,13 @@ function KeyboardShortcutsDialog({ const { t } = useTranslation(); return ( - - {isShown ? :
} -
+ +

{t('Keyboard & mouse shortcuts')}

+ + + + +
); } diff --git a/src/renderer/src/components/OutSegTemplateEditor.tsx b/src/renderer/src/components/OutSegTemplateEditor.tsx index 3361e021..1fd0997e 100644 --- a/src/renderer/src/components/OutSegTemplateEditor.tsx +++ b/src/renderer/src/components/OutSegTemplateEditor.tsx @@ -3,11 +3,10 @@ import { useDebounce } from 'use-debounce'; import i18n from 'i18next'; import { useTranslation } from 'react-i18next'; import { WarningSignIcon, ErrorIcon, Button, IconButton, TickIcon, ResetIcon } from 'evergreen-ui'; -import withReactContent from 'sweetalert2-react-content'; import { IoIosHelpCircle } from 'react-icons/io'; import { motion, AnimatePresence } from 'framer-motion'; -import Swal from '../swal'; +import { ReactSwal } from '../swal'; import HighlightedText from './HighlightedText'; import { defaultOutSegTemplate, segNumVariable, segSuffixVariable, GenerateOutSegFileNames } from '../util/outputNameTemplate'; import useUserSettings from '../hooks/useUserSettings'; @@ -15,8 +14,6 @@ import Switch from './Switch'; import Select from './Select'; import TextInput from './TextInput'; -const ReactSwal = withReactContent(Swal); - const electron = window.require('electron'); const formatVariable = (variable) => `\${${variable}}`; diff --git a/src/renderer/src/components/Sheet.tsx b/src/renderer/src/components/Sheet.tsx index 038571df..4b67011d 100644 --- a/src/renderer/src/components/Sheet.tsx +++ b/src/renderer/src/components/Sheet.tsx @@ -7,7 +7,7 @@ import styles from './Sheet.module.css'; function Sheet({ visible, onClosePress, children, maxWidth = 800, style }: { - visible: boolean, onClosePress: () => void, children: ReactNode, maxWidth?: number, style?: CSSProperties + visible: boolean, onClosePress: () => void, children: ReactNode, maxWidth?: number | string, style?: CSSProperties }) { const { t } = useTranslation(); diff --git a/src/renderer/src/dialogs/html5ify.tsx b/src/renderer/src/dialogs/html5ify.tsx index 9b577c50..19e79def 100644 --- a/src/renderer/src/dialogs/html5ify.tsx +++ b/src/renderer/src/dialogs/html5ify.tsx @@ -1,13 +1,9 @@ import { useState, useCallback } from 'react'; -import { Checkbox, RadioGroup, Paragraph } from 'evergreen-ui'; import i18n from 'i18next'; -import withReactContent from 'sweetalert2-react-content'; -import Swal from '../swal'; +import { ReactSwal } from '../swal'; import { Html5ifyMode } from '../../../../types'; - - -const ReactSwal = withReactContent(Swal); +import Checkbox from '../components/Checkbox'; // eslint-disable-next-line import/prefer-default-export @@ -31,33 +27,50 @@ export async function askForHtml5ifySpeed({ allowedOptions, showRemember, initia let selectedOption: Html5ifyMode = initialOption != null && inputOptions[initialOption] ? initialOption : Object.keys(inputOptions)[0]! as Html5ifyMode; let rememberChoice = !!initialOption; - const Html = () => { + function AskForHtml5ifySpeed() { const [option, setOption] = useState(selectedOption); const [remember, setRemember] = useState(rememberChoice); + const onOptionChange = useCallback((e) => { - selectedOption = e.target.value; + selectedOption = e.currentTarget.value; setOption(selectedOption); }, []); - const onRememberChange = useCallback((e) => { - rememberChoice = e.target.checked; + + const onRememberChange = useCallback((checked) => { + rememberChoice = checked; setRemember(rememberChoice); }, []); + return (
- {i18n.t('These options will let you convert files to a format that is supported by the player. You can try different options and see which works with your file. Note that the conversion is for preview only. When you run an export, the output will still be lossless with full quality')} - ({ label, value }))} - value={option} - onChange={onOptionChange} - /> - {showRemember && } +

{i18n.t('These options will let you convert files to a format that is supported by the player. You can try different options and see which works with your file. Note that the conversion is for preview only. When you run an export, the output will still be lossless with full quality')}

+ + {Object.entries(inputOptions).map(([value, label]) => { + const id = `html5ify-${value}`; + return ( +
+ + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + +
+ ); + })} + + {showRemember && }
); - }; + } const { value: response } = await ReactSwal.fire({ title: i18n.t('Convert to supported format'), - html: , + html: , showCancelButton: true, }); diff --git a/src/renderer/src/dialogs/index.tsx b/src/renderer/src/dialogs/index.tsx index 73c9db11..69013e77 100644 --- a/src/renderer/src/dialogs/index.tsx +++ b/src/renderer/src/dialogs/index.tsx @@ -1,23 +1,22 @@ import { CSSProperties, ReactNode, useState } from 'react'; -import { ArrowRightIcon, HelpIcon, TickCircleIcon, WarningSignIcon, InfoSignIcon, Checkbox, IconComponent } from 'evergreen-ui'; +import { ArrowRightIcon, HelpIcon, TickCircleIcon, WarningSignIcon, InfoSignIcon, IconComponent } from 'evergreen-ui'; import i18n from 'i18next'; import { Trans } from 'react-i18next'; -import withReactContent from 'sweetalert2-react-content'; import SyntaxHighlighter from 'react-syntax-highlighter'; -import { tomorrow as syntaxStyle } from 'react-syntax-highlighter/dist/esm/styles/hljs'; +import { tomorrow as lightSyntaxStyle, tomorrowNight as darkSyntaxStyle } from 'react-syntax-highlighter/dist/esm/styles/hljs'; import JSON5 from 'json5'; -import { SweetAlertOptions } from 'sweetalert2'; +import type { SweetAlertOptions } from 'sweetalert2'; import { formatDuration } from '../util/duration'; -import Swal, { swalToastOptions, toast } from '../swal'; +import Swal, { ReactSwal, swalToastOptions, toast } from '../swal'; import { parseYouTube } from '../edlFormats'; import CopyClipboardButton from '../components/CopyClipboardButton'; +import Checkbox from '../components/Checkbox'; import { isWindows, showItemInFolder } from '../util'; import { ParseTimecode, SegmentBase } from '../types'; const { dialog, shell } = window.require('@electron/remote'); -const ReactSwal = withReactContent(Swal); export async function promptTimeOffset({ initialValue, title, text, inputPlaceholder, parseTimecode }: { initialValue?: string | undefined, title: string, text?: string | undefined, inputPlaceholder: string, parseTimecode: ParseTimecode }) { const { value } = await Swal.fire({ @@ -119,12 +118,12 @@ export async function askForFileOpenAction(inputOptions: Record) {Object.entries(inputOptions).map(([key, text]) => ( ))}
@@ -390,18 +389,18 @@ const CleanupChoices = ({ cleanupChoicesInitial, onChange: onChangeProp }) => {

{i18n.t('What do you want to do after exporting a file or when pressing the "delete source file" button?')}

- onChange('closeFile', e.target.checked)} /> + onChange('closeFile', checked)} />
- onChange('trashTmpFiles', e.target.checked)} /> - onChange('trashSourceFile', e.target.checked)} /> - onChange('trashProjectFile', e.target.checked)} /> - onChange('deleteIfTrashFails', e.target.checked)} /> + onChange('trashTmpFiles', checked)} /> + onChange('trashSourceFile', checked)} /> + onChange('trashProjectFile', checked)} /> + onChange('deleteIfTrashFails', checked)} />
- onChange('askForCleanup', e.target.checked)} /> - onChange('cleanupAfterExport', e.target.checked)} /> + onChange('askForCleanup', checked)} /> + onChange('cleanupAfterExport', checked)} />
); @@ -442,7 +441,7 @@ export async function createRandomSegments(fileDuration: number) { const { durationMin, durationMax, gapMin, gapMax } = response; - const randomInRange = (min, max) => min + Math.random() * (max - min); + const randomInRange = (min: number, max: number) => min + Math.random() * (max - min); const edl: SegmentBase[] = []; for (let start = randomInRange(gapMin, gapMax); start < fileDuration && edl.length < maxSegments; start += randomInRange(gapMin, gapMax)) { @@ -560,7 +559,7 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) => html: (
- Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. + Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected.
{i18n.t('Variables')}: segment.label, segment.start, segment.end, segment.duration, segment.tags.*
@@ -568,7 +567,7 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) =>
{i18n.t('Examples')}:
{Object.entries(examples).map(([key, { name }]) => ( - ))} @@ -580,9 +579,9 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) => return value; } -export function showJson5Dialog({ title, json }: { title: string, json: unknown }) { +export function showJson5Dialog({ title, json, darkMode }: { title: string, json: unknown, darkMode: boolean }) { const html = ( - + {JSON5.stringify(json, null, 2)} ); @@ -598,7 +597,7 @@ export async function openDirToast({ filePath, text, html, ...props }: SweetAler const swal = text ? toast : ReactSwal; // @ts-expect-error todo - const { value } = await swal.fire({ + const { value } = await swal.fire({ ...swalToastOptions, showConfirmButton: true, confirmButtonText: i18n.t('Show'), @@ -611,19 +610,31 @@ export async function openDirToast({ filePath, text, html, ...props }: SweetAler if (value) showItemInFolder(filePath); } -const UnorderedList = ({ children }) =>
    {children}
; -// @ts-expect-error todo -const ListItem = ({ icon: Icon, iconColor, children, style }: { icon: IconComponent, iconColor?: string, children: ReactNode, style?: CSSProperties }) =>
  • {Icon && } {children}
  • ; +const UnorderedList = ({ children }) => ( +
      {children}
    +); +const ListItem = ({ icon: Icon, iconColor, children, style }: { icon: IconComponent, iconColor?: string, children: ReactNode, style?: CSSProperties }) => ( +
  • + {Icon && } + {children} +
  • +); -const Notices = ({ notices }) => notices.map((msg) => {msg}); -const Warnings = ({ warnings }) => warnings.map((msg) => {msg}); -const OutputIncorrectSeeHelpMenu = () => {i18n.t('If output does not look right, see the Help menu.')}; +const Notices = ({ notices }: { notices: string[] }) => notices.map((msg) => ( + {msg} +)); +const Warnings = ({ warnings }: { warnings: string[] }) => warnings.map((msg) => ( + {msg} +)); +const OutputIncorrectSeeHelpMenu = () => ( + {i18n.t('If output does not look right, see the Help menu.')} +); -export async function openExportFinishedToast({ filePath, warnings, notices }) { +export async function openExportFinishedToast({ filePath, warnings, notices }: { filePath: string, warnings: string[], notices: string[] }) { const hasWarnings = warnings.length > 0; const html = ( - {hasWarnings ? i18n.t('Export finished with warning(s)', { count: warnings.length }) : i18n.t('Export is done!')} + {hasWarnings ? i18n.t('Export finished with warning(s)', { count: warnings.length }) : i18n.t('Export is done!')} {i18n.t('Please test the output file in your desired player/editor before you delete the source file.')} @@ -634,7 +645,7 @@ export async function openExportFinishedToast({ filePath, warnings, notices }) { await openDirToast({ filePath, html, width: 800, position: 'center', timer: hasWarnings ? undefined : 30000 }); } -export async function openConcatFinishedToast({ filePath, warnings, notices }) { +export async function openConcatFinishedToast({ filePath, warnings, notices }: { filePath: string, warnings: string[], notices: string[] }) { const hasWarnings = warnings.length > 0; const html = ( diff --git a/src/renderer/src/dialogs/parameters.tsx b/src/renderer/src/dialogs/parameters.tsx index 5463b16e..0b11189d 100644 --- a/src/renderer/src/dialogs/parameters.tsx +++ b/src/renderer/src/dialogs/parameters.tsx @@ -1,14 +1,14 @@ import { useState, useCallback, useRef, useEffect } from 'react'; -import { Button, TextInputField, LinkIcon } from 'evergreen-ui'; import i18n from 'i18next'; -import withReactContent from 'sweetalert2-react-content'; +import { FaLink } from 'react-icons/fa'; -import Swal from '../swal'; +import Swal, { ReactSwal } from '../swal'; +import Button from '../components/Button'; +import TextInput from '../components/TextInput'; const { shell } = window.require('electron'); -const ReactSwal = withReactContent(Swal); export interface ParameterDialogParameter { value: string, label?: string, hint?: string } export type ParameterDialogParameters = Record; @@ -37,15 +37,28 @@ const ParametersInput = ({ description, parameters: parametersIn, onChange, onSu }, []); return ( -
    +
    {description &&

    {description}

    } - {docUrl &&

    } + {docUrl &&

    }
    - {Object.entries(parametersIn).map(([key, parameter], i) => ( - handleChange(key, e.target.value)} hint={parameter.hint} /> - ))} + {Object.entries(parametersIn).map(([key, parameter], i) => { + const id = `parameter-${key}`; + return ( +
    + + handleChange(key, e.target.value)} + style={{ marginBottom: '.2em' }} + /> + {parameter.hint &&
    {parameter.hint}
    } +
    + ); + })} diff --git a/src/renderer/src/index.tsx b/src/renderer/src/index.tsx index 02a21bee..14450b3d 100644 --- a/src/renderer/src/index.tsx +++ b/src/renderer/src/index.tsx @@ -6,8 +6,6 @@ import * as Electron from 'electron'; import Remote from '@electron/remote'; import type path from 'node:path'; -import 'sweetalert2/dist/sweetalert2.css'; - import '@fontsource/open-sans/300.css'; import '@fontsource/open-sans/300-italic.css'; import '@fontsource/open-sans/400.css'; @@ -28,6 +26,7 @@ import ErrorBoundary from './ErrorBoundary'; import './i18n'; import './main.css'; +import './swal2.scss'; type TypedRemote = Omit & { diff --git a/src/renderer/src/main.css b/src/renderer/src/main.css index 08a8f64d..1559bc26 100644 --- a/src/renderer/src/main.css +++ b/src/renderer/src/main.css @@ -11,6 +11,8 @@ https://www.radix-ui.com/docs/colors/palette-composition/understanding-the-scale @import '@radix-ui/colors/greenDark.css'; @import '@radix-ui/colors/cyan.css'; @import '@radix-ui/colors/cyanDark.css'; +@import '@radix-ui/colors/blue.css'; +@import '@radix-ui/colors/blueDark.css'; @import '@radix-ui/colors/gray.css'; @import '@radix-ui/colors/grayDark.css'; @import '@radix-ui/colors/blackA.css'; @@ -85,6 +87,17 @@ code.highlighted { outline: revert; } +.link-button { + all: unset; + cursor: pointer; + text-decoration: underline; + text-underline-offset: .15em; + text-decoration-thickness: .05em; +} +.link-button:focus { + outline: revert; +} + /* https://stackoverflow.com/questions/18270894/html5-video-does-not-hide-controls-in-fullscreen-mode-in-chrome */ video.main-player::-webkit-media-controls { display:none !important; diff --git a/src/renderer/src/reporting.tsx b/src/renderer/src/reporting.tsx index 032fc36b..0bd56421 100644 --- a/src/renderer/src/reporting.tsx +++ b/src/renderer/src/reporting.tsx @@ -1,11 +1,9 @@ -import withReactContent from 'sweetalert2-react-content'; import i18n from 'i18next'; import { Trans } from 'react-i18next'; -import { CSSProperties } from 'react'; import CopyClipboardButton from './components/CopyClipboardButton'; import { isStoreBuild, isMasBuild, isWindowsStoreBuild } from './util'; -import Swal from './swal'; +import { ReactSwal } from './swal'; const electron = window.require('electron'); @@ -16,23 +14,18 @@ const { app } = remote; const { platform } = remote.require('./index.js'); -const ReactSwal = withReactContent(Swal); - -const linkStyle: CSSProperties = { fontWeight: 'bold', cursor: 'pointer' }; - - // eslint-disable-next-line import/prefer-default-export export function openSendReportDialog(err: unknown | undefined, state?: unknown) { const reportInstructions = isStoreBuild ? ( -

    Please send an email to electron.shell.openExternal('mailto:losslesscut@mifi.no')}>losslesscut@mifi.no where you describe what you were doing.

    +

    Please send an email to electron.shell.openExternal('mailto:losslesscut@mifi.no')}>losslesscut@mifi.no where you describe what you were doing.

    ) : (

    - If you're having a problem or question about LosslessCut, please first check the links in the Help menu. If you cannot find any resolution, you may ask a question in electron.shell.openExternal('https://github.com/mifi/lossless-cut/discussions')}>GitHub discussions or on electron.shell.openExternal('https://github.com/mifi/lossless-cut')}>Discord. + If you're having a problem or question about LosslessCut, please first check the links in the Help menu. If you cannot find any resolution, you may ask a question in electron.shell.openExternal('https://github.com/mifi/lossless-cut/discussions')}>GitHub discussions or on electron.shell.openExternal('https://github.com/mifi/lossless-cut')}>Discord.

    - If you believe that you found a bug in LosslessCut, you may electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>report a bug. + If you believe that you found a bug in LosslessCut, you may electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>report a bug.

    ); @@ -67,11 +60,11 @@ export function openSendReportDialog(err: unknown | undefined, state?: unknown)
    {reportInstructions} -

    Include the following text:

    +

    Include the following text:

    - {!isStoreBuild &&

    You might want to redact any sensitive information like paths.

    } + {!isStoreBuild &&

    You might want to redact any sensitive information like paths.

    } -
    +
    {text}
    diff --git a/src/renderer/src/swal.ts b/src/renderer/src/swal.ts index 08ebe0a9..4874c1e6 100644 --- a/src/renderer/src/swal.ts +++ b/src/renderer/src/swal.ts @@ -1,6 +1,6 @@ -import SwalRaw, { SweetAlertOptions } from 'sweetalert2'; - -import { primaryColor } from './colors'; +import SwalRaw from 'sweetalert2/dist/sweetalert2.js'; +import type { SweetAlertOptions } from 'sweetalert2'; +import withReactContent from 'sweetalert2-react-content'; const { systemPreferences } = window.require('@electron/remote'); @@ -8,7 +8,7 @@ const { systemPreferences } = window.require('@electron/remote'); const animationSettings = systemPreferences.getAnimationSettings(); let commonSwalOptions: SweetAlertOptions = { - confirmButtonColor: primaryColor, + target: '#swal2-container-wrapper', }; if (animationSettings.prefersReducedMotion) { @@ -53,3 +53,5 @@ export const errorToast = (text: string) => toast.fire({ icon: 'error', text, }); + +export const ReactSwal = withReactContent(Swal); diff --git a/src/renderer/src/swal2.scss b/src/renderer/src/swal2.scss new file mode 100644 index 00000000..3b93855a --- /dev/null +++ b/src/renderer/src/swal2.scss @@ -0,0 +1,46 @@ +@import 'sweetalert2/src/variables'; + +// see colors.ts primaryColor +$swal2-confirm-button-background-color: var(--cyan9); + +$myswal-background: var(--gray1); +$myswal-foreground: var(--gray12); +$swal2-outline-color: lighten($swal2-outline-color, 10%); + +$swal2-background: $myswal-background; +$swal2-html-container-color: $myswal-foreground; +$swal2-title-color: $myswal-foreground; +$swal2-backdrop: rgba(0, 0, 0, .75); + +$swal2-close-button-color: var(--gray11); + +// FOOTER +$swal2-footer-border-color: var(--gray2); +$swal2-footer-color: $myswal-background; + +// TIMER POGRESS BAR +$swal2-timer-progress-bar-background: var(--gray8); + +// INPUT +$swal2-input-color: $myswal-foreground; +$swal2-input-background: var(--gray3); + +// VALIDATION MESSAGE +$swal2-validation-message-background: var(--gray3); +$swal2-validation-message-color: $myswal-foreground; + +// QUEUE +$swal2-progress-step-background: var(--gray5); + +// COMMON VARIABLES FOR CONFIRM AND CANCEL BUTTONS +$swal2-button-focus-box-shadow: 0 0 0 1px $swal2-background, 0 0 0 3px $swal2-outline-color; + +// TOAST +$swal2-toast-background: $myswal-background; +$swal2-toast-button-focus-box-shadow: 0 0 0 1px $swal2-background, 0 0 0 3px $swal2-outline-color; + +.swal2-textarea::placeholder { + color: var(--gray8); +} + +@import 'sweetalert2/src/sweetalert2.scss'; diff --git a/yarn.lock b/yarn.lock index 7598a35e..7ab554fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1709,6 +1709,42 @@ __metadata: languageName: node linkType: hard +"@radix-ui/primitive@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/primitive@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + checksum: 2b93e161d3fdabe9a64919def7fa3ceaecf2848341e9211520c401181c9eaebb8451c630b066fad2256e5c639c95edc41de0ba59c40eff37e799918d019822d1 + languageName: node + linkType: hard + +"@radix-ui/react-checkbox@npm:^1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-checkbox@npm:1.0.4" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/primitive": "npm:1.0.1" + "@radix-ui/react-compose-refs": "npm:1.0.1" + "@radix-ui/react-context": "npm:1.0.1" + "@radix-ui/react-presence": "npm:1.0.1" + "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-use-controllable-state": "npm:1.0.1" + "@radix-ui/react-use-previous": "npm:1.0.1" + "@radix-ui/react-use-size": "npm:1.0.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: e3f2f169c017349e3e7844911f116641e44a50d9cc3ba9e270a6bc9d2118641ac515c67fe2a611dad98eefb29ae1e2e6a47a81abd44570faaabe7056ec3f02b1 + languageName: node + linkType: hard + "@radix-ui/react-compose-refs@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-compose-refs@npm:1.0.0" @@ -1720,6 +1756,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-compose-refs@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-compose-refs@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 2b9a613b6db5bff8865588b6bf4065f73021b3d16c0a90b2d4c23deceeb63612f1f15de188227ebdc5f88222cab031be617a9dd025874c0487b303be3e5cc2a8 + languageName: node + linkType: hard + "@radix-ui/react-context@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-context@npm:1.0.0" @@ -1731,6 +1782,42 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-context@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-context@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: a02187a3bae3a0f1be5fab5ad19c1ef06ceff1028d957e4d9994f0186f594a9c3d93ee34bacb86d1fa8eb274493362944398e1c17054d12cb3b75384f9ae564b + languageName: node + linkType: hard + +"@radix-ui/react-presence@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-presence@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-compose-refs": "npm:1.0.1" + "@radix-ui/react-use-layout-effect": "npm:1.0.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 406f0b5a54ea4e7881e15bddc3863234bb14bf3abd4a6e56ea57c6df6f9265a9ad5cfa158e3a98614f0dcbbb7c5f537e1f7158346e57cc3f29b522d62cf28823 + languageName: node + linkType: hard + "@radix-ui/react-primitive@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-primitive@npm:1.0.1" @@ -1744,6 +1831,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-primitive@npm:1.0.3": + version: 1.0.3 + resolution: "@radix-ui/react-primitive@npm:1.0.3" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-slot": "npm:1.0.2" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: bedb934ac07c710dc5550a7bfc7065d47e099d958cde1d37e4b1947ae5451f1b7e6f8ff5965e242578bf2c619065e6038c3a3aa779e5eafa7da3e3dbc685799f + languageName: node + linkType: hard + "@radix-ui/react-slot@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-slot@npm:1.0.1" @@ -1756,6 +1863,22 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-slot@npm:1.0.2": + version: 1.0.2 + resolution: "@radix-ui/react-slot@npm:1.0.2" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-compose-refs": "npm:1.0.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 734866561e991438fbcf22af06e56b272ed6ee8f7b536489ee3bf2f736f8b53bf6bc14ebde94834aa0aceda854d018a0ce20bb171defffbaed1f566006cbb887 + languageName: node + linkType: hard + "@radix-ui/react-switch@npm:^1.0.1": version: 1.0.1 resolution: "@radix-ui/react-switch@npm:1.0.1" @@ -1786,6 +1909,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-callback-ref@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: b9fd39911c3644bbda14a84e4fca080682bef84212b8d8931fcaa2d2814465de242c4cfd8d7afb3020646bead9c5e539d478cea0a7031bee8a8a3bb164f3bc4c + languageName: node + linkType: hard + "@radix-ui/react-use-controllable-state@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-controllable-state@npm:1.0.0" @@ -1798,6 +1936,22 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-controllable-state@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-use-controllable-state@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-use-callback-ref": "npm:1.0.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: dee2be1937d293c3a492cb6d279fc11495a8f19dc595cdbfe24b434e917302f9ac91db24e8cc5af9a065f3f209c3423115b5442e65a5be9fd1e9091338972be9 + languageName: node + linkType: hard + "@radix-ui/react-use-layout-effect@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-layout-effect@npm:1.0.0" @@ -1809,6 +1963,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-layout-effect@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-use-layout-effect@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: bed9c7e8de243a5ec3b93bb6a5860950b0dba359b6680c84d57c7a655e123dec9b5891c5dfe81ab970652e7779fe2ad102a23177c7896dde95f7340817d47ae5 + languageName: node + linkType: hard + "@radix-ui/react-use-previous@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-previous@npm:1.0.0" @@ -1820,6 +1989,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-previous@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-use-previous@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 66b4312e857c58b75f3bf62a2048ef090b79a159e9da06c19a468c93e62336969c33dbef60ff16969f00b20386cc25d138f6a353f1658b35baac0a6eff4761b9 + languageName: node + linkType: hard + "@radix-ui/react-use-size@npm:1.0.0": version: 1.0.0 resolution: "@radix-ui/react-use-size@npm:1.0.0" @@ -1832,6 +2016,22 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-size@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-use-size@npm:1.0.1" + dependencies: + "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-use-layout-effect": "npm:1.0.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6cc150ad1e9fa85019c225c5a5d50a0af6cdc4653dad0c21b4b40cd2121f36ee076db326c43e6bc91a69766ccff5a84e917d27970176b592577deea3c85a3e26 + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.10.0": version: 4.10.0 resolution: "@rollup/rollup-android-arm-eabi@npm:4.10.0" @@ -2797,6 +2997,16 @@ __metadata: languageName: node linkType: hard +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 + languageName: node + linkType: hard + "app-builder-bin@npm:4.0.0": version: 4.0.0 resolution: "app-builder-bin@npm:4.0.0" @@ -3175,6 +3385,13 @@ __metadata: languageName: node linkType: hard +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98 + languageName: node + linkType: hard + "bl@npm:^4.0.3": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -3271,6 +3488,15 @@ __metadata: languageName: node linkType: hard +"braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 + languageName: node + linkType: hard + "broccoli-node-api@npm:^1.7.0": version: 1.7.0 resolution: "broccoli-node-api@npm:1.7.0" @@ -3619,6 +3845,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:>=3.0.0 <4.0.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df + languageName: node + linkType: hard + "chownr@npm:^1.1.1": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -5822,6 +6067,15 @@ __metadata: languageName: node linkType: hard +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea + languageName: node + linkType: hard + "finalhandler@npm:1.2.0": version: 1.2.0 resolution: "finalhandler@npm:1.2.0" @@ -6290,7 +6544,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^5.1.2": +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -6878,6 +7132,13 @@ __metadata: languageName: node linkType: hard +"immutable@npm:^4.0.0": + version: 4.3.6 + resolution: "immutable@npm:4.3.6" + checksum: 59fedb67f26e265035616b27e33ef90b53b434cf76fb09212ec2d6ae32ee8d2fe2641e6dc32dbc78498c521fbf5f72c6740d39affba63a0a36a3884272371857 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -7062,6 +7323,15 @@ __metadata: languageName: node linkType: hard +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 078e51b4f956c2c5fd2b26bb2672c3ccf7e1faff38e0ebdba45612265f4e3d9fc3127a1fa8370bbf09eab61339203c3d3b7af5662cbf8be4030f8fac37745b0e + languageName: node + linkType: hard + "is-boolean-object@npm:^1.1.0": version: 1.1.2 resolution: "is-boolean-object@npm:1.1.2" @@ -7172,7 +7442,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -7893,6 +8163,7 @@ __metadata: "@fontsource/open-sans": "npm:^4.5.14" "@octokit/core": "npm:5" "@radix-ui/colors": "npm:^0.1.8" + "@radix-ui/react-checkbox": "npm:^1.0.4" "@radix-ui/react-switch": "npm:^1.0.1" "@tsconfig/node18": "npm:^18.2.2" "@tsconfig/strictest": "npm:^2.0.2" @@ -7967,6 +8238,7 @@ __metadata: react-syntax-highlighter: "npm:^15.4.3" react-use: "npm:^17.4.0" rimraf: "npm:^5.0.5" + sass: "npm:^1.77.2" screenfull: "npm:^6.0.2" scroll-into-view-if-needed: "npm:^2.2.28" semver: "npm:^7.6.0" @@ -8675,6 +8947,13 @@ __metadata: languageName: node linkType: hard +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20 + languageName: node + linkType: hard + "normalize-url@npm:^6.0.1": version: 6.1.0 resolution: "normalize-url@npm:6.1.0" @@ -9200,7 +9479,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.2.3": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc @@ -9789,6 +10068,15 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 196b30ef6ccf9b6e18c4e1724b7334f72a093d011a99f3b5920470f0b3406a51770867b3e1ae9711f227ef7a7065982f6ee2ce316746b2cb42c88efe44297fe7 + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.4": version: 1.0.5 resolution: "reflect.getprototypeof@npm:1.0.5" @@ -10259,6 +10547,19 @@ __metadata: languageName: node linkType: hard +"sass@npm:^1.77.2": + version: 1.77.2 + resolution: "sass@npm:1.77.2" + dependencies: + chokidar: "npm:>=3.0.0 <4.0.0" + immutable: "npm:^4.0.0" + source-map-js: "npm:>=0.6.2 <2.0.0" + bin: + sass: sass.js + checksum: 4df71f1a01cd59613e7a25bfcec96ddf06e3546c238ba3238b96c6ac0dcf34b9ce238b4de7b39656f6cb0a5e7acccde19f53b521ae4abcdcbe600e0de9c97644 + languageName: node + linkType: hard + "sax@npm:^1.2.4": version: 1.2.4 resolution: "sax@npm:1.2.4" @@ -10616,6 +10917,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:>=0.6.2 <2.0.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 74f331cfd2d121c50790c8dd6d3c9de6be21926de80583b23b37029b0f37aefc3e019fa91f9a10a5e120c08135297e1ecf312d561459c45908cb1e0e365f49e5 + languageName: node + linkType: hard + "source-map-js@npm:^1.0.2": version: 1.0.2 resolution: "source-map-js@npm:1.0.2"