diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index eecb01c..cfc8f7a 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673924BD0B8E00B0B741 /* Fonts.swift */; }; D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; }; D69F00AE24BEA29100E37622 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AD24BEA29100E37622 /* GeminiResponse.swift */; }; + D69F00B024BEA84D00E37622 /* NavigationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AF24BEA84D00E37622 /* NavigationManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -190,6 +191,7 @@ D664673924BD0B8E00B0B741 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiDataTask.swift; sourceTree = ""; }; D69F00AD24BEA29100E37622 /* GeminiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiResponse.swift; sourceTree = ""; }; + D69F00AF24BEA84D00E37622 /* NavigationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -286,6 +288,7 @@ children = ( D626645E24BBF1C200DF9B88 /* AppDelegate.swift */, D626646024BBF1C200DF9B88 /* ContentView.swift */, + D69F00AF24BEA84D00E37622 /* NavigationManager.swift */, D626646224BBF1C300DF9B88 /* Assets.xcassets */, D626646724BBF1C300DF9B88 /* Main.storyboard */, D626646A24BBF1C300DF9B88 /* Info.plist */, @@ -668,6 +671,7 @@ files = ( D626646124BBF1C200DF9B88 /* ContentView.swift in Sources */, D626645F24BBF1C200DF9B88 /* AppDelegate.swift in Sources */, + D69F00B024BEA84D00E37622 /* NavigationManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Gemini/AppDelegate.swift b/Gemini/AppDelegate.swift index bd8ddd4..20551c5 100644 --- a/Gemini/AppDelegate.swift +++ b/Gemini/AppDelegate.swift @@ -14,11 +14,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! - let url = URL(string: "gemini://gemini.circumlunar.space/")! + let homePage = URL(string: "gemini://gemini.circumlunar.space/")! + private(set) lazy var navigationManager = NavigationManager(url: homePage) func applicationDidFinishLaunching(_ aNotification: Notification) { // Create the SwiftUI view that provides the window contents. - let contentView = ContentView(url: url) + let contentView = ContentView(navigator: navigationManager) // Create the window and set the content view. window = NSWindow( diff --git a/Gemini/ContentView.swift b/Gemini/ContentView.swift index 2552b91..96db521 100644 --- a/Gemini/ContentView.swift +++ b/Gemini/ContentView.swift @@ -11,7 +11,7 @@ import GeminiRenderer import GeminiProtocol struct ContentView: View { - let url: URL + let navigator: NavigationManager @State var task: GeminiDataTask? @State var document: Document? @State var errorMessage: String? @@ -19,12 +19,13 @@ struct ContentView: View { var body: some View { mainView .frame(minWidth: 480, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) + .onReceive(navigator.$currentURL, perform: self.urlChanged) } @ViewBuilder private var mainView: some View { if let document = document { - DocumentView(document: document) + DocumentView(document: document, changeURL: navigator.changeURL) } else if let errorMessage = errorMessage { VStack { Text("An error occurred") @@ -47,6 +48,7 @@ struct ContentView: View { } private func loadDocument() { + let url = navigator.currentURL task = try! GeminiDataTask(url: url, completion: { (response) in switch response { case let .failure(error): @@ -58,13 +60,20 @@ struct ContentView: View { } self.document = GeminiParser.parse(text: text, baseURL: url) } + self.task = nil }) task!.resume() } + + private func urlChanged(_ newValue: URL) { + self.task = nil + self.document = nil + self.errorMessage = nil + } } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView(url: URL(string: "gemini://gemini.circumlunar.space/")!) + ContentView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!)) } } diff --git a/Gemini/NavigationManager.swift b/Gemini/NavigationManager.swift new file mode 100644 index 0000000..2bddc5b --- /dev/null +++ b/Gemini/NavigationManager.swift @@ -0,0 +1,22 @@ +// +// NavigationManager.swift +// Gemini +// +// Created by Shadowfacts on 7/14/20. +// + +import Foundation + +class NavigationManager: ObservableObject { + + @Published var currentURL: URL + + init(url: URL) { + self.currentURL = url + } + + func changeURL(_ url: URL) { + self.currentURL = url + } + +} diff --git a/GeminiRenderer/DocumentView.swift b/GeminiRenderer/DocumentView.swift index 97b8bf6..ef85aef 100644 --- a/GeminiRenderer/DocumentView.swift +++ b/GeminiRenderer/DocumentView.swift @@ -11,17 +11,19 @@ import GeminiFormat public struct DocumentView: View { private let document: Document private let blocks: [RenderingBlock] + private let changeURL: ((URL) -> Void)? - public init(document: Document) { + public init(document: Document, changeURL: ((URL) -> Void)? = nil) { self.document = document self.blocks = document.renderingBlocks + self.changeURL = changeURL } public var body: some View { ScrollView(.vertical) { MaybeLazyVStack(alignment: .leading) { ForEach(blocks.indices) { (index) in - RenderingBlockView(block: blocks[index]) + RenderingBlockView(block: blocks[index], changeURL: changeURL) } }.padding() } diff --git a/GeminiRenderer/RenderingBlockView.swift b/GeminiRenderer/RenderingBlockView.swift index d7a3f67..0ea62e0 100644 --- a/GeminiRenderer/RenderingBlockView.swift +++ b/GeminiRenderer/RenderingBlockView.swift @@ -10,6 +10,12 @@ import GeminiFormat struct RenderingBlockView: View { let block: RenderingBlock + let changeURL: ((URL) -> Void)? + + init(block: RenderingBlock, changeURL: ((URL) -> Void)? = nil) { + self.block = block + self.changeURL = changeURL + } @ViewBuilder var body: some View { @@ -20,11 +26,15 @@ struct RenderingBlockView: View { .frame(maxWidth: .infinity, alignment: .leading) case let .link(url, text: linkText): let text = linkText ?? url.absoluteString - Text(verbatim: text) - .font(.documentBody) - .foregroundColor(.blue) - .underline() - .frame(maxWidth: .infinity, alignment: .leading) + Button { + self.changeURL?(url) + } label: { + Text(verbatim: text) + .font(.documentBody) + .foregroundColor(.blue) + .underline() + .frame(maxWidth: .infinity, alignment: .leading) + }.buttonStyle(LinkButtonStyle()) case let .preformatted(text, alt: _): ScrollView(.horizontal) { Text(verbatim: text)