aboutsummaryrefslogtreecommitdiff
path: root/iTexSnip/Views
diff options
context:
space:
mode:
Diffstat (limited to 'iTexSnip/Views')
-rw-r--r--iTexSnip/Views/DetailedSnippetView.swift145
-rw-r--r--iTexSnip/Views/MenuBarView.swift258
-rw-r--r--iTexSnip/Views/PreferencesView.swift18
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()
+}