mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-21 00:16:37 +00:00
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:
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user