mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 03:33:14 +01:00
improve dark mode #1969
This commit is contained in:
parent
0f3e2eb100
commit
122d79c300
@ -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",
|
||||
|
@ -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,6 +2509,7 @@ function App() {
|
||||
// throw new Error('Test error boundary');
|
||||
|
||||
return (
|
||||
<>
|
||||
<SegColorsContext.Provider value={segColorsContext}>
|
||||
<UserSettingsContext.Provider value={userSettingsContext}>
|
||||
<ThemeProvider value={theme}>
|
||||
@ -2800,6 +2804,9 @@ function App() {
|
||||
</ThemeProvider>
|
||||
</UserSettingsContext.Provider>
|
||||
</SegColorsContext.Provider>
|
||||
|
||||
<div id="swal2-container-wrapper" className={darkMode ? 'dark-theme' : undefined} style={{ color: 'var(--gray12)', background: 'var(--gray1)' }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<void>,
|
||||
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<void>, onInfoClick: (json: unknown, title: string) => void,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -360,6 +357,7 @@ function StreamsSelector({
|
||||
const [editingStream, setEditingStream] = useState<EditingStream>();
|
||||
const [editingTag, setEditingTag] = useState<string>();
|
||||
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 (
|
||||
<>
|
||||
<p style={{ margin: '.5em 2em .5em 1em' }}>{t('Click to select which tracks to keep when exporting:')}</p>
|
||||
|
||||
<div style={fileStyle}>
|
||||
{/* We only support editing main file metadata for now */}
|
||||
<FileHeading path={mainFilePath} formatData={mainFileFormatData} chapters={mainFileChapters} onEditClick={() => setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} />
|
||||
<FileHeading onInfoClick={onInfoClick} path={mainFilePath} formatData={mainFileFormatData} chapters={mainFileChapters} onEditClick={() => setEditingFile(mainFilePath)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(mainFilePath, enabled)} onExtractAllStreamsPress={onExtractAllStreamsPress} />
|
||||
<table style={tableStyle}>
|
||||
<Thead />
|
||||
|
||||
@ -420,6 +422,7 @@ function StreamsSelector({
|
||||
updateStreamParams={updateStreamParams}
|
||||
formatTimecode={formatTimecode}
|
||||
loadSubtitleTrackToSegments={loadSubtitleTrackToSegments}
|
||||
onInfoClick={onInfoClick}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
@ -428,7 +431,7 @@ function StreamsSelector({
|
||||
|
||||
{externalFilesEntries.map(([path, { streams: externalFileStreams, formatData }]) => (
|
||||
<div key={path} style={fileStyle}>
|
||||
<FileHeading path={path} formatData={formatData} onTrashClick={() => removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} />
|
||||
<FileHeading path={path} formatData={formatData} onTrashClick={() => removeFile(path)} setCopyAllStreams={(enabled) => setCopyAllStreamsForPath(path, enabled)} onInfoClick={onInfoClick} />
|
||||
|
||||
<table style={tableStyle}>
|
||||
<Thead />
|
||||
@ -446,6 +449,7 @@ function StreamsSelector({
|
||||
paramsByStreamId={paramsByStreamId}
|
||||
updateStreamParams={updateStreamParams}
|
||||
formatTimecode={formatTimecode}
|
||||
onInfoClick={onInfoClick}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
|
@ -9,4 +9,5 @@
|
||||
padding: 0 .5em 0 .3em;
|
||||
outline: .05em solid var(--gray8);
|
||||
border: .05em solid var(--gray7);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
33
src/renderer/src/components/Checkbox.module.css
Normal file
33
src/renderer/src/components/Checkbox.module.css
Normal file
@ -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;
|
||||
}
|
25
src/renderer/src/components/Checkbox.tsx
Normal file
25
src/renderer/src/components/Checkbox.tsx
Normal file
@ -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 (
|
||||
<div style={{ display: 'flex', alignItems: 'center', ...style }}>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<Root className={classes['CheckboxRoot']} disabled={disabled} {...props} id={id}>
|
||||
<Indicator className={classes['CheckboxIndicator']}>
|
||||
<FaCheck style={{ fontSize: '.7em' }} />
|
||||
</Indicator>
|
||||
</Root>
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label className={classes['Label']} htmlFor={id} style={{ opacity: disabled ? 0.5 : undefined }}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<div style={{ marginBottom: '1em' }}><FaExclamationTriangle style={{ color: 'var(--orange8)', fontSize: '1.3em', verticalAlign: 'middle', marginRight: '.2em' }} /> {text}</div>
|
||||
);
|
||||
}
|
||||
|
||||
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<void>, alwaysConcatMultipleFiles: boolean, setAlwaysConcatMultipleFiles: (a: boolean) => void,
|
||||
}) {
|
||||
@ -167,72 +173,65 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={t('Merge/concatenate files')}
|
||||
shouldCloseOnOverlayClick={false}
|
||||
isShown={isShown}
|
||||
onCloseComplete={onHide}
|
||||
topOffset="3vh"
|
||||
width="90vw"
|
||||
footer={(
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
||||
<Checkbox checked={enableReadFileMeta} onChange={(e) => setEnableReadFileMeta(e.target.checked)} label={t('Check compatibility')} marginLeft={10} marginRight={10} />
|
||||
<Button iconBefore={CogIcon} onClick={() => setSettingsVisible(true)}>{t('Options')}</Button>
|
||||
{fileFormat && detectedFileFormat ? (
|
||||
<OutputFormatSelect style={{ height: 30, maxWidth: 180 }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />
|
||||
) : (
|
||||
<Button disabled isLoading>{t('Loading')}</Button>
|
||||
)}
|
||||
<Button iconBefore={<AiOutlineMergeCells />} isLoading={detectedFileFormat == null} disabled={!isOutFileNameValid} appearance="primary" onClick={onConcatClick}>{t('Merge!')}</Button>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
||||
<Paragraph marginRight=".5em">{t('Output file name')}:</Paragraph>
|
||||
<TextInput value={outFileName || ''} onChange={(e) => setOutFileName(e.target.value)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div style={containerStyle}>
|
||||
<div style={{ whiteSpace: 'pre-wrap', fontSize: 14, marginBottom: 10 }}>
|
||||
<Sheet visible={isShown} onClosePress={onHide} maxWidth="100%" style={{ padding: '0 2em' }}>
|
||||
<h2>{t('Merge/concatenate files')}</h2>
|
||||
|
||||
<div style={{ marginBottom: '1em' }}>
|
||||
<div style={{ whiteSpace: 'pre-wrap', fontSize: '.9em', marginBottom: '1em' }}>
|
||||
{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).')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ backgroundColor: 'var(--gray1)', borderRadius: '.1em' }}>
|
||||
{paths.map((path, index) => (
|
||||
<div key={path} style={rowStyle} title={path}>
|
||||
<div>
|
||||
{index + 1}
|
||||
{'. '}
|
||||
<span style={{ color: 'rgba(0,0,0,0.7)' }}>{basename(path)}</span>
|
||||
{!allFilesMetaCache[path] && <FaQuestionCircle color="#996A13" style={{ marginLeft: 10 }} />}
|
||||
{problemsByFile[path] && <IconButton appearance="minimal" icon={FaExclamationTriangle} onClick={() => onProblemsByFileClick(path)} title={i18n.t('Mismatches detected')} color="#996A13" style={{ marginLeft: 10 }} />}
|
||||
<span>{basename(path)}</span>
|
||||
{!allFilesMetaCache[path] && <FaQuestionCircle style={{ color: 'var(--orange8)', verticalAlign: 'middle', marginLeft: '1em' }} />}
|
||||
{problemsByFile[path] && <IconButton appearance="minimal" icon={FaExclamationTriangle} onClick={() => onProblemsByFileClick(path)} title={i18n.t('Mismatches detected')} style={{ color: 'var(--orange8)', marginLeft: '1em' }} />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', marginBottom: '.5em', gap: '.5em' }}>
|
||||
<Checkbox checked={enableReadFileMeta} onCheckedChange={(checked) => setEnableReadFileMeta(!!checked)} label={t('Check compatibility')} />
|
||||
|
||||
<Button onClick={() => setSettingsVisible(true)} style={{ height: '1.7em' }}><FaCog style={{ fontSize: '1em', verticalAlign: 'middle' }} /> {t('Options')}</Button>
|
||||
|
||||
{fileFormat && detectedFileFormat && (
|
||||
<OutputFormatSelect style={{ height: '1.7em', maxWidth: '20em' }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end', marginBottom: '1em' }}>
|
||||
<div style={{ marginRight: '.5em' }}>{t('Output file name')}:</div>
|
||||
<TextInput value={outFileName || ''} onChange={(e) => setOutFileName(e.target.value)} />
|
||||
<Button disabled={detectedFileFormat == null || !isOutFileNameValid} onClick={onConcatClick} style={{ fontSize: '1.3em', padding: '0 .3em', marginLeft: '1em' }}><AiOutlineMergeCells style={{ fontSize: '1.4em', verticalAlign: 'middle' }} /> {t('Merge!')}</Button>
|
||||
</div>
|
||||
|
||||
{enableReadFileMeta && (!allFilesMeta || Object.values(problemsByFile).length > 0) && (
|
||||
<Alert intent="warning">{t('A mismatch was detected in at least one file. You may proceed, but the resulting file might not be playable.')}</Alert>
|
||||
<Alert text={t('A mismatch was detected in at least one file. You may proceed, but the resulting file might not be playable.')} />
|
||||
)}
|
||||
{!enableReadFileMeta && (
|
||||
<Alert intent="warning">{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.')}</Alert>
|
||||
<Alert text={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.')} />
|
||||
)}
|
||||
</Dialog>
|
||||
</Sheet>
|
||||
|
||||
<Dialog isShown={settingsVisible} onCloseComplete={() => setSettingsVisible(false)} title={t('Merge options')} hasCancel={false} confirmLabel={t('Close')}>
|
||||
<Checkbox checked={includeAllStreams} onChange={(e) => 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.')}`} />
|
||||
<EvergreenCheckbox checked={includeAllStreams} onChange={(e) => 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.')}`} />
|
||||
|
||||
<Checkbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
|
||||
<EvergreenCheckbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
|
||||
|
||||
{fileFormat != null && isMov(fileFormat) && <Checkbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />}
|
||||
{fileFormat != null && isMov(fileFormat) && <EvergreenCheckbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />}
|
||||
|
||||
<Checkbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
|
||||
<EvergreenCheckbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
|
||||
|
||||
<Checkbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
|
||||
<EvergreenCheckbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
|
||||
|
||||
<Checkbox checked={clearBatchFilesAfterConcat} onChange={(e) => setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} />
|
||||
<EvergreenCheckbox checked={clearBatchFilesAfterConcat} onChange={(e) => setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} />
|
||||
|
||||
<Paragraph>{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.')}</Paragraph>
|
||||
</Dialog>
|
||||
|
@ -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';
|
||||
|
@ -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<KeyboardAction, { name: string, category?: Category, be
|
||||
|
||||
const renderKeys = (keys: string[]) => keys.map((key, i) => (
|
||||
<Fragment key={key}>
|
||||
{i > 0 && <FaPlus size={8} style={{ marginLeft: 4, marginRight: 4, color: 'rgba(0,0,0,0.5)' }} />}
|
||||
{i > 0 && <FaPlus style={{ fontSize: '.4em', opacity: 0.8, marginLeft: '.4em', marginRight: '.4em' }} />}
|
||||
<kbd>{key.toUpperCase()}</kbd>
|
||||
</Fragment>
|
||||
));
|
||||
@ -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<Category, ReactNode> = {
|
||||
[zoomOperationsCategory]: [
|
||||
<div key="1" style={{ ...rowStyle, alignItems: 'center' }}>
|
||||
<Text>{t('Zoom in/out timeline')}</Text>
|
||||
<span>{t('Zoom in/out timeline')}</span>
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
<FaMouse style={{ marginRight: 3 }} />
|
||||
<Text>{t('Mouse scroll/wheel up/down')}</Text>
|
||||
<FaMouse style={{ marginRight: '.3em' }} />
|
||||
<span>{t('Mouse scroll/wheel up/down')}</span>
|
||||
</div>,
|
||||
|
||||
<div key="2" style={{ ...rowStyle, alignItems: 'center' }}>
|
||||
<Text>{t('Pan timeline')}</Text>
|
||||
<span>{t('Pan timeline')}</span>
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
{getModifier(mouseWheelZoomModifierKey).map((v) => <kbd key={v} style={{ marginRight: '.7em' }}>{v}</kbd>)}
|
||||
<FaMouse style={{ marginRight: 3 }} />
|
||||
<Text>{t('Mouse scroll/wheel up/down')}</Text>
|
||||
<FaMouse style={{ marginRight: '.3em' }} />
|
||||
<span>{t('Mouse scroll/wheel up/down')}</span>
|
||||
</div>,
|
||||
],
|
||||
};
|
||||
@ -723,14 +724,14 @@ const KeyboardShortcuts = memo(({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ color: 'black', marginBottom: '1em' }}>
|
||||
<div>
|
||||
<div style={{ marginBottom: '1em' }}>
|
||||
<div style={{ marginBottom: '1em' }}>
|
||||
<SearchInput ref={searchInputRef} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search" width="100%" />
|
||||
</div>
|
||||
|
||||
{categoriesWithActions.map(([category, actionsInCategory]) => (
|
||||
<div key={category}>
|
||||
{category !== 'undefined' && <Heading marginTop={30} marginBottom={14}>{category}</Heading>}
|
||||
{category !== 'undefined' && <div style={{ marginTop: '2em', marginBottom: '.7em', fontSize: '1.4em' }}>{category}</div>}
|
||||
|
||||
{actionsInCategory.map(([action, actionObj]) => {
|
||||
const actionName = (actionObj && actionObj.name) || action;
|
||||
@ -742,8 +743,8 @@ const KeyboardShortcuts = memo(({
|
||||
<div key={action} style={rowStyle}>
|
||||
<div>
|
||||
{beforeContent}
|
||||
<Text title={action} marginRight={10}>{actionName}</Text>
|
||||
<div style={{ fontSize: '.8em', opacity: 0.4 }} title={t('API action name: {{action}}', { action })}>{action}</div>
|
||||
<span title={action} style={{ marginRight: '.5em', opacity: 0.9 }}>{actionName}</span>
|
||||
<div style={{ fontSize: '.8em', opacity: 0.3 }} title={t('API action name: {{action}}', { action })}>{action}</div>
|
||||
</div>
|
||||
|
||||
<div style={{ flexGrow: 1 }} />
|
||||
@ -757,7 +758,7 @@ const KeyboardShortcuts = memo(({
|
||||
</div>
|
||||
))}
|
||||
|
||||
{bindingsForThisAction.length === 0 && <Text color="muted">{t('No binding')}</Text>}
|
||||
{bindingsForThisAction.length === 0 && <span style={{ opacity: 0.8, fontSize: '.8em' }}>{t('No binding')}</span>}
|
||||
</div>
|
||||
|
||||
<IconButton title={t('Bind new key to action')} appearance="minimal" intent="success" icon={AddIcon} onClick={() => onAddBindingClick(action)} />
|
||||
@ -785,17 +786,13 @@ function KeyboardShortcutsDialog({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={t('Keyboard & mouse shortcuts')}
|
||||
isShown={isShown}
|
||||
confirmLabel={t('Done')}
|
||||
hasCancel={false}
|
||||
onCloseComplete={onHide}
|
||||
onConfirm={onHide}
|
||||
topOffset="3vh"
|
||||
>
|
||||
{isShown ? <KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> : <div />}
|
||||
</Dialog>
|
||||
<Sheet visible={isShown} onClosePress={onHide} maxWidth="40em" style={{ padding: '0 2em' }}>
|
||||
<h2>{t('Keyboard & mouse shortcuts')}</h2>
|
||||
|
||||
<KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
||||
<Button onClick={onHide}>{t('Done')}</Button>
|
||||
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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}}`;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 (
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<Paragraph>{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')}</Paragraph>
|
||||
<RadioGroup
|
||||
options={Object.entries(inputOptions).map(([value, label]) => ({ label, value }))}
|
||||
value={option}
|
||||
<p>{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')}</p>
|
||||
|
||||
{Object.entries(inputOptions).map(([value, label]) => {
|
||||
const id = `html5ify-${value}`;
|
||||
return (
|
||||
<div key={value}>
|
||||
<input
|
||||
id={id}
|
||||
type="radio"
|
||||
name="html5ify-speed"
|
||||
value={value}
|
||||
checked={option === value}
|
||||
onChange={onOptionChange}
|
||||
/>
|
||||
{showRemember && <Checkbox checked={remember} onChange={onRememberChange} label={i18n.t('Use this for all files until LosslessCut is restarted?')} />}
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label htmlFor={id} style={{ marginLeft: '.5em' }}>{label}</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
})}
|
||||
|
||||
{showRemember && <Checkbox checked={remember} onCheckedChange={onRememberChange} label={i18n.t('Use this for all files until LosslessCut is restarted?')} style={{ marginTop: '.5em' }} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { value: response } = await ReactSwal.fire({
|
||||
title: i18n.t('Convert to supported format'),
|
||||
html: <Html />,
|
||||
html: <AskForHtml5ifySpeed />,
|
||||
showCancelButton: true,
|
||||
});
|
||||
|
||||
|
@ -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<string, string>)
|
||||
|
||||
{Object.entries(inputOptions).map(([key, text]) => (
|
||||
<button type="button" key={key} onClick={() => onClick(key)} className="button-unstyled" style={{ display: 'block', marginBottom: '.5em' }}>
|
||||
<ArrowRightIcon color="rgba(0,0,0,0.5)" verticalAlign="middle" /> {text}
|
||||
<ArrowRightIcon style={{ color: 'var(--gray10)' }} verticalAlign="middle" /> {text}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button type="button" onClick={() => onClick()} className="button-unstyled" style={{ display: 'block', marginTop: '.5em' }}>
|
||||
<ArrowRightIcon color="rgba(150,0,0,1)" /> {i18n.t('Cancel')}
|
||||
<ArrowRightIcon style={{ color: 'var(--red11)' }} /> {i18n.t('Cancel')}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@ -390,18 +389,18 @@ const CleanupChoices = ({ cleanupChoicesInitial, onChange: onChangeProp }) => {
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<p>{i18n.t('What do you want to do after exporting a file or when pressing the "delete source file" button?')}</p>
|
||||
|
||||
<Checkbox label={i18n.t('Close currently opened file')} checked={closeFile} disabled={trashSourceFile || trashTmpFiles} onChange={(e) => onChange('closeFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Close currently opened file')} checked={closeFile} disabled={trashSourceFile || trashTmpFiles} onCheckedChange={(checked) => onChange('closeFile', checked)} />
|
||||
|
||||
<div style={{ marginTop: 25 }}>
|
||||
<Checkbox label={i18n.t('Trash auto-generated files')} checked={trashTmpFiles} onChange={(e) => onChange('trashTmpFiles', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash original source file')} checked={trashSourceFile} onChange={(e) => onChange('trashSourceFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash project LLC file')} checked={trashProjectFile} onChange={(e) => onChange('trashProjectFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Permanently delete the files if trash fails?')} disabled={!(trashTmpFiles || trashProjectFile || trashSourceFile)} checked={deleteIfTrashFails} onChange={(e) => onChange('deleteIfTrashFails', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash auto-generated files')} checked={trashTmpFiles} onCheckedChange={(checked) => onChange('trashTmpFiles', checked)} />
|
||||
<Checkbox label={i18n.t('Trash original source file')} checked={trashSourceFile} onCheckedChange={(checked) => onChange('trashSourceFile', checked)} />
|
||||
<Checkbox label={i18n.t('Trash project LLC file')} checked={trashProjectFile} onCheckedChange={(checked) => onChange('trashProjectFile', checked)} />
|
||||
<Checkbox label={i18n.t('Permanently delete the files if trash fails?')} disabled={!(trashTmpFiles || trashProjectFile || trashSourceFile)} checked={deleteIfTrashFails} onCheckedChange={(checked) => onChange('deleteIfTrashFails', checked)} />
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 25 }}>
|
||||
<Checkbox label={i18n.t('Show this dialog every time?')} checked={askForCleanup} onChange={(e) => onChange('askForCleanup', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Do all of this automatically after exporting a file?')} checked={cleanupAfterExport} onChange={(e) => onChange('cleanupAfterExport', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Show this dialog every time?')} checked={askForCleanup} onCheckedChange={(checked) => onChange('askForCleanup', checked)} />
|
||||
<Checkbox label={i18n.t('Do all of this automatically after exporting a file?')} checked={cleanupAfterExport} onCheckedChange={(checked) => onChange('cleanupAfterExport', checked)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -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: (
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ marginBottom: '1em' }}>
|
||||
<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. <button type="button" className="button-unstyled" style={{ fontWeight: 'bold' }} onClick={() => shell.openExternal('https://github.com/mifi/lossless-cut/blob/master/expressions.md')}>View available syntax.</button></Trans>
|
||||
<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. <button type="button" className="link-button" onClick={() => shell.openExternal('https://github.com/mifi/lossless-cut/blob/master/expressions.md')}>View available syntax.</button></Trans>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '1em' }}><b>{i18n.t('Variables')}:</b> segment.label, segment.start, segment.end, segment.duration, segment.tags.*</div>
|
||||
@ -568,7 +567,7 @@ export async function selectSegmentsByExprDialog(inputValidator: (v: string) =>
|
||||
<div><b>{i18n.t('Examples')}:</b></div>
|
||||
|
||||
{Object.entries(examples).map(([key, { name }]) => (
|
||||
<button key={key} type="button" onClick={() => addExample(key)} className="button-unstyled" style={{ display: 'block', marginBottom: '.1em' }}>
|
||||
<button key={key} type="button" onClick={() => addExample(key)} className="link-button" style={{ display: 'block', marginBottom: '.1em' }}>
|
||||
{name}
|
||||
</button>
|
||||
))}
|
||||
@ -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 = (
|
||||
<SyntaxHighlighter language="javascript" style={syntaxStyle} customStyle={{ textAlign: 'left', maxHeight: 300, overflowY: 'auto', fontSize: 14 }}>
|
||||
<SyntaxHighlighter language="javascript" style={darkMode ? darkSyntaxStyle : lightSyntaxStyle} customStyle={{ textAlign: 'left', maxHeight: 300, overflowY: 'auto', fontSize: 14 }}>
|
||||
{JSON5.stringify(json, null, 2)}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
@ -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<string>({
|
||||
...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 }) => <ul style={{ paddingLeft: '1em' }}>{children}</ul>;
|
||||
// @ts-expect-error todo
|
||||
const ListItem = ({ icon: Icon, iconColor, children, style }: { icon: IconComponent, iconColor?: string, children: ReactNode, style?: CSSProperties }) => <li style={{ listStyle: 'none', ...style }}>{Icon && <Icon color={iconColor} size={14} marginRight=".3em" />} {children}</li>;
|
||||
const UnorderedList = ({ children }) => (
|
||||
<ul style={{ paddingLeft: '1em' }}>{children}</ul>
|
||||
);
|
||||
const ListItem = ({ icon: Icon, iconColor, children, style }: { icon: IconComponent, iconColor?: string, children: ReactNode, style?: CSSProperties }) => (
|
||||
<li style={{ listStyle: 'none', ...style }}>
|
||||
{Icon && <Icon style={{ color: iconColor }} size={14} marginRight=".4em" />}
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
|
||||
const Notices = ({ notices }) => notices.map((msg) => <ListItem key={msg} icon={InfoSignIcon} iconColor="info">{msg}</ListItem>);
|
||||
const Warnings = ({ warnings }) => warnings.map((msg) => <ListItem key={msg} icon={WarningSignIcon} iconColor="warning">{msg}</ListItem>);
|
||||
const OutputIncorrectSeeHelpMenu = () => <ListItem icon={HelpIcon}>{i18n.t('If output does not look right, see the Help menu.')}</ListItem>;
|
||||
const Notices = ({ notices }: { notices: string[] }) => notices.map((msg) => (
|
||||
<ListItem key={msg} icon={InfoSignIcon} iconColor="var(--blue9)">{msg}</ListItem>
|
||||
));
|
||||
const Warnings = ({ warnings }: { warnings: string[] }) => warnings.map((msg) => (
|
||||
<ListItem key={msg} icon={WarningSignIcon} iconColor="var(--orange8)">{msg}</ListItem>
|
||||
));
|
||||
const OutputIncorrectSeeHelpMenu = () => (
|
||||
<ListItem icon={HelpIcon}>{i18n.t('If output does not look right, see the Help menu.')}</ListItem>
|
||||
);
|
||||
|
||||
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 = (
|
||||
<UnorderedList>
|
||||
<ListItem icon={TickCircleIcon} iconColor={hasWarnings ? 'warning' : 'success'} style={{ fontWeight: 'bold' }}>{hasWarnings ? i18n.t('Export finished with warning(s)', { count: warnings.length }) : i18n.t('Export is done!')}</ListItem>
|
||||
<ListItem icon={TickCircleIcon} iconColor={hasWarnings ? 'var(--orange8)' : 'var(--green11)'} style={{ fontWeight: 'bold' }}>{hasWarnings ? i18n.t('Export finished with warning(s)', { count: warnings.length }) : i18n.t('Export is done!')}</ListItem>
|
||||
<ListItem icon={InfoSignIcon}>{i18n.t('Please test the output file in your desired player/editor before you delete the source file.')}</ListItem>
|
||||
<OutputIncorrectSeeHelpMenu />
|
||||
<Notices notices={notices} />
|
||||
@ -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 = (
|
||||
<UnorderedList>
|
||||
|
@ -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<string, ParameterDialogParameter>;
|
||||
@ -37,15 +37,28 @@ const ParametersInput = ({ description, parameters: parametersIn, onChange, onSu
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ textAlign: 'left', padding: '.5em', borderRadius: '.3em' }}>
|
||||
{description && <p>{description}</p>}
|
||||
|
||||
{docUrl && <p><Button iconBefore={LinkIcon} onClick={() => shell.openExternal(docUrl)}>Read more</Button></p>}
|
||||
{docUrl && <p><Button onClick={() => shell.openExternal(docUrl)}><FaLink style={{ fontSize: '.8em' }} /> Read more</Button></p>}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
{Object.entries(parametersIn).map(([key, parameter], i) => (
|
||||
<TextInputField ref={i === 0 ? firstInputRef : undefined} key={key} label={parameter.label || key} value={getParameter(key)} onChange={(e) => handleChange(key, e.target.value)} hint={parameter.hint} />
|
||||
))}
|
||||
{Object.entries(parametersIn).map(([key, parameter], i) => {
|
||||
const id = `parameter-${key}`;
|
||||
return (
|
||||
<div key={key} style={{ marginBottom: '.5em' }}>
|
||||
<label htmlFor={id} style={{ display: 'block', fontFamily: 'monospace', marginBottom: '.3em' }}>{parameter.label || key}</label>
|
||||
<TextInput
|
||||
id={id}
|
||||
ref={i === 0 ? firstInputRef : undefined}
|
||||
value={getParameter(key)}
|
||||
onChange={(e) => handleChange(key, e.target.value)}
|
||||
style={{ marginBottom: '.2em' }}
|
||||
/>
|
||||
{parameter.hint && <div style={{ opacity: 0.6, fontSize: '0.8em' }}>{parameter.hint}</div>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<input type="submit" value="submit" style={{ display: 'none' }} />
|
||||
</form>
|
||||
|
@ -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<typeof Remote, 'require'> & {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
? (
|
||||
<p><Trans>Please send an email to <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('mailto:losslesscut@mifi.no')}>losslesscut@mifi.no</span> where you describe what you were doing.</Trans></p>
|
||||
<p><Trans>Please send an email to <span className="link-button" role="button" onClick={() => electron.shell.openExternal('mailto:losslesscut@mifi.no')}>losslesscut@mifi.no</span> where you describe what you were doing.</Trans></p>
|
||||
) : (
|
||||
<Trans>
|
||||
<p>
|
||||
If you're having a problem or question about LosslessCut, please first check the links in the <b>Help</b> menu. If you cannot find any resolution, you may ask a question in <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/discussions')}>GitHub discussions</span> or on <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut')}>Discord.</span>
|
||||
If you're having a problem or question about LosslessCut, please first check the links in the <b>Help</b> menu. If you cannot find any resolution, you may ask a question in <span className="link-button" role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/discussions')}>GitHub discussions</span> or on <span className="link-button" role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut')}>Discord.</span>
|
||||
</p>
|
||||
<p>
|
||||
If you believe that you found a bug in LosslessCut, you may <span style={linkStyle} role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>report a bug</span>.
|
||||
If you believe that you found a bug in LosslessCut, you may <span className="link-button" role="button" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/issues')}>report a bug</span>.
|
||||
</p>
|
||||
</Trans>
|
||||
);
|
||||
@ -67,11 +60,11 @@ export function openSendReportDialog(err: unknown | undefined, state?: unknown)
|
||||
<div style={{ textAlign: 'left', overflow: 'auto', maxHeight: 300, overflowY: 'auto' }}>
|
||||
{reportInstructions}
|
||||
|
||||
<p><Trans>Include the following text:</Trans> <CopyClipboardButton text={text} /></p>
|
||||
<p style={{ marginBottom: 0 }}><Trans>Include the following text:</Trans> <CopyClipboardButton text={text} /></p>
|
||||
|
||||
{!isStoreBuild && <p style={{ fontSize: '.8em', color: 'rgba(0,0,0,0.5)' }}><Trans>You might want to redact any sensitive information like paths.</Trans></p>}
|
||||
{!isStoreBuild && <p style={{ marginTop: '.2em', fontSize: '.8em', opacity: 0.7 }}><Trans>You might want to redact any sensitive information like paths.</Trans></p>}
|
||||
|
||||
<div style={{ fontWeight: 600, fontSize: 12, whiteSpace: 'pre-wrap', color: '#900' }} contentEditable suppressContentEditableWarning>
|
||||
<div style={{ fontWeight: 600, fontSize: '.75em', fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: 'var(--gray11)', backgroundColor: 'var(--gray3)', padding: '.3em' }} contentEditable suppressContentEditableWarning>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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);
|
||||
|
46
src/renderer/src/swal2.scss
Normal file
46
src/renderer/src/swal2.scss
Normal file
@ -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';
|
314
yarn.lock
314
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"
|
||||
|
Loading…
Reference in New Issue
Block a user