improve dark mode #1969

pull/2465/head
Mikael Finstad 1 year ago
parent 25050414ca
commit 010f2ad3fe
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -41,9 +41,8 @@
"devDependencies": {
"@adamscybot/react-leaflet-component-marker": "^2.0.0",
"@fontsource/open-sans": "^4.5.14",
"@radix-ui/colors": "1",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-switch": "^1.2.2",
"@tsconfig/node18": "^18.2.2",
"@tsconfig/node20": "^20.1.4",

@ -7,8 +7,8 @@ import { ReactSortable } from 'react-sortablejs';
import isEqual from 'lodash/isEqual';
import useDebounce from 'react-use/lib/useDebounce';
import scrollIntoView from 'scroll-into-view-if-needed';
import { Dialog } from 'evergreen-ui';
import Dialog, { ConfirmButton } from './components/Dialog';
import Swal from './swal';
import useContextMenu from './hooks/useContextMenu';
import useUserSettings from './hooks/useUserSettings';
@ -458,21 +458,19 @@ function SegmentList({
onSegmentTagsCloseComplete();
}, [editingSegmentTags, editingSegmentTagsSegmentIndex, onSegmentTagsCloseComplete, updateSegAtIndex]);
const dialogRef = useRef<HTMLDialogElement>(null);
return (
<>
<Dialog
title={t('Edit segment tags')}
isShown={editingSegmentTagsSegmentIndex != null}
hasCancel={false}
isConfirmDisabled={editingTag != null}
confirmLabel={t('Save')}
onConfirm={onSegmentTagsConfirm}
onCloseComplete={onSegmentTagsCloseComplete}
>
<div style={{ color: 'black' }}>
<TagEditor customTags={editingSegmentTags} editingTag={editingTag} setEditingTag={setEditingTag} onTagsChange={onTagsChange} onTagReset={onTagReset} addTagTitle={t('Add segment tag')} addTagText={t('Enter tag key')} />
</div>
</Dialog>
{editingSegmentTagsSegmentIndex != null && (
<Dialog ref={dialogRef} autoOpen onClose={onSegmentTagsCloseComplete} style={{ width: '100%', maxWidth: '40em' }}>
<h1 style={{ marginTop: 0 }}>{t('Edit segment tags')}</h1>
<TagEditor customTags={editingSegmentTags} editingTag={editingTag} setEditingTag={setEditingTag} onTagsChange={onTagsChange} onTagReset={onTagReset} addTagTitle={t('Add segment tag')} />
<ConfirmButton onClick={onSegmentTagsConfirm} disabled={editingTag != null}><FaSave style={{ verticalAlign: 'baseline', fontSize: '.8em', marginRight: '.3em' }} />{t('Save')}</ConfirmButton>
</Dialog>
)}
<motion.div
style={{ width, background: controlsBackground, borderLeft: '1px solid var(--gray-7)', color: 'var(--gray-11)', transition: darkModeTransition, display: 'flex', flexDirection: 'column', overflowY: 'hidden' }}

@ -1,12 +1,13 @@
import { memo, useState, useMemo, useCallback, Dispatch, SetStateAction, CSSProperties, ReactNode, ChangeEventHandler } from 'react';
import { memo, useState, useMemo, useCallback, Dispatch, SetStateAction, CSSProperties, ReactNode, ChangeEventHandler, useRef } from 'react';
import { FaImage, FaCheckCircle, FaPaperclip, FaVideo, FaVideoSlash, FaFileImport, FaVolumeUp, FaVolumeMute, FaBan, FaFileExport } from 'react-icons/fa';
import { GoFileBinary } from 'react-icons/go';
import { MdSubtitles } from 'react-icons/md';
import { Checkbox, BookIcon, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Heading, SortAscIcon, SortDescIcon, Dialog, ForkIcon, WarningSignIcon } from 'evergreen-ui';
import { BookIcon, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, SortAscIcon, SortDescIcon, ForkIcon, WarningSignIcon } from 'evergreen-ui';
import { useTranslation } from 'react-i18next';
import prettyBytes from 'pretty-bytes';
import Dialog from './components/Dialog';
import AutoExportToggler from './components/AutoExportToggler';
import Select from './components/Select';
import { showJson5Dialog } from './dialogs';
@ -19,6 +20,7 @@ import { CustomTagsByFile, FilesMeta, FormatTimecode, ParamsByStreamId, StreamPa
import useUserSettings from './hooks/useUserSettings';
import tryShowGpsMap from './gps';
import Button from './components/Button';
import Checkbox from './components/Checkbox';
const dispositionOptions = ['default', 'dub', 'original', 'comment', 'lyrics', 'karaoke', 'forced', 'hearing_impaired', 'visual_impaired', 'clean_effects', 'attached_pic', 'captions', 'descriptions', 'dependent', 'metadata'];
@ -58,7 +60,7 @@ const EditFileDialog = memo(({ editingFile, allFilesMeta, customTagsByFile, setC
});
}, [editingFile, setCustomTagsByFile]);
return <TagEditor existingTags={existingTags} customTags={customTags} editingTag={editingTag} setEditingTag={setEditingTag} onTagsChange={onTagsChange} onTagReset={onTagReset} addTagTitle={t('Add metadata')} addTagText={t('Enter metadata key')} />;
return <TagEditor existingTags={existingTags} customTags={customTags} editingTag={editingTag} setEditingTag={setEditingTag} onTagsChange={onTagsChange} onTagReset={onTagReset} addTagTitle={t('Add metadata')} />;
});
const getStreamDispositionsObj = (stream: FFprobeStream) => ((stream && stream.disposition) || {});
@ -82,13 +84,13 @@ function StreamParametersEditor({ stream, streamParams, updateStreamParams }: {
if (stream.codec_name === 'h264') {
ui.push(
// eslint-disable-next-line no-param-reassign
<Checkbox key="bsfH264Mp4toannexb" checked={!!streamParams.bsfH264Mp4toannexb} label={t('Enable "{{filterName}}" bitstream filter.', { filterName: 'h264_mp4toannexb' })} onChange={(e) => updateStreamParams((params) => { params.bsfH264Mp4toannexb = e.target.checked; })} />,
<Checkbox key="bsfH264Mp4toannexb" checked={!!streamParams.bsfH264Mp4toannexb} label={t('Enable "{{filterName}}" bitstream filter.', { filterName: 'h264_mp4toannexb' })} onCheckedChange={(checked) => updateStreamParams((params) => { params.bsfH264Mp4toannexb = checked === true; })} />,
);
}
if (stream.codec_name === 'hevc') {
ui.push(
// eslint-disable-next-line no-param-reassign
<Checkbox key="bsfHevcMp4toannexb" checked={!!streamParams.bsfHevcMp4toannexb} label={t('Enable "{{filterName}}" bitstream filter.', { filterName: 'hevc_mp4toannexb' })} onChange={(e) => updateStreamParams((params) => { params.bsfHevcMp4toannexb = e.target.checked; })} />,
<Checkbox key="bsfHevcMp4toannexb" checked={!!streamParams.bsfHevcMp4toannexb} label={t('Enable "{{filterName}}" bitstream filter.', { filterName: 'hevc_mp4toannexb' })} onCheckedChange={(checked) => updateStreamParams((params) => { params.bsfHevcMp4toannexb = checked === true; })} />,
);
}
@ -140,21 +142,19 @@ const EditStreamDialog = memo(({ editingStream: { streamId: editingStreamId, pat
});
}, [editingFile, editingStreamId, updateStreamParams]);
const dialogRef = useRef<HTMLDialogElement>(null);
if (!editingStream) return null;
return (
<Dialog
title={t('Edit track {{trackNum}} metadata', { trackNum: editingStream && (editingStream.index + 1) })}
isShown={!!editingStream}
hasCancel={false}
isConfirmDisabled={editingTag != null}
confirmLabel={t('Done')}
onCloseComplete={() => setEditingStream(undefined)}
>
<div style={{ color: 'black' }}>
<Heading>Parameters</Heading>
{editingStream != null && <StreamParametersEditor stream={editingStream} streamParams={streamParams} updateStreamParams={(setter) => updateStreamParams(editingFile, editingStreamId, setter)} />}
<Heading>Tags</Heading>
<TagEditor existingTags={existingTags} customTags={customTags} editingTag={editingTag} setEditingTag={setEditingTag} onTagsChange={onTagsChange} onTagReset={onTagReset} addTagTitle={t('Add metadata')} addTagText={t('Enter metadata key')} />
</div>
<Dialog ref={dialogRef} autoOpen onClose={() => setEditingStream(undefined)} style={{ maxWidth: '40em' }}>
<h1 style={{ marginTop: 0 }}>{t('Edit track {{trackNum}} metadata', { trackNum: editingStream && (editingStream.index + 1) })}</h1>
<h2>Parameters</h2>
{editingStream != null && <StreamParametersEditor stream={editingStream} streamParams={streamParams} updateStreamParams={(setter) => updateStreamParams(editingFile, editingStreamId, setter)} />}
<h2>Tags</h2>
<TagEditor existingTags={existingTags} customTags={customTags} editingTag={editingTag} setEditingTag={setEditingTag} onTagsChange={onTagsChange} onTagReset={onTagReset} addTagTitle={t('Add metadata')} />
</Dialog>
);
});
@ -416,6 +416,8 @@ function StreamsSelector({
showJson5Dialog({ title, json, darkMode });
}, [darkMode]);
const dialogRef = useRef<HTMLDialogElement>(null);
return (
<>
<p style={{ margin: '.5em 2em .5em 1em' }}>{t('Click to select which tracks to keep when exporting:')}</p>
@ -503,19 +505,13 @@ function StreamsSelector({
)}
</div>
<Dialog
title={t('Edit file metadata')}
width="80%"
isShown={editingFile != null}
hasCancel={false}
confirmLabel={t('Done')}
onCloseComplete={() => setEditingFile(undefined)}
isConfirmDisabled={editingTag != null}
>
<div style={{ color: 'black' }}>
{editingFile != null && <EditFileDialog editingFile={editingFile} editingTag={editingTag} setEditingTag={setEditingTag} allFilesMeta={allFilesMeta} customTagsByFile={customTagsByFile} setCustomTagsByFile={setCustomTagsByFile} />}
</div>
</Dialog>
{editingFile != null && (
<Dialog ref={dialogRef} autoOpen onClose={() => setEditingFile(undefined)} style={{ maxWidth: '40em' }}>
<h1 style={{ marginTop: 0 }}>{t('Edit file metadata')}</h1>
<EditFileDialog editingFile={editingFile} editingTag={editingTag} setEditingTag={setEditingTag} allFilesMeta={allFilesMeta} customTagsByFile={customTagsByFile} setCustomTagsByFile={setCustomTagsByFile} />
</Dialog>
)}
{editingStream != null && (
<EditStreamDialog

@ -14,5 +14,5 @@
.button:disabled {
opacity: .5;
cursor: initial;
cursor: not-allowed;
}

@ -2,8 +2,10 @@ import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef } from 'react';
import styles from './Button.module.css';
export type ButtonProps = DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
// eslint-disable-next-line react/display-name
const Button = forwardRef<HTMLButtonElement, DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>>(({ type = 'button', ...props }, ref) => (
const Button = forwardRef<HTMLButtonElement, ButtonProps>(({ type = 'button', ...props }, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading, react/button-has-type
<button ref={ref} className={styles['button']} type={type} {...props} />
));

@ -3,6 +3,7 @@
}
.CheckboxRoot {
flex-shrink: 0;
background-color: var(--gray-8);
width: 1em;
height: 1em;

@ -1,6 +1,6 @@
import { memo, useState, useCallback, useEffect, useMemo, CSSProperties } from 'react';
import { memo, useState, useCallback, useEffect, useMemo, CSSProperties, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { IconButton, Checkbox as EvergreenCheckbox, Dialog, Paragraph } from 'evergreen-ui';
import { IconButton } from 'evergreen-ui';
import { AiOutlineMergeCells } from 'react-icons/ai';
import { FaQuestionCircle, FaExclamationTriangle, FaCog } from 'react-icons/fa';
import i18n from 'i18next';
@ -19,6 +19,7 @@ import Sheet from './Sheet';
import TextInput from './TextInput';
import Button from './Button';
import { defaultMergedFileTemplate, generateMergedFileNames, maxFileNameLength } from '../util/outputNameTemplate';
import Dialog from './Dialog';
const { basename } = window.require('path');
@ -196,6 +197,8 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi
onConcat({ paths, includeAllStreams, streams: fileMeta!.streams, outFileName, fileFormat, clearBatchFilesAfterConcat });
}, [clearBatchFilesAfterConcat, fileFormat, fileMeta, includeAllStreams, onConcat, outFileName, paths]);
const dialogRef = useRef<HTMLDialogElement>(null);
return (
<>
<Sheet visible={isShown} onClosePress={onHide} maxWidth="100%" style={{ padding: '0 2em' }}>
@ -251,21 +254,25 @@ function ConcatDialog({ isShown, onHide, paths, onConcat, alwaysConcatMultipleFi
)}
</Sheet>
<Dialog isShown={settingsVisible} onCloseComplete={() => setSettingsVisible(false)} title={t('Merge options')} hasCancel={false} confirmLabel={t('Close')}>
<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.')}`} />
{settingsVisible && (
<Dialog ref={dialogRef} autoOpen onClose={() => setSettingsVisible(false)} style={{ maxWidth: '40em' }}>
<h1 style={{ marginTop: 0 }}>{t('Merge options')}</h1>
<Checkbox checked={includeAllStreams} onCheckedChange={(checked) => setIncludeAllStreams(checked === true)} 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={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
<Checkbox checked={preserveMetadataOnMerge} onCheckedChange={(checked) => setPreserveMetadataOnMerge(checked === true)} label={t('Preserve original metadata when merging? (slow)')} />
{fileFormat != null && isMov(fileFormat) && <EvergreenCheckbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />}
{fileFormat != null && isMov(fileFormat) && <Checkbox checked={preserveMovData} onCheckedChange={(checked) => setPreserveMovData(checked === true)} label={t('Preserve all MP4/MOV metadata?')} />}
<EvergreenCheckbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
<Checkbox checked={segmentsToChapters} onCheckedChange={(checked) => setSegmentsToChapters(checked === true)} label={t('Create chapters from merged segments? (slow)')} />
<EvergreenCheckbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
<Checkbox checked={alwaysConcatMultipleFiles} onCheckedChange={(checked) => setAlwaysConcatMultipleFiles(checked === true)} label={t('Always open this dialog when opening multiple files')} />
<EvergreenCheckbox checked={clearBatchFilesAfterConcat} onChange={(e) => setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} />
<Checkbox checked={clearBatchFilesAfterConcat} onCheckedChange={(checked) => setClearBatchFilesAfterConcat(checked === true)} 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>
<p>{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.')}</p>
</Dialog>
)}
</>
);
}

@ -1,14 +1,16 @@
import { memo, useCallback } from 'react';
import { memo, ReactNode, useCallback } from 'react';
import { FaClipboard } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
import { MotionStyle, motion, useAnimation } from 'framer-motion';
import i18n from '../i18n';
const electron = window.require('electron');
const { clipboard } = electron;
function CopyClipboardButton({ text, style }: { text: string, style?: MotionStyle }) {
const { t } = useTranslation();
function CopyClipboardButton({ text, style, children = ({ onClick }) => <FaClipboard title={i18n.t('Copy to clipboard')} onClick={onClick} /> }: {
text: string,
style?: MotionStyle,
children?: (p: { onClick: () => void }) => ReactNode,
}) {
const animation = useAnimation();
const onClick = useCallback(() => {
@ -21,7 +23,7 @@ function CopyClipboardButton({ text, style }: { text: string, style?: MotionStyl
return (
<motion.span animate={animation} style={{ display: 'inline-block', cursor: 'pointer', ...style }}>
<FaClipboard title={t('Copy to clipboard')} onClick={onClick} />
{children({ onClick })}
</motion.span>
);
}

@ -0,0 +1,62 @@
dialog::backdrop {
background-color: var(--black-a7);
animation: overlayShow 400ms cubic-bezier(0.16, 1, 0.3, 1);
}
dialog {
border: .1em solid var(--black-a3);
background: var(--white-a8);
color: var(--gray-12);
backdrop-filter: blur(2em);
border-radius: .5em;
padding: 1.7em;
animation: contentShow 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
:global(.dark-theme) dialog {
border: .1em solid var(--white-a2);
background: var(--black-a4);
}
.close {
all: unset;
border-radius: 100%;
height: 2em;
width: 2em;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--gray-12);
position: absolute;
top: 1em;
right: 1em;
background-color: var(--gray-3);
}
.close:hover {
background-color: var(--gray-4);
}
.close:focus {
box-shadow: 0 0 0 .1em var(--gray-7);
}
@keyframes overlayShow {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes contentShow {
from {
opacity: 0;
transform: translate(0, -5%) scale(0.96);
}
to {
opacity: 1;
transform: translate(0, 0) scale(1);
}
}

@ -0,0 +1,54 @@
import { FaTimes } from 'react-icons/fa';
import { DetailedHTMLProps, DialogHTMLAttributes, useCallback, useEffect, forwardRef } from 'react';
import styles from './Dialog.module.css';
import Button, { ButtonProps } from './Button';
import i18n from '../i18n';
type Props = Omit<DetailedHTMLProps<DialogHTMLAttributes<HTMLDialogElement>, HTMLDialogElement>, 'open'> & {
autoOpen?: boolean | undefined,
};
// eslint-disable-next-line react/display-name
const Dialog = forwardRef<HTMLDialogElement, Props>(({ children, autoOpen, onClose, onClick, ...props }, ref) => {
useEffect(() => {
// eslint-disable-next-line react/destructuring-assignment
if (autoOpen) (ref as React.RefObject<HTMLDialogElement>).current?.showModal();
// eslint-disable-next-line react/destructuring-assignment
else (ref as React.RefObject<HTMLDialogElement>).current?.close();
}, [autoOpen, ref]);
const handleClick = useCallback((e: React.MouseEvent<HTMLDialogElement>) => {
if (!(ref != null && 'current' in ref && ref.current != null)) return;
const dialogDimensions = ref.current.getBoundingClientRect();
if (e.clientX < dialogDimensions.left
|| e.clientX > dialogDimensions.right
|| e.clientY < dialogDimensions.top
|| e.clientY > dialogDimensions.bottom) {
onClose?.(e);
}
onClick?.(e);
}, [onClick, onClose, ref]);
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, react/jsx-props-no-spreading
<dialog {...props} ref={ref} onClose={onClose} onClick={handleClick}>
{children}
<form method="dialog">
<Button type="submit" className={styles['close']} aria-label={i18n.t('Close')}>
<FaTimes />
</Button>
</form>
</dialog>
);
});
export const ConfirmButton = ({ style, ...props }: ButtonProps) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<Button style={{ fontSize: '1.2em', ...style }} {...props} />
);
export default Dialog;

@ -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, Dialog } from 'evergreen-ui';
import { SearchInput, PlusIcon, UndoIcon, TakeActionIcon, IconButton, Button, DeleteIcon, AddIcon } from 'evergreen-ui';
import { FaMouse, FaPlus, FaStepForward, FaStepBackward } from 'react-icons/fa';
import Mousetrap from 'mousetrap';
import groupBy from 'lodash/groupBy';
@ -8,7 +8,6 @@ import orderBy from 'lodash/orderBy';
import uniq from 'lodash/uniq';
import useUserSettings from '../hooks/useUserSettings';
import Swal from '../swal';
import SetCutpointButton from './SetCutpointButton';
import SegmentCutpointButton from './SegmentCutpointButton';
import { getModifier } from '../hooks/useTimelineScroll';
@ -16,6 +15,7 @@ import { KeyBinding, KeyboardAction, ModifierKey } from '../../../../types';
import { StateSegment } from '../types';
import Sheet from './Sheet';
import { splitKeyboardKeys } from '../util';
import Dialog, { ConfirmButton } from './Dialog';
type Category = string;
@ -92,32 +92,30 @@ const CreateBinding = memo(({
const isComboInvalid = validKeysDown.length === 0 && keysDown.length > 0;
const dialogRef = useRef<HTMLDialogElement>(null);
const fixedKeys = useMemo(() => fixKeys(keysDown), [keysDown]);
if (!isShown) return null;
return (
<Dialog
title={t('Bind new key to action')}
isShown={action != null}
confirmLabel={t('Save')}
cancelLabel={t('Cancel')}
onCloseComplete={() => setCreatingBinding(undefined)}
onConfirm={() => action != null && onNewKeyBindingConfirmed(action, keysDown)}
onCancel={() => setCreatingBinding(undefined)}
>
{isShown ? (
<div style={{ color: 'black' }}>
<Paragraph marginBottom={10}><TakeActionIcon verticalAlign="middle" marginRight={5} /> {actionsMap[action].name} <span style={{ color: 'rgba(0,0,0,0.5)' }}>({action})</span></Paragraph>
<Paragraph>{t('Please press your desired key combination. Make sure it doesn\'t conflict with any other binding or system hotkeys.')}</Paragraph>
<div style={{ margin: '20px 0' }}>{renderKeys(validKeysDown.length > 0 ? validKeysDown : keysDown)}</div>
{isComboInvalid && <InlineAlert marginBottom={20} intent="danger">{t('Combination is invalid')}</InlineAlert>}
<div>
{!keysDown.includes('esc') && <Button iconBefore={PlusIcon} onClick={() => addKeyDown('esc')}>ESC</Button>}
{keysDown.length > 0 && <Button intent="warning" iconBefore={UndoIcon} onClick={() => setKeysDown([])}>{t('Start over')}</Button>}
</div>
</div>
) : <div />}
<Dialog ref={dialogRef} autoOpen onClose={() => setCreatingBinding(undefined)} style={{ maxWidth: '40em' }}>
<h1 style={{ marginTop: 0 }}>{t('Bind new key to action')}</h1>
<p><TakeActionIcon verticalAlign="middle" marginRight={5} /> {actionsMap[action].name} <span style={{ color: 'var(--gray-10)' }}>({action})</span></p>
<p>{t('Please press your desired key combination. Make sure it doesn\'t conflict with any other binding or system hotkeys.')}</p>
<div style={{ margin: '20px 0' }}>{renderKeys(validKeysDown.length > 0 ? validKeysDown : keysDown)}</div>
{isComboInvalid && <p>{t('Combination is invalid')}</p>}
<div style={{ marginBottom: '1em' }}>
{!keysDown.includes('esc') && <Button iconBefore={PlusIcon} onClick={() => addKeyDown('esc')}>ESC</Button>}
{keysDown.length > 0 && <Button intent="warning" iconBefore={UndoIcon} onClick={() => setKeysDown([])}>{t('Start over')}</Button>}
</div>
<ConfirmButton disabled={fixedKeys.length === 0 || isComboInvalid} onClick={() => action != null && onNewKeyBindingConfirmed(action, keysDown)}>{t('Save')}</ConfirmButton>
</Dialog>
);
});
@ -758,22 +756,15 @@ const KeyboardShortcuts = memo(({
const stringifyKeys = (keys: string[]) => keys.join('+');
const onNewKeyBindingConfirmed = useCallback(async (action: KeyboardAction, keys: string[]) => {
const fixedKeys = fixKeys(keys);
if (fixedKeys.length === 0) return;
const keysStr = stringifyKeys(fixedKeys);
const keysStr = stringifyKeys(keys);
console.log('new key binding', action, keysStr);
const duplicate = keyBindings.find((existingBinding) => existingBinding.keys === keysStr);
let shouldReplaceDuplicate: KeyBinding | undefined;
if (duplicate) {
const { isConfirmed } = await Swal.fire({
icon: 'warning',
title: t('Duplicate keyboard combination'),
text: t('Combination is already bound to "{{alreadyBoundKey}}". Do you want to replace the existing binding?', { alreadyBoundKey: actionsMap[duplicate.action]?.name }),
confirmButtonText: t('Replace'),
focusCancel: true,
showCancelButton: true,
});
// eslint-disable-next-line no-alert
const isConfirmed = window.confirm(t('Combination is already bound to "{{alreadyBoundKey}}". Do you want to replace the existing binding?', { alreadyBoundKey: actionsMap[duplicate.action]?.name }));
if (isConfirmed) {
shouldReplaceDuplicate = duplicate;
} else {

@ -18,7 +18,7 @@
}
:global(.dark-theme) .settings tr.header {
background-color: var(--white-a6);
background-color: var(--white-a3);
}
.settings {

@ -1,36 +1,50 @@
import { memo, useRef, useState, useMemo, useCallback, useEffect, FormEventHandler, MouseEventHandler } from 'react';
import { memo, useRef, useState, useMemo, useCallback, useEffect, FormEventHandler } from 'react';
import { useTranslation } from 'react-i18next';
import { TextInput, TrashIcon, ResetIcon, TickIcon, EditIcon, PlusIcon, Button, IconButton } from 'evergreen-ui';
import { TrashIcon, ResetIcon, TickIcon, EditIcon, PlusIcon, IconButton } from 'evergreen-ui';
import invariant from 'tiny-invariant';
import { askForMetadataKey } from '../dialogs';
import { SegmentTags, segmentTagsSchema } from '../types';
import CopyClipboardButton from './CopyClipboardButton';
import { errorToast } from '../swal';
import TextInput from './TextInput';
import Button from './Button';
const { clipboard } = window.require('electron');
const activeColor = '#429777';
const activeColor = 'var(--gray-12)';
const emptyObject = {};
function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editingTag, setEditingTag, onTagsChange, onTagReset, addTagTitle, addTagText }: {
existingTags?: SegmentTags, customTags?: SegmentTags | undefined, editingTag: string | undefined, setEditingTag: (v: string | undefined) => void, onTagsChange: (keyValues: Record<string, string>) => void, onTagReset: (tag: string) => void, addTagTitle: string, addTagText: string,
function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editingTag, setEditingTag, onTagsChange, onTagReset, addTagTitle }: {
existingTags?: SegmentTags,
customTags?: SegmentTags | undefined,
editingTag: string | undefined,
setEditingTag: (v: string | undefined) => void,
onTagsChange: (keyValues: Record<string, string>) => void,
onTagReset: (tag: string) => void,
addTagTitle: string,
}) {
const { t } = useTranslation();
const ref = useRef<HTMLInputElement>(null);
const [editingTagVal, setEditingTagVal] = useState<string>();
const [newTag, setNewTag] = useState<string>();
const [newTagKey, setNewTagKey] = useState<string>();
const [newTagKeyInput, setNewTagKeyInput] = useState<string>('');
const mergedTags = useMemo(() => ({ ...existingTags, ...customTags, ...(newTag ? { [newTag]: '' } : undefined) }), [customTags, existingTags, newTag]);
const newTagKeyInputError = useMemo(() => !!newTagKeyInput && newTagKeyInput.includes('='), [newTagKeyInput]);
const mergedTags = useMemo(() => ({
...existingTags,
...customTags,
...(newTagKey && { [newTagKey]: '' }),
}), [customTags, existingTags, newTagKey]);
const onResetClick = useCallback(() => {
invariant(editingTag != null);
onTagReset(editingTag);
setEditingTag(undefined);
setNewTag(undefined);
setNewTagKey(undefined);
}, [editingTag, onTagReset, setEditingTag]);
const onPasteClick = useCallback(async () => {
@ -53,9 +67,9 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
}, [editingTag, editingTagVal, onTagsChange, setEditingTag]);
const onEditClick = useCallback((tag?: string) => {
if (newTag) {
if (newTagKey) {
saveTag();
setNewTag(undefined);
setNewTagKey(undefined);
} else if (editingTag != null) {
if (editingTagVal !== existingTags[editingTag]) {
saveTag();
@ -66,28 +80,31 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
setEditingTag(tag);
setEditingTagVal(tag && String(mergedTags[tag]));
}
}, [editingTag, editingTagVal, existingTags, mergedTags, newTag, onResetClick, saveTag, setEditingTag]);
}, [editingTag, editingTagVal, existingTags, mergedTags, newTagKey, onResetClick, saveTag, setEditingTag]);
const onSubmit = useCallback<FormEventHandler<HTMLFormElement>>((e) => {
e.preventDefault();
onEditClick();
}, [onEditClick]);
const onAddPress = useCallback<MouseEventHandler<HTMLButtonElement>>(async (e) => {
e.preventDefault();
(e.target as HTMLButtonElement).blur();
if (newTag || editingTag != null) {
const add = useCallback(() => {
if (newTagKey || editingTag != null) {
// save any unsaved edit
onEditClick();
return;
}
const tag = await askForMetadataKey({ title: addTagTitle, text: addTagText });
if (!tag || Object.keys(mergedTags).includes(tag)) return;
setEditingTag(tag);
if (!newTagKeyInput || newTagKeyInputError || Object.keys(mergedTags).includes(newTagKeyInput)) return;
setEditingTag(newTagKeyInput);
setEditingTagVal('');
setNewTag(tag);
}, [addTagText, addTagTitle, editingTag, mergedTags, newTag, onEditClick, setEditingTag]);
setNewTagKey(newTagKeyInput);
setNewTagKeyInput('');
}, [editingTag, mergedTags, newTagKey, newTagKeyInput, newTagKeyInputError, onEditClick, setEditingTag]);
const onAddSubmit = useCallback<FormEventHandler<HTMLFormElement>>((e) => {
e.preventDefault();
add();
}, [add]);
useEffect(() => {
ref.current?.focus();
@ -95,7 +112,7 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
return (
<>
<table style={{ color: 'black', marginBottom: 10 }}>
<table style={{ marginBottom: 10 }}>
<tbody>
{Object.keys(mergedTags).map((tag) => {
const editingThis = tag === editingTag;
@ -105,15 +122,15 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
return (
<tr key={tag}>
<td style={{ paddingRight: 20, color: thisTagNew ? activeColor : 'rgba(0,0,0,0.6)' }}>{tag}</td>
<td style={{ paddingRight: '1em', color: thisTagNew ? activeColor : 'var(--gray-11)' }}>{tag}</td>
<td style={{ display: 'flex', alignItems: 'center' }}>
{editingThis ? (
<form style={{ display: 'inline' }} onSubmit={onSubmit}>
<TextInput ref={ref} placeholder={t('Enter value')} value={editingTagVal || ''} onChange={(e) => setEditingTagVal(e.target.value)} style={{ textTransform: editingTag === 'language' ? 'lowercase' : undefined }} />
<TextInput ref={ref} placeholder={t('Enter value')} value={editingTagVal || ''} onChange={(e) => setEditingTagVal(e.target.value)} style={{ padding: '.4em', textTransform: editingTag === 'language' ? 'lowercase' : undefined }} />
</form>
) : (
<span style={{ padding: '.5em 0', color: thisTagCustom ? activeColor : undefined, fontWeight: thisTagCustom ? 'bold' : undefined }}>{mergedTags[tag] ? String(mergedTags[tag]) : `<${t('empty')}>`}</span>
<span style={{ padding: '.5em 0', color: thisTagCustom ? activeColor : 'var(--gray-11)', fontWeight: thisTagCustom ? 'bold' : undefined }}>{mergedTags[tag] ? String(mergedTags[tag]) : `<${t('empty')}>`}</span>
)}
{(editingTag == null || editingThis) && <IconButton icon={Icon} title={t('Edit')} appearance="minimal" style={{ marginLeft: '.4em' }} onClick={() => onEditClick(tag)} intent={editingThis ? 'success' : 'none'} />}
{editingThis && <IconButton icon={thisTagNew ? TrashIcon : ResetIcon} title={thisTagNew ? t('Delete') : t('Reset')} appearance="minimal" onClick={onResetClick} intent="danger" />}
@ -124,13 +141,23 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
</tbody>
</table>
<Button iconBefore={PlusIcon} onClick={onAddPress}>{addTagTitle}</Button>
{(!newTagKey && (editingTag == null || mergedTags[editingTag] == null)) && (
<>
<form onSubmit={onAddSubmit}>
<TextInput value={newTagKeyInput} onChange={(e) => setNewTagKeyInput(e.target.value)} placeholder={addTagTitle} style={{ marginBottom: '1em', padding: '.4em', marginRight: '1em' }} />
<Button type="button" title={addTagTitle} onClick={add} style={{ padding: '.3em' }}><PlusIcon style={{ verticalAlign: 'middle' }} /></Button>
</form>
{newTagKeyInputError && <div>{t('Invalid character(s) found in key')}</div>}
</>
)}
<div style={{ marginTop: '1em' }}>
<span style={{ marginRight: '1em' }}>{t('Batch')}:</span>
<CopyClipboardButton text={JSON.stringify(mergedTags)} style={{ marginRight: '.3em', verticalAlign: 'middle' }} />
<div style={{ marginTop: '1em', marginBottom: '1em' }}>
<CopyClipboardButton text={JSON.stringify(mergedTags, null, 2)} style={{ marginRight: '.3em', verticalAlign: 'middle' }}>
{({ onClick }) => <Button onClick={onClick} style={{ display: 'block' }}>{t('Copy to clipboard')}</Button>}
</CopyClipboardButton>
<Button appearance="minimal" onClick={onPasteClick}>{t('Paste')}</Button>
<Button onClick={onPasteClick}>{t('Paste')}</Button>
</div>
</>
);

@ -378,18 +378,6 @@ export async function askForAlignSegments() {
};
}
export async function askForMetadataKey({ title, text }: { title: string, text: string }) {
const { value } = await Swal.fire<string>({
title,
text,
input: 'text',
showCancelButton: true,
inputPlaceholder: 'key',
inputValidator: (v) => (v.includes('=') ? i18n.t('Invalid character(s) found in key') : null),
});
return value;
}
export async function confirmExtractAllStreamsDialog() {
const { value } = await Swal.fire<string>({
text: i18n.t('Please confirm that you want to extract all tracks as separate files'),

@ -1,4 +1,4 @@
@import 'sweetalert2/src/variables';
$swal2-outline-color: rgba(100, 150, 200, 0.5) !default;
// see colors.ts primaryColor
$swal2-confirm-button-background-color: var(--cyan-9);

@ -1506,130 +1506,6 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-dialog@npm:^1.1.11":
version: 1.1.11
resolution: "@radix-ui/react-dialog@npm:1.1.11"
dependencies:
"@radix-ui/primitive": "npm:1.1.2"
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-context": "npm:1.1.2"
"@radix-ui/react-dismissable-layer": "npm:1.1.7"
"@radix-ui/react-focus-guards": "npm:1.1.2"
"@radix-ui/react-focus-scope": "npm:1.1.4"
"@radix-ui/react-id": "npm:1.1.1"
"@radix-ui/react-portal": "npm:1.1.6"
"@radix-ui/react-presence": "npm:1.1.4"
"@radix-ui/react-primitive": "npm:2.1.0"
"@radix-ui/react-slot": "npm:1.2.0"
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
aria-hidden: "npm:^1.2.4"
react-remove-scroll: "npm:^2.6.3"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10/04b8889fb2c29809468f2e83d31a39d5c2b12c2ac286c3cb70eb20dea82bd21b9711ddfd2f9885f3240675c91507771703c8dfc6083bf95cecdd80c99e16e8c5
languageName: node
linkType: hard
"@radix-ui/react-dismissable-layer@npm:1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-dismissable-layer@npm:1.1.7"
dependencies:
"@radix-ui/primitive": "npm:1.1.2"
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-primitive": "npm:2.1.0"
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
"@radix-ui/react-use-escape-keydown": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10/83e6116d932e30a8ee35aad0c9b25a0c5b31358e5d117679db10d7f2336fa9aac272a3c4c246075fea3e718f14d7dbbacc28605473f0e8274b519edb0c79be23
languageName: node
linkType: hard
"@radix-ui/react-focus-guards@npm:1.1.2":
version: 1.1.2
resolution: "@radix-ui/react-focus-guards@npm:1.1.2"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/618658e2b98575198b94ccfdd27f41beb37f83721c9a04617e848afbc47461124ae008d703d713b9644771d96d4852e49de322cf4be3b5f10a4f94d200db5248
languageName: node
linkType: hard
"@radix-ui/react-focus-scope@npm:1.1.4":
version: 1.1.4
resolution: "@radix-ui/react-focus-scope@npm:1.1.4"
dependencies:
"@radix-ui/react-compose-refs": "npm:1.1.2"
"@radix-ui/react-primitive": "npm:2.1.0"
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10/ba405724703e151b56a787ee8c8a259fb46f51d9e2ea755597bcebb9ff0b854ca591d9bd09d6b5168871d1150104b36342fd1f47e33b3759925d19800bd580a0
languageName: node
linkType: hard
"@radix-ui/react-id@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-id@npm:1.1.1"
dependencies:
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db
languageName: node
linkType: hard
"@radix-ui/react-portal@npm:1.1.6":
version: 1.1.6
resolution: "@radix-ui/react-portal@npm:1.1.6"
dependencies:
"@radix-ui/react-primitive": "npm:2.1.0"
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: 10/a521c1ed242a404bee59d8fc881b3a22d6135cfa4bb34d63c13b141d37e1572e31b324f3f60f629415e61963a916bddd5ce00ef42a8175dc9f3cba968ed5d4c9
languageName: node
linkType: hard
"@radix-ui/react-presence@npm:1.1.4":
version: 1.1.4
resolution: "@radix-ui/react-presence@npm:1.1.4"
@ -1709,19 +1585,6 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-use-callback-ref@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/cde8c40f1d4e79e6e71470218163a746858304bad03758ac84dc1f94247a046478e8e397518350c8d6609c84b7e78565441d7505bb3ed573afce82cfdcd19faf
languageName: node
linkType: hard
"@radix-ui/react-use-controllable-state@npm:1.2.2":
version: 1.2.2
resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2"
@ -1753,21 +1616,6 @@ __metadata:
languageName: node
linkType: hard
"@radix-ui/react-use-escape-keydown@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1"
dependencies:
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
peerDependencies:
"@types/react": "*"
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/0eb0756c2c55ddcde9ff01446ab01c085ab2bf799173e97db7ef5f85126f9e8600225570801a1f64740e6d14c39ffe8eed7c14d29737345a5797f4622ac96f6f
languageName: node
linkType: hard
"@radix-ui/react-use-layout-effect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1"
@ -3004,15 +2852,6 @@ __metadata:
languageName: node
linkType: hard
"aria-hidden@npm:^1.2.4":
version: 1.2.4
resolution: "aria-hidden@npm:1.2.4"
dependencies:
tslib: "npm:^2.0.0"
checksum: 10/df4bc15423aaaba3729a7d40abcbf6d3fffa5b8fd5eb33d3ac8b7da0110c47552fca60d97f2e1edfbb68a27cae1da499f1c3896966efb3e26aac4e3b57e3cc8b
languageName: node
linkType: hard
"aria-query@npm:^5.3.0":
version: 5.3.0
resolution: "aria-query@npm:5.3.0"
@ -4416,13 +4255,6 @@ __metadata:
languageName: node
linkType: hard
"detect-node-es@npm:^1.1.0":
version: 1.1.0
resolution: "detect-node-es@npm:1.1.0"
checksum: 10/e46307d7264644975b71c104b9f028ed1d3d34b83a15b8a22373640ce5ea630e5640b1078b8ea15f202b54641da71e4aa7597093bd4b91f113db520a26a37449
languageName: node
linkType: hard
"detect-node@npm:^2.0.4":
version: 2.1.0
resolution: "detect-node@npm:2.1.0"
@ -6309,13 +6141,6 @@ __metadata:
languageName: node
linkType: hard
"get-nonce@npm:^1.0.0":
version: 1.0.1
resolution: "get-nonce@npm:1.0.1"
checksum: 10/ad5104871d114a694ecc506a2d406e2331beccb961fe1e110dc25556b38bcdbf399a823a8a375976cd8889668156a9561e12ebe3fa6a4c6ba169c8466c2ff868
languageName: node
linkType: hard
"get-stream@npm:^5.1.0":
version: 5.2.0
resolution: "get-stream@npm:5.2.0"
@ -7843,7 +7668,6 @@ __metadata:
"@octokit/core": "npm:5"
"@radix-ui/colors": "npm:^3.0.0"
"@radix-ui/react-checkbox": "npm:^1.2.3"
"@radix-ui/react-dialog": "npm:^1.1.11"
"@radix-ui/react-switch": "npm:^1.2.2"
"@tsconfig/node18": "npm:^18.2.2"
"@tsconfig/node20": "npm:^20.1.4"
@ -9379,41 +9203,6 @@ __metadata:
languageName: node
linkType: hard
"react-remove-scroll-bar@npm:^2.3.7":
version: 2.3.8
resolution: "react-remove-scroll-bar@npm:2.3.8"
dependencies:
react-style-singleton: "npm:^2.2.2"
tslib: "npm:^2.0.0"
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/6c0f8cff98b9f49a4ee2263f1eedf12926dced5ce220fbe83bd93544460e2a7ec8ec39b35d1b2a75d2fced0b2d64afeb8e66f830431ca896e05a20585f9fc350
languageName: node
linkType: hard
"react-remove-scroll@npm:^2.6.3":
version: 2.6.3
resolution: "react-remove-scroll@npm:2.6.3"
dependencies:
react-remove-scroll-bar: "npm:^2.3.7"
react-style-singleton: "npm:^2.2.3"
tslib: "npm:^2.1.0"
use-callback-ref: "npm:^1.3.3"
use-sidecar: "npm:^1.1.3"
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/d4dfd38e4381fa6059c8b810568b2d3a31fe21168bb3e2f57d1b1885ee08736fbd5a3fd83936faef0d17031c9c4175a1af83885bfc6c4280611f025447b19a4c
languageName: node
linkType: hard
"react-reverse-portal@npm:^2.1.2":
version: 2.1.2
resolution: "react-reverse-portal@npm:2.1.2"
@ -9439,22 +9228,6 @@ __metadata:
languageName: node
linkType: hard
"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3":
version: 2.2.3
resolution: "react-style-singleton@npm:2.2.3"
dependencies:
get-nonce: "npm:^1.0.0"
tslib: "npm:^2.0.0"
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/62498094ff3877a37f351b29e6cad9e38b2eb1ac3c0cb27ebf80aee96554f80b35e17bdb552bcd7ac8b7cb9904fea93ea5668f2057c73d38f90b5d46bb9b27ab
languageName: node
linkType: hard
"react-syntax-highlighter@npm:^15.4.3":
version: 15.4.5
resolution: "react-syntax-highlighter@npm:15.4.5"
@ -10819,9 +10592,9 @@ __metadata:
linkType: hard
"sweetalert2@npm:^11.11.0":
version: 11.11.0
resolution: "sweetalert2@npm:11.11.0"
checksum: 10/8a5ead894f4e15b9f74a7bb95077972b85c92367dd157401bfe7b4cd933382d23640f634105fc984760c0ed02a9910f4a54a357eaebd999cbac3cd10e1f03237
version: 11.21.0
resolution: "sweetalert2@npm:11.21.0"
checksum: 10/97d216b459a5e6502e179cc37aacc47885b6d76b2b0930109b3d55b44fe6f072549974b43045ce684b9107bf737c37fc702a6b82cd3fd01cee128ac017057def
languageName: node
linkType: hard
@ -11135,20 +10908,13 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^2.0.0":
"tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7
languageName: node
linkType: hard
"tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0":
version: 2.5.0
resolution: "tslib@npm:2.5.0"
checksum: 10/ea556fbdf396fe15dbd45e242754e86e7c36e0dce8644404a7c8a81ae1e940744dc639569aeca1ae370a7f804d82872f3fd8564eb23be9adb7618201d0314dac
languageName: node
linkType: hard
"tsx@npm:^4.7.1":
version: 4.7.1
resolution: "tsx@npm:4.7.1"
@ -11489,21 +11255,6 @@ __metadata:
languageName: node
linkType: hard
"use-callback-ref@npm:^1.3.3":
version: 1.3.3
resolution: "use-callback-ref@npm:1.3.3"
dependencies:
tslib: "npm:^2.0.0"
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/adf06a7b6a27d3651c325ac9b66d2b82ccacaed7450b85b211d123e91d9a23cb5a587fcc6db5b4fd07ac7233e5abf024d30cf02ddc2ec46bca712151c0836151
languageName: node
linkType: hard
"use-debounce@npm:^5.1.0":
version: 5.2.1
resolution: "use-debounce@npm:5.2.1"
@ -11513,22 +11264,6 @@ __metadata:
languageName: node
linkType: hard
"use-sidecar@npm:^1.1.3":
version: 1.1.3
resolution: "use-sidecar@npm:1.1.3"
dependencies:
detect-node-es: "npm:^1.1.0"
tslib: "npm:^2.0.0"
peerDependencies:
"@types/react": "*"
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
checksum: 10/2fec05eb851cdfc4a4657b1dfb434e686f346c3265ffc9db8a974bb58f8128bd4a708a3cc00e8f51655fccf81822ed4419ebed42f41610589e3aab0cf2492edb
languageName: node
linkType: hard
"use-trace-update@npm:^1.3.0":
version: 1.3.2
resolution: "use-trace-update@npm:1.3.2"

Loading…
Cancel
Save