From cfa76314d773ae8c3a9cbc6a7da12885715556ad Mon Sep 17 00:00:00 2001 From: debont80 Date: Tue, 30 Jun 2026 20:41:03 -0400 Subject: [PATCH] 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 --- src/renderer/src/components/DownloadBar.tsx | 128 +++++++++++++++++++- 1 file changed, 125 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/components/DownloadBar.tsx b/src/renderer/src/components/DownloadBar.tsx index c77b725..a98a070 100644 --- a/src/renderer/src/components/DownloadBar.tsx +++ b/src/renderer/src/components/DownloadBar.tsx @@ -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(null) const confirmDup = useRef(false) + // Per-download options (M5/M6/UX1): advanced features. + const [showAdvanced, setShowAdvanced] = useState(false) + const [downloadOptions, setDownloadOptions] = useState(DEFAULT_DOWNLOAD_OPTIONS) + const [incognito, setIncognito] = useState(false) + const [commandPreview, setCommandPreview] = useState(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 { + 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 { )} + {!playlist && ( +
+
+ + +
+ {commandPreview && ( +
+ {commandPreview.ok ? ( + <> +
{commandPreview.command}
+ + ) : ( +
+ Error: {commandPreview.error} +
+ )} +
+ )} + {showAdvanced && ( +
+ setIncognito(!!d.checked)} + label="Incognito mode (no logging, no history, no cookies)" + /> +
+ +
+
+ )} +
+ )} +
Format