import { useState, useEffect } from 'react'; import { SettingsTab, useSettingsStore } from '../store/useSettingsStore'; import { Download, Palette, Globe, Folder, Key, Moon, Terminal, Puzzle, Info, Plus, Trash2, Copy, RefreshCw, Code } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; import { invoke } from '@tauri-apps/api/core'; import { WindowDragRegion } from './WindowDragRegion'; import appIcon from '../assets/app-icon.png'; const settingsTabs: { type: SettingsTab; label: string; icon: typeof Download }[] = [ { type: 'downloads', label: 'Downloads', icon: Download }, { type: 'lookandfeel', label: 'Look and feel', icon: Palette }, { type: 'network', label: 'Network', icon: Globe }, { type: 'locations', label: 'Locations', icon: Folder }, { type: 'sitelogins', label: 'Site Logins', icon: Key }, { type: 'power', label: 'Power', icon: Moon }, { type: 'engine', label: 'Engine', icon: Terminal }, { type: 'integrations', label: 'Integrations', icon: Puzzle }, { type: 'about', label: 'About', icon: Info }, ]; interface AvailableReleaseUpdate { version: string; tag_name: string; title: string; release_notes: string; release_url: string; published_at: string | null; } type ReleaseCheckOutcome = | { type: 'UpdateAvailable'; update: AvailableReleaseUpdate } | { type: 'UpToDate'; latest_version: string; local_version: string }; export default function SettingsView() { const settings = useSettingsStore(); const activeTab = settings.activeSettingsTab; // Local state for versions const [aria2Version, setAria2Version] = useState('Checking...'); const [ytdlpVersion, setYtdlpVersion] = useState('Checking...'); const [ffmpegVersion, setFfmpegVersion] = useState('Checking...'); const [denoVersion, setDenoVersion] = useState('Checking...'); const getEngineStatus = (v: string) => { if (v === 'Checking...') return Checking...; if (v.startsWith('Error')) return Error / Missing; return Ready; }; // Local state for adding site login const [loginPattern, setLoginPattern] = useState(''); const [loginUser, setLoginUser] = useState(''); const [loginPass, setLoginPass] = useState(''); const [loginError, setLoginError] = useState(''); // Toast notifications const [toastMessage, setToastMessage] = useState(''); const [isCheckingForUpdates, setIsCheckingForUpdates] = useState(false); useEffect(() => { if (toastMessage) { const t = setTimeout(() => setToastMessage(''), 2000); return () => clearTimeout(t); } }, [toastMessage]); // Fetch engine versions when Engine tab is opened useEffect(() => { if (settings.activeView === 'settings' && activeTab === 'engine') { invoke('test_aria2c') .then(v => setAria2Version(v)) .catch(e => setAria2Version('Error: ' + e)); invoke('test_ytdlp') .then(v => setYtdlpVersion(v)) .catch(e => setYtdlpVersion('Error: ' + e)); invoke('test_ffmpeg') .then(v => setFfmpegVersion(v)) .catch(e => setFfmpegVersion('Error: ' + e)); invoke('test_deno') .then(v => setDenoVersion(v)) .catch(e => setDenoVersion('Error: ' + e)); } }, [settings.activeView, activeTab]); const showToast = (msg: string) => { setToastMessage(msg); }; const handleCheckForUpdates = async () => { if (isCheckingForUpdates) return; setIsCheckingForUpdates(true); showToast('Checking for updates...'); try { const result = await invoke('check_for_updates'); if (result.type === 'UpToDate') { showToast(`Firelink ${result.latest_version} is up to date`); } else if (result.type === 'UpdateAvailable') { showToast(`Firelink ${result.update.version} is available`); } else { showToast('The update check returned an unexpected response'); } } catch (error) { showToast(`Update check failed: ${String(error)}`); } finally { setIsCheckingForUpdates(false); } }; const handleBrowseCategory = async (category: string) => { const currentPath = (settings.downloadDirectories || {})[category] || ''; try { const selected = await open({ directory: true, multiple: false, defaultPath: currentPath.startsWith('~') ? undefined : currentPath }); if (selected && typeof selected === 'string') { settings.setCategoryDirectory(category, selected); } } catch (e) { console.error(`Failed to select folder for ${category}:`, e); } }; const handleBrowseBulk = async () => { try { const base = await open({ directory: true, multiple: false }); if (base && typeof base === 'string') { const cleanBase = base.replace(/\/$/, ''); settings.setCategoryDirectory('Musics', `${cleanBase}/Musics`); settings.setCategoryDirectory('Movies', `${cleanBase}/Movies`); settings.setCategoryDirectory('Compressed', `${cleanBase}/Compressed`); settings.setCategoryDirectory('Documents', `${cleanBase}/Documents`); settings.setCategoryDirectory('Pictures', `${cleanBase}/Pictures`); settings.setCategoryDirectory('Applications', `${cleanBase}/Applications`); settings.setCategoryDirectory('Other', `${cleanBase}/Other`); showToast("Updated all categories to use base folder"); } } catch (e) { console.error("Failed to browse base path:", e); } }; const handleAddLogin = async () => { if (!loginPattern.trim() || !loginUser.trim()) { setLoginError("Please enter a URL pattern and a username."); return; } const id = crypto.randomUUID(); if (loginPass) { try { await invoke('set_keychain_password', { id, password: loginPass }); } catch (e) { console.error("Failed to save password to keychain:", e); setLoginError("Failed to save password securely."); return; } } settings.addSiteLogin({ id, urlPattern: loginPattern.trim(), username: loginUser.trim() }); setLoginPattern(''); setLoginUser(''); setLoginPass(''); setLoginError(''); showToast("Added site credential"); }; const copyToken = () => { navigator.clipboard.writeText(settings.extensionPairingToken); showToast("Token copied to clipboard!"); }; const activeTabLabel = settingsTabs.find(tab => tab.type === activeTab)?.label ?? 'Downloads'; const TabButton = ({ type, icon: Icon, label }: { type: SettingsTab; icon: typeof Download; label: string }) => { const active = activeTab === type; return ( ); }; return (
{/* Toast Notification */} {toastMessage && (
{toastMessage}
)} {/* SwiftUI SettingsPaneContainer-style horizontal tab strip */}
{settingsTabs.map(tab => ( ))}
{/* Content Area */}

{activeTabLabel}

{/* Downloads Pane */} {activeTab === 'downloads' && (
Default connections: For new downloads
settings.setPerServerConnections(Number(e.target.value))} className="app-control w-24 text-center" />
Parallel downloads: Max simultaneous active files
settings.setMaxConcurrentDownloads(Number(e.target.value))} className="app-control w-24 text-center" />
Global speed limit: 0 = unlimited speed
settings.setGlobalSpeedLimit(e.target.value)} placeholder="0" className="app-control w-24 text-center font-mono pr-9" /> KiB/s
Automatic retries: If a connection fails
settings.setMaxAutomaticRetries(Number(e.target.value))} className="app-control w-24 text-center" />
)} {/* Look & Feel Pane */} {activeTab === 'lookandfeel' && (

App Theme

Theme
{[ { value: 'system', label: 'System', colors: ['#f4f4f5', '#252525'] }, { value: 'light', label: 'Light', colors: ['#ffffff', '#e9e9ec'] }, { value: 'dark', label: 'Dark', colors: ['#1a1a1a', '#292929'] }, { value: 'dracula', label: 'Dracula', colors: ['#282a36', '#ff79c6'] }, { value: 'nord', label: 'Nord', colors: ['#2e3440', '#88c0d0'] }, ].map(({ value, label, colors }) => ( ))}

Select a color palette for the app's user interface.

Display

Font Size
List Row Density

macOS Integration

)} {/* Network Pane */} {activeTab === 'network' && (

Proxy

Mode
{[ ['none', 'No Proxy'], ['system', 'Use System Proxy'], ['custom', 'Custom Proxy'], ].map(([value, label]) => ( ))}
{settings.proxyMode === 'custom' && ( <>
Proxy Host settings.setProxyHost(e.target.value)} placeholder="127.0.0.1" className="app-control w-40 font-mono" />
Proxy Port settings.setProxyPort(Number(e.target.value))} className="app-control w-24 text-center" />
)}

