sync app filled in
This commit is contained in:
141
apps/macos-sync/Sources/CairnquireSync/UI/DropZoneView.swift
Normal file
141
apps/macos-sync/Sources/CairnquireSync/UI/DropZoneView.swift
Normal file
@@ -0,0 +1,141 @@
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
public struct DropZoneView: View {
|
||||
@State private var isDragging = false
|
||||
@State private var uploadResult: UploadResult?
|
||||
@State private var error: String?
|
||||
@State private var isUploading = false
|
||||
@State private var uploadProgress: Double = 0
|
||||
|
||||
public let uploadManager: UploadManager
|
||||
|
||||
public init(uploadManager: UploadManager) {
|
||||
self.uploadManager = uploadManager
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
Image(systemName: "arrow.up.doc.fill")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(isDragging ? .blue : .secondary)
|
||||
|
||||
Text("Drop files here")
|
||||
.font(.title2)
|
||||
|
||||
if isUploading {
|
||||
ProgressView(value: uploadProgress)
|
||||
.frame(width: 180)
|
||||
Text("\(Int(uploadProgress * 100))%")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
} else if let result = uploadResult {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 32))
|
||||
.foregroundColor(.green)
|
||||
|
||||
Text("Uploaded \(result.filename)")
|
||||
.font(.headline)
|
||||
|
||||
Text("URL copied to clipboard")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(result.url.absoluteString)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.blue)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
} else if let error = error {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.system(size: 32))
|
||||
.foregroundColor(.red)
|
||||
|
||||
Text("Upload failed")
|
||||
.font(.headline)
|
||||
|
||||
Text(error)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 300, height: 250)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(isDragging ? Color.blue.opacity(0.1) : Color.secondary.opacity(0.1))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.strokeBorder(isDragging ? Color.blue : Color.secondary.opacity(0.3), style: StrokeStyle(lineWidth: 2, dash: [8]))
|
||||
)
|
||||
)
|
||||
.onDrop(of: [.fileURL], isTargeted: $isDragging) { providers in
|
||||
guard let provider = providers.first else { return false }
|
||||
|
||||
_ = provider.loadObject(ofClass: URL.self) { url, error in
|
||||
guard let url = url else { return }
|
||||
|
||||
Task {
|
||||
await MainActor.run {
|
||||
isUploading = true
|
||||
uploadProgress = 0
|
||||
self.error = nil
|
||||
self.uploadResult = nil
|
||||
}
|
||||
|
||||
do {
|
||||
let result = try await uploadFileResolvingDuplicates(url, onProgress: { progress in
|
||||
Task { @MainActor in
|
||||
uploadProgress = min(max(progress, 0), 1)
|
||||
}
|
||||
})
|
||||
await MainActor.run {
|
||||
uploadResult = result
|
||||
isUploading = false
|
||||
uploadProgress = 0
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.error = error.localizedDescription
|
||||
isUploading = false
|
||||
uploadProgress = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadFileResolvingDuplicates(
|
||||
_ url: URL,
|
||||
onProgress: UploadProgressHandler?
|
||||
) async throws -> UploadResult {
|
||||
var duplicateAction: UploadDuplicateAction?
|
||||
|
||||
while true {
|
||||
do {
|
||||
return try await uploadManager.uploadFile(
|
||||
url,
|
||||
duplicateAction: duplicateAction,
|
||||
onProgress: onProgress
|
||||
)
|
||||
} catch let error as APIError where error.isUploadConflict {
|
||||
guard let action = promptForUploadDuplicate(error, filename: url.lastPathComponent) else {
|
||||
throw UploadCancelledError()
|
||||
}
|
||||
duplicateAction = action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
// This preview won't work without a real UploadManager
|
||||
Text("DropZoneView Preview")
|
||||
}
|
||||
Reference in New Issue
Block a user