aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--iGopherBrowser.xcodeproj/project.pbxproj37
-rw-r--r--iGopherBrowser.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved32
-rw-r--r--iGopherBrowser/ContentView.swift176
-rw-r--r--iGopherBrowser/SidebarView.swift23
-rw-r--r--iGopherBrowser/iGopherBrowser.entitlements2
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>