1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-22 10:22:31 +01:00

improve export confirm

This commit is contained in:
Mikael Finstad 2023-03-10 15:08:58 +08:00
parent 365e1fd1a8
commit 391cab538c
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
6 changed files with 210 additions and 107 deletions

View File

@ -1,6 +1,6 @@
import React, { memo, useCallback, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { WarningSignIcon, Button, CrossIcon } from 'evergreen-ui';
import { WarningSignIcon, CrossIcon } from 'evergreen-ui';
import { FaRegCheckCircle } from 'react-icons/fa';
import i18n from 'i18next';
import { useTranslation, Trans } from 'react-i18next';
@ -15,6 +15,7 @@ import ToggleExportConfirm from './ToggleExportConfirm';
import OutSegTemplateEditor from './OutSegTemplateEditor';
import HighlightedText, { highlightedTextStyle } from './HighlightedText';
import Select from './Select';
import Switch from './Switch';
import { primaryTextColor } from '../colors';
import { withBlur } from '../util';
@ -106,8 +107,6 @@ const ExportConfirm = memo(({
toast.fire({ icon: 'info', timer: 10000, text: `${avoidNegativeTs}: ${texts[avoidNegativeTs]}` });
}, [avoidNegativeTs]);
const outSegTemplateHelpIcon = <HelpIcon onClick={onOutSegTemplateHelpPress} />;
const canEditTemplate = !willMerge || !autoDeleteMergedSegments;
// https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container
@ -127,94 +126,199 @@ const ExportConfirm = memo(({
<CrossIcon size={24} style={{ position: 'absolute', right: 0, top: 0, padding: 15, boxSizing: 'content-box', cursor: 'pointer' }} role="button" onClick={onClosePress} />
<h2 style={{ marginTop: 0, marginBottom: '.5em' }}>{t('Export options')}</h2>
<ul style={{ margin: 0 }}>
{selectedSegments.length !== nonFilteredSegmentsOrInverse.length && <li><FaRegCheckCircle size={12} style={{ marginRight: 3 }} />{t('{{selectedSegments}} of {{nonFilteredSegments}} segments selected', { selectedSegments: selectedSegments.length, nonFilteredSegments: nonFilteredSegmentsOrInverse.length })}</li>}
<li>
{selectedSegments.length > 1 ? t('Export mode for {{segments}} segments', { segments: selectedSegments.length }) : t('Export mode')} <ExportModeButton selectedSegments={selectedSegments} />
<HelpIcon onClick={onExportModeHelpPress} />
{effectiveExportMode === 'sesgments_to_chapters' && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Chapters only')} />}
</li>
<li>
{t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })}
<HelpIcon onClick={onOutFmtHelpPress} />
</li>
<li>
<Trans>Input has {{ numStreamsTotal }} tracks - <HighlightedText style={{ cursor: 'pointer' }} onClick={() => setStreamsSelectorShown(true)}>Keeping {{ numStreamsToCopy }} tracks</HighlightedText></Trans>
<HelpIcon onClick={onTracksHelpPress} />
{areWeCuttingProblematicStreams && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
{areWeCuttingProblematicStreams && <div style={warningStyle}><Trans>Warning: Cutting thumbnail tracks is known to cause problems. Consider disabling track {{ trackNumber: mainCopiedThumbnailStreams[0].index + 1 }}.</Trans></div>}
</li>
<li>
{t('Save output to path:')} <span role="button" onClick={changeOutDir} style={outDirStyle}>{outputDir}</span>
</li>
{canEditTemplate && (
<li>
<OutSegTemplateEditor filePath={filePath} helpIcon={outSegTemplateHelpIcon} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} />
</li>
)}
</ul>
<table className={styles.options}>
<tbody>
{selectedSegments.length !== nonFilteredSegmentsOrInverse.length && (
<tr>
<td colSpan={2}>
<FaRegCheckCircle size={12} style={{ marginRight: 3 }} />{t('{{selectedSegments}} of {{nonFilteredSegments}} segments selected', { selectedSegments: selectedSegments.length, nonFilteredSegments: nonFilteredSegmentsOrInverse.length })}
</td>
<td />
</tr>
)}
<tr>
<td>
{selectedSegments.length > 1 ? t('Export mode for {{segments}} segments', { segments: selectedSegments.length }) : t('Export mode')}
</td>
<td>
<ExportModeButton selectedSegments={selectedSegments} />
</td>
<td>
{effectiveExportMode === 'sesgments_to_chapters' && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Chapters only')} />}
<HelpIcon onClick={onExportModeHelpPress} />
</td>
</tr>
<tr>
<td>
{t('Output container format:')}
</td>
<td>
{renderOutFmt({ height: 20, maxWidth: 150 })}
</td>
<td>
<HelpIcon onClick={onOutFmtHelpPress} />
</td>
</tr>
<tr>
<td>
<Trans>Input has {{ numStreamsTotal }} tracks</Trans>
{areWeCuttingProblematicStreams && (
<div style={warningStyle}><Trans>Warning: Cutting thumbnail tracks is known to cause problems. Consider disabling track {{ trackNumber: mainCopiedThumbnailStreams[0] ? mainCopiedThumbnailStreams[0].index + 1 : 0 }}.</Trans></div>
)}
</td>
<td>
<HighlightedText style={{ cursor: 'pointer' }} onClick={() => setStreamsSelectorShown(true)}><Trans>Keeping {{ numStreamsToCopy }} tracks</Trans></HighlightedText>
</td>
<td>
{areWeCuttingProblematicStreams && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
<HelpIcon onClick={onTracksHelpPress} />
</td>
</tr>
<tr>
<td>
{t('Save output to path:')}
</td>
<td>
<span role="button" onClick={changeOutDir} style={outDirStyle}>{outputDir}</span>
</td>
<td />
</tr>
{canEditTemplate && (
<tr>
<td colSpan={2}>
<OutSegTemplateEditor filePath={filePath} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} />
</td>
<td>
<HelpIcon onClick={onOutSegTemplateHelpPress} />
</td>
</tr>
)}
</tbody>
</table>
<h3 style={{ marginBottom: '.5em' }}>{t('Advanced options')}</h3>
{willMerge && (
<ul style={{ marginTop: 0, marginBottom: '1em' }}>
<li>
{t('Create chapters from merged segments? (slow)')} <Button height={20} onClick={toggleSegmentsToChapters}>{segmentsToChapters ? t('Yes') : t('No')}</Button>
<HelpIcon onClick={onSegmentsToChaptersHelpPress} />
</li>
<li>
{t('Preserve original metadata when merging? (slow)')} <Button height={20} onClick={togglePreserveMetadataOnMerge}>{preserveMetadataOnMerge ? t('Yes') : t('No')}</Button>
<HelpIcon onClick={onPreserveMetadataOnMergeHelpPress} />
</li>
</ul>
)}
<table className={styles.options}>
<tbody>
{willMerge && (
<>
<tr>
<td>
{t('Create chapters from merged segments? (slow)')}
</td>
<td>
<Switch checked={segmentsToChapters} onCheckedChange={toggleSegmentsToChapters} />
</td>
<td>
<HelpIcon onClick={onSegmentsToChaptersHelpPress} />
</td>
</tr>
<tr>
<td>
{t('Preserve original metadata when merging? (slow)')}
</td>
<td>
<Switch checked={preserveMetadataOnMerge} onCheckedChange={togglePreserveMetadataOnMerge} />
</td>
<td>
<HelpIcon onClick={onPreserveMetadataOnMergeHelpPress} />
</td>
</tr>
</>
)}
<p style={{ margin: '.5em 0' }}>{t('Depending on your specific file/player, you may have to try different options for best results.')}</p>
<tr>
<td style={{ paddingTop: '.5em', color: 'var(--gray11)' }} colSpan={2}>
{t('Depending on your specific file/player, you may have to try different options for best results.')}
</td>
<td />
</tr>
<ul style={{ margin: 0 }}>
{areWeCutting && (
<>
<li>
{t('Smart cut (experimental):')} <Button height={20} onClick={() => setEnableSmartCut((v) => !v)}>{enableSmartCut ? t('Yes') : t('No')}</Button>
<HelpIcon onClick={onSmartCutHelpPress} />
{needSmartCut && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Experimental functionality has been activated!')} />}
</li>
{!needSmartCut && (
<li>
{t('Cut mode:')} <KeyframeCutButton />
<HelpIcon onClick={onKeyframeCutHelpPress} /> {!keyframeCut && <span style={warningStyle}>{t('Note: Keyframe cut is recommended for most common files')}</span>}
</li>
)}
</>
)}
{areWeCutting && (
<>
<tr>
<td>
{t('Smart cut (experimental):')}
</td>
<td>
<Switch checked={enableSmartCut} onCheckedChange={() => setEnableSmartCut((v) => !v)} />
</td>
<td>
{needSmartCut && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Experimental functionality has been activated!')} />}
<HelpIcon onClick={onSmartCutHelpPress} />
</td>
</tr>
{!needSmartCut && (
<tr>
<td>
{t('Cut mode:')}
{!keyframeCut && <div style={warningStyle}>{t('Note: Keyframe cut is recommended for most common files')}</div>}
</td>
<td>
<KeyframeCutButton />
</td>
<td>
{!keyframeCut && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
<HelpIcon onClick={onKeyframeCutHelpPress} />
</td>
</tr>
)}
</>
)}
{isMov && (
<>
<li>
{t('Enable MOV Faststart?')} <MovFastStartButton />
<HelpIcon onClick={onMovFastStartHelpPress} /> {isIpod && !movFastStart && <span style={warningStyle}>{t('For the ipod format, it is recommended to activate this option')}</span>}
</li>
<li>
{t('Preserve all MP4/MOV metadata?')} <PreserveMovDataButton />
<HelpIcon onClick={onPreserveMovDataHelpPress} /> {isIpod && preserveMovData && <span style={warningStyle}>{t('For the ipod format, it is recommended to deactivate this option')}</span>}
</li>
</>
)}
{isMov && (
<>
<tr>
<td>
{t('Enable MOV Faststart?')}
</td>
<td>
<MovFastStartButton />
</td>
<td>
<HelpIcon onClick={onMovFastStartHelpPress} /> {isIpod && !movFastStart && <span style={warningStyle}>{t('For the ipod format, it is recommended to activate this option')}</span>}
</td>
</tr>
<tr>
<td>
{t('Preserve all MP4/MOV metadata?')}
{isIpod && preserveMovData && <div style={warningStyle}>{t('For the ipod format, it is recommended to deactivate this option')}</div>}
</td>
<td>
<PreserveMovDataButton />
</td>
<td>
{isIpod && preserveMovData && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
<HelpIcon onClick={onPreserveMovDataHelpPress} />
</td>
</tr>
</>
)}
{!needSmartCut && (
<li>
&quot;avoid_negative_ts&quot;
<Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}>
<option value="auto">auto</option>
<option value="make_zero">make_zero</option>
<option value="make_non_negative">make_non_negative</option>
<option value="disabled">disabled</option>
</Select>
<HelpIcon onClick={onAvoidNegativeTsHelpPress} />
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <div style={warningStyle}>{t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' })}</div>}
</li>
)}
</ul>
{!needSmartCut && (
<tr>
<td>
&quot;avoid_negative_ts&quot;
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <div style={warningStyle}>{t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' })}</div>}
</td>
<td>
<Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}>
<option value="auto">auto</option>
<option value="make_zero">make_zero</option>
<option value="make_non_negative">make_non_negative</option>
<option value="disabled">disabled</option>
</Select>
</td>
<td>
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
<HelpIcon onClick={onAvoidNegativeTsHelpPress} />
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</motion.div>

View File

@ -24,3 +24,14 @@
background: var(--blackA8);
}
table.options {
width: 100%;
}
table.options td:last-child {
text-align: right;
width: 3em;
}
table.options td:nth-child(2) {
text-align: right;
}

View File

@ -2,7 +2,7 @@ import React, { memo } from 'react';
import { primaryTextColor } from '../colors';
export const highlightedTextStyle = { textDecoration: 'underline', textUnderlineOffset: '.2em', textDecorationColor: primaryTextColor, color: 'var(--gray12)', borderRadius: '.4em', padding: '0 .3em' };
export const highlightedTextStyle = { textDecoration: 'underline', textUnderlineOffset: '.2em', textDecorationColor: primaryTextColor, color: 'var(--gray12)', borderRadius: '.4em' };
// eslint-disable-next-line react/jsx-props-no-spreading
const HighlightedText = memo(({ children, style, ...props }) => <span {...props} style={{ ...highlightedTextStyle, ...style }}>{children}</span>);

View File

@ -1,19 +1,15 @@
import React, { memo } from 'react';
import { Button } from 'evergreen-ui';
import { useTranslation } from 'react-i18next';
import { withBlur } from '../util';
import useUserSettings from '../hooks/useUserSettings';
import Switch from './Switch';
const MovFastStartButton = memo(() => {
const { t } = useTranslation();
const { movFastStart, toggleMovFastStart } = useUserSettings();
return (
<Button height={20} onClick={withBlur(toggleMovFastStart)}>
{movFastStart ? t('Yes') : t('No')}
</Button>
<Switch checked={movFastStart} onCheckedChange={withBlur(toggleMovFastStart)} />
);
});

View File

@ -15,9 +15,9 @@ const ReactSwal = withReactContent(Swal);
// eslint-disable-next-line no-template-curly-in-string
const extVar = '${EXT}';
const inputStyle = { flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', appearance: 'none', border: 'none' };
const inputStyle = { flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', border: '1px solid var(--gray6)', appearance: 'none' };
const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
const { safeOutputFileName, toggleSafeOutputFileName } = useUserSettings();
const [text, setText] = useState(outSegTemplate);
@ -86,12 +86,9 @@ const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate
return (
<>
<div>
<span>
{outSegFileNames != null && t('Output name(s):', { count: outSegFileNames.length })}
{' '}
{outSegFileNames != null && <HighlightedText role="button" onClick={onShowClick} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', cursor: needToShow ? undefined : 'pointer' }}>{outSegFileNames[currentSegIndexSafe] || outSegFileNames[0] || '-'}</HighlightedText>}
</span>
{helpIcon}
<div>{outSegFileNames != null && t('Output name(s):', { count: outSegFileNames.length })}</div>
{outSegFileNames != null && <HighlightedText role="button" onClick={onShowClick} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', cursor: needToShow ? undefined : 'pointer' }}>{outSegFileNames[currentSegIndexSafe] || outSegFileNames[0] || '-'}</HighlightedText>}
</div>
{needToShow && (
@ -107,8 +104,7 @@ const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate
<div style={{ maxWidth: 600 }}>
{error != null && <Alert intent="danger" appearance="card"><Heading color="danger">{i18n.t('There is an error in the file name template:')}</Heading><Text>{error}</Text></Alert>}
{isMissingExtension && <Alert intent="warning" appearance="card">{i18n.t('The file name template is missing {{ext}} and will result in a file without the suggested extension. This may result in an unplayable output file.', { ext: extVar })}</Alert>}
{/* eslint-disable-next-line no-template-curly-in-string */}
<div style={{ fontSize: '.8em', color: 'rgba(255,255,255,0.7)' }}>
<div style={{ fontSize: '.8em', color: 'var(--gray11)' }}>
{`${i18n.t('Variables')}`}{': '}
{['FILENAME', 'CUT_FROM', 'CUT_TO', 'SEG_NUM', 'SEG_LABEL', 'SEG_SUFFIX', 'EXT', 'SEG_TAGS.XX'].map((variable) => <span key={variable} role="button" style={{ cursor: 'pointer', marginRight: '.2em' }} onClick={() => setText((oldText) => `${oldText}\${${variable}}`)}>{variable}</span>)}
</div>

View File

@ -1,19 +1,15 @@
import React, { memo } from 'react';
import { Button } from 'evergreen-ui';
import { useTranslation } from 'react-i18next';
import { withBlur } from '../util';
import useUserSettings from '../hooks/useUserSettings';
import Switch from './Switch';
const PreserveMovDataButton = memo(() => {
const { t } = useTranslation();
const { preserveMovData, togglePreserveMovData } = useUserSettings();
return (
<Button height={20} onClick={withBlur(togglePreserveMovData)}>
{preserveMovData ? t('Yes') : t('No')}
</Button>
<Switch checked={preserveMovData} onCheckedChange={withBlur(togglePreserveMovData)} />
);
});