Wire M5/M6/UX1: command preview, incognito mode, per-download options (Recommended SHOULD-fix-for-1.0)

M5 (Command preview):
- Added 'Show command' button in DownloadBar that calls window.api.previewCommand()
- Displays the exact yt-dlp command line that will run for the current form state
- Helps users verify the exact behavior before download

M6 (Incognito mode):
- Added incognito checkbox in Advanced panel
- Passes through AddOptions to enable no-logging, no-history, no-cookies mode
- QueueItem already shows 'Private' badge when item.incognito is set

UX1 (Per-download options):
- Expanded Advanced panel now shows full DownloadOptionsForm
- Allows overriding audio format, video codec, container, embed-subtitles,
  SponsorBlock, format-sort for any single download (not just global settings)
- Options passed through AddOptions.options to startDownload
- Same form component used for both Settings defaults + per-download overrides

UI additions:
- Advanced toggle button (Settings icon) in DownloadBar
- Collapsible Advanced panel with incognito checkbox + DownloadOptionsForm
- Command preview panel shows the yt-dlp argv (monospace) or error message
- Both features only show for single videos (not playlists)

All plumbing was already in place (previewCommand IPC, StartDownloadOptions.options,
AddOptions.incognito, mockApi); only the renderer UI was missing.

typecheck + 243 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:41:03 -04:00
parent 7e3e5af52d
commit cfa76314d7
+125 -3
View File
@@ -25,13 +25,18 @@ import {
AppsListRegular,
CutRegular,
CalendarClockRegular,
WarningRegular
WarningRegular,
SettingsRegular,
WindowConsoleRegular
} from '@fluentui/react-icons'
import {
parseUrlShortcutContent,
type MediaInfo,
type FormatOption,
type PlaylistInfo
type PlaylistInfo,
type DownloadOptions,
DEFAULT_DOWNLOAD_OPTIONS,
type CommandPreviewResult
} from '@shared/ipc'
import { useDownloads, type MediaKind } from '../store/downloads'
import { QUALITY_OPTIONS } from '../qualityOptions'
@@ -41,6 +46,7 @@ import { Select } from './Select'
import { Hint } from './Hint'
import { SegmentedControl } from './ui/SegmentedControl'
import { IconButton } from './ui/IconButton'
import { DownloadOptionsForm } from './DownloadOptionsForm'
import { useClipboardLink, looksLikeUrl, firstUrlInText } from '../useClipboardLink'
import { logError } from '../reportError'
import { THUMB_LG } from '../thumbSizes'
@@ -246,6 +252,28 @@ const useStyles = makeStyles({
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
colorScheme: 'light dark'
},
// --- command preview & options panels ---
commandBlock: {
display: 'flex',
flexDirection: 'column',
gap: '8px'
},
commandPreviewPanel: {
padding: '12px',
backgroundColor: tokens.colorNeutralBackground2,
...shorthands.borderRadius(tokens.borderRadiusLarge),
border: `1px solid ${tokens.colorNeutralStroke2}`,
fontFamily: 'monospace',
fontSize: tokens.fontSizeBase200,
overflowX: 'auto',
color: tokens.colorNeutralForeground1
},
optionsPanel: {
padding: '12px',
backgroundColor: tokens.colorNeutralBackground2,
...shorthands.borderRadius(tokens.borderRadiusLarge),
border: `1px solid ${tokens.colorNeutralStroke2}`
}
})
@@ -285,6 +313,13 @@ export function DownloadBar(): React.JSX.Element {
const [dup, setDup] = useState<string | null>(null)
const confirmDup = useRef(false)
// Per-download options (M5/M6/UX1): advanced features.
const [showAdvanced, setShowAdvanced] = useState(false)
const [downloadOptions, setDownloadOptions] = useState<DownloadOptions>(DEFAULT_DOWNLOAD_OPTIONS)
const [incognito, setIncognito] = useState(false)
const [commandPreview, setCommandPreview] = useState<CommandPreviewResult | null>(null)
const [previewLoading, setPreviewLoading] = useState(false)
function onDragOver(e: React.DragEvent): void {
e.preventDefault()
if (!dragActive) setDragActive(true)
@@ -368,11 +403,35 @@ export function DownloadBar(): React.JSX.Element {
setItemKinds({})
}
async function loadCommandPreview(): Promise<void> {
const trimmed = url.trim()
if (!trimmed) {
setCommandPreview(null)
return
}
setPreviewLoading(true)
try {
const result = await window.api.previewCommand({
id: 'preview',
url: trimmed,
kind,
quality,
options: downloadOptions
})
setCommandPreview(result)
} catch (e) {
setCommandPreview({ ok: false, error: (e as Error).message })
} finally {
setPreviewLoading(false)
}
}
function onUrlChange(next: string): void {
setUrl(next)
// 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)
confirmDup.current = false
}
@@ -489,6 +548,8 @@ export function DownloadBar(): React.JSX.Element {
...meta,
trim: trimSpec,
scheduledFor,
options: downloadOptions,
incognito,
format: {
id: selectedFormat.id,
hasAudio: selectedFormat.hasAudio,
@@ -496,7 +557,13 @@ export function DownloadBar(): React.JSX.Element {
}
})
} else {
addFromUrl(trimmed, kind, quality, { ...meta, trim: trimSpec, scheduledFor })
addFromUrl(trimmed, kind, quality, {
...meta,
trim: trimSpec,
scheduledFor,
options: downloadOptions,
incognito
})
}
setUrl('')
@@ -504,6 +571,8 @@ export function DownloadBar(): React.JSX.Element {
setShowTrim(false)
setScheduleAt('')
setShowSchedule(false)
setShowAdvanced(false)
setCommandPreview(null)
clearProbe()
}
@@ -771,6 +840,59 @@ export function DownloadBar(): React.JSX.Element {
</div>
)}
{!playlist && (
<div className={styles.commandBlock}>
<div className={styles.optButtons}>
<Button
size="small"
appearance="subtle"
icon={<SettingsRegular />}
onClick={() => setShowAdvanced((v) => !v)}
>
{showAdvanced ? 'Advanced · on' : 'Advanced'}
</Button>
<Button
size="small"
appearance="subtle"
icon={previewLoading ? <Spinner size="tiny" /> : <WindowConsoleRegular />}
onClick={loadCommandPreview}
disabled={!url.trim() || previewLoading}
>
{commandPreview ? 'Command · shown' : 'Show command'}
</Button>
</div>
{commandPreview && (
<div className={styles.commandPreviewPanel}>
{commandPreview.ok ? (
<>
<div>{commandPreview.command}</div>
</>
) : (
<div style={{ color: tokens.colorStatusDangerForeground1 }}>
Error: {commandPreview.error}
</div>
)}
</div>
)}
{showAdvanced && (
<div className={styles.optionsPanel}>
<Checkbox
checked={incognito}
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>
</div>
)}
</div>
)}
<div className={styles.controls}>
<div className={styles.control}>
<Caption1>Format</Caption1>