diff options
-rw-r--r-- | iGopherBrowser.xcodeproj/project.pbxproj | 37 | ||||
-rw-r--r-- | iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 32 | ||||
-rw-r--r-- | iGopherBrowser/ContentView.swift | 176 | ||||
-rw-r--r-- | iGopherBrowser/SidebarView.swift | 23 | ||||
-rw-r--r-- | iGopherBrowser/iGopherBrowser.entitlements | 2 |
5 files changed, 236 insertions, 34 deletions
diff --git a/iGopherBrowser.xcodeproj/project.pbxproj b/iGopherBrowser.xcodeproj/project.pbxproj index 7e025a9..346e488 100644 --- a/iGopherBrowser.xcodeproj/project.pbxproj +++ b/iGopherBrowser.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -12,6 +12,8 @@ 3E1BCC5A2B297E9B00A4CB69 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1BCC592B297E9B00A4CB69 /* Item.swift */; }; 3E1BCC5C2B297E9C00A4CB69 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E1BCC5B2B297E9C00A4CB69 /* Assets.xcassets */; }; 3E1BCC602B297E9C00A4CB69 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E1BCC5F2B297E9C00A4CB69 /* Preview Assets.xcassets */; }; + 3E1BCC842B298A9B00A4CB69 /* SwiftGopherClient in Frameworks */ = {isa = PBXBuildFile; productRef = 3E1BCC832B298A9B00A4CB69 /* SwiftGopherClient */; }; + 3E1BCC862B299E9F00A4CB69 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1BCC852B299E9F00A4CB69 /* SidebarView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -23,6 +25,7 @@ 3E1BCC5D2B297E9C00A4CB69 /* iGopherBrowser.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iGopherBrowser.entitlements; sourceTree = "<group>"; }; 3E1BCC5F2B297E9C00A4CB69 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; 3E1BCC612B297E9C00A4CB69 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 3E1BCC852B299E9F00A4CB69 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -30,6 +33,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3E1BCC842B298A9B00A4CB69 /* SwiftGopherClient in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -41,6 +45,7 @@ children = ( 3E1BCC542B297E9B00A4CB69 /* iGopherBrowser */, 3E1BCC532B297E9B00A4CB69 /* Products */, + 3E1BCC7A2B29885900A4CB69 /* Frameworks */, ); sourceTree = "<group>"; }; @@ -62,6 +67,7 @@ 3E1BCC5D2B297E9C00A4CB69 /* iGopherBrowser.entitlements */, 3E1BCC612B297E9C00A4CB69 /* Info.plist */, 3E1BCC5E2B297E9C00A4CB69 /* Preview Content */, + 3E1BCC852B299E9F00A4CB69 /* SidebarView.swift */, ); path = iGopherBrowser; sourceTree = "<group>"; @@ -74,6 +80,13 @@ path = "Preview Content"; sourceTree = "<group>"; }; + 3E1BCC7A2B29885900A4CB69 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = "<group>"; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -90,6 +103,9 @@ dependencies = ( ); name = iGopherBrowser; + packageProductDependencies = ( + 3E1BCC832B298A9B00A4CB69 /* SwiftGopherClient */, + ); productName = iGopherBrowser; productReference = 3E1BCC522B297E9B00A4CB69 /* iGopherBrowser.app */; productType = "com.apple.product-type.application"; @@ -118,6 +134,9 @@ Base, ); mainGroup = 3E1BCC492B297E9B00A4CB69; + packageReferences = ( + 3E1BCC802B298A4700A4CB69 /* XCLocalSwiftPackageReference "../swift-gopher" */, + ); productRefGroup = 3E1BCC532B297E9B00A4CB69 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -145,6 +164,7 @@ buildActionMask = 2147483647; files = ( 3E1BCC582B297E9B00A4CB69 /* ContentView.swift in Sources */, + 3E1BCC862B299E9F00A4CB69 /* SidebarView.swift in Sources */, 3E1BCC5A2B297E9B00A4CB69 /* Item.swift in Sources */, 3E1BCC562B297E9B00A4CB69 /* iGopherBrowserApp.swift in Sources */, ); @@ -367,6 +387,21 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 3E1BCC802B298A4700A4CB69 /* XCLocalSwiftPackageReference "../swift-gopher" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../swift-gopher"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 3E1BCC832B298A9B00A4CB69 /* SwiftGopherClient */ = { + isa = XCSwiftPackageProductDependency; + package = 3E1BCC802B298A4700A4CB69 /* XCLocalSwiftPackageReference "../swift-gopher" */; + productName = SwiftGopherClient; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 3E1BCC4A2B297E9B00A4CB69 /* Project object */; } diff --git a/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..85a7d8c --- /dev/null +++ b/iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307", + "version" : "1.0.5" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio", + "state" : { + "revision" : "702cd7c56d5d44eeba73fdf83918339b26dc855c", + "version" : "2.62.0" + } + } + ], + "version" : 2 +} diff --git a/iGopherBrowser/ContentView.swift b/iGopherBrowser/ContentView.swift index a53473d..df4cbc6 100644 --- a/iGopherBrowser/ContentView.swift +++ b/iGopherBrowser/ContentView.swift @@ -8,59 +8,169 @@ import SwiftUI import SwiftData +import swiftGopherClient + +struct GopherNode: Identifiable { + let id = UUID() + var host: String + let port: Int + var selector: String + var message: String? + let item: gopherItem? + var children: [GopherNode]? +} + struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] + + @State var url: String = "" + @State private var gopherItems: [gopherItem] = [] + @State public var hosts: [GopherNode] = [] + + let client = GopherClient() var body: some View { - NavigationSplitView { - List { - ForEach(items) { item in - NavigationLink { - Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") - } label: { - Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) + NavigationView { + + SidebarView(hosts: hosts, onSelect: { node in + performGopherRequest(host: node.host, port: node.port, selector: node.selector) + + }) + .listStyle(SidebarListStyle()) + + ZStack(alignment: .bottom) { + + + + VStack(spacing: 0) { + 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) + .listRowInsets(EdgeInsets()) + .listRowSeparator(.hidden) + } else { + Text(item.message) + .onTapGesture { + performGopherRequest(host: item.host, port: item.port, selector: item.selector) + } + + } + } } - } - .onDelete(perform: deleteItems) - } -#if os(macOS) - .navigationSplitViewColumnWidth(min: 180, ideal: 200) -#endif - .toolbar { + .background(Color.white) + .cornerRadius(10) + HStack(spacing: 10) { + HStack { + TextField("Enter a URL", text: $url) #if os(iOS) - ToolbarItem(placement: .navigationBarTrailing) { - EditButton() - } + .keyboardType(.URL) + .autocapitalization(.none) #endif - ToolbarItem { - Button(action: addItem) { - Label("Add Item", systemImage: "plus") + .padding(10) + Spacer() + } + //.background(Color.white) + .cornerRadius(30) + + Button("Go", action: { + performGopherRequest() + }) + .keyboardShortcut(.defaultAction) + .onSubmit { + performGopherRequest() + } + .padding(10) } } } - } detail: { - Text("Select an item") } } - - private func addItem() { - withAnimation { - let newItem = Item(timestamp: Date()) - modelContext.insert(newItem) + + public func getHostAndPort(from urlString: String, defaultPort: Int = 70, defaultHost: String = "gopher.navan.dev") -> (host: String, port: Int) { + if let urlComponents = URLComponents(string: urlString), + let host = urlComponents.host { + let port = urlComponents.port ?? defaultPort + return (host, port) + } else { + // Fallback for simpler formats like "localhost:8080" + let components = urlString.split(separator: ":") + let host = components.first.map(String.init) ?? defaultHost + let port = (components.count > 1 ? Int(components[1]) : nil) ?? defaultPort + return (host, port) + } + } + + private func performGopherRequest(host: String = "", port: Int = -1, selector: String = "") { + + var res = getHostAndPort(from: self.url) + + if host != "" { + res.host = host + } + + if port != -1 { + res.port = port + } + + self.url = "\(res.host):\(res.port)\(selector)" + + client.sendRequest(to: res.host, port: res.port, message: "\(selector)\r\n") { result in + DispatchQueue.main.async { + switch result { + case .success(let resp): + print(resp) + var newNode = GopherNode(host: res.host, port: res.port, selector: selector, item: nil, children: convertToHostNodes(resp)) + print(newNode.selector) + if let index = self.hosts.firstIndex(where: { $0.host == res.host && $0.port == res.port }) { + if newNode.selector == "" || newNode.selector == "/" { + print("do something") + } else { + print("parent already exists") + //hosts[index] = newNode + hosts[index].children = hosts[index].children?.map { child in + if child.selector == newNode.selector { + newNode.message = child.message + return newNode + } else { + return child + } + } + } + } else { + hosts.append(newNode) + print("created new") + } + self.gopherItems = resp + + case .failure(let error): + print("Error \(error)") + var item = gopherItem(rawLine: "Error \(error)") + item.message = "Error \(error)" + self.gopherItems = [item] + } + } } } + - private func deleteItems(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(items[index]) - } +} + +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 } #Preview { ContentView() - .modelContainer(for: Item.self, inMemory: true) + //.modelContainer(for: Item.self, inMemory: true) } diff --git a/iGopherBrowser/SidebarView.swift b/iGopherBrowser/SidebarView.swift new file mode 100644 index 0000000..61c1360 --- /dev/null +++ b/iGopherBrowser/SidebarView.swift @@ -0,0 +1,23 @@ +// +// SidebarView.swift +// iGopherBrowser +// +// Created by Navan Chauhan on 12/13/23. +// + +import Foundation +import SwiftUI + +struct SidebarView: View { + let hosts: [GopherNode] + var onSelect: (GopherNode) -> Void + + var body: some View { + List(hosts, children: \.children) { node in + Text(node.message ?? node.host) + .onTapGesture { + onSelect(node) + } + } + } +} diff --git a/iGopherBrowser/iGopherBrowser.entitlements b/iGopherBrowser/iGopherBrowser.entitlements index 53000f3..068b8e8 100644 --- a/iGopherBrowser/iGopherBrowser.entitlements +++ b/iGopherBrowser/iGopherBrowser.entitlements @@ -16,5 +16,7 @@ <true/> <key>com.apple.security.files.user-selected.read-only</key> <true/> + <key>com.apple.security.network.client</key> + <true/> </dict> </plist> |