HuggingChat-Mac/Views/AnimatedImageView.swift (167 lines of code) (raw):

// // ResponseToolBar.swift // HuggingChat-Mac // // Created by Cyril Zakka on 11/29/24. // import SwiftUI import Pow import QuickLook import UniformTypeIdentifiers struct AnimatedImageView: View { @Environment(\.colorScheme) private var colorScheme @State private var quickLookURL: URL? @State private var imageDataToExport: Data? @State private var isAnimating: Bool = false let imageURL: URL // File exporter @State private var showFileExporter = false { didSet { if let floatingPanel = NSApp.windows.first(where: { $0 is FloatingPanel }) as? FloatingPanel { floatingPanel.updateFileImporterVisibility(showFileExporter) } } } private func prepareForExport() { Task { do { let (data, _) = try await URLSession.shared.data(from: imageURL) DispatchQueue.main.async { self.imageDataToExport = data self.showFileExporter = true } } catch { print("Error preparing for export: \(error)") } } } private func copyToClipboard() { Task { do { let (data, _) = try await URLSession.shared.data(from: imageURL) if let nsImage = NSImage(data: data) { let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.writeObjects([nsImage]) } } catch { print("Error copying to clipboard: \(error)") } } } private func prepareForQuickLook() { Task { do { let (data, _) = try await URLSession.shared.data(from: imageURL) // Create temporary file for Quick Look let tempDir = FileManager.default.temporaryDirectory let tempFileURL = tempDir.appendingPathComponent(UUID().uuidString + ".png") try data.write(to: tempFileURL) DispatchQueue.main.async { self.quickLookURL = tempFileURL } } catch { print("Error preparing for Quick Look: \(error)") } } } var body: some View { ZStack { AsyncImage( url: imageURL, transaction: .init(animation: .easeInOut(duration: 1.8)) ) { phase in ZStack { if colorScheme == .dark { Color.black .frame(width: 100, height: 100) .opacity(isAnimating ? 1:0) } else { Color.clear .frame(width: 100, height: 100) .opacity(isAnimating ? 1:0) } switch phase { case .success(let image): image .resizable() .aspectRatio(1, contentMode: .fit) .zIndex(1) .transition(colorScheme == .dark ? .movingParts.filmExposure:.movingParts.snapshot) .onTapGesture { prepareForQuickLook() } .contextMenu { Button { prepareForExport() } label: { Label("Save As...", systemImage: "square.and.arrow.down.on.square") } Button { copyToClipboard() } label: { Label("Copy Image", systemImage: "doc.on.doc") } } case .failure(_): ZStack(alignment: .center) { if colorScheme == .dark { Color.black } else { Color.clear } VStack(spacing: 10) { Image(systemName: "exclamationmark.triangle.fill") .imageScale(.large) .symbolVariant(.slash) Text("Error loading image") .font(.caption) .multilineTextAlignment(.center) }.padding(.horizontal, 5) } .aspectRatio(1, contentMode: .fit) .transition(.opacity) .clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous)) .frame(height: 100) // .frame(maxWidth: .infinity, alignment: .leading) case .empty: EmptyView() @unknown default: EmptyView() } } .onAppear { withAnimation(.easeInOut) { isAnimating = true } } .aspectRatio(contentMode: .fit) } .clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous)) .frame(maxWidth: .infinity, alignment: .leading) } .frame(height: 100) .quickLookPreview($quickLookURL) .fileExporter( isPresented: $showFileExporter, document: imageDataToExport.map { ImageFileDocument(imageData: $0) }, contentType: .png, defaultFilename: "image.png" ) { result in if case .failure(let error) = result { print("Error exporting file: \(error.localizedDescription)") } } } } // Document type for file export struct ImageFileDocument: FileDocument { static var readableContentTypes = [UTType.png] let imageData: Data init(imageData: Data) { self.imageData = imageData } init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents else { throw CocoaError(.fileReadCorruptFile) } self.imageData = data } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { FileWrapper(regularFileWithContents: imageData) } } #Preview { AnimatedImageView(imageURL: URL(string: "https://huggingface.co/chat/conversation/674be2b16a073f26942d7cd6/output/c90fe5b3b6342fba6994ae51460ff48859ceb0516fbceb35c10f3641bd1661ad")!) .frame(width: 400) }