From fdbacb8a7f5773d3789b7dd93ededd350181b4e4 Mon Sep 17 00:00:00 2001 From: NimBold Date: Wed, 10 Jun 2026 18:49:55 +0330 Subject: [PATCH] feat(ui): overhaul context menu and toolbar for modern hig - Flattened playback controls in context menu - Added 'Copy File Path' context action for completed downloads - Reorganized context menu to prioritize Open/Finder actions and pin Properties to bottom - Removed ControlGroup and explicit placements in toolbar for clean borderless style - Renamed Start/Stop toolbar items to Resume/Pause with proper SF Symbols - Added informative tooltips to toolbar actions --- Sources/Firelink/ContentView.swift | 39 +++++++-------- Sources/Firelink/DownloadTable.swift | 72 ++++++++++++++++------------ 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/Sources/Firelink/ContentView.swift b/Sources/Firelink/ContentView.swift index 04116ae..64415d6 100644 --- a/Sources/Firelink/ContentView.swift +++ b/Sources/Firelink/ContentView.swift @@ -117,29 +117,14 @@ struct ContentView: View { StatusBar() } .toolbar { - ToolbarItem(placement: .primaryAction) { + ToolbarItemGroup { Button { controller.pendingAddQueueID = queueID openWindow(id: "add-downloads") } label: { - Label("Add", systemImage: "plus") + Label("Add Download", systemImage: "plus") } - } - - ToolbarItemGroup { - let canStop = selectedItems.isEmpty ? hasActiveDownloads(in: queueID) : selectedItems.contains(where: { $0.status == .downloading }) - Button { - if selectedItems.isEmpty { - controller.pauseActiveDownloads(queueID: queueID) - } else { - for item in selectedItems where item.status == .downloading { - controller.pause(item) - } - } - } label: { - Label(selectedItems.isEmpty ? "Stop All" : "Stop", systemImage: "stop.fill") - } - .disabled(!canStop) + .help("Add a new download") let canStart = selectedItems.isEmpty ? hasQueuedDownloads(in: queueID) : selectedItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) Button { @@ -151,9 +136,25 @@ struct ContentView: View { } } } label: { - Label(selectedItems.isEmpty ? "Start Queue" : "Start", systemImage: "play.fill") + Label(selectedItems.isEmpty ? "Resume All" : "Resume", systemImage: "play.fill") } + .help(selectedItems.isEmpty ? "Resume all downloads" : "Resume selected downloads") .disabled(!canStart) + + let canStop = selectedItems.isEmpty ? hasActiveDownloads(in: queueID) : selectedItems.contains(where: { $0.status == .downloading || $0.status == .queued }) + Button { + if selectedItems.isEmpty { + controller.pauseActiveDownloads(queueID: queueID) + } else { + for item in selectedItems where item.status == .downloading || item.status == .queued { + controller.pause(item) + } + } + } label: { + Label(selectedItems.isEmpty ? "Pause All" : "Pause", systemImage: "pause.fill") + } + .help(selectedItems.isEmpty ? "Pause all active downloads" : "Pause selected downloads") + .disabled(!canStop) } } .background { diff --git a/Sources/Firelink/DownloadTable.swift b/Sources/Firelink/DownloadTable.swift index 0ddbbe5..b4eed60 100644 --- a/Sources/Firelink/DownloadTable.swift +++ b/Sources/Firelink/DownloadTable.swift @@ -271,42 +271,38 @@ struct DownloadTable: View { showInFinder(target) } } label: { - Label(targetItems.count > 1 ? "Show in Finder (\(targetItems.count))" : "Show in Finder", systemImage: "finder") + Label(targetItems.count > 1 ? "Show in Finder (\(targetItems.count))" : "Show in Finder", systemImage: "magnifyingglass") } Divider() - Divider() - - Menu("Controls") { - if targetItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) { - Button { - for target in targetItems where target.status == .paused || target.status == .failed || target.status == .canceled { - controller.resume(target) - } - } label: { - Label("Start", systemImage: "play.fill") + if targetItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) { + Button { + for target in targetItems where target.status == .paused || target.status == .failed || target.status == .canceled { + controller.resume(target) } + } label: { + Label("Resume", systemImage: "play.fill") } + } - if targetItems.contains(where: { $0.status == .downloading || $0.status == .queued }) { - Button { - for target in targetItems where target.status == .downloading || target.status == .queued { - controller.pause(target) - } - } label: { - Label("Stop", systemImage: "stop.fill") + if targetItems.contains(where: { $0.status == .downloading || $0.status == .queued }) { + Button { + for target in targetItems where target.status == .downloading || target.status == .queued { + controller.pause(target) } + } label: { + Label("Pause", systemImage: "pause.fill") } + } - if targetItems.contains(where: { $0.status == .completed || $0.status == .failed || $0.status == .canceled }) { - Button { - for target in targetItems where target.status == .completed || target.status == .failed || target.status == .canceled { - controller.redownload(target) - } - } label: { - Label("Redownload", systemImage: "arrow.clockwise") + if targetItems.contains(where: { $0.status == .completed || $0.status == .failed || $0.status == .canceled }) { + Button { + for target in targetItems where target.status == .completed || target.status == .failed || target.status == .canceled { + controller.redownload(target) } + } label: { + Label("Redownload", systemImage: "arrow.clockwise") } } @@ -336,12 +332,16 @@ struct DownloadTable: View { Label(targetItems.count > 1 ? "Copy Addresses" : "Copy Address", systemImage: "link") } - Button { - for target in targetItems { - openWindow(id: "download-properties", value: target.id) + if targetItems.allSatisfy({ $0.status == .completed }) { + Button { + NSPasteboard.general.clearContents() + let paths = targetItems.map { $0.destinationPath }.joined(separator: "\n") + if !paths.isEmpty { + NSPasteboard.general.setString(paths, forType: .string) + } + } label: { + Label(targetItems.count > 1 ? "Copy File Paths" : "Copy File Path", systemImage: "doc.on.doc") } - } label: { - Label(targetItems.count > 1 ? "Properties (\(targetItems.count))" : "Properties", systemImage: "info.circle") } Divider() @@ -349,7 +349,17 @@ struct DownloadTable: View { Button(role: .destructive) { pendingDeleteItems = itemIDs } label: { - Label("Remove", systemImage: "trash") + Label("Remove from List", systemImage: "trash") + } + + Divider() + + Button { + for target in targetItems { + openWindow(id: "download-properties", value: target.id) + } + } label: { + Label(targetItems.count > 1 ? "Properties (\(targetItems.count))" : "Properties", systemImage: "info.circle") } } }