Initial commit: AeroFetch — yt-dlp/YouTube downloader for Windows

Electron + React (Fluent UI) desktop frontend for yt-dlp:
- Download queue with live progress, concurrency cap, cancel/retry
- Format/quality picker via yt-dlp probe; audio extraction to MP3
- Settings + history persistence (electron-store / JSON)
- Clipboard link auto-detect; persisted light/dark theme
- NSIS + portable packaging (binaries bundled at build time, not in git)

UI: "Studio" sidebar layout with a toffee-brown theme (light + neutral dark).
Dropdowns use a native <select> and tooltips a CSS-only Hint, to avoid a
dev-machine GPU overlay flicker/blank issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 18:17:41 -04:00
commit 1a2270c95e
39 changed files with 11351 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
import { makeStyles, mergeClasses, tokens, shorthands } from '@fluentui/react-components'
// A native <select> styled to match Fluent inputs. We use this instead of
// Fluent's <Dropdown> because Fluent's portal/popover menu is a composited
// overlay in the WebContents, and on this dev machine's GPU/driver that overlay
// paint blanks the whole window (same family as the documented flicker —
// hardware acceleration is already disabled; see memory "aerofetch-gpu-flicker").
// The native <select> popup is drawn by the OS, so it can't trigger the blank.
// The popup's light/dark styling follows the `color-scheme` set on the app root
// in App.tsx.
const useStyles = makeStyles({
select: {
width: '100%',
height: '32px',
padding: '0 8px',
fontSize: tokens.fontSizeBase300,
fontFamily: tokens.fontFamilyBase,
color: tokens.colorNeutralForeground1,
backgroundColor: tokens.colorNeutralBackground1,
border: `1px solid ${tokens.colorNeutralStroke1}`,
...shorthands.borderRadius(tokens.borderRadiusMedium),
cursor: 'pointer',
':hover': {
borderColor: tokens.colorNeutralStroke1Hover
},
':focus-visible': {
outline: 'none',
borderColor: tokens.colorCompoundBrandStroke
}
}
})
export interface SelectOption {
value: string
label: string
}
interface SelectProps {
value: string
options: SelectOption[]
onChange: (value: string) => void
className?: string
'aria-label'?: string
}
export function Select({
value,
options,
onChange,
className,
'aria-label': ariaLabel
}: SelectProps): React.JSX.Element {
const styles = useStyles()
return (
<select
className={mergeClasses(styles.select, className)}
value={value}
onChange={(e) => onChange(e.target.value)}
aria-label={ariaLabel}
>
{options.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
)
}