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
This commit is contained in:
NimBold
2026-06-10 18:49:55 +03:30
parent ffba883961
commit fdbacb8a7f
2 changed files with 61 additions and 50 deletions
+20 -19
View File
@@ -117,29 +117,14 @@ struct ContentView: View {
StatusBar() StatusBar()
} }
.toolbar { .toolbar {
ToolbarItem(placement: .primaryAction) { ToolbarItemGroup {
Button { Button {
controller.pendingAddQueueID = queueID controller.pendingAddQueueID = queueID
openWindow(id: "add-downloads") openWindow(id: "add-downloads")
} label: { } label: {
Label("Add", systemImage: "plus") Label("Add Download", systemImage: "plus")
} }
} .help("Add a new download")
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)
let canStart = selectedItems.isEmpty ? hasQueuedDownloads(in: queueID) : selectedItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) let canStart = selectedItems.isEmpty ? hasQueuedDownloads(in: queueID) : selectedItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled })
Button { Button {
@@ -151,9 +136,25 @@ struct ContentView: View {
} }
} }
} label: { } 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) .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 { .background {
+41 -31
View File
@@ -271,42 +271,38 @@ struct DownloadTable: View {
showInFinder(target) showInFinder(target)
} }
} label: { } 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()
Divider() if targetItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) {
Button {
Menu("Controls") { for target in targetItems where target.status == .paused || target.status == .failed || target.status == .canceled {
if targetItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) { controller.resume(target)
Button {
for target in targetItems where target.status == .paused || target.status == .failed || target.status == .canceled {
controller.resume(target)
}
} label: {
Label("Start", systemImage: "play.fill")
} }
} label: {
Label("Resume", systemImage: "play.fill")
} }
}
if targetItems.contains(where: { $0.status == .downloading || $0.status == .queued }) { if targetItems.contains(where: { $0.status == .downloading || $0.status == .queued }) {
Button { Button {
for target in targetItems where target.status == .downloading || target.status == .queued { for target in targetItems where target.status == .downloading || target.status == .queued {
controller.pause(target) controller.pause(target)
}
} label: {
Label("Stop", systemImage: "stop.fill")
} }
} label: {
Label("Pause", systemImage: "pause.fill")
} }
}
if targetItems.contains(where: { $0.status == .completed || $0.status == .failed || $0.status == .canceled }) { if targetItems.contains(where: { $0.status == .completed || $0.status == .failed || $0.status == .canceled }) {
Button { Button {
for target in targetItems where target.status == .completed || target.status == .failed || target.status == .canceled { for target in targetItems where target.status == .completed || target.status == .failed || target.status == .canceled {
controller.redownload(target) controller.redownload(target)
}
} label: {
Label("Redownload", systemImage: "arrow.clockwise")
} }
} label: {
Label("Redownload", systemImage: "arrow.clockwise")
} }
} }
@@ -336,12 +332,16 @@ struct DownloadTable: View {
Label(targetItems.count > 1 ? "Copy Addresses" : "Copy Address", systemImage: "link") Label(targetItems.count > 1 ? "Copy Addresses" : "Copy Address", systemImage: "link")
} }
Button { if targetItems.allSatisfy({ $0.status == .completed }) {
for target in targetItems { Button {
openWindow(id: "download-properties", value: target.id) 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() Divider()
@@ -349,7 +349,17 @@ struct DownloadTable: View {
Button(role: .destructive) { Button(role: .destructive) {
pendingDeleteItems = itemIDs pendingDeleteItems = itemIDs
} label: { } 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")
} }
} }
} }