Self-review M5/M6/UX1: accurate preview, invalidate on change, reset overrides
Fixes found reviewing the previous commit:
- M5 preview was inaccurate: loadCommandPreview omitted formatId/formatHasAudio
and trim, so the "exact command" differed from what download() actually spawns
whenever a probe-selected format or a trim range was set. toggleCommandPreview
now mirrors download()'s StartDownloadOptions (format + trim included).
- Stale preview: a shown command only cleared on URL change. Now it also clears
on kind, quality, format, trim, download-options change, and on probe — so the
displayed command is never out of sync with the form.
- Command button is now a real toggle (Show command / Hide command) instead of a
no-op "Command · shown" label.
- Per-download overrides are now one-shot: incognito + downloadOptions reset to
defaults after a download, matching trim/schedule and preventing incognito from
silently carrying into the next download.
- Style: replaced two inline style={{}} usages with makeStyles classes
(commandError), hardcoded 'monospace' -> tokens.fontFamilyMonospace, dropped an
unnecessary fragment, and let long command lines wrap (pre-wrap/break-all).
typecheck + 243 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -264,12 +264,20 @@ const useStyles = makeStyles({
|
||||
backgroundColor: tokens.colorNeutralBackground2,
|
||||
...shorthands.borderRadius(tokens.borderRadiusLarge),
|
||||
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: tokens.fontFamilyMonospace,
|
||||
fontSize: tokens.fontSizeBase200,
|
||||
overflowX: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-all',
|
||||
color: tokens.colorNeutralForeground1
|
||||
},
|
||||
commandError: {
|
||||
color: tokens.colorStatusDangerForeground1
|
||||
},
|
||||
optionsPanel: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '12px',
|
||||
padding: '12px',
|
||||
backgroundColor: tokens.colorNeutralBackground2,
|
||||
...shorthands.borderRadius(tokens.borderRadiusLarge),
|
||||
@@ -403,20 +411,28 @@ export function DownloadBar(): React.JSX.Element {
|
||||
setItemKinds({})
|
||||
}
|
||||
|
||||
async function loadCommandPreview(): Promise<void> {
|
||||
const trimmed = url.trim()
|
||||
if (!trimmed) {
|
||||
// Fetch (or toggle off) the exact yt-dlp command line for the current form
|
||||
// state. The options mirror what download() sends so the preview matches the
|
||||
// command that will actually run -- including a probe-selected format and any
|
||||
// trim spec (M5).
|
||||
async function toggleCommandPreview(): Promise<void> {
|
||||
if (commandPreview) {
|
||||
setCommandPreview(null)
|
||||
return
|
||||
}
|
||||
const trimmed = url.trim()
|
||||
if (!trimmed) return
|
||||
setPreviewLoading(true)
|
||||
try {
|
||||
const result = await window.api.previewCommand({
|
||||
id: 'preview',
|
||||
url: trimmed,
|
||||
kind,
|
||||
quality,
|
||||
options: downloadOptions
|
||||
kind: usingFormats ? 'video' : kind,
|
||||
quality: usingFormats && selectedFormat ? selectedFormat.label : quality,
|
||||
formatId: usingFormats ? selectedFormat?.id : undefined,
|
||||
formatHasAudio: usingFormats ? selectedFormat?.hasAudio : undefined,
|
||||
options: downloadOptions,
|
||||
trim: trim.trim() || undefined
|
||||
})
|
||||
setCommandPreview(result)
|
||||
} catch (e) {
|
||||
@@ -431,13 +447,15 @@ export function DownloadBar(): React.JSX.Element {
|
||||
// Any probed info -- or a duplicate warning -- is stale once the URL changes.
|
||||
if (info || probeError || playlist) clearProbe()
|
||||
if (dup) setDup(null)
|
||||
if (commandPreview) setCommandPreview(null)
|
||||
setCommandPreview(null)
|
||||
confirmDup.current = false
|
||||
}
|
||||
|
||||
function onKindChange(next: MediaKind): void {
|
||||
setKind(next)
|
||||
setQuality(QUALITY_OPTIONS[next][0])
|
||||
// The command changes with kind/quality, so any shown preview is now stale.
|
||||
setCommandPreview(null)
|
||||
}
|
||||
|
||||
async function fetchFormats(): Promise<void> {
|
||||
@@ -447,6 +465,8 @@ export function DownloadBar(): React.JSX.Element {
|
||||
setProbeError(null)
|
||||
setInfo(null)
|
||||
setPlaylist(null)
|
||||
// Probing may switch the URL to the format-picker path, changing the command.
|
||||
setCommandPreview(null)
|
||||
try {
|
||||
const res = await window.api.probe(trimmed)
|
||||
if (res.ok && res.kind === 'playlist' && res.playlist) {
|
||||
@@ -573,6 +593,11 @@ export function DownloadBar(): React.JSX.Element {
|
||||
setShowSchedule(false)
|
||||
setShowAdvanced(false)
|
||||
setCommandPreview(null)
|
||||
// Per-download overrides are one-shot (like trim/schedule): reset so the next
|
||||
// download starts from the global defaults, and incognito never silently
|
||||
// carries over to a following download.
|
||||
setIncognito(false)
|
||||
setDownloadOptions(DEFAULT_DOWNLOAD_OPTIONS)
|
||||
clearProbe()
|
||||
}
|
||||
|
||||
@@ -814,7 +839,10 @@ export function DownloadBar(): React.JSX.Element {
|
||||
>
|
||||
<Textarea
|
||||
value={trim}
|
||||
onChange={(_, d) => setTrim(d.value)}
|
||||
onChange={(_, d) => {
|
||||
setTrim(d.value)
|
||||
setCommandPreview(null)
|
||||
}}
|
||||
placeholder={'0:30-1:45\n3:00-3:30'}
|
||||
resize="vertical"
|
||||
/>
|
||||
@@ -855,22 +883,18 @@ export function DownloadBar(): React.JSX.Element {
|
||||
size="small"
|
||||
appearance="subtle"
|
||||
icon={previewLoading ? <Spinner size="tiny" /> : <WindowConsoleRegular />}
|
||||
onClick={loadCommandPreview}
|
||||
onClick={toggleCommandPreview}
|
||||
disabled={!url.trim() || previewLoading}
|
||||
>
|
||||
{commandPreview ? 'Command · shown' : 'Show command'}
|
||||
{commandPreview ? 'Hide command' : 'Show command'}
|
||||
</Button>
|
||||
</div>
|
||||
{commandPreview && (
|
||||
<div className={styles.commandPreviewPanel}>
|
||||
{commandPreview.ok ? (
|
||||
<>
|
||||
<div>{commandPreview.command}</div>
|
||||
</>
|
||||
commandPreview.command
|
||||
) : (
|
||||
<div style={{ color: tokens.colorStatusDangerForeground1 }}>
|
||||
Error: {commandPreview.error}
|
||||
</div>
|
||||
<span className={styles.commandError}>Error: {commandPreview.error}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -881,13 +905,14 @@ export function DownloadBar(): React.JSX.Element {
|
||||
onChange={(_, d) => setIncognito(!!d.checked)}
|
||||
label="Incognito mode (no logging, no history, no cookies)"
|
||||
/>
|
||||
<div style={{ marginTop: '12px' }}>
|
||||
<DownloadOptionsForm
|
||||
value={downloadOptions}
|
||||
onChange={setDownloadOptions}
|
||||
kind={kind}
|
||||
/>
|
||||
</div>
|
||||
<DownloadOptionsForm
|
||||
value={downloadOptions}
|
||||
onChange={(next) => {
|
||||
setDownloadOptions(next)
|
||||
setCommandPreview(null)
|
||||
}}
|
||||
kind={kind}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -916,7 +941,10 @@ export function DownloadBar(): React.JSX.Element {
|
||||
size="large"
|
||||
value={selectedFormat?.id ?? ''}
|
||||
options={info.formats.map((f) => ({ value: f.id, label: f.label }))}
|
||||
onChange={setFormatId}
|
||||
onChange={(v) => {
|
||||
setFormatId(v)
|
||||
setCommandPreview(null)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
@@ -925,7 +953,10 @@ export function DownloadBar(): React.JSX.Element {
|
||||
size="large"
|
||||
value={quality}
|
||||
options={QUALITY_OPTIONS[kind].map((q) => ({ value: q, label: q }))}
|
||||
onChange={setQuality}
|
||||
onChange={(v) => {
|
||||
setQuality(v)
|
||||
setCommandPreview(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user