{settings.proxyMode === 'none' && 'Downloads ignore configured proxies.'} {settings.proxyMode === 'system' && 'Downloads use the matching macOS system proxy when one is configured.'} {settings.proxyMode === 'custom' && (settings.proxyHost ? `Downloads use http://${settings.proxyHost}:${settings.proxyPort}.` : 'Enter a proxy host and port to enable the custom proxy.')}

Identity

Custom User Agent
settings.setCustomUserAgent(e.target.value)} placeholder="e.g. Mozilla/5.0..." className="app-control w-full font-mono text-[11px]" />

Spoofs the browser User-Agent to bypass download restrictions. Leave blank for default.

)} {/* Locations Pane */} {activeTab === 'locations' && (
All Categories Base
{['Musics', 'Movies', 'Compressed', 'Documents', 'Pictures', 'Applications', 'Other'].map((category) => (
{category}
settings.setCategoryDirectory(category, e.target.value)} className="app-control w-64 text-[11px] px-2" />
))}
)} {/* Site Logins Pane */} {activeTab === 'sitelogins' && (

Site Credentials

{/* Site Logins List */}
{(settings.siteLogins || []).length === 0 ? (

No saved logins.

) : ( (settings.siteLogins || []).map((login) => (

{login.urlPattern}

User: {login.username}

)) )}
{/* Add Site Login Form */}

Add Site Credentials

{loginError && (

{loginError}

)}
setLoginPattern(e.target.value)} placeholder="e.g. *.example.com or example.com/downloads" className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-full text-text-primary focus:outline-none" />
setLoginUser(e.target.value)} placeholder="Username" className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-full text-text-primary focus:outline-none" />
setLoginPass(e.target.value)} placeholder="Password" className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-full text-text-primary focus:outline-none" />
)} {/* Power Pane */} {activeTab === 'power' && (

Power Management

)} {/* Engine Pane */} {activeTab === 'engine' && (

Media Downloader & Engines

Core Downloader (Aria2)

Version: {aria2Version}
Status: {getEngineStatus(aria2Version)}

Media Extractors

yt-dlp: {ytdlpVersion} {getEngineStatus(ytdlpVersion)}
FFmpeg: {ffmpegVersion} {getEngineStatus(ffmpegVersion)}
Deno: {denoVersion} {getEngineStatus(denoVersion)}

yt-dlp reads browser cookies to bypass video download limits or access restricted media. Firelink does not save browser cookies.

)} {/* Integrations Pane */} {activeTab === 'integrations' && (

Connect Browser Extension

Capture downloads directly from your browser in three easy steps.

{/* Step Guide Cards */}
{/* Step 1 */}
1

Copy Token

This secure token authorizes your browser extension.

{/* Step 2 */}
2

Get Extension

Install the Firelink Companion extension on your browser.

{/* Step 3 */}
3

Paste & Connect

Click the Firelink icon in your browser's toolbar and paste the copied token.

{/* Status Info */}
Extension Server Status: ● Listening on 127.0.0.1:23522 (Active)
)} {/* About Pane */} {activeTab === 'about' && (
{/* Header Box */}
Firelink Icon

Firelink

Version 0.7.3

A native macOS download manager for fast, organized, segmented transfers.

{/* Updates Section */}

Updates

Check for Updates

Firelink checks GitHub Releases for new versions.

{/* Credits Footer */}
Created by NimBold Source Code
Powered by aria2yt-dlpffmpegDeno MIT License
Copyright © 2026 NimBold. All rights reserved.
)}
); };