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()
}
.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 {
+41 -31
View File
@@ -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")
}
}
}