From 4c21a360834981bc023e364b890215412f7c062b Mon Sep 17 00:00:00 2001 From: NimBold Date: Fri, 12 Jun 2026 20:59:49 +0330 Subject: [PATCH] feat(desktop): align UI with SwiftUI --- apps/desktop/src/App.tsx | 7 + apps/desktop/src/components/DownloadTable.tsx | 100 +++++----- apps/desktop/src/components/SettingsView.tsx | 187 ++++++++++-------- apps/desktop/src/components/Sidebar.tsx | 121 ++++++------ .../src/components/WindowDragRegion.tsx | 19 ++ apps/desktop/src/index.css | 98 +++++---- apps/desktop/src/store/useDownloadStore.ts | 2 +- apps/desktop/src/store/useSettingsStore.ts | 29 ++- 8 files changed, 329 insertions(+), 234 deletions(-) create mode 100644 apps/desktop/src/components/WindowDragRegion.tsx diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 77add28..c2de485 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -8,6 +8,7 @@ import { listen } from "@tauri-apps/api/event"; import { useDownloadStore } from "./store/useDownloadStore"; import { useSettingsStore } from "./store/useSettingsStore"; import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification'; +import { invoke } from "@tauri-apps/api/core"; function App() { const [filter, setFilter] = useState('all'); @@ -16,11 +17,17 @@ function App() { const isSidebarVisible = useSettingsStore(state => state.isSidebarVisible); const activeView = useSettingsStore(state => state.activeView); const appFontSize = useSettingsStore(state => state.appFontSize); + const showDockBadge = useSettingsStore(state => state.showDockBadge); + const activeDownloadCount = useDownloadStore(state => state.downloads.filter(download => download.status === 'downloading').length); useEffect(() => { window.document.documentElement.setAttribute('data-font-size', appFontSize); }, [appFontSize]); + useEffect(() => { + invoke('update_dock_badge', { count: showDockBadge ? activeDownloadCount : 0 }).catch(() => {}); + }, [showDockBadge, activeDownloadCount]); + useEffect(() => { // Request notification permissions const initNotifications = async () => { diff --git a/apps/desktop/src/components/DownloadTable.tsx b/apps/desktop/src/components/DownloadTable.tsx index 8e1d31a..78538c1 100644 --- a/apps/desktop/src/components/DownloadTable.tsx +++ b/apps/desktop/src/components/DownloadTable.tsx @@ -5,7 +5,7 @@ import { SidebarFilter } from './Sidebar'; import { Play, Pause, Plus, Trash2, FileText, Image as ImageIcon, Music, Film, Box, Archive, FileQuestion, MoreVertical, PanelLeft } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; import { homeDir } from '@tauri-apps/api/path'; -import { getCurrentWindow } from '@tauri-apps/api/window'; +import { WindowDragRegion } from './WindowDragRegion'; interface DownloadTableProps { filter: SidebarFilter; @@ -18,7 +18,7 @@ export const DownloadTable: React.FC = ({ filter }) => { const getPaddingY = () => { switch (listRowDensity) { case 'compact': return 'py-1'; - case 'spacious': return 'py-4'; + case 'relaxed': return 'py-4'; default: return 'py-3'; } }; @@ -103,58 +103,54 @@ export const DownloadTable: React.FC = ({ filter }) => { return (
+
+ - {/* Modern Toolbar */} -
{ - if (e.button === 0 && (e.target as HTMLElement).closest('.no-drag') === null) { - getCurrentWindow().startDragging(); - } - }} - > - -

{getFilterTitle()}

+ {/* Download Toolbar */} +
+ +

{getFilterTitle()}

-
- - - - +
+ + + + +
diff --git a/apps/desktop/src/components/SettingsView.tsx b/apps/desktop/src/components/SettingsView.tsx index 35cf363..7167f5b 100644 --- a/apps/desktop/src/components/SettingsView.tsx +++ b/apps/desktop/src/components/SettingsView.tsx @@ -1,17 +1,28 @@ import { useState, useEffect } from 'react'; -import { useSettingsStore } from '../store/useSettingsStore'; +import { SettingsTab, useSettingsStore } from '../store/useSettingsStore'; import { Download, Palette, Globe, Folder, Key, Moon, Terminal, Puzzle, Info, Plus, Trash2, Copy, RefreshCw } from 'lucide-react'; import { open } from '@tauri-apps/plugin-dialog'; import { invoke } from '@tauri-apps/api/core'; +import { WindowDragRegion } from './WindowDragRegion'; -type TabType = 'downloads' | 'lookandfeel' | 'network' | 'locations' | 'sitelogins' | 'power' | 'engine' | 'integrations' | 'about'; +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 }, +]; export default function SettingsView() { const settings = useSettingsStore(); - const [activeTab, setActiveTab] = useState('downloads'); + const activeTab = settings.activeSettingsTab; // Local state for versions const [aria2Version, setAria2Version] = useState('Checking...'); @@ -118,57 +129,55 @@ export default function SettingsView() { showToast("Token copied to clipboard!"); }; - const TabButton = ({ type, icon: Icon, label }: { type: TabType; icon: any; label: string }) => { + 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}
)} - {/* Header (Horizontal Tab Bar) */} -
-
-

