macOS client store and API
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import AppKit
|
||||
import OpenAPIRuntime
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
@@ -35,24 +36,26 @@ func pngFromTIFF(_ data: Data) -> Data? {
|
||||
@Observable
|
||||
final class Attachments {
|
||||
var images: [PendingImage] = []
|
||||
let server: Server
|
||||
let spike: SpikeLog
|
||||
let api: API
|
||||
let name: String
|
||||
|
||||
init(server: Server, name: String) {
|
||||
self.server = server
|
||||
init(spike: SpikeLog, api: API, name: String) {
|
||||
self.spike = spike
|
||||
self.api = api
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func add(_ data: Data, from source: String) {
|
||||
guard let kind = imageType(data), let image = NSImage(data: data) else {
|
||||
server.log("\(name): \(source) gave \(data.count) bytes that aren't PNG, JPEG, GIF or WebP")
|
||||
spike.log("\(name): \(source) gave \(data.count) bytes that aren't PNG, JPEG, GIF or WebP")
|
||||
return
|
||||
}
|
||||
if data.count > maxImageBytes {
|
||||
server.log("\(name): \(source) image is over \(maxImageBytes >> 20) MiB")
|
||||
spike.log("\(name): \(source) image is over \(maxImageBytes >> 20) MiB")
|
||||
return
|
||||
}
|
||||
server.log("\(name): \(source) attached a \(kind), \(data.count) bytes")
|
||||
spike.log("\(name): \(source) attached a \(kind), \(data.count) bytes")
|
||||
images.append(PendingImage(data: data, image: image))
|
||||
}
|
||||
|
||||
@@ -81,7 +84,7 @@ final class Attachments {
|
||||
}
|
||||
|
||||
func add(providers: [NSItemProvider], from source: String) {
|
||||
server.log("\(name): \(source) with types \(providers.map { $0.registeredTypeIdentifiers })")
|
||||
spike.log("\(name): \(source) with types \(providers.map { $0.registeredTypeIdentifiers })")
|
||||
for p in providers {
|
||||
let types = [UTType.png, .jpeg, .gif, .webP, .tiff, .fileURL].filter { p.hasItemConformingToTypeIdentifier($0.identifier) }
|
||||
guard let t = types.first else {
|
||||
@@ -90,7 +93,7 @@ final class Attachments {
|
||||
_ = p.loadDataRepresentation(for: t) { data, error in
|
||||
Task { @MainActor in
|
||||
guard let data else {
|
||||
self.server.log("\(self.name): \(source) load failed: \(String(describing: error))")
|
||||
self.spike.log("\(self.name): \(source) load failed: \(String(describing: error))")
|
||||
return
|
||||
}
|
||||
if t == .fileURL, let url = URL(dataRepresentation: data, relativeTo: nil), let file = try? Data(contentsOf: url) {
|
||||
@@ -108,17 +111,28 @@ final class Attachments {
|
||||
func send(_ text: String) {
|
||||
let images = self.images
|
||||
self.images = []
|
||||
server.log("\(name): send \(text.debugDescription) with \(images.count) images")
|
||||
spike.log("\(name): send \(text.debugDescription) with \(images.count) images")
|
||||
Task {
|
||||
for img in images {
|
||||
do {
|
||||
server.log("\(name): uploaded blob \(try await server.upload(img.data))")
|
||||
spike.log("\(name): uploaded blob \(try await upload(img.data))")
|
||||
} catch {
|
||||
server.log("\(name): upload failed: \(error)")
|
||||
spike.log("\(name): upload failed: \(errorText(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func upload(_ data: Data) async throws -> String {
|
||||
let body = HTTPBody(data)
|
||||
let typed: Operations.UploadBlob.Input.Body = switch imageType(data) {
|
||||
case "png": .png(body)
|
||||
case "jpeg": .jpeg(body)
|
||||
case "gif": .imageGif(body)
|
||||
default: .imageWebp(body)
|
||||
}
|
||||
return try await api.client.uploadBlob(body: typed).created.body.json.id
|
||||
}
|
||||
}
|
||||
|
||||
struct Thumbnails: View {
|
||||
@@ -143,13 +157,14 @@ struct Thumbnails: View {
|
||||
}
|
||||
|
||||
struct ComposerSpike: View {
|
||||
@Environment(Server.self) private var server
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(SpikeLog.self) private var spike
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
SwiftUIComposer(attachments: Attachments(server: server, name: "TextEditor"))
|
||||
AppKitComposerPanel(attachments: Attachments(server: server, name: "NSTextView"))
|
||||
RoutedComposer(attachments: Attachments(server: server, name: "Routed"))
|
||||
SwiftUIComposer(attachments: Attachments(spike: spike, api: store.api, name: "TextEditor"))
|
||||
AppKitComposerPanel(attachments: Attachments(spike: spike, api: store.api, name: "NSTextView"))
|
||||
RoutedComposer(attachments: Attachments(spike: spike, api: store.api, name: "Routed"))
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
@@ -215,7 +230,7 @@ struct RoutedComposer: View {
|
||||
.focusedValue(\.composer, attachments)
|
||||
.onKeyPress(.return, phases: .down) { press in
|
||||
let marked = (NSApp.keyWindow?.firstResponder as? NSTextView)?.hasMarkedText() ?? false
|
||||
attachments.server.log("Routed: onKeyPress return, modifiers \(press.modifiers.rawValue), hasMarkedText \(marked)")
|
||||
attachments.spike.log("Routed: onKeyPress return, modifiers \(press.modifiers.rawValue), hasMarkedText \(marked)")
|
||||
if press.modifiers.contains(.shift) || marked {
|
||||
return .ignored
|
||||
}
|
||||
@@ -233,7 +248,7 @@ struct RoutedComposer: View {
|
||||
}
|
||||
}
|
||||
.onChange(of: targeted) {
|
||||
attachments.server.log("Routed: overlay drop targeted \(targeted)")
|
||||
attachments.spike.log("Routed: overlay drop targeted \(targeted)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +269,7 @@ struct SwiftUIComposer: View {
|
||||
.border(Color.secondary.opacity(0.4))
|
||||
.onKeyPress(.return, phases: .down) { press in
|
||||
let marked = (NSApp.keyWindow?.firstResponder as? NSTextView)?.hasMarkedText() ?? false
|
||||
attachments.server.log("TextEditor: onKeyPress return, modifiers \(press.modifiers.rawValue), hasMarkedText \(marked)")
|
||||
attachments.spike.log("TextEditor: onKeyPress return, modifiers \(press.modifiers.rawValue), hasMarkedText \(marked)")
|
||||
if press.modifiers.contains(.shift) {
|
||||
return .ignored
|
||||
}
|
||||
@@ -296,7 +311,7 @@ final class ComposerTextView: NSTextView {
|
||||
|
||||
override func keyDown(with event: NSEvent) {
|
||||
if event.keyCode == 36 || event.keyCode == 76 {
|
||||
attachments.server.log("NSTextView: keyDown return, modifiers \(event.modifierFlags.intersection(.deviceIndependentFlagsMask).rawValue), hasMarkedText \(hasMarkedText())")
|
||||
attachments.spike.log("NSTextView: keyDown return, modifiers \(event.modifierFlags.intersection(.deviceIndependentFlagsMask).rawValue), hasMarkedText \(hasMarkedText())")
|
||||
}
|
||||
super.keyDown(with: event)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user