diff --git a/apps/desktop/index.html b/apps/desktop/index.html index ff93803..c2c2a84 100644 --- a/apps/desktop/index.html +++ b/apps/desktop/index.html @@ -4,7 +4,10 @@ - Tauri + React + Typescript + Firelink Download Manager + + + diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 047239e..dee3551 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -4402,7 +4402,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "window-vibrancy", + "window-vibrancy 0.6.0", "windows 0.61.3", ] @@ -4440,6 +4440,7 @@ dependencies = [ "tokio", "tokio-tungstenite", "tower-http", + "window-vibrancy 0.7.1", ] [[package]] @@ -5683,6 +5684,21 @@ dependencies = [ "windows-version", ] +[[package]] +name = "window-vibrancy" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010797bd7c40396fbc59d3105089fed0885fe267a0ef4a0a4646df54e28647f6" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.60.2", + "windows-version", +] + [[package]] name = "windows" version = "0.61.3" diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 58513fa..890dabd 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = ["tray-icon", "image-png"] } +tauri = { version = "2", features = ["macos-private-api", "tray-icon", "image-png"] } tauri-plugin-opener = "2" tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } @@ -47,3 +47,4 @@ rusqlite = { version = "0.40.1", features = ["bundled"] } chrono = "0.4.38" cocoa = "0.25" objc = "0.2.7" +window-vibrancy = "0.7.1" diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 4c2f903..721f0b1 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -714,7 +714,7 @@ pub(crate) async fn start_media_download_internal( let spd_re = Regex::new(r"at\s+([^\s]+)").unwrap(); let eta_re = Regex::new(r"ETA\s+([^\s]+)").unwrap(); - tokio::spawn(async move { + tauri::async_runtime::spawn(async move { let _keep_alive = config_path; // Keep the temp file alive let mut reader = BufReader::new(stdout).lines(); let mut current_track: f64 = 0.0; @@ -1207,6 +1207,16 @@ pub fn run() { crate::scheduler::spawn_scheduler(app.handle().clone()); + #[cfg(target_os = "macos")] + { + use tauri::Manager; + use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial}; + if let Some(window) = app.get_webview_window("main") { + apply_vibrancy(&window, NSVisualEffectMaterial::Sidebar, None, None) + .expect("Unsupported platform! 'apply_vibrancy' is only supported on macOS"); + } + } + let resource_dir = app.path().resource_dir().unwrap(); let aria2c_path = resource_dir.join("binaries").join("aria2c"); @@ -1233,7 +1243,7 @@ pub fn run() { let app_handle_clone = app.handle().clone(); let aria2_port_clone = aria2_port; let aria2_secret_clone = aria2_secret.clone(); - tokio::spawn(async move { + tauri::async_runtime::spawn(async move { tokio::time::sleep(std::time::Duration::from_millis(500)).await; let ws_url = format!("ws://127.0.0.1:{}/jsonrpc", aria2_port_clone); diff --git a/apps/desktop/src-tauri/src/scheduler.rs b/apps/desktop/src-tauri/src/scheduler.rs index 5cb9e38..a9397b1 100644 --- a/apps/desktop/src-tauri/src/scheduler.rs +++ b/apps/desktop/src-tauri/src/scheduler.rs @@ -20,7 +20,7 @@ struct SchedulerSettings { } pub fn spawn_scheduler(app_handle: tauri::AppHandle) { - tokio::spawn(async move { + tauri::async_runtime::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(10)); loop { interval.tick().await; diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index b97ac9c..af231ad 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -18,12 +18,14 @@ "minWidth": 800, "minHeight": 600, "titleBarStyle": "Overlay", - "hiddenTitle": true + "hiddenTitle": true, + "transparent": true } ], "security": { "csp": null - } + }, + "macOSPrivateApi": true }, "bundle": { "active": true, diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 11cd835..b1e93d2 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -13,12 +13,7 @@ import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"; import SchedulerView from "./components/SchedulerView"; import SpeedLimiterView from "./components/SpeedLimiterView"; -const localDateKey = (date: Date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; -}; + const handleDeepLinks = (deepLinks: string[]) => { for (const rawDeepLink of deepLinks) { @@ -239,21 +234,24 @@ function App() { }, []); return ( -
- - {/* Left Side Panel - Curved Second Layer on Top */} +
-
- { setFilter(f); useSettingsStore.getState().setActiveView('downloads'); }} /> +
+ { + setFilter(f); + useSettingsStore.getState().setActiveView('downloads'); + }} + />
- - {/* Main Content - Base Layer */} -
+ +
{activeView === 'downloads' && } {activeView === 'settings' && } @@ -264,7 +262,7 @@ function App() { {/* Status Bar */}
- + Ready
diff --git a/apps/desktop/src/components/DownloadTable.tsx b/apps/desktop/src/components/DownloadTable.tsx index ed47234..93aea3d 100644 --- a/apps/desktop/src/components/DownloadTable.tsx +++ b/apps/desktop/src/components/DownloadTable.tsx @@ -5,7 +5,6 @@ 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 { WindowDragRegion } from './WindowDragRegion'; interface DownloadTableProps { filter: SidebarFilter; @@ -13,7 +12,7 @@ interface DownloadTableProps { export const DownloadTable: React.FC = ({ filter }) => { const { downloads, toggleAddModal, updateDownload, removeDownload, clearFinished, redownload } = useDownloadStore(); - const { isSidebarVisible, toggleSidebar, listRowDensity } = useSettingsStore(); + const { isSidebarVisible, toggleSidebar } = useSettingsStore(); const [contextMenu, setContextMenu] = useState<{ x: number; y: number; id: string } | null>(null); @@ -87,11 +86,7 @@ export const DownloadTable: React.FC = ({ filter }) => { }; const contextItem = contextMenu ? downloads.find(d => d.id === contextMenu.id) : null; - const rowPadding = { - compact: 'py-2', - standard: 'py-2.5', - relaxed: 'py-3.5' - }[listRowDensity]; + const getCategoryIcon = (category: string) => { switch(category) { @@ -107,174 +102,181 @@ export const DownloadTable: React.FC = ({ filter }) => { } return ( -
-
- - - {/* Download Toolbar */} -
+
+
+ {!isSidebarVisible && ( -
-

{getFilterTitle()}

-

- {filteredDownloads.length} {filteredDownloads.length === 1 ? 'item' : 'items'} -

-
+ )} +
Firelink
-
- -
- - -
- -
+
+ + + + + + +
- {/* List */} -
-
-
-
FILE
-
SIZE
-
STATUS
-
SPEED
-
ETA
-
DATE ADDED
-
-
- {filteredDownloads.length === 0 ? ( -
-
-
- -
-
-

No downloads here

-

Add a link to start a new transfer.

-
- +
+
+ {getFilterTitle()} + {filteredDownloads.length} +
+
+ +
+
+
File Name
+
Size
+
Status
+
Speed
+
ETA
+
Date Added
+
+ +
+ {filteredDownloads.length === 0 ? ( +
+
+
+ + + No downloads yet · Use + to add a link +
+
+
Idle
+
+
+
- ) : ( - filteredDownloads.map(d => ( + + {Array.from({ length: 12 }).map((_, index) => ( +
+ ))} +
+ ) : ( +
+ {filteredDownloads.map(d => (
{ e.preventDefault(); - setContextMenu({ - x: e.clientX, - y: e.clientY, - id: d.id - }); + setContextMenu({ x: e.clientX, y: e.clientY, id: d.id }); }} > -
-
-
- {getCategoryIcon(d.category)} -
- {d.fileName} -
-
-
- {d.status === 'downloading' || d.status === 'paused' ? ( -
-
-
-
-
- {((d.fraction || 0) * 100).toFixed(1)}% -
-
- ) : ( - {d.size || '-'} - )} -
-
- - {d.status} +
+ + {getCategoryIcon(d.category)} + + + {d.fileName}
-
{d.speed}
-
{d.eta}
-
-
- - {d.dateAdded ? new Date(d.dateAdded).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) : '-'} - -
- {d.status === 'downloading' && ( - - )} - {d.status === 'paused' && ( - - )} - + +
+ + {d.status === 'downloading' || d.status === 'paused' + ? `${((d.fraction || 0) * parseInt((d.size || '').replace(/[^0-9.]/g, '') || '0')).toFixed(1)} GB / ${d.size || '-'}` + : d.size || '-'} + +
+ +
+
+
+ + {d.status === 'downloading' || d.status === 'paused' ? `${((d.fraction || 0) * 100).toFixed(0)}%` : d.status.charAt(0).toUpperCase() + d.status.slice(1)} +
+ {(d.status === 'downloading' || d.status === 'paused') && ( +
+
+
+ )} +
+
+ +
+ {d.status === 'downloading' ? d.speed : '-'} +
+ +
+ {d.status === 'downloading' ? d.eta : '-'} +
+ +
+ + {d.dateAdded ? new Date(d.dateAdded).toLocaleDateString() : '-'} + + +
+ {d.status === 'downloading' && ( + + )} + {d.status === 'paused' && ( + + )} +
- )) - )} -
+ ))} + {Array.from({ length: Math.max(0, 10 - filteredDownloads.length) }).map((_, index) => ( +
+ ))} +
+ )}
diff --git a/apps/desktop/src/components/SettingsView.tsx b/apps/desktop/src/components/SettingsView.tsx index 4af75f9..cd3d65f 100644 --- a/apps/desktop/src/components/SettingsView.tsx +++ b/apps/desktop/src/components/SettingsView.tsx @@ -236,89 +236,69 @@ export default function SettingsView() { {/* Downloads Pane */} {activeTab === 'downloads' && ( -
-

Download Options

- -
- -
- settings.setMaxConcurrentDownloads(Number(e.target.value))} - className="flex-1 accent-accent" - /> - - {settings.maxConcurrentDownloads} - +
+
+
+ Parallel downloads +
+ {settings.maxConcurrentDownloads} + settings.setMaxConcurrentDownloads(Number(e.target.value))} + className="accent-accent w-24" + /> +
-
- -
- -
+
+ Default connections 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-accent" + className="app-control w-16 text-center" /> - For new downloads (1 to 16)
-
- -
- -
+
+ Global speed limit 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-accent" + placeholder="0" + className="app-control w-24 text-center font-mono" /> - e.g. 500K, 1M, or 0 for unlimited
-
- -
- -
+
+ Automatic retries 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-accent" + className="app-control w-16 text-center" /> - If a connection fails (0 to 10)
-
-
@@ -326,89 +306,69 @@ export default function SettingsView() { {/* Look & Feel Pane */} {activeTab === 'lookandfeel' && ( -
-

App Theme

- -
- -
- {[ - { value: 'system', label: 'System Default' }, - { value: 'light', label: 'Light' }, - { value: 'dark', label: 'Dark' }, - { value: 'dracula', label: 'Dracula' }, - { value: 'nord', label: 'Nord' }, - ].map(({ value, label }) => ( - - ))} -

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

+
+
+
+ App Theme +
-

Display

- -
- - +
+
+ Font Size + +
+
+ List Row Density + +
-
- - -
- -

macOS Integration

- -
-
@@ -416,77 +376,55 @@ export default function SettingsView() { {/* Network Pane */} {activeTab === 'network' && ( -
-

Proxy & User Agent

- -
- -
- - - +
+
+
+ Proxy Mode +
+ {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 === 'custom' && ( -
-
- - settings.setProxyHost(e.target.value)} - placeholder="127.0.0.1" - className="bg-bg-input border border-border-modal rounded-md px-3 py-1 text-text-primary font-mono text-xs focus:outline-none" - /> -
-
- - settings.setProxyPort(Number(e.target.value))} - className="bg-bg-input border border-border-modal rounded-md px-3 py-1 text-text-primary font-mono text-xs w-[100px] focus:outline-none" - /> -
-
- )} - -
- -
+
+
+ Custom User Agent 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-accent" + className="app-control flex-1 ml-4 font-mono text-[11px]" /> -

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

@@ -494,69 +432,63 @@ export default function SettingsView() { {/* Locations Pane */} {activeTab === 'locations' && ( -
-

Download Directories

+
+
+ +
- - -
-

Default Categories Paths

- - {/* Bulk Directory Selector */} -
- +
+
+ All Categories Base
{['Musics', 'Movies', 'Compressed', 'Documents', 'Pictures', 'Applications', 'Other'].map((category) => ( -
- +
+ {category}
settings.setCategoryDirectory(category, e.target.value)} - className="flex-1 bg-bg-input border border-border-modal rounded-md px-3 py-1 text-xs text-text-primary font-mono" + className="app-control w-48 text-[11px]" />
))} - -
+ +
diff --git a/apps/desktop/src/components/Sidebar.tsx b/apps/desktop/src/components/Sidebar.tsx index c165409..fff871e 100644 --- a/apps/desktop/src/components/Sidebar.tsx +++ b/apps/desktop/src/components/Sidebar.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Inbox, Zap, CheckCircle2, CircleDashed, Film, Music, FileText, Box, Image as ImageIcon, Archive, FileQuestion, - List, CalendarClock, Gauge, Settings, Plus, Play, Pause, Edit2, Trash2 + List, CalendarClock, Gauge, Settings, Plus, Play, Pause, Edit2, Trash2, PanelLeft } from 'lucide-react'; import { useDownloadStore, DownloadCategory, Queue } from '../store/useDownloadStore'; import { ActiveView, useSettingsStore } from '../store/useSettingsStore'; @@ -18,7 +18,7 @@ interface SidebarProps { export const Sidebar: React.FC = (props) => { const { selectedFilter, onSelectFilter } = props; const { downloads, queues, addQueue, renameQueue, removeQueue, startQueue, pauseQueue } = useDownloadStore(); - const { activeView, setActiveView } = useSettingsStore(); + const { activeView, setActiveView, toggleSidebar } = useSettingsStore(); const [isAddingQueue, setIsAddingQueue] = useState(false); const [newQueueName, setNewQueueName] = useState(''); @@ -64,19 +64,13 @@ export const Sidebar: React.FC = (props) => { ); }; return ( -