1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-26 23:36:08 +03:00
vimr/VimR/PreviewTool.swift

235 lines
8.2 KiB
Swift
Raw Normal View History

2017-01-22 16:22:05 +03:00
/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import RxSwift
import PureLayout
import WebKit
import Swifter
class PreviewTool: NSView, UiComponent, WKNavigationDelegate {
2017-02-07 00:54:22 +03:00
enum Action {
case refreshNow
case reverseSearch(to: Marked<Position>)
2017-02-07 00:54:22 +03:00
case scroll(to: Marked<Position>)
2017-02-07 00:54:22 +03:00
case setAutomaticReverseSearch(to: Bool)
case setAutomaticForwardSearch(to: Bool)
case setRefreshOnWrite(to: Bool)
}
2017-01-22 16:22:05 +03:00
typealias StateType = MainWindow.State
2017-02-07 00:54:22 +03:00
let menuItems: [NSMenuItem]
2017-01-25 00:39:19 +03:00
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
2017-04-26 20:40:42 +03:00
self.emit = emitter.typedEmit()
2017-02-07 00:54:22 +03:00
self.uuid = state.uuid
let configuration = WKWebViewConfiguration()
configuration.userContentController = self.userContentController
self.webview = WKWebView(frame: CGRect.zero, configuration: configuration)
let refreshMenuItem = NSMenuItem(title: "Refresh Now", action: nil, keyEquivalent: "")
let forwardSearchMenuItem = NSMenuItem(title: "Forward Search", action: nil, keyEquivalent: "")
let reverseSearchMenuItem = NSMenuItem(title: "Reverse Search", action: nil, keyEquivalent: "")
let automaticForward = self.automaticForwardMenuItem
let automaticReverse = self.automaticReverseMenuItem
let refreshOnWrite = self.refreshOnWriteMenuItem
automaticForward.boolState = state.previewTool.isForwardSearchAutomatically
automaticReverse.boolState = state.previewTool.isReverseSearchAutomatically
refreshOnWrite.boolState = state.previewTool.isRefreshOnWrite
self.menuItems = [
refreshMenuItem,
forwardSearchMenuItem,
reverseSearchMenuItem,
NSMenuItem.separator(),
automaticForward,
automaticReverse,
NSMenuItem.separator(),
refreshOnWrite,
]
2017-01-22 16:22:05 +03:00
super.init(frame: .zero)
self.configureForAutoLayout()
2017-02-07 00:54:22 +03:00
refreshMenuItem.target = self
refreshMenuItem.action = #selector(PreviewTool.refreshNowAction)
forwardSearchMenuItem.target = self
forwardSearchMenuItem.action = #selector(PreviewTool.forwardSearchAction)
reverseSearchMenuItem.target = self
reverseSearchMenuItem.action = #selector(PreviewTool.reverseSearchAction)
automaticForward.target = self
automaticForward.action = #selector(PreviewTool.automaticForwardSearchAction)
automaticReverse.target = self
automaticReverse.action = #selector(PreviewTool.automaticReverseSearchAction)
refreshOnWrite.target = self
refreshOnWrite.action = #selector(PreviewTool.refreshOnWriteAction)
2017-01-22 16:22:05 +03:00
self.addViews()
self.webview.navigationDelegate = self
2017-02-28 21:21:22 +03:00
self.webview.load(URLRequest(url: state.preview.server!))
2017-02-06 00:39:55 +03:00
source
2017-02-07 00:54:22 +03:00
.observeOn(MainScheduler.instance)
.subscribe(onNext: { state in
2017-03-31 20:32:01 +03:00
if state.viewToBeFocused != nil, case .preview = state.viewToBeFocused! {
2017-02-25 00:47:32 +03:00
self.beFirstResponder()
}
2017-02-07 00:54:22 +03:00
self.automaticForwardMenuItem.boolState = state.previewTool.isForwardSearchAutomatically
self.automaticReverseMenuItem.boolState = state.previewTool.isReverseSearchAutomatically
self.refreshOnWriteMenuItem.boolState = state.previewTool.isRefreshOnWrite
if state.previewTool.isForwardSearchAutomatically
2017-02-12 16:07:52 +03:00
&& state.preview.editorPosition.hasDifferentMark(as: self.editorPosition)
{
self.forwardSearch(position: state.preview.editorPosition.payload)
2017-02-07 00:54:22 +03:00
}
self.editorPosition = state.preview.editorPosition
2017-02-07 00:54:22 +03:00
guard state.preview.updateDate > self.lastUpdateDate else { return }
guard let serverUrl = state.preview.server else { return }
if serverUrl != self.url {
self.url = serverUrl
self.scrollTop = 0
self.previewPosition = Marked(Position.beginning)
}
2017-02-07 00:54:22 +03:00
self.lastUpdateDate = state.preview.updateDate
self.webview.load(URLRequest(url: serverUrl))
}, onCompleted: {
2017-02-28 21:21:22 +03:00
// We have to do the following to avoid a crash... Dunno why... -_-
self.webviewMessageHandler.subject.onCompleted()
self.webview.navigationDelegate = nil
self.webview.removeFromSuperview()
2017-02-07 00:54:22 +03:00
})
.disposed(by: self.disposeBag)
2017-02-07 00:54:22 +03:00
self.webviewMessageHandler.source
.throttle(0.75, latest: true, scheduler: self.scheduler)
.subscribe(onNext: { [unowned self] (position, scrollTop) in
self.previewPosition = Marked(position)
self.scrollTop = scrollTop
2017-04-22 16:56:13 +03:00
self.emit(UuidAction(uuid: self.uuid, action: .scroll(to: self.previewPosition)))
2017-02-06 00:39:55 +03:00
})
.disposed(by: self.disposeBag)
2017-01-22 16:22:05 +03:00
}
fileprivate func addViews() {
2017-02-07 00:54:22 +03:00
self.webview.navigationDelegate = self
self.userContentController.add(webviewMessageHandler, name: "com_vimr_tools_preview_markdown")
self.webview.configureForAutoLayout()
2017-01-22 16:22:05 +03:00
self.addSubview(self.webview)
self.webview.autoPinEdgesToSuperviewEdges()
}
func webView(_: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
NSLog("ERROR preview component's webview: \(error)")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webview.evaluateJavaScript("document.body.scrollTop = \(self.scrollTop)")
}
2017-04-22 16:56:13 +03:00
fileprivate let emit: (UuidAction<Action>) -> Void
2017-02-07 00:54:22 +03:00
fileprivate let uuid: String
fileprivate let webview: WKWebView
2017-02-06 00:39:55 +03:00
fileprivate let disposeBag = DisposeBag()
2017-02-07 00:54:22 +03:00
fileprivate let scheduler = ConcurrentDispatchQueueScheduler(qos: .userInitiated)
2017-01-22 16:22:05 +03:00
fileprivate var isOpen = false
fileprivate var url: URL?
2017-02-07 00:54:22 +03:00
fileprivate var lastUpdateDate = Date.distantPast
2017-02-12 15:41:12 +03:00
fileprivate var editorPosition = Marked(Position.beginning)
fileprivate var previewPosition = Marked(Position.beginning)
fileprivate var scrollTop = 0
2017-02-07 00:54:22 +03:00
fileprivate let userContentController = WKUserContentController()
fileprivate let webviewMessageHandler = WebviewMessageHandler()
fileprivate let automaticForwardMenuItem = NSMenuItem(title: "Automatic Forward Search",
action: nil,
keyEquivalent: "")
fileprivate let automaticReverseMenuItem = NSMenuItem(title: "Automatic Reverse Search",
action: nil,
keyEquivalent: "")
fileprivate let refreshOnWriteMenuItem = NSMenuItem(title: "Refresh on Write", action: nil, keyEquivalent: "")
2017-01-22 16:22:05 +03:00
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
2017-02-07 00:54:22 +03:00
fileprivate func forwardSearch(position: Position) {
self.webview.evaluateJavaScript("scrollToPosition(\(position.row), \(position.column));")
}
}
// MARK: - Actions
extension PreviewTool {
func refreshNowAction(_: Any?) {
2017-04-22 16:56:13 +03:00
self.emit(UuidAction(uuid: self.uuid, action: .refreshNow))
2017-02-07 00:54:22 +03:00
}
func forwardSearchAction(_: Any?) {
self.forwardSearch(position: self.editorPosition.payload)
2017-02-07 00:54:22 +03:00
}
func reverseSearchAction(_: Any?) {
2017-02-12 13:24:15 +03:00
self.previewPosition = Marked(self.previewPosition.payload) // set a new mark
2017-04-22 16:56:13 +03:00
self.emit(UuidAction(uuid: self.uuid, action: .reverseSearch(to: self.previewPosition)))
2017-02-07 00:54:22 +03:00
}
func automaticForwardSearchAction(_ sender: NSMenuItem) {
2017-04-22 16:56:13 +03:00
self.emit(UuidAction(uuid: self.uuid, action: .setAutomaticForwardSearch(to: !sender.boolState)))
2017-02-07 00:54:22 +03:00
}
func automaticReverseSearchAction(_ sender: NSMenuItem) {
2017-04-22 16:56:13 +03:00
self.emit(UuidAction(uuid: self.uuid, action: .setAutomaticReverseSearch(to: !sender.boolState)))
2017-02-07 00:54:22 +03:00
}
func refreshOnWriteAction(_ sender: NSMenuItem) {
2017-04-22 16:56:13 +03:00
self.emit(UuidAction(uuid: self.uuid, action: .setRefreshOnWrite(to: !sender.boolState)))
2017-02-07 00:54:22 +03:00
}
}
fileprivate class WebviewMessageHandler: NSObject, WKScriptMessageHandler {
var source: Observable<(Position, Int)> {
2017-02-07 00:54:22 +03:00
return self.subject.asObservable()
}
deinit {
self.subject.onCompleted()
}
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
guard let msgBody = message.body as? [String: Int] else {
return
}
guard let lineBegin = msgBody["lineBegin"],
let columnBegin = msgBody["columnBegin"],
let scrollTop = msgBody["scrollTop"]
else {
2017-02-07 00:54:22 +03:00
return
}
self.subject.onNext((Position(row: lineBegin, column: columnBegin), scrollTop))
2017-02-07 00:54:22 +03:00
}
fileprivate let subject = PublishSubject<(Position, Int)>()
2017-01-22 16:22:05 +03:00
}