mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-31 04:58:11 +00:00
feat: add monochrome tray icon and download table enhancements
- Added proper monochrome template tray icon loaded explicitly with precise dimensions - Added redownload functionality for completed or failed items - Added double-click to open completed files directly from the table - Added 'Copy Address' context menu action - Improved context menu organization and conditionally displayed actions
This commit is contained in:
@@ -19,6 +19,7 @@ rm -rf "$APP_DIR"
|
|||||||
mkdir -p "$MACOS_DIR" "$RESOURCES_DIR"
|
mkdir -p "$MACOS_DIR" "$RESOURCES_DIR"
|
||||||
cp ".build/$CONFIGURATION/$APP_NAME" "$MACOS_DIR/$APP_NAME"
|
cp ".build/$CONFIGURATION/$APP_NAME" "$MACOS_DIR/$APP_NAME"
|
||||||
cp "$ROOT_DIR/Resources/$ICON_NAME.icns" "$RESOURCES_DIR/$ICON_NAME.icns"
|
cp "$ROOT_DIR/Resources/$ICON_NAME.icns" "$RESOURCES_DIR/$ICON_NAME.icns"
|
||||||
|
cp "$ROOT_DIR/Sources/Firelink/Assets.xcassets/MenuBarIcon.imageset/MenuBarIconTemplate.png" "$RESOURCES_DIR/MenuBarIconTemplate.png"
|
||||||
|
|
||||||
ARIA2C_PATH=$(which aria2c || true)
|
ARIA2C_PATH=$(which aria2c || true)
|
||||||
if [[ -n "$ARIA2C_PATH" && -x "$ARIA2C_PATH" ]]; then
|
if [[ -n "$ARIA2C_PATH" && -x "$ARIA2C_PATH" ]]; then
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "MenuBarIconTemplate.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@@ -244,6 +244,24 @@ final class DownloadController: ObservableObject {
|
|||||||
pumpQueue()
|
pumpQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func redownload(_ item: DownloadItem) {
|
||||||
|
trashFiles(for: item)
|
||||||
|
restrictQueueToAutoResume = false
|
||||||
|
update(item.id) {
|
||||||
|
$0.status = .queued
|
||||||
|
$0.progress = 0
|
||||||
|
$0.speedText = "-"
|
||||||
|
$0.etaText = "-"
|
||||||
|
$0.connectionCount = 0
|
||||||
|
$0.message = "Redownloading"
|
||||||
|
$0.autoResumeOnLaunch = true
|
||||||
|
}
|
||||||
|
queuePumpScope = queuePumpScope.includingItem(item.id)
|
||||||
|
automaticRetryCounts[item.id] = nil
|
||||||
|
saveDownloads()
|
||||||
|
pumpQueue()
|
||||||
|
}
|
||||||
|
|
||||||
func cancel(_ item: DownloadItem) {
|
func cancel(_ item: DownloadItem) {
|
||||||
activeHandles[item.id]?.cancel()
|
activeHandles[item.id]?.cancel()
|
||||||
activeHandles[item.id] = nil
|
activeHandles[item.id] = nil
|
||||||
|
|||||||
@@ -218,6 +218,13 @@ struct DownloadTable: View {
|
|||||||
.frame(width: tableWidth, alignment: .leading)
|
.frame(width: tableWidth, alignment: .leading)
|
||||||
.background(selection.contains(item.id) ? Color.accentColor.opacity(0.12) : Color.clear)
|
.background(selection.contains(item.id) ? Color.accentColor.opacity(0.12) : Color.clear)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture(count: 2) {
|
||||||
|
if item.status == .completed {
|
||||||
|
openFile(item)
|
||||||
|
} else {
|
||||||
|
openWindow(value: item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
let index = sortedItems.firstIndex(where: { $0.id == item.id })
|
let index = sortedItems.firstIndex(where: { $0.id == item.id })
|
||||||
|
|
||||||
@@ -326,12 +333,14 @@ struct DownloadTable: View {
|
|||||||
private func rowContextMenu(for item: DownloadItem) -> some View {
|
private func rowContextMenu(for item: DownloadItem) -> some View {
|
||||||
let targetItems = selection.contains(item.id) ? controller.downloads.filter { selection.contains($0.id) } : [item]
|
let targetItems = selection.contains(item.id) ? controller.downloads.filter { selection.contains($0.id) } : [item]
|
||||||
|
|
||||||
Button {
|
if targetItems.allSatisfy({ $0.status == .completed }) {
|
||||||
for target in targetItems {
|
Button {
|
||||||
openWindow(value: target.id)
|
for target in targetItems {
|
||||||
|
openFile(target)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(targetItems.count > 1 ? "Open (\(targetItems.count))" : "Open", systemImage: "doc")
|
||||||
}
|
}
|
||||||
} label: {
|
|
||||||
Label(targetItems.count > 1 ? "Properties (\(targetItems.count))" : "Properties", systemImage: "info.circle")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@@ -354,9 +363,9 @@ struct DownloadTable: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetItems.contains(where: { $0.status == .downloading }) {
|
if targetItems.contains(where: { $0.status == .downloading || $0.status == .queued }) {
|
||||||
Button {
|
Button {
|
||||||
for target in targetItems where target.status == .downloading {
|
for target in targetItems where target.status == .downloading || target.status == .queued {
|
||||||
controller.pause(target)
|
controller.pause(target)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
@@ -364,6 +373,18 @@ struct DownloadTable: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
if targetItems.contains(where: { $0.status != .completed && $0.status != .downloading }) {
|
if targetItems.contains(where: { $0.status != .completed && $0.status != .downloading }) {
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(controller.queues) { queue in
|
ForEach(controller.queues) { queue in
|
||||||
@@ -375,8 +396,25 @@ struct DownloadTable: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Add to Queue", systemImage: "list.bullet")
|
Label("Move to Queue", systemImage: "list.bullet")
|
||||||
}
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
NSPasteboard.general.clearContents()
|
||||||
|
let urls = targetItems.map { $0.url.absoluteString }.joined(separator: "\n")
|
||||||
|
NSPasteboard.general.setString(urls, forType: .string)
|
||||||
|
} label: {
|
||||||
|
Label(targetItems.count > 1 ? "Copy Addresses" : "Copy Address", systemImage: "link")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
for target in targetItems {
|
||||||
|
openWindow(value: target.id)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(targetItems.count > 1 ? "Properties (\(targetItems.count))" : "Properties", systemImage: "info.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@@ -384,7 +422,7 @@ struct DownloadTable: View {
|
|||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
pendingDeleteItems = Set(targetItems.map(\.id))
|
pendingDeleteItems = Set(targetItems.map(\.id))
|
||||||
} label: {
|
} label: {
|
||||||
Label("Delete", systemImage: "trash")
|
Label("Remove", systemImage: "trash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,6 +561,15 @@ struct DownloadTable: View {
|
|||||||
}
|
}
|
||||||
return candidate
|
return candidate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func openFile(_ item: DownloadItem) {
|
||||||
|
let fileURL = item.destinationDirectory.appendingPathComponent(item.fileName)
|
||||||
|
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||||
|
NSWorkspace.shared.open(fileURL)
|
||||||
|
} else {
|
||||||
|
NSWorkspace.shared.open(existingFolder(for: item.destinationDirectory))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DownloadRow: View {
|
private struct DownloadRow: View {
|
||||||
|
|||||||
@@ -60,9 +60,21 @@ struct FirelinkApp: App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuBarExtra("Firelink", systemImage: "arrow.down.circle", isInserted: $showMenuBarIcon) {
|
MenuBarExtra(isInserted: $showMenuBarIcon) {
|
||||||
TrayMenuView()
|
TrayMenuView()
|
||||||
.environmentObject(controller)
|
.environmentObject(controller)
|
||||||
|
} label: {
|
||||||
|
if let nsImage = { () -> NSImage? in
|
||||||
|
guard let url = Bundle.main.url(forResource: "MenuBarIconTemplate", withExtension: "png"),
|
||||||
|
let img = NSImage(contentsOf: url) else { return nil }
|
||||||
|
img.size = NSSize(width: 21, height: 21)
|
||||||
|
img.isTemplate = true
|
||||||
|
return img
|
||||||
|
}() {
|
||||||
|
Image(nsImage: nsImage)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "arrow.down.circle")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user