Show images in Files and add client_open to open files on a client Mac
This commit is contained in:
@@ -44,6 +44,12 @@ private func bytes(_ n: Int64) -> String {
|
||||
return "\((Double(n) / 1024 / 1024).formatted(.number.precision(.fractionLength(1)).grouping(.never))) MiB"
|
||||
}
|
||||
|
||||
private let imageExts: Set<String> = ["png", "jpg", "jpeg", "gif", "webp", "svg"]
|
||||
|
||||
private func isImage(_ path: String) -> Bool {
|
||||
imageExts.contains((path as NSString).pathExtension.lowercased())
|
||||
}
|
||||
|
||||
private func shown(_ c: FileContent) -> Int64 {
|
||||
Int64(c.text.utf8.count)
|
||||
}
|
||||
@@ -56,6 +62,7 @@ private final class FilesModel {
|
||||
private(set) var content: FileContent?
|
||||
private(set) var error = ""
|
||||
private(set) var generation = 0
|
||||
private(set) var loads = 0
|
||||
@ObservationIgnored private var target = 0
|
||||
@ObservationIgnored private var timer: Task<Void, Never>?
|
||||
|
||||
@@ -104,6 +111,7 @@ private final class FilesModel {
|
||||
if started == target {
|
||||
content = fresh
|
||||
listing = nil
|
||||
loads += 1
|
||||
}
|
||||
} else {
|
||||
let fresh = try await store.api.client.getTaskDir(path: .init(taskId: taskId), query: .init(path: p.dir)).ok.body.json
|
||||
@@ -142,6 +150,9 @@ private final class FilesModel {
|
||||
return ""
|
||||
}
|
||||
if let c = content {
|
||||
if place.file.map(isImage) == true {
|
||||
return bytes(c.size)
|
||||
}
|
||||
if c.binary {
|
||||
return "\(bytes(c.size)) · binary"
|
||||
}
|
||||
@@ -229,11 +240,14 @@ private struct FilesBody: View {
|
||||
EmptyText(text: "No task selected.")
|
||||
} else if !model.error.isEmpty {
|
||||
EmptyText(text: model.error, error: true)
|
||||
} else if let c = model.content {
|
||||
if c.binary {
|
||||
} else if let c = model.content, let taskId = store.selectedTaskId {
|
||||
if let file = model.place.file, isImage(file) {
|
||||
FileImage(taskId: taskId, path: file, load: model.loads)
|
||||
.id(model.generation)
|
||||
} else if c.binary {
|
||||
EmptyText(text: "Binary file.")
|
||||
} else if model.place.file?.lowercased().hasSuffix(".md") == true {
|
||||
FileMarkdown(content: c, taskId: store.selectedTaskId, dir: model.place.dir)
|
||||
FileMarkdown(content: c, taskId: taskId, dir: model.place.dir)
|
||||
.id(model.generation)
|
||||
} else {
|
||||
FileText(content: c)
|
||||
@@ -340,6 +354,58 @@ private struct FileMarkdown: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct FileImage: View {
|
||||
let taskId: String
|
||||
let path: String
|
||||
let load: Int
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var image: NSImage?
|
||||
@ViewState private var error = ""
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let image {
|
||||
ScrollView {
|
||||
Image(nsImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: image.size.width, maxHeight: image.size.height)
|
||||
.overlay(Rectangle().stroke(Palette.border))
|
||||
.onTapGesture {
|
||||
overlays.image = image
|
||||
}
|
||||
.help("Open full size")
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
} else if !error.isEmpty {
|
||||
EmptyText(text: error, error: true)
|
||||
} else {
|
||||
EmptyText(text: "Loading…")
|
||||
}
|
||||
}
|
||||
.task(id: load) {
|
||||
do {
|
||||
let data = try await ImageCache.fetch(.worktree(taskId: taskId, path: path), store.api)
|
||||
guard let img = NSImage(data: data) else {
|
||||
image = nil
|
||||
error = "Not a readable image."
|
||||
return
|
||||
}
|
||||
image = img
|
||||
error = ""
|
||||
} catch {
|
||||
if Task.isCancelled {
|
||||
return
|
||||
}
|
||||
image = nil
|
||||
self.error = errorText(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct FileText: View {
|
||||
let content: FileContent
|
||||
|
||||
|
||||
Reference in New Issue
Block a user