diff options
author | Navan Chauhan <navanchauhan@gmail.com> | 2024-07-28 16:40:07 -0600 |
---|---|---|
committer | Navan Chauhan <navanchauhan@gmail.com> | 2024-07-28 16:40:07 -0600 |
commit | 0cfb7885d12b71ee2f3fa9dc7e03d6e7de7c1d03 (patch) | |
tree | 05e86abbd71d585de40ca09aa678c660d524df38 | |
parent | 67789c4f15760c6109e56e50a0620120cb11c72c (diff) |
swift-format
-rw-r--r-- | .swift-format | 5 | ||||
-rw-r--r-- | iGopherBrowser/BookmarksView.swift | 32 | ||||
-rw-r--r-- | iGopherBrowser/BrowserView.swift | 1001 | ||||
-rw-r--r-- | iGopherBrowser/ContentView.swift | 64 | ||||
-rw-r--r-- | iGopherBrowser/FileView.swift | 254 | ||||
-rw-r--r-- | iGopherBrowser/Helpers.swift | 54 | ||||
-rw-r--r-- | iGopherBrowser/SearchInputView.swift | 44 | ||||
-rw-r--r-- | iGopherBrowser/SettingsView.swift | 251 | ||||
-rw-r--r-- | iGopherBrowser/SidebarView.swift | 24 | ||||
-rw-r--r-- | iGopherBrowser/iGopherBrowserApp.swift | 40 |
10 files changed, 911 insertions, 858 deletions
diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..95d360e --- /dev/null +++ b/.swift-format @@ -0,0 +1,5 @@ +{ + "indentation": { + "spaces": 4 + } +} diff --git a/iGopherBrowser/BookmarksView.swift b/iGopherBrowser/BookmarksView.swift index b23196b..fa95459 100644 --- a/iGopherBrowser/BookmarksView.swift +++ b/iGopherBrowser/BookmarksView.swift @@ -11,27 +11,27 @@ import SwiftUI struct BookmarksView: View { - enum sectionType: String, CaseIterable, Identifiable { - case bookmarks, history - var id: Self { self } - } + enum sectionType: String, CaseIterable, Identifiable { + case bookmarks, history + var id: Self { self } + } - @State private var selectedSection: sectionType = .bookmarks + @State private var selectedSection: sectionType = .bookmarks - var body: some View { - VStack { - Picker("Section", selection: $selectedSection) { - ForEach(sectionType.allCases) { section in - Text(section.rawValue.capitalized) + var body: some View { + VStack { + Picker("Section", selection: $selectedSection) { + ForEach(sectionType.allCases) { section in + Text(section.rawValue.capitalized) + } + }.pickerStyle(.segmented).padding(.top, 20).padding(.leading, 10).padding(.trailing, 10) + .padding(.bottom, 10) + Text("You picked \(selectedSection.rawValue.capitalized)") + Spacer() } - }.pickerStyle(.segmented).padding(.top, 20).padding(.leading, 10).padding(.trailing, 10) - .padding(.bottom, 10) - Text("You picked \(selectedSection.rawValue.capitalized)") - Spacer() } - } } #Preview { - BookmarksView() + BookmarksView() } diff --git a/iGopherBrowser/BrowserView.swift b/iGopherBrowser/BrowserView.swift index 880bccd..cd983a5 100644 --- a/iGopherBrowser/BrowserView.swift +++ b/iGopherBrowser/BrowserView.swift @@ -6,515 +6,558 @@ // import GopherHelpers +import SwiftGopherClient import SwiftUI import TelemetryClient -import SwiftGopherClient func openURL(url: URL) { - #if os(OSX) - NSWorkspace.shared.open(url) - #else - UIApplication.shared.open(url) - #endif + #if os(OSX) + NSWorkspace.shared.open(url) + #else + UIApplication.shared.open(url) + #endif } #if canImport(UIKit) - extension View { - func hideKeyboard() { - UIApplication.shared.sendAction( - #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + extension View { + func hideKeyboard() { + UIApplication.shared.sendAction( + #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } } - } #endif struct BrowserView: View { - @AppStorage("homeURL") var homeURL: URL = URL(string: "gopher://gopher.navan.dev:70/")! - @AppStorage("accentColour", store: .standard) var accentColour: Color = Color(.blue) - @AppStorage("linkColour", store: .standard) var linkColour: Color = Color(.white) - @AppStorage("shareThroughProxy", store: .standard) var shareThroughProxy: Bool = true - - @State var homeURLString = "gopher://gopher.navan.dev:70/" - - @State var url: String = "" - @State private var gopherItems: [gopherItem] = [] - - @Binding public var hosts: [GopherNode] - @Binding var selectedNode: GopherNode? - - @State private var backwardStack: [GopherNode] = [] - @State private var forwardStack: [GopherNode] = [] - - @State private var searchText: String = "" - @State private var showSearchInput = false - @State var selectedSearchItem: Int? - - @State private var showPreferences = false - @State private var showBookmarks = false - - @Namespace var topID - @State private var scrollToTop: Bool = false - - @State var currentTask: Task<Void, Never>? - - let client = GopherClient() - - var body: some View { - NavigationStack { - VStack(spacing: 0) { - if gopherItems.count >= 1 { - ScrollViewReader { proxy in - - List { - ForEach(Array(gopherItems.enumerated()), id: \.offset) { idx, item in - if item.parsedItemType == .info { - Text(item.message) - .font(.system(size: 12, design: .monospaced)) - .frame(height: 20) - .listRowSeparator(.hidden) - .padding(.vertical, -8) - .id(idx) - } else if item.parsedItemType == .directory { - Button(action: { - performGopherRequest(host: item.host, port: item.port, selector: item.selector) - #if canImport(UIKit) - hideKeyboard() - #endif - }) { - HStack { - Text(Image(systemName: "folder")) - Text(item.message) - Spacer() - }.foregroundStyle(linkColour) - }.buttonStyle(PlainButtonStyle()) - .id(idx) - } else if item.parsedItemType == .search { - Button(action: { - #if canImport(UIKit) - hideKeyboard() - #endif - self.selectedSearchItem = idx - self.showSearchInput = true - }) { - HStack { - Text(Image(systemName: "magnifyingglass")) - Text(item.message) - Spacer() - }.foregroundStyle(linkColour) - }.buttonStyle(PlainButtonStyle()) - .id(idx) - } else if item.parsedItemType == .text { - NavigationLink(destination: FileView(item: item)) { - HStack { - Text(Image(systemName: "doc.plaintext")) - Text(item.message) - Spacer() - }.foregroundStyle(linkColour) - } - .id(idx) - } else if item.selector.hasPrefix("URL:") { - if let url = URL(string: item.selector.replacingOccurrences(of: "URL:", with: "")) - { - //UIApplication.shared.canOpenURL(url) { - Button(action: { - openURL(url: url) - }) { - HStack { - Image(systemName: "link") - Text(item.message) - Spacer() - }.foregroundStyle(linkColour) - }.buttonStyle(PlainButtonStyle()) - .id(idx) - } - } else if [.doc, .image, .gif, .movie, .sound, .bitmap].contains( - item.parsedItemType) - { - NavigationLink(destination: FileView(item: item)) { - HStack { - Text(Image(systemName: itemToImageType(item))) - Text(item.message) - Spacer() - }.foregroundStyle(linkColour) - .id(idx) - } + @AppStorage("homeURL") var homeURL: URL = URL(string: "gopher://gopher.navan.dev:70/")! + @AppStorage("accentColour", store: .standard) var accentColour: Color = Color(.blue) + @AppStorage("linkColour", store: .standard) var linkColour: Color = Color(.white) + @AppStorage("shareThroughProxy", store: .standard) var shareThroughProxy: Bool = true + + @State var homeURLString = "gopher://gopher.navan.dev:70/" + + @State var url: String = "" + @State private var gopherItems: [gopherItem] = [] + + @Binding public var hosts: [GopherNode] + @Binding var selectedNode: GopherNode? + + @State private var backwardStack: [GopherNode] = [] + @State private var forwardStack: [GopherNode] = [] + + @State private var searchText: String = "" + @State private var showSearchInput = false + @State var selectedSearchItem: Int? + + @State private var showPreferences = false + @State private var showBookmarks = false + + @Namespace var topID + @State private var scrollToTop: Bool = false + + @State var currentTask: Task<Void, Never>? + + let client = GopherClient() + + var body: some View { + NavigationStack { + VStack(spacing: 0) { + if gopherItems.count >= 1 { + ScrollViewReader { proxy in + + List { + ForEach(Array(gopherItems.enumerated()), id: \.offset) { idx, item in + if item.parsedItemType == .info { + Text(item.message) + .font(.system(size: 12, design: .monospaced)) + .frame(height: 20) + .listRowSeparator(.hidden) + .padding(.vertical, -8) + .id(idx) + } else if item.parsedItemType == .directory { + Button(action: { + performGopherRequest( + host: item.host, port: item.port, + selector: item.selector) + #if canImport(UIKit) + hideKeyboard() + #endif + }) { + HStack { + Text(Image(systemName: "folder")) + Text(item.message) + Spacer() + }.foregroundStyle(linkColour) + }.buttonStyle(PlainButtonStyle()) + .id(idx) + } else if item.parsedItemType == .search { + Button(action: { + #if canImport(UIKit) + hideKeyboard() + #endif + self.selectedSearchItem = idx + self.showSearchInput = true + }) { + HStack { + Text(Image(systemName: "magnifyingglass")) + Text(item.message) + Spacer() + }.foregroundStyle(linkColour) + }.buttonStyle(PlainButtonStyle()) + .id(idx) + } else if item.parsedItemType == .text { + NavigationLink(destination: FileView(item: item)) { + HStack { + Text(Image(systemName: "doc.plaintext")) + Text(item.message) + Spacer() + }.foregroundStyle(linkColour) + } + .id(idx) + } else if item.selector.hasPrefix("URL:") { + if let url = URL( + string: item.selector.replacingOccurrences( + of: "URL:", with: "")) + { + //UIApplication.shared.canOpenURL(url) { + Button(action: { + openURL(url: url) + }) { + HStack { + Image(systemName: "link") + Text(item.message) + Spacer() + }.foregroundStyle(linkColour) + }.buttonStyle(PlainButtonStyle()) + .id(idx) + } + } else if [.doc, .image, .gif, .movie, .sound, .bitmap].contains( + item.parsedItemType) + { + NavigationLink(destination: FileView(item: item)) { + HStack { + Text(Image(systemName: itemToImageType(item))) + Text(item.message) + Spacer() + }.foregroundStyle(linkColour) + .id(idx) + } + } else { + Button(action: { + TelemetryManager.send( + "applicationBrowsedUnknown", + with: [ + "gopherURL": + "\(item.host):\(item.port)\(item.selector)" + ]) + performGopherRequest( + host: item.host, port: item.port, + selector: item.selector) + }) { + HStack { + Text(Image(systemName: "questionmark.app.dashed")) + Text(item.message) + Spacer() + }.foregroundStyle(linkColour) + }.buttonStyle(PlainButtonStyle()) + .id(idx) + } + + } + } + //.background(Color.white) + .cornerRadius(10) + .onChange(of: scrollToTop) { + // TODO: Cleanup + withAnimation { + proxy.scrollTo(0, anchor: .top) + + } + } + } + .sheet(isPresented: $showSearchInput) { + if let index = selectedSearchItem, gopherItems.indices.contains(index) { + let searchItem = gopherItems[index] + SearchInputView( + host: searchItem.host, + port: searchItem.port, + selector: searchItem.selector, + searchText: $searchText, + onSearch: { query in + performGopherRequest( + host: searchItem.host, port: searchItem.port, + selector: "\(searchItem.selector)\t\(query)") + showSearchInput = false + } + ) + } else { + + VStack { + Text("Weird bug. Please Dismiss -> Press Go -> Try Again") + Button("Dismiss") { + self.showSearchInput = false + }.onAppear { + TelemetryManager.send( + "applicationSearchError", with: ["gopherURL": "\(self.url)"] + ) + } + } + + } + } } else { - Button(action: { - TelemetryManager.send( - "applicationBrowsedUnknown", - with: ["gopherURL": "\(item.host):\(item.port)\(item.selector)"]) - performGopherRequest(host: item.host, port: item.port, selector: item.selector) - }) { - HStack { - Text(Image(systemName: "questionmark.app.dashed")) - Text(item.message) - Spacer() - }.foregroundStyle(linkColour) - }.buttonStyle(PlainButtonStyle()) - .id(idx) + Spacer() + Text("Welcome to iGopher Browser") + Spacer() } + #if os(iOS) + VStack { + HStack(spacing: 10) { + HStack { + Spacer() + + TextField("Enter a URL", text: $url) + .keyboardType(.URL) + .autocapitalization(.none) + .padding(10) + .background(Color.gray.opacity(0.2)) + .cornerRadius(10) + Spacer() + } + //.background(Color.white) + .cornerRadius(30) + + Button( + "Go", + action: { + TelemetryManager.send( + "applicationClickedGo", with: ["gopherURL": "\(self.url)"]) + performGopherRequest(clearForward: false) + + } + ) + .keyboardShortcut(.defaultAction) + .onSubmit { + performGopherRequest() + } + Spacer() + } + .padding(.bottom, 10) + .padding(.top, 5) + HStack { + Spacer() + Button { + print(homeURL, "home") + TelemetryManager.send( + "applicationClickedHome", with: ["gopherURL": "\(self.url)"]) + performGopherRequest( + host: homeURL.host ?? "gopher.navan.dev", + port: homeURL.port ?? 70, + selector: homeURL.path) + } label: { + Label("Home", systemImage: "house") + .labelStyle(.iconOnly) + } + Spacer() + Button { + if let curNode = backwardStack.popLast() { + forwardStack.append(curNode) + if let prevNode = backwardStack.popLast() { + TelemetryManager.send( + "applicationClickedBack", + with: [ + "gopherURL": + "\(prevNode.host):\(prevNode.port)\(prevNode.selector)" + ]) + performGopherRequest( + host: prevNode.host, port: prevNode.port, + selector: prevNode.selector, + clearForward: false) + } + } + } label: { + Label("Back", systemImage: "chevron.left") + .labelStyle(.iconOnly) + } + .disabled(backwardStack.count < 2) + Spacer() + Button { + if let nextNode = forwardStack.popLast() { + TelemetryManager.send( + "applicationClickedForward", + with: [ + "gopherURL": + "\(nextNode.host):\(nextNode.port)\(nextNode.selector)" + ]) + performGopherRequest( + host: nextNode.host, port: nextNode.port, + selector: nextNode.selector, + clearForward: false) + } + } label: { + Label("Forward", systemImage: "chevron.right") + .labelStyle(.iconOnly) + } + .disabled(forwardStack.isEmpty) + Spacer() + if shareThroughProxy { + ShareLink(item: URL(string: "https://gopher.navan.dev/\(url)")!) { + Label("Share", systemImage: "square.and.arrow.up").labelStyle( + .iconOnly) + } + } else { + ShareLink(item: URL(string: "gopher://\(url)")!) { + Label("Share", systemImage: "square.and.arrow.up").labelStyle( + .iconOnly) + } + } + Spacer() + // Button { + // showBookmarks = true + // } label: { + // Label("Bookmarks", systemImage: "book") + // .labelStyle(.iconOnly) + // }.sheet(isPresented: $showBookmarks) { + // BookmarksView() + // .presentationDetents([.height(400), .medium, .large]) + // .presentationDragIndicator(.automatic) + // } + // Spacer() + Button { + self.showPreferences = true + } label: { + Label("Settings", systemImage: "gear") + .labelStyle(.iconOnly) + } + Spacer() + } + } + #else + HStack(spacing: 10) { + HStack { + Spacer() + Button { + TelemetryManager.send( + "applicationClickedHome", with: ["gopherURL": "\(self.url)"]) + performGopherRequest( + host: homeURL.host ?? "gopher.navan.dev", + port: homeURL.port ?? 70, + selector: homeURL.path) + } label: { + Label("Home", systemImage: "house") + .labelStyle(.iconOnly) + } - } + #if os(visionOS) + Button { + self.showPreferences = true + } label: { + Label("Settings", systemImage: "gear") + .labelStyle(.iconOnly) + } + #endif + + Button { + if let curNode = backwardStack.popLast() { + forwardStack.append(curNode) + if let prevNode = backwardStack.popLast() { + TelemetryManager.send( + "applicationClickedBack", + with: [ + "gopherURL": + "\(prevNode.host):\(prevNode.port)\(prevNode.selector)" + ]) + performGopherRequest( + host: prevNode.host, port: prevNode.port, + selector: prevNode.selector, + clearForward: false) + } + } + } label: { + Label("Back", systemImage: "chevron.left") + .labelStyle(.iconOnly) + } + .disabled(backwardStack.count < 2) + + Button { + if let nextNode = forwardStack.popLast() { + TelemetryManager.send( + "applicationClickedForward", + with: [ + "gopherURL": + "\(nextNode.host):\(nextNode.port)\(nextNode.selector)" + ]) + performGopherRequest( + host: nextNode.host, port: nextNode.port, + selector: nextNode.selector, + clearForward: false) + } + } label: { + Label("Forward", systemImage: "chevron.right") + .labelStyle(.iconOnly) + } + .disabled(forwardStack.isEmpty) + + TextField("Enter a URL", text: $url) + #if !os(OSX) + .keyboardType(.URL) + .autocapitalization(.none) + #endif + .padding(10) + } + //.background(Color.white) + .cornerRadius(30) + if shareThroughProxy { + ShareLink(item: URL(string: "https://gopher.navan.dev/\(url)")!) { + Label("Share", systemImage: "square.and.arrow.up").labelStyle( + .iconOnly) + } + } else { + ShareLink(item: URL(string: "gopher://\(url)")!) { + Label("Share", systemImage: "square.and.arrow.up").labelStyle( + .iconOnly) + } + } + Button( + "Go", + action: { + TelemetryManager.send( + "applicationClickedGo", with: ["gopherURL": "\(self.url)"]) + performGopherRequest(clearForward: false) + } + ) + .keyboardShortcut(.defaultAction) + .onSubmit { + performGopherRequest() + } + Spacer() + } + #endif } - //.background(Color.white) - .cornerRadius(10) - .onChange(of: scrollToTop) { - // TODO: Cleanup - withAnimation { - proxy.scrollTo(0, anchor: .top) - - } + } + .onChange(of: selectedNode) { + if let node = selectedNode { + performGopherRequest(host: node.host, port: node.port, selector: node.selector) } - } - .sheet(isPresented: $showSearchInput) { - if let index = selectedSearchItem, gopherItems.indices.contains(index) { - let searchItem = gopherItems[index] - SearchInputView( - host: searchItem.host, - port: searchItem.port, - selector: searchItem.selector, - searchText: $searchText, - onSearch: { query in - performGopherRequest( - host: searchItem.host, port: searchItem.port, - selector: "\(searchItem.selector)\t\(query)") - showSearchInput = false + } + .onOpenURL { gopherURL in + self.url = gopherURL.absoluteString + performGopherRequest() + } + .sheet( + isPresented: $showPreferences, + onDismiss: { + print("badm", homeURL, homeURLString) + if let url = URL(string: homeURLString) { + self.homeURL = url } - ) - } else { + } + ) { + #if os(macOS) + SettingsView() + #else + SettingsView(homeURL: $homeURL, homeURLString: $homeURLString) + #endif + } + .accentColor(accentColour) + } - VStack { - Text("Weird bug. Please Dismiss -> Press Go -> Try Again") - Button("Dismiss") { - self.showSearchInput = false - }.onAppear { - TelemetryManager.send( - "applicationSearchError", with: ["gopherURL": "\(self.url)"]) - } - } + private func performGopherRequest( + host: String = "", port: Int = -1, selector: String = "", clearForward: Bool = true + ) { + // TODO: Remove getHostandPort call here, and call it before calling performGopherRequest + print("recieved ", host, port, selector) + var res = getHostAndPort(from: self.url) + if host != "" { + res.host = host + if selector != "" { + res.selector = selector + } else { + res.selector = "" } - } - } else { - Spacer() - Text("Welcome to iGopher Browser") - Spacer() } - #if os(iOS) - VStack { - HStack(spacing: 10) { - HStack { - Spacer() - - TextField("Enter a URL", text: $url) - .keyboardType(.URL) - .autocapitalization(.none) - .padding(10) - .background(Color.gray.opacity(0.2)) - .cornerRadius(10) - Spacer() - } - //.background(Color.white) - .cornerRadius(30) - - Button( - "Go", - action: { - TelemetryManager.send("applicationClickedGo", with: ["gopherURL": "\(self.url)"]) - performGopherRequest(clearForward: false) + if port != -1 { + res.port = port + } + + self.url = "\(res.host):\(res.port)\(res.selector)" + + currentTask?.cancel() + + let myHost = res.host + let myPort = res.port + let mySelector = res.selector + + currentTask = Task { + do { + try Task.checkCancellation() + let resp = try await client.sendRequest( + to: myHost, port: myPort, message: "\(mySelector)\r\n") + + var newNode = GopherNode( + host: myHost, port: myPort, selector: mySelector, item: nil, + children: convertToHostNodes(resp)) + + backwardStack.append(newNode) + if clearForward { + forwardStack.removeAll() } - ) - .keyboardShortcut(.defaultAction) - .onSubmit { - performGopherRequest() - } - Spacer() - } - .padding(.bottom, 10) - .padding(.top, 5) - HStack { - Spacer() - Button { - print(homeURL, "home") - TelemetryManager.send("applicationClickedHome", with: ["gopherURL": "\(self.url)"]) - performGopherRequest( - host: homeURL.host ?? "gopher.navan.dev", port: homeURL.port ?? 70, - selector: homeURL.path) - } label: { - Label("Home", systemImage: "house") - .labelStyle(.iconOnly) - } - Spacer() - Button { - if let curNode = backwardStack.popLast() { - forwardStack.append(curNode) - if let prevNode = backwardStack.popLast() { - TelemetryManager.send( - "applicationClickedBack", - with: ["gopherURL": "\(prevNode.host):\(prevNode.port)\(prevNode.selector)"]) - performGopherRequest( - host: prevNode.host, port: prevNode.port, selector: prevNode.selector, - clearForward: false) - } - } - } label: { - Label("Back", systemImage: "chevron.left") - .labelStyle(.iconOnly) - } - .disabled(backwardStack.count < 2) - Spacer() - Button { - if let nextNode = forwardStack.popLast() { - TelemetryManager.send( - "applicationClickedForward", - with: ["gopherURL": "\(nextNode.host):\(nextNode.port)\(nextNode.selector)"]) - performGopherRequest( - host: nextNode.host, port: nextNode.port, selector: nextNode.selector, - clearForward: false) - } - } label: { - Label("Forward", systemImage: "chevron.right") - .labelStyle(.iconOnly) - } - .disabled(forwardStack.isEmpty) - Spacer() - if shareThroughProxy { - ShareLink(item: URL(string: "https://gopher.navan.dev/\(url)")!) { - Label("Share", systemImage: "square.and.arrow.up").labelStyle(.iconOnly) - } - } else { - ShareLink(item: URL(string: "gopher://\(url)")!) { - Label("Share", systemImage: "square.and.arrow.up").labelStyle(.iconOnly) - } - } - Spacer() - // Button { - // showBookmarks = true - // } label: { - // Label("Bookmarks", systemImage: "book") - // .labelStyle(.iconOnly) - // }.sheet(isPresented: $showBookmarks) { - // BookmarksView() - // .presentationDetents([.height(400), .medium, .large]) - // .presentationDragIndicator(.automatic) - // } - // Spacer() - Button { - self.showPreferences = true - } label: { - Label("Settings", systemImage: "gear") - .labelStyle(.iconOnly) - } - Spacer() - } - } - #else - HStack(spacing: 10) { - HStack { - Spacer() - Button { - TelemetryManager.send("applicationClickedHome", with: ["gopherURL": "\(self.url)"]) - performGopherRequest( - host: homeURL.host ?? "gopher.navan.dev", port: homeURL.port ?? 70, - selector: homeURL.path) - } label: { - Label("Home", systemImage: "house") - .labelStyle(.iconOnly) - } - - #if os(visionOS) - Button { - self.showPreferences = true - } label: { - Label("Settings", systemImage: "gear") - .labelStyle(.iconOnly) + + if let index = self.hosts.firstIndex(where: { + $0.host == myHost && $0.port == myPort + }) { + // TODO: Handle case where first link visited is a subdirectory, should the sidebar auto fetch the rest? + hosts[index].children = hosts[index].children?.map { child in + if child.selector == newNode.selector { + newNode.message = child.message + return newNode + } else { + return child + } + } + } else { + newNode.selector = "/" + hosts.append(newNode) } - #endif - - Button { - if let curNode = backwardStack.popLast() { - forwardStack.append(curNode) - if let prevNode = backwardStack.popLast() { - TelemetryManager.send( - "applicationClickedBack", - with: ["gopherURL": "\(prevNode.host):\(prevNode.port)\(prevNode.selector)"]) - performGopherRequest( - host: prevNode.host, port: prevNode.port, selector: prevNode.selector, - clearForward: false) - } + //TODO: Fix this stupid bodge + if self.url != "\(myHost):\(myPort)\(mySelector)" { + print("Different URL being processed right now... Cancelling") + } else { + self.gopherItems = resp } - } label: { - Label("Back", systemImage: "chevron.left") - .labelStyle(.iconOnly) - } - .disabled(backwardStack.count < 2) - - Button { - if let nextNode = forwardStack.popLast() { - TelemetryManager.send( - "applicationClickedForward", - with: ["gopherURL": "\(nextNode.host):\(nextNode.port)\(nextNode.selector)"]) - performGopherRequest( - host: nextNode.host, port: nextNode.port, selector: nextNode.selector, - clearForward: false) + + } catch is CancellationError { + print("Request was cancelled") + } catch { + TelemetryManager.send( + "applicationRequestError", + with: ["gopherURL": "\(self.url)", "errorMessage": "\(error)"]) + print("Error \(error)") + var item = gopherItem(rawLine: "Error \(error)") + item.message = "Error \(error)" + if self.url != "\(myHost):\(myPort)\(mySelector)" { + print("Different URL being processed right now... Cancelling") + } else { + self.gopherItems = [item] } - } label: { - Label("Forward", systemImage: "chevron.right") - .labelStyle(.iconOnly) - } - .disabled(forwardStack.isEmpty) - - TextField("Enter a URL", text: $url) - #if !os(OSX) - .keyboardType(.URL) - .autocapitalization(.none) - #endif - .padding(10) } - //.background(Color.white) - .cornerRadius(30) - if shareThroughProxy { - ShareLink(item: URL(string: "https://gopher.navan.dev/\(url)")!) { - Label("Share", systemImage: "square.and.arrow.up").labelStyle(.iconOnly) - } - } else { - ShareLink(item: URL(string: "gopher://\(url)")!) { - Label("Share", systemImage: "square.and.arrow.up").labelStyle(.iconOnly) - } - } - Button( - "Go", - action: { - TelemetryManager.send("applicationClickedGo", with: ["gopherURL": "\(self.url)"]) - performGopherRequest(clearForward: false) - } - ) - .keyboardShortcut(.defaultAction) - .onSubmit { - performGopherRequest() - } - Spacer() - } - #endif - } - } - .onChange(of: selectedNode) { - if let node = selectedNode { - performGopherRequest(host: node.host, port: node.port, selector: node.selector) - } - } - .onOpenURL { gopherURL in - self.url = gopherURL.absoluteString - performGopherRequest() - } - .sheet( - isPresented: $showPreferences, - onDismiss: { - print("badm", homeURL, homeURLString) - if let url = URL(string: homeURLString) { - self.homeURL = url } - } - ) { - #if os(macOS) - SettingsView() - #else - SettingsView(homeURL: $homeURL, homeURLString: $homeURLString) - #endif - } - .accentColor(accentColour) - } - - private func performGopherRequest( - host: String = "", port: Int = -1, selector: String = "", clearForward: Bool = true - ) { - // TODO: Remove getHostandPort call here, and call it before calling performGopherRequest - print("recieved ", host, port, selector) - var res = getHostAndPort(from: self.url) - - if host != "" { - res.host = host - if selector != "" { - res.selector = selector - } else { - res.selector = "" - } } - if port != -1 { - res.port = port - } - - self.url = "\(res.host):\(res.port)\(res.selector)" - - currentTask?.cancel() - - let myHost = res.host - let myPort = res.port - let mySelector = res.selector - - currentTask = Task { - do { - try Task.checkCancellation() - let resp = try await client.sendRequest(to: myHost, port: myPort, message: "\(mySelector)\r\n") - - var newNode = GopherNode(host: myHost, port: myPort, selector: mySelector, item: nil, children: convertToHostNodes(resp)) - - backwardStack.append(newNode) - if clearForward { - forwardStack.removeAll() - } - - if let index = self.hosts.firstIndex(where: { $0.host == myHost && $0.port == myPort }) { - // TODO: Handle case where first link visited is a subdirectory, should the sidebar auto fetch the rest? - hosts[index].children = hosts[index].children?.map { child in - if child.selector == newNode.selector { - newNode.message = child.message - return newNode - } else { - return child - } - } - } else { - newNode.selector = "/" - hosts.append(newNode) - } - //TODO: Fix this stupid bodge - if self.url != "\(myHost):\(myPort)\(mySelector)" { - print("Different URL being processed right now... Cancelling") - } else { - self.gopherItems = resp - } - - } catch is CancellationError { - print("Request was cancelled") - } catch { - TelemetryManager.send( - "applicationRequestError", with: ["gopherURL": "\(self.url)", "errorMessage": "\(error)"]) - print("Error \(error)") - var item = gopherItem(rawLine: "Error \(error)") - item.message = "Error \(error)" - if self.url != "\(myHost):\(myPort)\(mySelector)" { - print("Different URL being processed right now... Cancelling") - } else { - self.gopherItems = [item] - } - } - } - } - - private func convertToHostNodes(_ responseItems: [gopherItem]) -> [GopherNode] { - var returnItems: [GopherNode] = [] - responseItems.forEach { item in - if item.parsedItemType != .info { - returnItems.append( - GopherNode( - host: item.host, port: item.port, selector: item.selector, message: item.message, - item: item, children: nil)) - //print("found: \(item.message)") - } + private func convertToHostNodes(_ responseItems: [gopherItem]) -> [GopherNode] { + var returnItems: [GopherNode] = [] + responseItems.forEach { item in + if item.parsedItemType != .info { + returnItems.append( + GopherNode( + host: item.host, port: item.port, selector: item.selector, + message: item.message, + item: item, children: nil)) + //print("found: \(item.message)") + } + } + return returnItems } - return returnItems - } } diff --git a/iGopherBrowser/ContentView.swift b/iGopherBrowser/ContentView.swift index 06ad2a6..f65c786 100644 --- a/iGopherBrowser/ContentView.swift +++ b/iGopherBrowser/ContentView.swift @@ -6,50 +6,50 @@ // import GopherHelpers -import SwiftUI import SwiftGopherClient +import SwiftUI struct GopherNode: Identifiable, Equatable { - static func == (lhs: GopherNode, rhs: GopherNode) -> Bool { - return lhs.host == rhs.host && lhs.port == rhs.port && lhs.selector == rhs.selector - } - - let id = UUID() - var host: String - let port: Int - var selector: String - var message: String? - let item: gopherItem? - var children: [GopherNode]? + static func == (lhs: GopherNode, rhs: GopherNode) -> Bool { + return lhs.host == rhs.host && lhs.port == rhs.port && lhs.selector == rhs.selector + } + + let id = UUID() + var host: String + let port: Int + var selector: String + var message: String? + let item: gopherItem? + var children: [GopherNode]? } struct ContentView: View { - @State public var hosts: [GopherNode] = [] - @State private var selectedNode: GopherNode? + @State public var hosts: [GopherNode] = [] + @State private var selectedNode: GopherNode? - @State private var columnVisibility = NavigationSplitViewVisibility.automatic + @State private var columnVisibility = NavigationSplitViewVisibility.automatic - var body: some View { + var body: some View { - #if os(iOS) - BrowserView(hosts: $hosts, selectedNode: $selectedNode) - #else + #if os(iOS) + BrowserView(hosts: $hosts, selectedNode: $selectedNode) + #else - NavigationSplitView(columnVisibility: $columnVisibility) { - SidebarView( - hosts: hosts, - onSelect: { node in - selectedNode = node + NavigationSplitView(columnVisibility: $columnVisibility) { + SidebarView( + hosts: hosts, + onSelect: { node in + selectedNode = node - } - ) - .listStyle(.sidebar) - } detail: { - BrowserView(hosts: $hosts, selectedNode: $selectedNode) - } - #endif - } + } + ) + .listStyle(.sidebar) + } detail: { + BrowserView(hosts: $hosts, selectedNode: $selectedNode) + } + #endif + } } diff --git a/iGopherBrowser/FileView.swift b/iGopherBrowser/FileView.swift index 0e05d3d..b1fc0e1 100644 --- a/iGopherBrowser/FileView.swift +++ b/iGopherBrowser/FileView.swift @@ -7,152 +7,154 @@ import Foundation import GopherHelpers import QuickLook +import SwiftGopherClient import SwiftUI import TelemetryClient -import SwiftGopherClient func determineFileType(data: Data) -> String? { - let signatures: [Data: String] = [ - Data([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]): "png", - Data([0xFF, 0xD8, 0xFF]): "jpeg", - Data("GIF87a".utf8): "gif", - Data("GIF89a".utf8): "gif", - Data("BM".utf8): "bmp", - Data("%PDF-".utf8): "pdf", - Data([0x50, 0x4B, 0x03, 0x04]): "docx", - Data([0x50, 0x4B, 0x05, 0x06]): "docx", - Data([0x50, 0x4B, 0x07, 0x08]): "docx", - Data([0x49, 0x44, 0x33]): "mp3", - Data([0x52, 0x49, 0x46, 0x46]): "wav", - Data([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70]): "mp4", - Data([0x6D, 0x6F, 0x6F, 0x76]): "mov", - // Add other file signatures as needed - ] + let signatures: [Data: String] = [ + Data([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]): "png", + Data([0xFF, 0xD8, 0xFF]): "jpeg", + Data("GIF87a".utf8): "gif", + Data("GIF89a".utf8): "gif", + Data("BM".utf8): "bmp", + Data("%PDF-".utf8): "pdf", + Data([0x50, 0x4B, 0x03, 0x04]): "docx", + Data([0x50, 0x4B, 0x05, 0x06]): "docx", + Data([0x50, 0x4B, 0x07, 0x08]): "docx", + Data([0x49, 0x44, 0x33]): "mp3", + Data([0x52, 0x49, 0x46, 0x46]): "wav", + Data([0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70]): "mp4", + Data([0x6D, 0x6F, 0x6F, 0x76]): "mov", + // Add other file signatures as needed + ] - // Debug file signatures - // for byte in data.prefix(10) { - // print(String(format: "%02x", byte), terminator: " ") - // } + // Debug file signatures + // for byte in data.prefix(10) { + // print(String(format: "%02x", byte), terminator: " ") + // } - // Check for each signature - for (signature, fileType) in signatures { - if data.starts(with: signature) { - return fileType + // Check for each signature + for (signature, fileType) in signatures { + if data.starts(with: signature) { + return fileType + } } - } - return nil + return nil } struct FileView: View { - var item: gopherItem - let client = GopherClient() - @State private var fileContent: [String] = [] - @State private var fileURL: URL? - @State private var QLURL: URL? + var item: gopherItem + let client = GopherClient() + @State private var fileContent: [String] = [] + @State private var fileURL: URL? + @State private var QLURL: URL? - var body: some View { - if item.parsedItemType == .text { - GeometryReader { geometry in - ScrollView { - LazyVStack(alignment: .leading, spacing: 0) { - ForEach(fileContent.indices, id: \.self) { index in - Text(fileContent[index]) - .font(.system(.body, design: .monospaced)) - .textSelection(.enabled) - .padding(.vertical, 2) - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - .task { - readFile(item) - } - .listStyle(PlainListStyle()) - } - } else if [.doc, .image, .gif, .movie, .sound, .bitmap].contains(item.parsedItemType) { // Preview Document: .pdf, .docx, e.t.c - // Quicklook - if let url = fileURL { - Button("Preview Document") { - print(url) - QLURL = url - }.quickLookPreview($QLURL) - } else { - Text("Loading Document...") - .onAppear { - readFile(item) - } - } + var body: some View { + if item.parsedItemType == .text { + GeometryReader { geometry in + ScrollView { + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(fileContent.indices, id: \.self) { index in + Text(fileContent[index]) + .font(.system(.body, design: .monospaced)) + .textSelection(.enabled) + .padding(.vertical, 2) + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .task { + readFile(item) + } + .listStyle(PlainListStyle()) + } + } else if [.doc, .image, .gif, .movie, .sound, .bitmap].contains(item.parsedItemType) { // Preview Document: .pdf, .docx, e.t.c + // Quicklook + if let url = fileURL { + Button("Preview Document") { + print(url) + QLURL = url + }.quickLookPreview($QLURL) + } else { + Text("Loading Document...") + .onAppear { + readFile(item) + } + } + } } - } - private func readFile(_ item: gopherItem) { - self.client.sendRequest(to: item.host, port: item.port, message: "\(item.selector)\r\n") { - result in - // Dispatch the result handling back to the main thread - switch result { - case .success(let resp): - if var data = resp.first?.rawData { - let tempDirURL = FileManager.default.temporaryDirectory + private func readFile(_ item: gopherItem) { + self.client.sendRequest(to: item.host, port: item.port, message: "\(item.selector)\r\n") { + result in + // Dispatch the result handling back to the main thread + switch result { + case .success(let resp): + if var data = resp.first?.rawData { + let tempDirURL = FileManager.default.temporaryDirectory - do { - var fileData = Data() - print("Readable byees", data.readableBytes) - while data.readableBytes > 0 { - if let bytes = data.readBytes(length: data.readableBytes) { - fileData.append(contentsOf: bytes) - } - } - print("Read entire file") - if item.parsedItemType == .text { - print("parsing string file") - if let string = String(data: fileData, encoding: .utf8) { - print("updating state") - let lines = string.components(separatedBy: .newlines) - let chunkSize = 100 - self.fileContent = stride(from: 0, to: lines.count, by: chunkSize).map { - lines[$0..<min($0 + chunkSize, lines.count)].joined(separator: "\n") - } - } else { - print("Could not get file") - } - return - } - let fileURL = tempDirURL.appendingPathComponent( - UUID().uuidString + ".\(determineFileType(data: fileData) ?? "unkown")") - print(fileURL) + do { + var fileData = Data() + print("Readable byees", data.readableBytes) + while data.readableBytes > 0 { + if let bytes = data.readBytes(length: data.readableBytes) { + fileData.append(contentsOf: bytes) + } + } + print("Read entire file") + if item.parsedItemType == .text { + print("parsing string file") + if let string = String(data: fileData, encoding: .utf8) { + print("updating state") + let lines = string.components(separatedBy: .newlines) + let chunkSize = 100 + self.fileContent = stride(from: 0, to: lines.count, by: chunkSize) + .map { + lines[$0..<min($0 + chunkSize, lines.count)].joined( + separator: "\n") + } + } else { + print("Could not get file") + } + return + } + let fileURL = tempDirURL.appendingPathComponent( + UUID().uuidString + ".\(determineFileType(data: fileData) ?? "unkown")") + print(fileURL) - if determineFileType(data: fileData) == nil { - TelemetryManager.send( - "applicationUnableToDetectFiletype", - with: ["gopherURL": "\(item.host):\(item.port)\(item.selector)"]) + if determineFileType(data: fileData) == nil { + TelemetryManager.send( + "applicationUnableToDetectFiletype", + with: ["gopherURL": "\(item.host):\(item.port)\(item.selector)"]) + } + + try fileData.write(to: fileURL) + self.fileURL = fileURL + } catch { + print("Error writing file to temp directory: \(error)") + } + } + case .failure(_): + self.fileContent = ["Unable to fetch file due to network error."] } + } + + } + private func getTempFileURL(_ data: [UInt8]) -> URL? { + let tempDirURL = FileManager.default.temporaryDirectory + let fileURL = tempDirURL.appendingPathComponent(UUID().uuidString + ".pdf") + print(fileURL) + do { + let fileData = Data(data) try fileData.write(to: fileURL) - self.fileURL = fileURL - } catch { + return fileURL + } catch { print("Error writing file to temp directory: \(error)") - } + return nil } - case .failure(_): - self.fileContent = ["Unable to fetch file due to network error."] - } - } - - } - - private func getTempFileURL(_ data: [UInt8]) -> URL? { - let tempDirURL = FileManager.default.temporaryDirectory - let fileURL = tempDirURL.appendingPathComponent(UUID().uuidString + ".pdf") - print(fileURL) - do { - let fileData = Data(data) - try fileData.write(to: fileURL) - return fileURL - } catch { - print("Error writing file to temp directory: \(error)") - return nil } - } } diff --git a/iGopherBrowser/Helpers.swift b/iGopherBrowser/Helpers.swift index decc97e..521ffab 100644 --- a/iGopherBrowser/Helpers.swift +++ b/iGopherBrowser/Helpers.swift @@ -9,36 +9,36 @@ import Foundation import SwiftGopherClient public func getHostAndPort( - from urlString: String, defaultPort: Int = 70, defaultHost: String = "gopher.navan.dev" + from urlString: String, defaultPort: Int = 70, defaultHost: String = "gopher.navan.dev" ) -> (host: String, port: Int, selector: String) { - if let urlComponents = URLComponents(string: urlString), - let host = urlComponents.host - { - let port = urlComponents.port ?? defaultPort - let selector = urlComponents.path - print("Mainmain, ", urlComponents, host, port, selector) - return (host, port, selector) - } else { - // Fallback for simpler formats like "localhost:8080" - let components = urlString.split(separator: ":") - let host = components.first.map(String.init) ?? defaultHost + if let urlComponents = URLComponents(string: urlString), + let host = urlComponents.host + { + let port = urlComponents.port ?? defaultPort + let selector = urlComponents.path + print("Mainmain, ", urlComponents, host, port, selector) + return (host, port, selector) + } else { + // Fallback for simpler formats like "localhost:8080" + let components = urlString.split(separator: ":") + let host = components.first.map(String.init) ?? defaultHost - var port = (components.count > 1 ? Int(components[1]) : nil) ?? defaultPort - var selector = "/" + var port = (components.count > 1 ? Int(components[1]) : nil) ?? defaultPort + var selector = "/" - if components.count > 1 { - let portCompString = components[1] - let portCompComponents = portCompString.split(separator: "/", maxSplits: 1) - if portCompComponents.count > 1 { - port = Int(portCompComponents[0]) ?? defaultPort - selector = "/" + portCompComponents[1] + if components.count > 1 { + let portCompString = components[1] + let portCompComponents = portCompString.split(separator: "/", maxSplits: 1) + if portCompComponents.count > 1 { + port = Int(portCompComponents[0]) ?? defaultPort + selector = "/" + portCompComponents[1] - } else if portCompComponents.count == 1 { - port = Int(portCompComponents[0]) ?? defaultPort - } - } + } else if portCompComponents.count == 1 { + port = Int(portCompComponents[0]) ?? defaultPort + } + } - print("Else Else", components, host, port, selector) - return (host, port, selector) - } + print("Else Else", components, host, port, selector) + return (host, port, selector) + } } diff --git a/iGopherBrowser/SearchInputView.swift b/iGopherBrowser/SearchInputView.swift index 54e36c5..f85b94a 100644 --- a/iGopherBrowser/SearchInputView.swift +++ b/iGopherBrowser/SearchInputView.swift @@ -9,33 +9,33 @@ import SwiftUI struct SearchInputView: View { - var host: String - var port: Int - var selector: String - @Binding var searchText: String - var onSearch: (String) -> Void + var host: String + var port: Int + var selector: String + @Binding var searchText: String + var onSearch: (String) -> Void - @Environment(\.presentationMode) var presentationMode + @Environment(\.presentationMode) var presentationMode - var body: some View { - VStack { - Text("Enter your query") - TextField("Search", text: $searchText) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .padding() - HStack { + var body: some View { + VStack { + Text("Enter your query") + TextField("Search", text: $searchText) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding() + HStack { - Button("Cancel") { - presentationMode.wrappedValue.dismiss() - } - .padding() + Button("Cancel") { + presentationMode.wrappedValue.dismiss() + } + .padding() - Button("Search") { - onSearch(searchText) + Button("Search") { + onSearch(searchText) + } + .padding() + } } .padding() - } } - .padding() - } } diff --git a/iGopherBrowser/SettingsView.swift b/iGopherBrowser/SettingsView.swift index e3e1daf..1f48bad 100644 --- a/iGopherBrowser/SettingsView.swift +++ b/iGopherBrowser/SettingsView.swift @@ -9,149 +9,152 @@ import SwiftUI extension Color: RawRepresentable { - public init?(rawValue: String) { + public init?(rawValue: String) { - guard let data = Data(base64Encoded: rawValue) else { - self = .black - return - } + guard let data = Data(base64Encoded: rawValue) else { + self = .black + return + } + + do { + #if os(macOS) + let color = + try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSColor + ?? .black + #else + let color = + try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) + ?? .black + #endif + self = Color(color) + } catch { + self = .black + } - do { - #if os(macOS) - let color = - try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSColor ?? .black - #else - let color = - try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) ?? .black - #endif - self = Color(color) - } catch { - self = .black } - } + public var rawValue: String { - public var rawValue: String { + do { + #if os(macOS) + let data = + try NSKeyedArchiver.archivedData( + withRootObject: NSColor(self), requiringSecureCoding: false) as Data + #else + let data = + try NSKeyedArchiver.archivedData( + withRootObject: UIColor(self), requiringSecureCoding: false) as Data + #endif + return data.base64EncodedString() - do { - #if os(macOS) - let data = - try NSKeyedArchiver.archivedData( - withRootObject: NSColor(self), requiringSecureCoding: false) as Data - #else - let data = - try NSKeyedArchiver.archivedData( - withRootObject: UIColor(self), requiringSecureCoding: false) as Data - #endif - return data.base64EncodedString() + } catch { - } catch { + return "" - return "" + } } - } - } struct SettingsView: View { - @AppStorage("accentColour", store: .standard) var accentColour: Color = Color(.blue) - @AppStorage("linkColour", store: .standard) var linkColour: Color = Color(.white) - @AppStorage("shareThroughProxy", store: .standard) var shareThroughProxy: Bool = true - - #if os(macOS) - @AppStorage("homeURL") var homeURL: URL = URL(string: "gopher://gopher.navan.dev:70/")! - @State var homeURLString: String = "" - #else - @Binding var homeURL: URL - @Binding var homeURLString: String - #endif - @State private var showAlert = false - @State private var alertMessage: String = "" - @Environment(\.dismiss) var dismiss - - var body: some View { - Form { - Section(header: Text("Preferences")) { - VStack { - TextField("Home URL", text: $homeURLString) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .disableAutocorrection(true) - // .onAppear { - // // Convert URL to String when the view appears - // self.homeURLString = homeURL.absoluteString - // } - .onSubmit { - // Convert String back to URL when the user submits the text - if let url = URL(string: homeURLString) { - self.homeURL = url - } + @AppStorage("accentColour", store: .standard) var accentColour: Color = Color(.blue) + @AppStorage("linkColour", store: .standard) var linkColour: Color = Color(.white) + @AppStorage("shareThroughProxy", store: .standard) var shareThroughProxy: Bool = true + + #if os(macOS) + @AppStorage("homeURL") var homeURL: URL = URL(string: "gopher://gopher.navan.dev:70/")! + @State var homeURLString: String = "" + #else + @Binding var homeURL: URL + @Binding var homeURLString: String + #endif + @State private var showAlert = false + @State private var alertMessage: String = "" + @Environment(\.dismiss) var dismiss + + var body: some View { + Form { + Section(header: Text("Preferences")) { + VStack { + TextField("Home URL", text: $homeURLString) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .disableAutocorrection(true) + // .onAppear { + // // Convert URL to String when the view appears + // self.homeURLString = homeURL.absoluteString + // } + .onSubmit { + // Convert String back to URL when the user submits the text + if let url = URL(string: homeURLString) { + self.homeURL = url + } + } + HStack { + Button( + "Save", + action: { + if let url = URL(string: homeURLString) { + homeURL = url + print("Saved \(self.homeURL)") + #if os(iOS) + dismiss() + #endif + } else { + self.alertMessage = + "Unable to convert \(homeURLString) to a URL" + self.showAlert = true + } + }) + Button( + "Reset Preferences", + action: { + self.homeURL = URL(string: "gopher://gopher.navan.dev:70/")! + #if os(iOS) + dismiss() + #endif + }) + } + } + } + Section(header: Text("UI Settings")) { + ColorPicker("Link Colour", selection: $linkColour) + ColorPicker("Accent Colour", selection: $accentColour) + Button("Reset Colours") { + self.linkColour = Color(.white) + self.accentColour = Color(.blue) + } + } + + Section { + Toggle("Share links through HTTP(s) proxy", isOn: $shareThroughProxy) + .toggleStyle(.switch) + } header: { + Text("Share Settings") + } footer: { + Text( + "Enabling this option shares Gopher URLs through an HTTP proxy, allowing people to view the page without needing a Gopher client" + ) + .font(.caption) + .foregroundColor(.gray) } - HStack { - Button( - "Save", - action: { - if let url = URL(string: homeURLString) { - homeURL = url - print("Saved \(self.homeURL)") - #if os(iOS) + #if os(visionOS) + Button("Done") { dismiss() - #endif - } else { - self.alertMessage = "Unable to convert \(homeURLString) to a URL" - self.showAlert = true } - }) - Button( - "Reset Preferences", - action: { - self.homeURL = URL(string: "gopher://gopher.navan.dev:70/")! - #if os(iOS) - dismiss() - #endif - }) - } - } - } - Section(header: Text("UI Settings")) { - ColorPicker("Link Colour", selection: $linkColour) - ColorPicker("Accent Colour", selection: $accentColour) - Button("Reset Colours") { - self.linkColour = Color(.white) - self.accentColour = Color(.blue) + #endif } - } - - Section { - Toggle("Share links through HTTP(s) proxy", isOn: $shareThroughProxy) - .toggleStyle(.switch) - } header: { - Text("Share Settings") - } footer: { - Text( - "Enabling this option shares Gopher URLs through an HTTP proxy, allowing people to view the page without needing a Gopher client" - ) - .font(.caption) - .foregroundColor(.gray) - } - #if os(visionOS) - Button("Done") { - dismiss() + #if os(OSX) + .padding(20) + .frame(width: 350, height: 350) + #endif + .alert(isPresented: $showAlert) { + Alert( + title: Text("Error Saving"), + message: Text(alertMessage), + dismissButton: .default(Text("Got it!")) + ) } - #endif - } - #if os(OSX) - .padding(20) - .frame(width: 350, height: 350) - #endif - .alert(isPresented: $showAlert) { - Alert( - title: Text("Error Saving"), - message: Text(alertMessage), - dismissButton: .default(Text("Got it!")) - ) } - } } diff --git a/iGopherBrowser/SidebarView.swift b/iGopherBrowser/SidebarView.swift index f19670f..7d3f9c3 100644 --- a/iGopherBrowser/SidebarView.swift +++ b/iGopherBrowser/SidebarView.swift @@ -9,18 +9,18 @@ import Foundation import SwiftUI struct SidebarView: View { - let hosts: [GopherNode] - var onSelect: (GopherNode) -> Void + let hosts: [GopherNode] + var onSelect: (GopherNode) -> Void - var body: some View { - VStack { - List(hosts, children: \.children) { node in - Text(node.message ?? node.host) - .onTapGesture { - onSelect(node) - } - } + var body: some View { + VStack { + List(hosts, children: \.children) { node in + Text(node.message ?? node.host) + .onTapGesture { + onSelect(node) + } + } + } + .navigationTitle("Your Gophertree") } - .navigationTitle("Your Gophertree") - } } diff --git a/iGopherBrowser/iGopherBrowserApp.swift b/iGopherBrowser/iGopherBrowserApp.swift index e18d6f5..f9a1d7b 100644 --- a/iGopherBrowser/iGopherBrowserApp.swift +++ b/iGopherBrowser/iGopherBrowserApp.swift @@ -10,27 +10,27 @@ import TelemetryClient @main struct iGopherBrowserApp: App { - var body: some Scene { - WindowGroup { - ContentView() + var body: some Scene { + WindowGroup { + ContentView() + } + .commands { + #if os(macOS) + SidebarCommands() + #endif + } + #if os(macOS) + Settings { + SettingsView() + } + #endif } - .commands { - #if os(macOS) - SidebarCommands() - #endif - } - #if os(macOS) - Settings { - SettingsView() - } - #endif - } - init() { - let configuration = TelemetryManagerConfiguration( - appID: "400187ED-ADA9-4AB4-91F8-8825AD8FC67C") - TelemetryManager.initialize(with: configuration) + init() { + let configuration = TelemetryManagerConfiguration( + appID: "400187ED-ADA9-4AB4-91F8-8825AD8FC67C") + TelemetryManager.initialize(with: configuration) - TelemetryManager.send("applicationDidFinishLaunching") - } + TelemetryManager.send("applicationDidFinishLaunching") + } } |