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,
|
backgroundColor: tokens.colorNeutralBackground2,
|
||||||
...shorthands.borderRadius(tokens.borderRadiusLarge),
|
...shorthands.borderRadius(tokens.borderRadiusLarge),
|
||||||
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
||||||
fontFamily: 'monospace',
|
fontFamily: tokens.fontFamilyMonospace,
|
||||||
fontSize: tokens.fontSizeBase200,
|
fontSize: tokens.fontSizeBase200,
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-all',
|
||||||
color: tokens.colorNeutralForeground1
|
color: tokens.colorNeutralForeground1
|
||||||
},
|
},
|
||||||
|
commandError: {
|
||||||
|
color: tokens.colorStatusDangerForeground1
|
||||||
|
},
|
||||||
optionsPanel: {
|
optionsPanel: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '12px',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
backgroundColor: tokens.colorNeutralBackground2,
|
backgroundColor: tokens.colorNeutralBackground2,
|
||||||
...shorthands.borderRadius(tokens.borderRadiusLarge),
|
...shorthands.borderRadius(tokens.borderRadiusLarge),
|
||||||
@@ -403,20 +411,28 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
setItemKinds({})
|
setItemKinds({})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadCommandPreview(): Promise<void> {
|
// Fetch (or toggle off) the exact yt-dlp command line for the current form
|
||||||
const trimmed = url.trim()
|
// state. The options mirror what download() sends so the preview matches the
|
||||||
if (!trimmed) {
|
// command that will actually run -- including a probe-selected format and any
|
||||||
|
// trim spec (M5).
|
||||||
|
async function toggleCommandPreview(): Promise<void> {
|
||||||
|
if (commandPreview) {
|
||||||
setCommandPreview(null)
|
setCommandPreview(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const trimmed = url.trim()
|
||||||
|
if (!trimmed) return
|
||||||
setPreviewLoading(true)
|
setPreviewLoading(true)
|
||||||
try {
|
try {
|
||||||
const result = await window.api.previewCommand({
|
const result = await window.api.previewCommand({
|
||||||
id: 'preview',
|
id: 'preview',
|
||||||
url: trimmed,
|
url: trimmed,
|
||||||
kind,
|
kind: usingFormats ? 'video' : kind,
|
||||||
quality,
|
quality: usingFormats && selectedFormat ? selectedFormat.label : quality,
|
||||||
options: downloadOptions
|
formatId: usingFormats ? selectedFormat?.id : undefined,
|
||||||
|
formatHasAudio: usingFormats ? selectedFormat?.hasAudio : undefined,
|
||||||
|
options: downloadOptions,
|
||||||
|
trim: trim.trim() || undefined
|
||||||
})
|
})
|
||||||
setCommandPreview(result)
|
setCommandPreview(result)
|
||||||
} catch (e) {
|
} 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.
|
// Any probed info -- or a duplicate warning -- is stale once the URL changes.
|
||||||
if (info || probeError || playlist) clearProbe()
|
if (info || probeError || playlist) clearProbe()
|
||||||
if (dup) setDup(null)
|
if (dup) setDup(null)
|
||||||
if (commandPreview) setCommandPreview(null)
|
setCommandPreview(null)
|
||||||
confirmDup.current = false
|
confirmDup.current = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKindChange(next: MediaKind): void {
|
function onKindChange(next: MediaKind): void {
|
||||||
setKind(next)
|
setKind(next)
|
||||||
setQuality(QUALITY_OPTIONS[next][0])
|
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> {
|
async function fetchFormats(): Promise<void> {
|
||||||
@@ -447,6 +465,8 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
setProbeError(null)
|
setProbeError(null)
|
||||||
setInfo(null)
|
setInfo(null)
|
||||||
setPlaylist(null)
|
setPlaylist(null)
|
||||||
|
// Probing may switch the URL to the format-picker path, changing the command.
|
||||||
|
setCommandPreview(null)
|
||||||
try {
|
try {
|
||||||
const res = await window.api.probe(trimmed)
|
const res = await window.api.probe(trimmed)
|
||||||
if (res.ok && res.kind === 'playlist' && res.playlist) {
|
if (res.ok && res.kind === 'playlist' && res.playlist) {
|
||||||
@@ -573,6 +593,11 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
setShowSchedule(false)
|
setShowSchedule(false)
|
||||||
setShowAdvanced(false)
|
setShowAdvanced(false)
|
||||||
setCommandPreview(null)
|
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()
|
clearProbe()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,7 +839,10 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={trim}
|
value={trim}
|
||||||
onChange={(_, d) => setTrim(d.value)}
|
onChange={(_, d) => {
|
||||||
|
setTrim(d.value)
|
||||||
|
setCommandPreview(null)
|
||||||
|
}}
|
||||||
placeholder={'0:30-1:45\n3:00-3:30'}
|
placeholder={'0:30-1:45\n3:00-3:30'}
|
||||||
resize="vertical"
|
resize="vertical"
|
||||||
/>
|
/>
|
||||||
@@ -855,22 +883,18 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
size="small"
|
size="small"
|
||||||
appearance="subtle"
|
appearance="subtle"
|
||||||
icon={previewLoading ? <Spinner size="tiny" /> : <WindowConsoleRegular />}
|
icon={previewLoading ? <Spinner size="tiny" /> : <WindowConsoleRegular />}
|
||||||
onClick={loadCommandPreview}
|
onClick={toggleCommandPreview}
|
||||||
disabled={!url.trim() || previewLoading}
|
disabled={!url.trim() || previewLoading}
|
||||||
>
|
>
|
||||||
{commandPreview ? 'Command · shown' : 'Show command'}
|
{commandPreview ? 'Hide command' : 'Show command'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{commandPreview && (
|
{commandPreview && (
|
||||||
<div className={styles.commandPreviewPanel}>
|
<div className={styles.commandPreviewPanel}>
|
||||||
{commandPreview.ok ? (
|
{commandPreview.ok ? (
|
||||||
<>
|
commandPreview.command
|
||||||
<div>{commandPreview.command}</div>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div style={{ color: tokens.colorStatusDangerForeground1 }}>
|
<span className={styles.commandError}>Error: {commandPreview.error}</span>
|
||||||
Error: {commandPreview.error}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -881,14 +905,15 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
onChange={(_, d) => setIncognito(!!d.checked)}
|
onChange={(_, d) => setIncognito(!!d.checked)}
|
||||||
label="Incognito mode (no logging, no history, no cookies)"
|
label="Incognito mode (no logging, no history, no cookies)"
|
||||||
/>
|
/>
|
||||||
<div style={{ marginTop: '12px' }}>
|
|
||||||
<DownloadOptionsForm
|
<DownloadOptionsForm
|
||||||
value={downloadOptions}
|
value={downloadOptions}
|
||||||
onChange={setDownloadOptions}
|
onChange={(next) => {
|
||||||
|
setDownloadOptions(next)
|
||||||
|
setCommandPreview(null)
|
||||||
|
}}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -916,7 +941,10 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
size="large"
|
size="large"
|
||||||
value={selectedFormat?.id ?? ''}
|
value={selectedFormat?.id ?? ''}
|
||||||
options={info.formats.map((f) => ({ value: f.id, label: f.label }))}
|
options={info.formats.map((f) => ({ value: f.id, label: f.label }))}
|
||||||
onChange={setFormatId}
|
onChange={(v) => {
|
||||||
|
setFormatId(v)
|
||||||
|
setCommandPreview(null)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Select
|
<Select
|
||||||
@@ -925,7 +953,10 @@ export function DownloadBar(): React.JSX.Element {
|
|||||||
size="large"
|
size="large"
|
||||||
value={quality}
|
value={quality}
|
||||||
options={QUALITY_OPTIONS[kind].map((q) => ({ value: q, label: q }))}
|
options={QUALITY_OPTIONS[kind].map((q) => ({ value: q, label: q }))}
|
||||||
onChange={setQuality}
|
onChange={(v) => {
|
||||||
|
setQuality(v)
|
||||||
|
setCommandPreview(null)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user