summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2024-07-27 17:33:53 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2024-07-27 17:33:53 -0600
commita6fd12f4562bfa0247c483b4b0ffabb6e7f64957 (patch)
treebc4bbf2c4f4e9e0e19fa26b4ebf733a8cafda3f0
initial commitHEADmaster
-rw-r--r--.gitignore8
-rw-r--r--Package.resolved78
-rw-r--r--Package.swift27
-rw-r--r--Sources/iGopherBrowserGTK/BrowserView.swift179
-rw-r--r--Sources/iGopherBrowserGTK/ToolbarView.swift38
-rw-r--r--Sources/iGopherBrowserGTK/iGopherBrowserGTK.swift21
-rw-r--r--Tests/iGopherBrowserGTKTests/iGopherBrowserGTKTests.swift12
7 files changed, 363 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0023a53
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+/.build
+/Packages
+xcuserdata/
+DerivedData/
+.swiftpm/configuration/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 0000000..c0ef7fd
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,78 @@
+{
+ "originHash" : "0b8a8a31d2e41ebf9bd9991cc9065d0de7fbdbd08bbf046bb2b353e6f2e45fb2",
+ "pins" : [
+ {
+ "identity" : "adwaita",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/AparokshaUI/Adwaita",
+ "state" : {
+ "revision" : "c82957e2398a766458ff22129db26b46de75248b",
+ "version" : "0.2.6"
+ }
+ },
+ {
+ "identity" : "levenshteintransformations",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/david-swift/LevenshteinTransformations",
+ "state" : {
+ "revision" : "c5665914a26d3f96e1b724acc7254b344acf4d05",
+ "version" : "0.1.2"
+ }
+ },
+ {
+ "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" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d",
+ "version" : "1.1.2"
+ }
+ },
+ {
+ "identity" : "swift-gopher",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/navanchauhan/swift-gopher.git",
+ "state" : {
+ "branch" : "master",
+ "revision" : "b1d4c766f4bdd0f5c37d72941a73b4f8b6a3272c"
+ }
+ },
+ {
+ "identity" : "swift-nio",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-nio",
+ "state" : {
+ "revision" : "e4abde8be0e49dc7d66e6eed651254accdcd9533",
+ "version" : "2.69.0"
+ }
+ },
+ {
+ "identity" : "swift-nio-transport-services",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-nio-transport-services.git",
+ "state" : {
+ "revision" : "38ac8221dd20674682148d6451367f89c2652980",
+ "version" : "1.21.0"
+ }
+ },
+ {
+ "identity" : "swift-system",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-system.git",
+ "state" : {
+ "revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5",
+ "version" : "1.3.2"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..b567f78
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,27 @@
+// swift-tools-version: 5.10
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "iGopherBrowserGTK",
+ platforms: [
+ .macOS("10.15")
+ ],
+ dependencies: [
+ .package(url: "https://github.com/AparokshaUI/Adwaita", from: "0.2.0"),
+ .package(url: "https://github.com/navanchauhan/swift-gopher.git", branch: "master")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package, defining a module or a test suite.
+ // Targets can depend on other targets in this package and products from dependencies.
+ .executableTarget(
+ name: "iGopherBrowserGTK", dependencies: [
+ .product(name: "Adwaita", package: "Adwaita"),
+ .product(name: "SwiftGopherClient", package: "swift-gopher")
+ ]),
+ .testTarget(
+ name: "iGopherBrowserGTKTests",
+ dependencies: ["iGopherBrowserGTK"]),
+ ]
+)
diff --git a/Sources/iGopherBrowserGTK/BrowserView.swift b/Sources/iGopherBrowserGTK/BrowserView.swift
new file mode 100644
index 0000000..dce6d85
--- /dev/null
+++ b/Sources/iGopherBrowserGTK/BrowserView.swift
@@ -0,0 +1,179 @@
+//
+// BrowserView.swift
+//
+//
+// Created by Navan Chauhan on 7/27/24.
+//
+
+import Adwaita
+import Foundation
+import GopherHelpers
+import swiftGopherClient
+
+public func getHostAndPort(
+ 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
+ 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 = "/"
+
+ 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
+ }
+ }
+
+ return (host, port, selector)
+ }
+}
+
+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]?
+}
+
+struct BrowserView: View {
+ var app: GTUIApp
+
+ @State private var backwardStack: [GopherNode] = []
+ @State private var forwardStack: [GopherNode] = []
+
+ @State var url: String = "gopher://gopher.navan.dev:70"
+ @State private var gopherItems: [gopherItem] = []
+
+ let client = GopherClient()
+
+ var view: Body {
+ ScrollView {
+ List(.init(gopherItems.indices), selection: nil) { idx in
+ if gopherItems[idx].parsedItemType == .info {
+ Text(gopherItems[idx].message)
+ // .frame(minHeight: 20)
+ .halign(.start)
+ .padding(10, .leading)
+ } else if gopherItems[idx].parsedItemType == .directory {
+ Button(gopherItems[idx].message) {
+ performGopherRequest(
+ host: gopherItems[idx].host, port: gopherItems[idx].port,
+ selector: gopherItems[idx].selector)
+ }
+ } else {
+ Text(gopherItems[idx].message)
+ }
+
+ }
+ }
+ .topToolbar {
+ HStack {
+ Button(icon: .default(icon: .goPrevious)) {
+ if let curNode = backwardStack.popLast() {
+ forwardStack.append(curNode)
+ if let prevNode = backwardStack.popLast() {
+ performGopherRequest(
+ host: prevNode.host, port: prevNode.port, selector: prevNode.selector,
+ clearForward: false)
+ }
+ }
+ }
+ .padding()
+ Button(icon: .default(icon: .goNext)) {
+ if let nextNode = forwardStack.popLast() {
+ performGopherRequest(
+ host: nextNode.host, port: nextNode.port, selector: nextNode.selector,
+ clearForward: false)
+ }
+ }
+ .padding()
+ EntryRow("Enter a URL", text: $url)
+ .suffix {
+ Button(icon: .default(icon: .editCopy)) { State<Any>.copy(url) }
+ .flat()
+ .verticalCenter()
+ }
+ .padding(10)
+ Button("Go") {
+ performGopherRequest(clearForward: false)
+ }
+ }
+ }
+ .onAppear {
+ performGopherRequest()
+ }
+ }
+
+ private func performGopherRequest(
+ host: String = "", port: Int = -1, selector: String = "", clearForward: Bool = true
+ ) {
+ 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)"
+ client.sendRequest(to: res.host, port: res.port, message: "\(res.selector)\r\n") { result in
+ switch result {
+ case .success(let resp):
+ self.gopherItems = resp
+ let newNode = GopherNode(
+ host: res.host, port: res.port, selector: selector, item: nil,
+ children: convertToHostNodes(resp))
+ backwardStack.append(newNode)
+ if clearForward {
+ forwardStack.removeAll()
+ }
+ case .failure(let error):
+ self.gopherItems = [
+ gopherItem(rawLine: "Error \(error)")
+ ]
+ }
+ }
+ }
+
+ 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))
+ }
+ }
+ return returnItems
+ }
+}
diff --git a/Sources/iGopherBrowserGTK/ToolbarView.swift b/Sources/iGopherBrowserGTK/ToolbarView.swift
new file mode 100644
index 0000000..494e09e
--- /dev/null
+++ b/Sources/iGopherBrowserGTK/ToolbarView.swift
@@ -0,0 +1,38 @@
+//
+// ToolbarView.swift
+//
+//
+// Created by Navan Chauhan on 7/27/24.
+//
+
+import Adwaita
+
+struct ToolbarView: View {
+
+ var app: GTUIApp
+ var window: GTUIApplicationWindow
+
+ var view: Body {
+ HeaderBar.end {
+ Menu(icon: .default(icon: .openMenu), app: app, window: window) {
+ MenuButton("New Window", window: false) {
+ app.addWindow("main")
+ }
+ .keyboardShortcut("n".ctrl())
+ MenuButton("Close Window") {
+ window.close()
+ }
+ .keyboardShortcut("w".ctrl())
+ MenuSection {
+ MenuButton("Quit", window: false) {
+ app.quit()
+ }
+ .keyboardShortcut("q".ctrl())
+ }
+ }
+ .primary()
+ .tooltip("Main Menu")
+ }
+ }
+
+}
diff --git a/Sources/iGopherBrowserGTK/iGopherBrowserGTK.swift b/Sources/iGopherBrowserGTK/iGopherBrowserGTK.swift
new file mode 100644
index 0000000..db5ed45
--- /dev/null
+++ b/Sources/iGopherBrowserGTK/iGopherBrowserGTK.swift
@@ -0,0 +1,21 @@
+// The Swift Programming Language
+// https://docs.swift.org/swift-book
+
+import Adwaita
+
+@main
+struct iGopherBrowserGTK: App {
+ let id = "com.navanchauhan.igopherbrowsergtk"
+ var app: GTUIApp!
+
+ var scene: Scene {
+ Window(id: "main") { window in
+ BrowserView(app: app)
+ .topToolbar {
+ ToolbarView(app: app, window: window)
+ }
+ }
+ .defaultSize(width: 800, height: 800)
+ .title("iGopherBrowser (GTK Version)")
+ }
+}
diff --git a/Tests/iGopherBrowserGTKTests/iGopherBrowserGTKTests.swift b/Tests/iGopherBrowserGTKTests/iGopherBrowserGTKTests.swift
new file mode 100644
index 0000000..6bed949
--- /dev/null
+++ b/Tests/iGopherBrowserGTKTests/iGopherBrowserGTKTests.swift
@@ -0,0 +1,12 @@
+import XCTest
+@testable import iGopherBrowserGTK
+
+final class iGopherBrowserGTKTests: XCTestCase {
+ func testExample() throws {
+ // XCTest Documentation
+ // https://developer.apple.com/documentation/xctest
+
+ // Defining Test Cases and Test Methods
+ // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
+ }
+}