From 738635ca511b49d0742dc55b6f4e6ccc3a9177c1 Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Wed, 21 Dec 2016 07:33:39 +0100 Subject: [PATCH] GH-339 Make the preview component roughly work - re-model the view component hierarchy --- VimR.xcodeproj/project.pbxproj | 12 ++++++ VimR/BufferListComponent.swift | 2 +- VimR/Component.swift | 12 ++++-- VimR/FileBrowserComponent.swift | 2 +- VimR/MainWindowComponent.swift | 6 +-- VimR/MarkdownRenderer.swift | 43 +++++++++++++++++++++ VimR/PrefPane.swift | 2 +- VimR/PreviewComponent.swift | 66 ++++++++++++++++++++++++++++----- 8 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 VimR/MarkdownRenderer.swift diff --git a/VimR.xcodeproj/project.pbxproj b/VimR.xcodeproj/project.pbxproj index 6e9964f9..d0e5e62c 100644 --- a/VimR.xcodeproj/project.pbxproj +++ b/VimR.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 4B183E101E06E29C0079E8A8 /* CocoaMarkdown.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B183E0D1E06E2940079E8A8 /* CocoaMarkdown.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4B183E161E07C1420079E8A8 /* preview in Resources */ = {isa = PBXBuildFile; fileRef = 4B183E151E07C1420079E8A8 /* preview */; }; 4B183E1B1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B183E1A1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4B183E1E1E09931E0079E8A8 /* MarkdownRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183E1D1E09931E0079E8A8 /* MarkdownRenderer.swift */; }; 4B22F7F01D7C029400929B0E /* ScorerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B22F7EF1D7C029400929B0E /* ScorerTest.swift */; }; 4B22F7F21D7C6B9000929B0E /* ImageAndTextTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B22F7F11D7C6B9000929B0E /* ImageAndTextTableCell.swift */; }; 4B238BE11D3BF24200CBDD98 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B238BE01D3BF24200CBDD98 /* Application.swift */; }; @@ -303,6 +304,7 @@ 4B183E0D1E06E2940079E8A8 /* CocoaMarkdown.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaMarkdown.framework; path = Carthage/Build/Mac/CocoaMarkdown.framework; sourceTree = SOURCE_ROOT; }; 4B183E151E07C1420079E8A8 /* preview */ = {isa = PBXFileReference; lastKnownFileType = folder; path = preview; sourceTree = ""; }; 4B183E1A1E08748B0079E8A8 /* NeoVimAutoCommandEvent.generated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NeoVimAutoCommandEvent.generated.h; sourceTree = ""; }; + 4B183E1D1E09931E0079E8A8 /* MarkdownRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownRenderer.swift; sourceTree = ""; }; 4B1BB3521D16C5E500CA4FEF /* InputTestView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTestView.swift; sourceTree = ""; }; 4B22F7EF1D7C029400929B0E /* ScorerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScorerTest.swift; sourceTree = ""; }; 4B22F7F11D7C6B9000929B0E /* ImageAndTextTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageAndTextTableCell.swift; sourceTree = ""; }; @@ -495,6 +497,7 @@ 1929BA610ADEA2BA4424FBE5 /* Preview */ = { isa = PBXGroup; children = ( + 4B183E1C1E0992FD0079E8A8 /* Preview Renderers */, 1929B8DA5AA33536F0082200 /* PreviewComponent.swift */, 1929BB8BCA48637156F92945 /* PreviewService.swift */, ); @@ -523,6 +526,14 @@ name = "File Browser"; sourceTree = ""; }; + 4B183E1C1E0992FD0079E8A8 /* Preview Renderers */ = { + isa = PBXGroup; + children = ( + 4B183E1D1E09931E0079E8A8 /* MarkdownRenderer.swift */, + ); + name = "Preview Renderers"; + sourceTree = ""; + }; 4B1AC1AF1D7F395300898F0B /* Open Quickly */ = { isa = PBXGroup; children = ( @@ -1249,6 +1260,7 @@ 1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */, 4B6B0A781DA2A1A500212D6D /* FileOutlineView.swift in Sources */, 4BB409EE1DDA77E9005F39A2 /* ProxyWorkspaceBar.swift in Sources */, + 4B183E1E1E09931E0079E8A8 /* MarkdownRenderer.swift in Sources */, 4BAD84E81D7CA8FC00A79CC3 /* OpenQuicklyFilterOperation.swift in Sources */, 1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */, 1929B3CEE0C1A1850E9CCE2F /* BasicTypes.swift in Sources */, diff --git a/VimR/BufferListComponent.swift b/VimR/BufferListComponent.swift index 3123b136..187d3049 100644 --- a/VimR/BufferListComponent.swift +++ b/VimR/BufferListComponent.swift @@ -12,7 +12,7 @@ enum BufferListAction { case open(buffer: NeoVimBuffer) } -class BufferListComponent: ViewComponent, NSTableViewDataSource, NSTableViewDelegate { +class BufferListComponent: StandardViewComponent, NSTableViewDataSource, NSTableViewDelegate { fileprivate var buffers: [NeoVimBuffer] = [] fileprivate let bufferList = NSTableView.standardTableView() diff --git a/VimR/Component.swift b/VimR/Component.swift index df7719e7..ca89af24 100644 --- a/VimR/Component.swift +++ b/VimR/Component.swift @@ -12,6 +12,11 @@ protocol Flow: class { var sink: Observable { get } } +protocol ViewComponent: Flow { + + var view: NSView { get } +} + class PublishingFlow: Flow { var sink: Observable { @@ -62,7 +67,7 @@ class EmbeddableComponent: Flow { fileprivate let subject = PublishSubject() fileprivate let source: Observable - fileprivate let disposeBag = DisposeBag() + let disposeBag = DisposeBag() init(source: Observable) { self.source = source @@ -116,10 +121,11 @@ class StandardComponent: NSObject, Flow { } } -class ViewComponent: NSView, Flow { + +class StandardViewComponent: NSView, ViewComponent { var view: NSView { - preconditionFailure("Please override") + return self } var sink: Observable { diff --git a/VimR/FileBrowserComponent.swift b/VimR/FileBrowserComponent.swift index a14f0990..d27ac2a4 100644 --- a/VimR/FileBrowserComponent.swift +++ b/VimR/FileBrowserComponent.swift @@ -46,7 +46,7 @@ struct FileBrowserData: StandardPrefData { } } -class FileBrowserComponent: ViewComponent, ToolDataHolder { +class FileBrowserComponent: StandardViewComponent, ToolDataHolder { fileprivate let fileView: FileOutlineView fileprivate let fileItemService: FileItemService diff --git a/VimR/MainWindowComponent.swift b/VimR/MainWindowComponent.swift index ca0fac4b..6e17eddf 100644 --- a/VimR/MainWindowComponent.swift +++ b/VimR/MainWindowComponent.swift @@ -444,18 +444,18 @@ extension MainWindowComponent { let fileBrowserTool = self.tools[.fileBrowser]! if fileBrowserTool.isSelected { - if fileBrowserTool.viewComponent.isFirstResponder { + if fileBrowserTool.viewComponent.view.isFirstResponder { fileBrowserTool.toggle() self.focusNeoVimView(self) } else { - fileBrowserTool.viewComponent.beFirstResponder() + fileBrowserTool.viewComponent.view.beFirstResponder() } return } fileBrowserTool.toggle() - fileBrowserTool.viewComponent.beFirstResponder() + fileBrowserTool.viewComponent.view.beFirstResponder() } @IBAction func focusNeoVimView(_ sender: Any?) { diff --git a/VimR/MarkdownRenderer.swift b/VimR/MarkdownRenderer.swift new file mode 100644 index 00000000..c00c5d7f --- /dev/null +++ b/VimR/MarkdownRenderer.swift @@ -0,0 +1,43 @@ +/** + * Tae Won Ha - http://taewon.de - @hataewon + * See LICENSE + */ + +import Cocoa +import RxSwift +import PureLayout +import CocoaMarkdown + +enum PreviewRendererAction { + + case htmlString(html: String) + case error +} + +class MarkdownRenderer: StandardFlow { + + override func subscription(source: Observable) -> Disposable { + return source + .filter { $0 is PreviewAction } + .map { $0 as! PreviewAction } + .subscribe(onNext: { action in + switch action { + case let .refresh(url): + self.render(from: url) + + } + }) + } + + fileprivate func render(from url: URL) { + let doc = CMDocument(contentsOfFile: url.path) + let renderer = CMHTMLRenderer(document: doc) + + guard let html = renderer?.render() else { + self.publish(event: PreviewRendererAction.error) + return + } + + self.publish(event: PreviewRendererAction.htmlString(html: html)) + } +} diff --git a/VimR/PrefPane.swift b/VimR/PrefPane.swift index ab046245..ebf2323c 100644 --- a/VimR/PrefPane.swift +++ b/VimR/PrefPane.swift @@ -6,7 +6,7 @@ import Cocoa import RxSwift -class PrefPane: ViewComponent { +class PrefPane: StandardViewComponent { // Return true to place this to the upper left corner when the scroll view is bigger than this view. override var isFlipped: Bool { diff --git a/VimR/PreviewComponent.swift b/VimR/PreviewComponent.swift index 9d9cb6f3..d592a843 100644 --- a/VimR/PreviewComponent.swift +++ b/VimR/PreviewComponent.swift @@ -8,6 +8,11 @@ import RxSwift import PureLayout import WebKit +enum PreviewAction { + + case refresh(url: URL) +} + struct PreviewPrefData: StandardPrefData { static let `default` = PreviewPrefData() @@ -24,23 +29,44 @@ struct PreviewPrefData: StandardPrefData { } } -class PreviewComponent: ViewComponent { +class PreviewComponent: NSView, ViewComponent { + + fileprivate let flow: EmbeddableComponent fileprivate let previewService = PreviewService() + fileprivate let markdownRenderer: MarkdownRenderer - let webview = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) + fileprivate let webview = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override init(source: Observable) { - super.init(source: source) - - webview.loadHTMLString(self.previewService.emptyPreview(), baseURL: nil) + var sink: Observable { + return self.flow.sink } - override func addViews() { + var view: NSView { + return self + } + + init(source: Observable) { + self.flow = EmbeddableComponent(source: source) + self.markdownRenderer = MarkdownRenderer(source: self.flow.sink) + + super.init(frame: .zero) + self.configureForAutoLayout() + + self.flow.set(subscription: self.subscription) + + + self.webview.loadHTMLString(self.previewService.emptyPreview(), baseURL: nil) + + self.addViews() + self.addReactions() + } + + fileprivate func addViews() { let webview = self.webview webview.configureForAutoLayout() @@ -49,7 +75,7 @@ class PreviewComponent: ViewComponent { webview.autoPinEdgesToSuperviewEdges() } - override func subscription(source: Observable) -> Disposable { + fileprivate func subscription(source: Observable) -> Disposable { return source .filter { $0 is MainWindowAction } .map { $0 as! MainWindowAction } @@ -57,7 +83,11 @@ class PreviewComponent: ViewComponent { switch action { case let .currentBufferChanged(mainWindow, currentBuffer): - NSLog("\(currentBuffer)") + guard let url = currentBuffer.url else { + return + } + + self.flow.publish(event: PreviewAction.refresh(url: url)) default: return @@ -65,4 +95,22 @@ class PreviewComponent: ViewComponent { } }) } + + fileprivate func addReactions() { + self.markdownRenderer.sink + .filter { $0 is PreviewRendererAction } + .map { $0 as! PreviewRendererAction } + .subscribe(onNext: { action in + switch action { + + case let .htmlString(html): + self.webview.loadHTMLString(html, baseURL: nil) + + case .error: + self.webview.loadHTMLString(self.previewService.emptyPreview(), baseURL: nil) + + } + }) + .addDisposableTo(self.flow.disposeBag) + } }