Preferences

-
-
- - - - - - - - - + {/* SwiftUI SettingsPaneContainer-style horizontal tab strip */} +
+
+ {settingsTabs.map(tab => ( + + ))}
{/* Content Area */} -
+
+
+

{activeTabLabel}

+
{/* Downloads Pane */} {activeTab === 'downloads' && ( -
+

Download Options

@@ -178,7 +187,7 @@ export default function SettingsView() { type="range" min="1" max="12" value={settings.maxConcurrentDownloads} onChange={(e) => settings.setMaxConcurrentDownloads(Number(e.target.value))} - className="flex-1 accent-blue-500" + className="flex-1 accent-accent" /> {settings.maxConcurrentDownloads} @@ -193,7 +202,7 @@ export default function SettingsView() { type="number" min="1" max="16" value={settings.perServerConnections} onChange={(e) => settings.setPerServerConnections(Number(e.target.value))} - className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-24 text-text-primary focus:outline-none focus:border-blue-500" + className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-24 text-text-primary focus:outline-none focus:border-accent" /> For new downloads (1 to 16)
@@ -207,7 +216,7 @@ export default function SettingsView() { value={settings.globalSpeedLimit} onChange={(e) => settings.setGlobalSpeedLimit(e.target.value)} placeholder="Unlimited" - className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-32 font-mono text-text-primary focus:outline-none focus:border-blue-500" + className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-32 font-mono text-text-primary focus:outline-none focus:border-accent" /> e.g. 500K, 1M, or 0 for unlimited
@@ -220,7 +229,7 @@ export default function SettingsView() { type="number" min="0" max="10" value={settings.maxAutomaticRetries} onChange={(e) => settings.setMaxAutomaticRetries(Number(e.target.value))} - className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-24 text-text-primary focus:outline-none focus:border-blue-500" + className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-24 text-text-primary focus:outline-none focus:border-accent" /> If a connection fails (0 to 10)
@@ -232,7 +241,7 @@ export default function SettingsView() { type="checkbox" checked={settings.showNotifications} onChange={(e) => settings.setShowNotifications(e.target.checked)} - className="mt-0.5 rounded accent-blue-500" + className="mt-0.5 rounded accent-accent" />

Show notification when download completes

@@ -246,7 +255,7 @@ export default function SettingsView() { checked={settings.playCompletionSound && settings.showNotifications} disabled={!settings.showNotifications} onChange={(e) => settings.setPlayCompletionSound(e.target.checked)} - className="mt-0.5 rounded accent-blue-500 disabled:opacity-40" + className="mt-0.5 rounded accent-accent disabled:opacity-40" />

Play sound when download completes

@@ -258,39 +267,47 @@ export default function SettingsView() { {/* Look & Feel Pane */} {activeTab === 'lookandfeel' && ( -
-

Appearance Settings

+
+

App Theme

- +
- {['system', 'dark', 'light', 'dracula', 'nord'].map((t) => ( -
+

Display

+
@@ -299,33 +316,48 @@ export default function SettingsView() {
-
+

macOS Integration

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

Proxy & User Agent

@@ -336,7 +368,7 @@ export default function SettingsView() { type="radio" name="proxyMode" value="none" checked={settings.proxyMode === 'none'} onChange={() => settings.setProxyMode('none')} - className="accent-blue-500" + className="accent-accent" /> No proxy @@ -345,7 +377,7 @@ export default function SettingsView() { type="radio" name="proxyMode" value="system" checked={settings.proxyMode === 'system'} onChange={() => settings.setProxyMode('system')} - className="accent-blue-500" + className="accent-accent" /> Use system proxy @@ -354,7 +386,7 @@ export default function SettingsView() { type="radio" name="proxyMode" value="custom" checked={settings.proxyMode === 'custom'} onChange={() => settings.setProxyMode('custom')} - className="accent-blue-500" + className="accent-accent" /> Set proxy @@ -393,7 +425,7 @@ export default function SettingsView() { value={settings.customUserAgent} onChange={(e) => settings.setCustomUserAgent(e.target.value)} placeholder="e.g. Mozilla/5.0..." - className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-full font-mono text-[11px] text-text-primary focus:outline-none focus:border-blue-500" + className="bg-bg-input border border-border-modal rounded-md px-3 py-1.5 w-full font-mono text-[11px] text-text-primary focus:outline-none focus:border-accent" />

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

@@ -403,7 +435,7 @@ export default function SettingsView() { {/* Locations Pane */} {activeTab === 'locations' && ( -
+

Download Directories