diff options
Diffstat (limited to 'iTexSnip/Views')
-rw-r--r-- | iTexSnip/Views/DetailedSnippetView.swift | 145 | ||||
-rw-r--r-- | iTexSnip/Views/MenuBarView.swift | 258 | ||||
-rw-r--r-- | iTexSnip/Views/PreferencesView.swift | 18 |
3 files changed, 421 insertions, 0 deletions
diff --git a/iTexSnip/Views/DetailedSnippetView.swift b/iTexSnip/Views/DetailedSnippetView.swift new file mode 100644 index 0000000..c7386fa --- /dev/null +++ b/iTexSnip/Views/DetailedSnippetView.swift @@ -0,0 +1,145 @@ +// +// DetailedSnippetView.swift +// iTexSnip +// +// Created by Navan Chauhan on 10/21/24. +// + +import SwiftUI +import LaTeXSwiftUI + +struct DetailedSnippetView: View { + @Environment(\.modelContext) var modelContext + @Environment(\.dismiss) private var dismiss + + @State var showOriginal = false + + + var snippet: ImageSnippet + var body: some View { + VStack { + HStack { + Toggle("Show Original", isOn: $showOriginal) + .toggleStyle(.switch) + .padding() + Spacer() + HStack { + Button { + if snippet.rating == true { + updateRating(nil) + } else { + updateRating(true) + } + } label: { + if snippet.rating == true { + Image(systemName: "hand.thumbsup") + .foregroundStyle(.green) + .imageScale(.large) + } else { + Image(systemName: "hand.thumbsup") + .imageScale(.large) + } + }.buttonStyle(PlainButtonStyle()) + + Button { + if snippet.rating == false { + updateRating(nil) + } else { + updateRating(false) + } + } label: { + if snippet.rating == false { + Image(systemName: "hand.thumbsdown") + .foregroundStyle(.red) + .imageScale(.large) + } else { + Image(systemName: "hand.thumbsdown") + .imageScale(.large) + } + }.buttonStyle(PlainButtonStyle()) + + Button(role: .destructive) { + withAnimation { + modelContext.delete(snippet) + do { + try modelContext.save() + dismiss() + } catch { + print("Failed to delete snippet: \(error)") + } + } + } label: { + Image(systemName: "trash") + .imageScale(.large) + } + .padding() + .buttonStyle(PlainButtonStyle()) + }.padding() + } + if (showOriginal) { + HStack { + Spacer() + Image(nsImage: NSImage(data: snippet.image)!) + .resizable() + .clipped() + .cornerRadius(10) + .scaledToFit() + .frame(height: 100) + Spacer() + }.padding() + } + HStack { + Spacer() + LaTeXEquationView(equation: snippet.transcribedText!) + .clipped() + .scaledToFit() + .frame(height: 100) + Spacer() + } + GeometryReader { geometry in + HStack { + Button { + print("Should Copy") + } label: { + Image(systemName: "document.on.clipboard") + } + ScrollView(.horizontal) { + Text(snippet.transcribedText!) + .frame(height: 50) + .textSelection(.enabled) + } + .frame(width: geometry.size.width * 0.8) + .border(Color.red) + + Spacer() + Button { + print("Should Copy") + } label: { + Image(systemName: "document.on.clipboard") + } + } + } + Spacer() + } + } + + func updateRating(_ rating: Bool?) { + withAnimation { + self.snippet.rate(rating) + do { + try modelContext.save() + } catch { + print("Error saving rating: \(error)") + } + } + } +} + +struct LaTeXEquationView: View { + var equation: String + var body: some View { + LaTeX(equation) + .parsingMode(.all) + .font(.system(size: 28, weight: .bold)) + } +} diff --git a/iTexSnip/Views/MenuBarView.swift b/iTexSnip/Views/MenuBarView.swift new file mode 100644 index 0000000..feea7a8 --- /dev/null +++ b/iTexSnip/Views/MenuBarView.swift @@ -0,0 +1,258 @@ +// +// MenuBarView.swift +// iTexSnip +// +// Created by Navan Chauhan on 10/20/24. +// + +import LaTeXSwiftUI +import SwiftUI +import SwiftData +import AppKit + +@Model +class ImageSnippet { + var dateCreated: Date + var dateModifed: Date + var image: Data + var transcribedText: String? + var rating: Bool? + + init(image: NSImage, transcribedText: String? = nil) { + self.dateCreated = Date() + self.image = image.tiffRepresentation! + self.transcribedText = transcribedText + self.dateModifed = Date() + } + + func updateModifiedDate() { + self.dateModifed = Date() + } + + func rate(_ rating: Bool?) { + self.rating = rating + } +} + +struct MenuBarView: View { + @Environment(\.modelContext) var modelContext + @State var model: TexTellerModel? + @Query(sort: \ImageSnippet.dateModifed, order: .reverse) var snippets: [ImageSnippet] + + let columns = [ + GridItem(.flexible(), spacing: 16), + GridItem(.flexible(), spacing: 16) + ] + + var body: some View { + NavigationStack { + VStack { + HStack { + Text("iTeXSnip") + .font(.title) + Spacer() + Button { + pickImageFilesAndAddSnippet() + } label: { + Image(systemName: "photo.badge.plus") + .imageScale(.large) + } + .accessibilityLabel("Load Image File") + .buttonStyle(PlainButtonStyle()) + + Button { + takeScreenshotAndAddSnippet() + } label: { + Image(systemName: "scissors") + .imageScale(.large) + } + .accessibilityLabel("Screenshot") + .buttonStyle(PlainButtonStyle()) + Menu { + SettingsLink { + Text("Open Preferences") + } + Button("Quit") { + NSApplication.shared.terminate(nil) + } + } label: { + Image(systemName: "gear") + .imageScale(.large) + } + .buttonStyle(PlainButtonStyle()) + .accessibilityLabel("Preferences") + }.padding() + + ScrollView { + LazyVGrid(columns: columns, spacing: 16) { + ForEach(snippets) { snippet in + NavigationLink(destination: DetailedSnippetView(snippet: snippet)) { + SnippetView(snippet: snippet, deleteSnippet: deleteSnippet) + .frame(height: 200) + .padding() + } + // .frame(height: 300) + } + } + .padding() + } + + Spacer() + }.task { + do { + if self.model == nil { + let mymodel = try await TexTellerModel.asyncInit() + self.model = mymodel + print("Loaded da model") + } + } catch { + print("Failed to load da model: \(error)") + } + } + } + } + + func pickImageFilesAndAddSnippet() { + let panel = NSOpenPanel() + panel.allowedContentTypes = [.image] + panel.allowsMultipleSelection = false + panel.canChooseDirectories = false + panel.title = "Choose an image" + + if panel.runModal() == .OK { + if let url = panel.url, let image = NSImage(contentsOf: url) { + let newSnippet = ImageSnippet(image: image) + do { + if self.model != nil { + let latex = try self.model!.texIt(image) + newSnippet.transcribedText = latex + } + modelContext.insert(newSnippet) + try modelContext.save() + } catch { + print("Failed to save new snippet: \(error)") + } + } + } + } + + func takeScreenshotAndAddSnippet() { + if !CGPreflightScreenCaptureAccess() { + // App doesn't have permission, request permission + if !CGRequestScreenCaptureAccess() { + // Permission was denied, show alert to the user + DispatchQueue.main.async { + let alert = NSAlert() + alert.messageText = "Screen Recording Permission Denied" + alert.informativeText = "Please grant screen recording permissions in System Preferences > Security & Privacy." + alert.addButton(withTitle: "OK") + alert.runModal() + } + return + } + } + + let task = Process() + task.launchPath = "/usr/sbin/screencapture" + let tempPath = NSTemporaryDirectory() + "temp_itexsnip_ss.png" + task.arguments = ["-i", tempPath] + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe + + task.terminationHandler = { process in + DispatchQueue.main.async { + if FileManager.default.fileExists(atPath: tempPath) { + if let screenshotImage = NSImage(contentsOfFile: tempPath) { + let newSnippet = ImageSnippet(image: screenshotImage) + do { + if self.model != nil { + let latex = try self.model!.texIt(screenshotImage) + newSnippet.transcribedText = latex + } + self.modelContext.insert(newSnippet) + try self.modelContext.save() + } catch { + print("Failed to add snippet: \(error)") + } + } else { + print("Failed to get image...") + } + + do { + try FileManager.default.removeItem(atPath: tempPath) + print("Temp screenshot cleaned up") + } catch { + print("Failed to delete screenshot: \(error)") + } + + } else { + print("Screenshot was cancelled or failed") + } + } + } + + do { + try task.run() + } catch { + print("Failed to launch screencapture process: \(error)") + return + } + + } + + func deleteSnippet(snippet: ImageSnippet) { + do { + modelContext.delete(snippet) + try modelContext.save() + } catch { + print("Failed to delete snippet: \(error)") + } + } +} + +struct SnippetView: View { + var snippet: ImageSnippet + var deleteSnippet: (ImageSnippet) -> Void + + var body: some View { + VStack { + if let nsImage = NSImage(data: snippet.image) { + GeometryReader { geometry in + Image(nsImage: nsImage) + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + .frame(height: 100) + .clipped() + .cornerRadius(10) + } + .contextMenu { + Button(role: .destructive) { + deleteSnippet(snippet) + } label: { + Label("Delete", systemImage: "trash") + } + } + } + ScrollView(.horizontal) { + HStack{ + Spacer() + LaTeX(snippet.transcribedText ?? "") + .parsingMode(.all) + .frame(maxWidth: .infinity) + .frame(height: 100) + .layoutPriority(1) + .font(.system(size: 28, weight: .bold)) + Spacer() + } + }.scrollBounceBehavior(.basedOnSize, axes: [.horizontal]) + + } + } +} + +#Preview { + MenuBarView() +} diff --git a/iTexSnip/Views/PreferencesView.swift b/iTexSnip/Views/PreferencesView.swift new file mode 100644 index 0000000..81a7d38 --- /dev/null +++ b/iTexSnip/Views/PreferencesView.swift @@ -0,0 +1,18 @@ +// +// PreferencesView.swift +// iTexSnip +// +// Created by Navan Chauhan on 10/21/24. +// + +import SwiftUI + +struct PreferencesView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + PreferencesView() +} |