mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-28 08:13:17 +03:00
Refactor
This commit is contained in:
parent
f9ce73625d
commit
634088a6bd
@ -12,7 +12,8 @@
|
||||
1929B05B9D664052EC2D23EF /* FileOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BCE3E156C06EDF1F2806 /* FileOutlineView.swift */; };
|
||||
1929B08C6230B9C5AB72DAF1 /* Pref128ToCurrentConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5046239709E33516F5C /* Pref128ToCurrentConverter.swift */; };
|
||||
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */; };
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerService.swift */; };
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */; };
|
||||
1929B10EE7FE8DC251B741B2 /* RxRedux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B66A5E2D00EA143AFD86 /* RxRedux.swift */; };
|
||||
1929B1837C750CADB3A5BCB9 /* OpenQuicklyFileViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */; };
|
||||
1929B20CE35B43BB1CE023BA /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC2F05E9A5C0DB039739 /* Theme.swift */; };
|
||||
1929B29B95AD176D57942E08 /* UiRootReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B457B9D0FA4D21F3751E /* UiRootReducer.swift */; };
|
||||
@ -36,7 +37,7 @@
|
||||
1929B5543B1E31A26096E656 /* FileMonitorReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B04EC69F616EEFAF5F96 /* FileMonitorReducer.swift */; };
|
||||
1929B59FA5C286E010F70BEE /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BFC0A5A9C6DB09BE1368 /* Types.swift */; };
|
||||
1929B5A2EE366F79ED32744C /* KeysPrefReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B88B5FA08E897A3C2168 /* KeysPrefReducer.swift */; };
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B617C229B19DB3E987B8 /* PreviewService.swift */; };
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B617C229B19DB3E987B8 /* PreviewMiddleware.swift */; };
|
||||
1929B5F016431A76292D1E84 /* FileMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B365A6434354B568B04F /* FileMonitor.swift */; };
|
||||
1929B6388EAF16C190B82955 /* FileItemIgnorePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */; };
|
||||
1929B6460862447A31B5B082 /* ImageAndTextTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDC3F82CB4CB4FE56D1B /* ImageAndTextTableCell.swift */; };
|
||||
@ -54,13 +55,14 @@
|
||||
1929B9318D32146D58BB38EC /* AppKitCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A70931D60E04200E12030 /* AppKitCommons.swift */; };
|
||||
1929B94083273D4B321AD848 /* FileItemUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B56C8ED31834BA9D8543 /* FileItemUtils.swift */; };
|
||||
1929B98F94536E3912AD9F3B /* ArrayCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BAF13FAD5DA8D3762367 /* ArrayCommonsTest.swift */; };
|
||||
1929B990A143763A56CFCED0 /* PrefService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B364460D86F17E80943C /* PrefService.swift */; };
|
||||
1929B990A143763A56CFCED0 /* PrefMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B364460D86F17E80943C /* PrefMiddleware.swift */; };
|
||||
1929BA715337FE26155B2071 /* BufferList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA43449BA41666CD55ED /* BufferList.swift */; };
|
||||
1929BA76A1D97D8226F7CFB1 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6AD3396160AA2C46919 /* Debouncer.swift */; };
|
||||
1929BAAD7336FDFF1F78E749 /* ScorerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BF69B01107F358CF7EAD /* ScorerTest.swift */; };
|
||||
1929BAE4900D72A7877741B1 /* PrefWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BE168F31344B69E61B62 /* PrefWindow.swift */; };
|
||||
1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD4149D5A25C82064DD8 /* UiRoot.swift */; };
|
||||
1929BB4A9B2FA42A64CCCC76 /* MainWindowReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD83A13BF133741766CC /* MainWindowReducer.swift */; };
|
||||
1929BB67CAAD4F6CBD38DF0A /* RxRedux.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B66A5E2D00EA143AFD86 /* RxRedux.swift */; };
|
||||
1929BBE28654E4307AF1E2FD /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC2F05E9A5C0DB039739 /* Theme.swift */; };
|
||||
1929BCC7908DD899999B70BE /* AppearancePrefReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BED01F5D94BFCA4CF80F /* AppearancePrefReducer.swift */; };
|
||||
1929BCC9D3604933DFF07E2E /* FileBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA5C7099CDEB04B76BA4 /* FileBrowser.swift */; };
|
||||
@ -278,16 +280,17 @@
|
||||
1929B12CE56A9B36980288A4 /* OpenQuicklyReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyReducer.swift; sourceTree = "<group>"; };
|
||||
1929B14A5949FB64C4B2646F /* KeysPref.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeysPref.swift; sourceTree = "<group>"; };
|
||||
1929B1558455B3A74D93EF2A /* OpenQuicklyFileViewRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenQuicklyFileViewRow.swift; sourceTree = "<group>"; };
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerService.swift; sourceTree = "<group>"; };
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerMiddleware.swift; sourceTree = "<group>"; };
|
||||
1929B34FC23D805A8B29E8F7 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
|
||||
1929B364460D86F17E80943C /* PrefService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefService.swift; sourceTree = "<group>"; };
|
||||
1929B364460D86F17E80943C /* PrefMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefMiddleware.swift; sourceTree = "<group>"; };
|
||||
1929B365A6434354B568B04F /* FileMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMonitor.swift; sourceTree = "<group>"; };
|
||||
1929B457B9D0FA4D21F3751E /* UiRootReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRootReducer.swift; sourceTree = "<group>"; };
|
||||
1929B49E6924847AD085C8C9 /* PrefWindowReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefWindowReducer.swift; sourceTree = "<group>"; };
|
||||
1929B5046239709E33516F5C /* Pref128ToCurrentConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pref128ToCurrentConverter.swift; sourceTree = "<group>"; };
|
||||
1929B56C8ED31834BA9D8543 /* FileItemUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemUtils.swift; sourceTree = "<group>"; };
|
||||
1929B5D45C9792BBE76B8AFF /* StringCommonsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringCommonsTest.swift; sourceTree = "<group>"; };
|
||||
1929B617C229B19DB3E987B8 /* PreviewService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewService.swift; sourceTree = "<group>"; };
|
||||
1929B617C229B19DB3E987B8 /* PreviewMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewMiddleware.swift; sourceTree = "<group>"; };
|
||||
1929B66A5E2D00EA143AFD86 /* RxRedux.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxRedux.swift; sourceTree = "<group>"; };
|
||||
1929B67A10E6BB2986B2416E /* BufferListReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferListReducer.swift; sourceTree = "<group>"; };
|
||||
1929B694508FB5FDE607513A /* ToolsPrefReducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolsPrefReducer.swift; sourceTree = "<group>"; };
|
||||
1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePattern.swift; sourceTree = "<group>"; };
|
||||
@ -565,7 +568,7 @@
|
||||
1929B34FC23D805A8B29E8F7 /* Context.swift */,
|
||||
1929B32401E8914DE9BF76CA /* Components */,
|
||||
1929B5E773BDB3B4EE9D00C1 /* Reducers */,
|
||||
1929BFA93DC859DD76C46192 /* Services */,
|
||||
1929BFA93DC859DD76C46192 /* Middlewares */,
|
||||
1929BFC0A5A9C6DB09BE1368 /* Types.swift */,
|
||||
1929BA42AB6F1BF631B57399 /* SerializableStates.swift */,
|
||||
);
|
||||
@ -621,14 +624,14 @@
|
||||
name = "Open Quickly";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1929BFA93DC859DD76C46192 /* Services */ = {
|
||||
1929BFA93DC859DD76C46192 /* Middlewares */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1929B617C229B19DB3E987B8 /* PreviewService.swift */,
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerService.swift */,
|
||||
1929B364460D86F17E80943C /* PrefService.swift */,
|
||||
1929B617C229B19DB3E987B8 /* PreviewMiddleware.swift */,
|
||||
1929B1DC584C89C477E83FA2 /* HttpServerMiddleware.swift */,
|
||||
1929B364460D86F17E80943C /* PrefMiddleware.swift */,
|
||||
);
|
||||
name = Services;
|
||||
name = Middlewares;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B5012001EBA791000F76C46 /* Frameworks */ = {
|
||||
@ -807,6 +810,7 @@
|
||||
4B6423941D8EFD6100FC78C8 /* Workspace */,
|
||||
4B97E2CF1D33F92200FC0660 /* resources */,
|
||||
1929BA652D3B88FC071531EC /* UI */,
|
||||
1929B66A5E2D00EA143AFD86 /* RxRedux.swift */,
|
||||
);
|
||||
path = VimR;
|
||||
sourceTree = "<group>";
|
||||
@ -1059,8 +1063,8 @@
|
||||
4BE45C1D1FD2DBD2005C0A95 /* Logger.swift in Sources */,
|
||||
1929B8FB248D71BF88A35761 /* PreviewTool.swift in Sources */,
|
||||
1929B4B70926DE113E6BF990 /* PreviewReducer.swift in Sources */,
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewService.swift in Sources */,
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerService.swift in Sources */,
|
||||
1929B5C1BABBC0D09D97C3EF /* PreviewMiddleware.swift in Sources */,
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerMiddleware.swift in Sources */,
|
||||
1929B3AC66EFE35D68C020E3 /* PreviewToolReducer.swift in Sources */,
|
||||
1929B59FA5C286E010F70BEE /* Types.swift in Sources */,
|
||||
1929B6D8F5FC723B7109031F /* OpenQuicklyReducer.swift in Sources */,
|
||||
@ -1083,7 +1087,7 @@
|
||||
1929B3557317755A43513B17 /* OpenQuicklyWindow.swift in Sources */,
|
||||
1929B333855A5406C400DA92 /* OpenQuicklyFilterOperation.swift in Sources */,
|
||||
1929B1837C750CADB3A5BCB9 /* OpenQuicklyFileViewRow.swift in Sources */,
|
||||
1929B990A143763A56CFCED0 /* PrefService.swift in Sources */,
|
||||
1929B990A143763A56CFCED0 /* PrefMiddleware.swift in Sources */,
|
||||
1929BA76A1D97D8226F7CFB1 /* Debouncer.swift in Sources */,
|
||||
1929B71610FF1DC6E459BA49 /* PreviewUtils.swift in Sources */,
|
||||
1929B08C6230B9C5AB72DAF1 /* Pref128ToCurrentConverter.swift in Sources */,
|
||||
@ -1103,6 +1107,7 @@
|
||||
1929BE0F64A6CE5BCE2A5092 /* MainWindow+Delegates.swift in Sources */,
|
||||
1929B8F498D1E7C53F572CE2 /* KeysPref.swift in Sources */,
|
||||
1929B5A2EE366F79ED32744C /* KeysPrefReducer.swift in Sources */,
|
||||
1929BB67CAAD4F6CBD38DF0A /* RxRedux.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1130,6 +1135,7 @@
|
||||
1929B8E90A1378E494D481E7 /* PrefUtilsTest.swift in Sources */,
|
||||
1929B20CE35B43BB1CE023BA /* Theme.swift in Sources */,
|
||||
1929B9318D32146D58BB38EC /* AppKitCommons.swift in Sources */,
|
||||
1929B10EE7FE8DC251B741B2 /* RxRedux.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AdvancedPrefReducer {
|
||||
class AdvancedPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, AdvancedPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AdvancedPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -29,6 +30,6 @@ class AdvancedPrefReducer {
|
||||
state.useSnapshotUpdate = value
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
let baseServerUrl = URL(string: "http://localhost:\(NetUtils.openPort())")!
|
||||
|
||||
var initialAppState: AppState
|
||||
if let stateDict = UserDefaults.standard.value(forKey: PrefService.compatibleVersion) as? [String: Any] {
|
||||
if let stateDict = UserDefaults.standard.value(forKey: PrefMiddleware.compatibleVersion) as? [String: Any] {
|
||||
initialAppState = AppState(dict: stateDict) ?? .default
|
||||
} else {
|
||||
if let oldDict = UserDefaults.standard.value(forKey: PrefService.lastCompatibleVersion) as? [String: Any] {
|
||||
if let oldDict = UserDefaults.standard.value(forKey: PrefMiddleware.lastCompatibleVersion) as? [String: Any] {
|
||||
initialAppState = Pref128ToCurrentConverter.appState(from: oldDict)
|
||||
} else {
|
||||
initialAppState = .default
|
||||
@ -48,15 +48,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
baseServerUrl.appendingPathComponent(HtmlPreviewToolReducer.selectFirstPath)
|
||||
)
|
||||
|
||||
self.stateContext = Context(baseServerUrl: baseServerUrl, state: initialAppState)
|
||||
self.emit = self.stateContext.actionEmitter.typedEmit()
|
||||
self.context = Context(baseServerUrl: baseServerUrl, state: initialAppState)
|
||||
self.emit = self.context.actionEmitter.typedEmit()
|
||||
|
||||
self.openNewMainWindowOnLaunch = initialAppState.openNewMainWindowOnLaunch
|
||||
self.openNewMainWindowOnReactivation = initialAppState.openNewMainWindowOnReactivation
|
||||
self.useSnapshot = initialAppState.useSnapshotUpdate
|
||||
|
||||
let source = self.stateContext.stateSource
|
||||
self.uiRoot = UiRoot(source: source, emitter: self.stateContext.actionEmitter, state: initialAppState)
|
||||
let source = self.context.stateSource
|
||||
self.uiRoot = UiRoot(source: source, emitter: self.context.actionEmitter, state: initialAppState)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -101,7 +101,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
|
||||
}
|
||||
}
|
||||
|
||||
private let stateContext: Context
|
||||
private let context: Context
|
||||
private let emit: (Action) -> Void
|
||||
|
||||
private let uiRoot: UiRoot
|
||||
@ -168,7 +168,7 @@ extension AppDelegate {
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
self.stateContext.savePrefs()
|
||||
self.context.savePrefs()
|
||||
|
||||
if self.hasDirtyWindows && self.hasMainWindows {
|
||||
let alert = NSAlert()
|
||||
|
@ -5,15 +5,16 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AppDelegateReducer {
|
||||
class AppDelegateReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, AppDelegate.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AppDelegate.Action
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -40,7 +41,7 @@ class AppDelegateReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class AppearancePrefReducer {
|
||||
class AppearancePrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, AppearancePref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AppearancePref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
var appearance = state.mainWindowTemplate.appearance
|
||||
|
||||
@ -34,7 +35,7 @@ class AppearancePrefReducer {
|
||||
|
||||
self.modify(state: &state, with: appearance)
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
|
||||
private func modify(state: inout AppState, with appearance: AppearanceState) {
|
||||
|
@ -5,20 +5,21 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class BuffersListReducer {
|
||||
class BuffersListReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, BuffersList.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<BuffersList.Action>
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .open(buffer):
|
||||
state.currentBufferToSet = buffer
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: pair.state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -6,179 +6,120 @@
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
class Context {
|
||||
typealias AnyAction = Any
|
||||
extension ReduxTypes {
|
||||
|
||||
let stateSource: Observable<AppState>
|
||||
let actionEmitter = ActionEmitter()
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AnyAction
|
||||
}
|
||||
|
||||
class Context: ReduxContext {
|
||||
|
||||
// The following should only be used when Cmd-Q'ing
|
||||
func savePrefs() {
|
||||
self.prefService.applyPref(from: self.appState)
|
||||
self.prefMiddleware.applyPref(from: self.state)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL, state: AppState) {
|
||||
self.appState = state
|
||||
self.stateSource = self.stateSubject.asObservable()
|
||||
super.init(initialState: state)
|
||||
|
||||
let openQuicklyReducer = OpenQuicklyReducer()
|
||||
let previewMiddleware = PreviewMiddleware()
|
||||
let markdownReducer = MarkdownReducer(baseServerUrl: baseServerUrl)
|
||||
|
||||
let previewService = PreviewService()
|
||||
let httpService: HttpServerService = HttpServerService(port: baseServerUrl.port!)
|
||||
let httpMiddleware: HttpServerMiddleware = HttpServerMiddleware(port: baseServerUrl.port!)
|
||||
let uiRootReducer = UiRootReducer()
|
||||
let openQuicklyReducer = OpenQuicklyReducer()
|
||||
|
||||
// AppState
|
||||
Observable
|
||||
.of(
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: AppDelegateReducer(baseServerUrl: baseServerUrl).reduce)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: uiRootReducer.reduceMainWindow)
|
||||
.reduce(by: openQuicklyReducer.reduceMainWindow)
|
||||
.filter { $0.modified }
|
||||
.apply(self.prefService.applyMainWindow)
|
||||
.map { $0.state },
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: FileMonitorReducer().reduce)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: openQuicklyReducer.reduceOpenQuicklyWindow)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForAppState()
|
||||
.reduce(by: uiRootReducer.reduceUiRoot)
|
||||
.filterMapPair()
|
||||
)
|
||||
.merge()
|
||||
self.actionEmitter.observable
|
||||
.map { (state: self.state, action: $0, modified: false) }
|
||||
.reduce(
|
||||
by: [
|
||||
AppDelegateReducer(baseServerUrl: baseServerUrl).reduce,
|
||||
uiRootReducer.mainWindow.reduce,
|
||||
openQuicklyReducer.mainWindow.reduce,
|
||||
FileMonitorReducer().reduce,
|
||||
openQuicklyReducer.reduce,
|
||||
uiRootReducer.reduce,
|
||||
|
||||
// Preferences
|
||||
PrefWindowReducer().reduce,
|
||||
GeneralPrefReducer().reduce,
|
||||
ToolsPrefReducer().reduce,
|
||||
AppearancePrefReducer().reduce,
|
||||
AdvancedPrefReducer().reduce,
|
||||
KeysPrefReducer().reduce,
|
||||
],
|
||||
middlewares: [
|
||||
self.prefMiddleware.mainWindow.apply,
|
||||
self.prefMiddleware.apply,
|
||||
])
|
||||
.filter { $0.modified }
|
||||
.subscribe(onNext: self.emitAppState)
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
// MainWindow.State
|
||||
Observable
|
||||
.of(
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: MainWindowReducer().reduce)
|
||||
.reduce(by: markdownReducer.reduceMainWindow)
|
||||
.filter { $0.modified }
|
||||
.apply(previewService.applyMainWindow)
|
||||
.apply(httpService.applyMainWindow)
|
||||
.map { $0.state },
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: markdownReducer.reducePreviewTool)
|
||||
.reduce(by: PreviewToolReducer(baseServerUrl: baseServerUrl).reduce)
|
||||
.filter { $0.modified }
|
||||
.apply(previewService.applyPreviewTool)
|
||||
.map { $0.state },
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: HtmlPreviewToolReducer(baseServerUrl: baseServerUrl).reduce)
|
||||
.filter { $0.modified }
|
||||
.apply(httpService.applyHtmlPreview)
|
||||
.map { $0.state },
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: FileBrowserReducer().reduce)
|
||||
.filterMapPair(),
|
||||
self.actionSourceForMainWindow()
|
||||
.reduce(by: BuffersListReducer().reduce)
|
||||
.reduce(by: markdownReducer.reduceBufferList)
|
||||
.filter { $0.modified }
|
||||
.apply(previewService.applyBufferList)
|
||||
.map { $0.state }
|
||||
self.actionEmitter.observable
|
||||
.mapOmittingNil { action in
|
||||
guard let uuidAction = action as? UuidTagged else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let mainWindowState = self.state.mainWindows[uuidAction.uuid] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (mainWindowState, action, false)
|
||||
}
|
||||
.reduce(
|
||||
by: [
|
||||
MainWindowReducer().reduce,
|
||||
markdownReducer.mainWindow.reduce,
|
||||
markdownReducer.previewTool.reduce,
|
||||
PreviewToolReducer(baseServerUrl: baseServerUrl).reduce,
|
||||
HtmlPreviewToolReducer(baseServerUrl: baseServerUrl).reduce,
|
||||
FileBrowserReducer().reduce,
|
||||
BuffersListReducer().reduce,
|
||||
markdownReducer.buffersList.reduce,
|
||||
],
|
||||
middlewares: [
|
||||
previewMiddleware.mainWindow.apply,
|
||||
httpMiddleware.mainWindow.apply,
|
||||
previewMiddleware.previewTool.apply,
|
||||
httpMiddleware.htmlPreview.apply,
|
||||
previewMiddleware.buffersList.apply,
|
||||
]
|
||||
)
|
||||
.merge()
|
||||
.filter { $0.modified }
|
||||
.subscribe(onNext: self.emitAppState)
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
// Preferences
|
||||
Observable
|
||||
.of(
|
||||
self.prefStateSource(by: PrefWindowReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: GeneralPrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: ToolsPrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: AppearancePrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: AdvancedPrefReducer().reduce, prefService: prefService),
|
||||
self.prefStateSource(by: KeysPrefReducer().reduce, prefService: prefService)
|
||||
)
|
||||
.merge()
|
||||
.subscribe(onNext: self.emitAppState)
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
#if DEBUG
|
||||
// self.actionEmitter.observable.debug().subscribe().disposed(by: self.disposeBag)
|
||||
// stateSource.debug().subscribe().disposed(by: self.disposeBag)
|
||||
#endif
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stateSubject.onCompleted()
|
||||
}
|
||||
private let prefMiddleware = PrefMiddleware()
|
||||
|
||||
private let stateSubject = PublishSubject<AppState>()
|
||||
private let scheduler = SerialDispatchQueueScheduler(qos: .userInitiated)
|
||||
private let disposeBag = DisposeBag()
|
||||
private func emitAppState(_ tuple: (state: MainWindow.State, action: AnyAction, modified: Bool)) {
|
||||
guard let uuidAction = tuple.action as? UuidTagged else {
|
||||
return
|
||||
}
|
||||
|
||||
private var appState: AppState
|
||||
|
||||
private let prefService = PrefService()
|
||||
|
||||
private func emitAppState(_ mainWindow: UuidState<MainWindow.State>) {
|
||||
self.appState.mainWindows[mainWindow.uuid] = mainWindow.payload
|
||||
self.stateSubject.onNext(self.appState)
|
||||
self.state.mainWindows[uuidAction.uuid] = tuple.state
|
||||
self.stateSubject.onNext(self.state)
|
||||
|
||||
self.cleanUpAppState()
|
||||
}
|
||||
|
||||
private func emitAppState(_ appState: AppState) {
|
||||
self.appState = appState
|
||||
self.stateSubject.onNext(self.appState)
|
||||
private func emitAppState(_ tuple: ReduxTypes.ReduceTuple) {
|
||||
self.state = tuple.state
|
||||
self.stateSubject.onNext(self.state)
|
||||
|
||||
self.cleanUpAppState()
|
||||
}
|
||||
|
||||
private func cleanUpAppState() {
|
||||
self.appState.mainWindows.keys.forEach { uuid in
|
||||
self.appState.mainWindows[uuid]?.cwdToSet = nil
|
||||
self.appState.mainWindows[uuid]?.currentBufferToSet = nil
|
||||
self.appState.mainWindows[uuid]?.viewToBeFocused = nil
|
||||
self.appState.mainWindows[uuid]?.urlsToOpen.removeAll()
|
||||
self.state.mainWindows.keys.forEach { uuid in
|
||||
self.state.mainWindows[uuid]?.cwdToSet = nil
|
||||
self.state.mainWindows[uuid]?.currentBufferToSet = nil
|
||||
self.state.mainWindows[uuid]?.viewToBeFocused = nil
|
||||
self.state.mainWindows[uuid]?.urlsToOpen.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
private func actionSourceForAppState<ActionType>() -> Observable<StateActionPair<AppState, ActionType>> {
|
||||
return self.actionEmitter.observable
|
||||
.mapOmittingNil { $0 as? ActionType }
|
||||
.map { self.appStateActionPair(for: $0) }
|
||||
}
|
||||
|
||||
private func actionSourceForMainWindow<ActionType>()
|
||||
-> Observable<StateActionPair<UuidState<MainWindow.State>, ActionType>> {
|
||||
return self.actionEmitter.observable
|
||||
.mapOmittingNil { $0 as? UuidAction<ActionType> }
|
||||
.mapOmittingNil { self.mainWindowStateActionPair(for: $0) }
|
||||
}
|
||||
|
||||
private func prefStateSource<ActionType>(
|
||||
by reduce: @escaping (StateActionPair<AppState, ActionType>) -> StateActionPair<AppState, ActionType>,
|
||||
prefService: PrefService
|
||||
) -> Observable<AppState> {
|
||||
return self.actionSourceForAppState()
|
||||
.reduce(by: reduce)
|
||||
.filter { $0.modified }
|
||||
.apply(self.prefService.applyPref)
|
||||
.map { $0.state }
|
||||
}
|
||||
|
||||
private func appStateActionPair<ActionType>(for action: ActionType) -> StateActionPair<AppState, ActionType> {
|
||||
return StateActionPair(state: self.appState, action: action, modified: false)
|
||||
}
|
||||
|
||||
private func mainWindowStateActionPair<ActionType>(for action: UuidAction<ActionType>)
|
||||
-> StateActionPair<UuidState<MainWindow.State>, ActionType>? {
|
||||
guard let mainWindowState = self.appState.mainWindows[action.uuid] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: action.uuid, state: mainWindowState),
|
||||
action: action.payload,
|
||||
modified: false)
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class FileBrowserReducer {
|
||||
class FileBrowserReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, FileBrowser.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<FileBrowser.Action>
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .open(url, mode):
|
||||
state.urlsToOpen[url] = mode
|
||||
@ -29,6 +30,6 @@ class FileBrowserReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class FileMonitorReducer {
|
||||
class FileMonitorReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, FileMonitor.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = FileMonitor.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action {
|
||||
|
||||
case let .change(in: url):
|
||||
if let fileItem = FileItemUtils.item(for: url, root: state.openQuickly.root, create: false) {
|
||||
@ -28,6 +29,6 @@ class FileMonitorReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class GeneralPrefReducer {
|
||||
class GeneralPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, GeneralPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = GeneralPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -29,6 +30,6 @@ class GeneralPrefReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,10 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class HtmlPreviewToolReducer {
|
||||
class HtmlPreviewToolReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, HtmlPreviewTool.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<HtmlPreviewTool.Action>
|
||||
|
||||
static let basePath = "/tools/html-preview"
|
||||
static let selectFirstPath = "/tools/html-preview/select-first.html"
|
||||
@ -16,11 +17,11 @@ class HtmlPreviewToolReducer {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
let uuid = pair.state.uuid
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
let uuid = pair.action.uuid
|
||||
|
||||
switch pair.action {
|
||||
switch pair.action.payload {
|
||||
|
||||
case let .selectHtmlFile(url):
|
||||
state.htmlPreview.htmlFile = url
|
||||
@ -30,7 +31,7 @@ class HtmlPreviewToolReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
|
121
VimR/VimR/HttpServerMiddleware.swift
Normal file
121
VimR/VimR/HttpServerMiddleware.swift
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Swifter
|
||||
|
||||
class HttpServerMiddleware {
|
||||
|
||||
let htmlPreview: HtmlPreviewMiddleware
|
||||
let mainWindow: MainWindowMiddleware
|
||||
|
||||
init(port: Int) {
|
||||
let server = HttpServer()
|
||||
let resourceUrl = Bundle.main.resourceURL!
|
||||
let githubCssUrl = resourceUrl.appendingPathComponent("markdown/github-markdown.css")
|
||||
|
||||
self.htmlPreview = HtmlPreviewMiddleware(server: server, githubCssUrl: githubCssUrl)
|
||||
self.mainWindow = MainWindowMiddleware(server: server, githubCssUrl: githubCssUrl)
|
||||
|
||||
do {
|
||||
try server.start(in_port_t(port))
|
||||
stdoutLog.info("VimR http server started on http://localhost:\(port)")
|
||||
|
||||
let previewResourceUrl = resourceUrl.appendingPathComponent("preview")
|
||||
|
||||
server["\(MarkdownReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
server.GET["\(MarkdownReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
|
||||
server["\(HtmlPreviewToolReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
server.GET["\(HtmlPreviewToolReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
} catch {
|
||||
stdoutLog.error("Server could not be started on port \(port): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
class HtmlPreviewMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<HtmlPreviewTool.Action>
|
||||
|
||||
init(server: HttpServer, githubCssUrl: URL) {
|
||||
self.server = server
|
||||
self.githubCssUrl = githubCssUrl
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let state = result.state
|
||||
guard let serverUrl = state.htmlPreview.server, let htmlFileUrl = state.htmlPreview.htmlFile else {
|
||||
return result
|
||||
}
|
||||
|
||||
let basePath = serverUrl.payload.deletingLastPathComponent().path
|
||||
|
||||
self.server.GET[serverUrl.payload.path] = shareFile(htmlFileUrl.path)
|
||||
self.server["\(basePath)/:path"] = shareFilesFromDirectory(htmlFileUrl.parent.path)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let server: HttpServer
|
||||
private let githubCssUrl: URL
|
||||
}
|
||||
|
||||
class MainWindowMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
init(server: HttpServer, githubCssUrl: URL) {
|
||||
self.server = server
|
||||
self.githubCssUrl = githubCssUrl
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .newCurrentBuffer = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
let preview = result.state.preview
|
||||
guard case .markdown = preview.status,
|
||||
let buffer = preview.buffer,
|
||||
let html = preview.html,
|
||||
let server = preview.server else {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fileLog.debug("Serving \(html) on \(server)")
|
||||
|
||||
let htmlBasePath = server.deletingLastPathComponent().path
|
||||
|
||||
self.server["\(htmlBasePath)/:path"] = shareFilesFromDirectory(buffer.deletingLastPathComponent().path)
|
||||
self.server.GET[server.path] = shareFile(html.path)
|
||||
self.server.GET["\(htmlBasePath)/github-markdown.css"] = shareFile(self.githubCssUrl.path)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let server: HttpServer
|
||||
private let githubCssUrl: URL
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Swifter
|
||||
|
||||
class HttpServerService {
|
||||
|
||||
typealias HtmlPreviewPair = StateActionPair<UuidState<MainWindow.State>, HtmlPreviewTool.Action>
|
||||
typealias MainWindowPair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
|
||||
init(port: Int) {
|
||||
let resourceUrl = Bundle.main.resourceURL!
|
||||
self.githubCssUrl = resourceUrl.appendingPathComponent("markdown/github-markdown.css")
|
||||
|
||||
do {
|
||||
try self.server.start(in_port_t(port))
|
||||
stdoutLog.info("VimR http server started on http://localhost:\(port)")
|
||||
|
||||
let previewResourceUrl = resourceUrl.appendingPathComponent("preview")
|
||||
|
||||
self.server["\(MarkdownReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
self.server.GET["\(MarkdownReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
|
||||
self.server["\(HtmlPreviewToolReducer.basePath)/:path"] = shareFilesFromDirectory(previewResourceUrl.path)
|
||||
self.server.GET["\(HtmlPreviewToolReducer.basePath)/github-markdown.css"] = shareFile(githubCssUrl.path)
|
||||
} catch {
|
||||
NSLog("ERROR server could not be started on port \(port)")
|
||||
}
|
||||
}
|
||||
|
||||
func applyHtmlPreview(_ pair: HtmlPreviewPair) {
|
||||
let state = pair.state.payload
|
||||
|
||||
guard let serverUrl = state.htmlPreview.server, let htmlFileUrl = state.htmlPreview.htmlFile else {
|
||||
return
|
||||
}
|
||||
|
||||
let basePath = serverUrl.payload.deletingLastPathComponent().path
|
||||
|
||||
self.server.GET[serverUrl.payload.path] = shareFile(htmlFileUrl.path)
|
||||
self.server["\(basePath)/:path"] = shareFilesFromDirectory(htmlFileUrl.parent.path)
|
||||
}
|
||||
|
||||
func applyMainWindow(_ pair: MainWindowPair) {
|
||||
guard case .newCurrentBuffer = pair.action else {
|
||||
return
|
||||
}
|
||||
|
||||
let preview = pair.state.payload.preview
|
||||
guard case .markdown = preview.status,
|
||||
let buffer = preview.buffer,
|
||||
let html = preview.html,
|
||||
let server = preview.server
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
fileLog.debug("Serving \(html) on \(server)")
|
||||
|
||||
let htmlBasePath = server.deletingLastPathComponent().path
|
||||
|
||||
self.server["\(htmlBasePath)/:path"] = shareFilesFromDirectory(buffer.deletingLastPathComponent().path)
|
||||
self.server.GET[server.path] = shareFile(html.path)
|
||||
self.server.GET["\(htmlBasePath)/github-markdown.css"] = shareFile(self.githubCssUrl.path)
|
||||
}
|
||||
|
||||
private let server = HttpServer()
|
||||
private let githubCssUrl: URL
|
||||
}
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class KeysPrefReducer {
|
||||
class KeysPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, KeysPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = KeysPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -24,6 +25,6 @@ class KeysPrefReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class MainWindowReducer {
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .frameChanged(to:frame):
|
||||
state.frame = frame
|
||||
@ -30,8 +31,8 @@ class MainWindowReducer {
|
||||
|
||||
case let .setDirtyStatus(status):
|
||||
// When I gt or w around, we change tab somehow... Dunno why...
|
||||
if status == pair.state.payload.isDirty {
|
||||
return pair
|
||||
if status == tuple.state.isDirty {
|
||||
return tuple
|
||||
}
|
||||
|
||||
state.isDirty = status
|
||||
@ -78,10 +79,10 @@ class MainWindowReducer {
|
||||
state.appearance.theme = Marked(theme)
|
||||
|
||||
default:
|
||||
return pair
|
||||
return tuple
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,14 @@
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
class OpenQuicklyReducer {
|
||||
class OpenQuicklyReducer: ReducerType {
|
||||
|
||||
typealias OpenQuicklyWindowPair = StateActionPair<AppState, OpenQuicklyWindow.Action>
|
||||
typealias MainWindowPair = StateActionPair<AppState, UuidAction<MainWindow.Action>>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = OpenQuicklyWindow.Action
|
||||
|
||||
func reduceOpenQuicklyWindow(_ pair: OpenQuicklyWindowPair) -> OpenQuicklyWindowPair {
|
||||
let mainWindow = MainWindowReducer()
|
||||
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var appState = pair.state
|
||||
|
||||
appState.openQuickly.open = false
|
||||
@ -32,31 +34,37 @@ class OpenQuicklyReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
return (appState, pair.action, true)
|
||||
}
|
||||
|
||||
func reduceMainWindow(_ pair: MainWindowPair) -> MainWindowPair {
|
||||
switch pair.action.payload {
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
case .openQuickly:
|
||||
var appState = pair.state
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
guard let uuid = appState.currentMainWindowUuid else {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
switch pair.action.payload {
|
||||
|
||||
case .openQuickly:
|
||||
var appState = pair.state
|
||||
|
||||
guard let uuid = appState.currentMainWindowUuid else {
|
||||
return pair
|
||||
}
|
||||
|
||||
guard let cwd = appState.mainWindows[uuid]?.cwd else {
|
||||
return pair
|
||||
}
|
||||
|
||||
appState.openQuickly.open = true
|
||||
appState.openQuickly.cwd = cwd
|
||||
|
||||
return (appState, pair.action, true)
|
||||
|
||||
default:
|
||||
return pair
|
||||
|
||||
}
|
||||
|
||||
guard let cwd = appState.mainWindows[uuid]?.cwd else {
|
||||
return pair
|
||||
}
|
||||
|
||||
appState.openQuickly.open = true
|
||||
appState.openQuickly.cwd = cwd
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
|
||||
default:
|
||||
return pair
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
62
VimR/VimR/PrefMiddleware.swift
Normal file
62
VimR/VimR/PrefMiddleware.swift
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
class PrefMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = AnyAction
|
||||
|
||||
static let compatibleVersion = "168"
|
||||
static let lastCompatibleVersion = "128"
|
||||
|
||||
let mainWindow = MainWindowMiddleware()
|
||||
|
||||
// The following should only be used when Cmd-Q'ing
|
||||
func applyPref(from appState: AppState) {
|
||||
defaults.setValue(appState.dict(), forKey: PrefMiddleware.compatibleVersion)
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
defaults.setValue(result.state.dict(), forKey: PrefMiddleware.compatibleVersion)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class MainWindowMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .close = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
defaults.setValue(result.state.dict(), forKey: PrefMiddleware.compatibleVersion)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let defaults = UserDefaults.standard
|
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
class PrefService {
|
||||
|
||||
typealias MainWindowPair = StateActionPair<AppState, UuidAction<MainWindow.Action>>
|
||||
|
||||
static let compatibleVersion = "168"
|
||||
static let lastCompatibleVersion = "128"
|
||||
|
||||
// The following should only be used when Cmd-Q'ing
|
||||
func applyPref(from appState: AppState) {
|
||||
defaults.setValue(appState.dict(), forKey: PrefService.compatibleVersion)
|
||||
}
|
||||
|
||||
func applyPref<ActionType>(_ pair: StateActionPair<AppState, ActionType>) {
|
||||
defaults.setValue(pair.state.dict(), forKey: PrefService.compatibleVersion)
|
||||
}
|
||||
|
||||
func applyMainWindow(_ pair: MainWindowPair) {
|
||||
guard case .close = pair.action.payload else {
|
||||
return
|
||||
}
|
||||
|
||||
defaults.setValue(pair.state.dict(), forKey: PrefService.compatibleVersion)
|
||||
}
|
||||
}
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class PrefWindowReducer {
|
||||
class PrefWindowReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, PrefWindow.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = PrefWindow.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -19,6 +20,6 @@ class PrefWindowReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
186
VimR/VimR/PreviewMiddleware.swift
Normal file
186
VimR/VimR/PreviewMiddleware.swift
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import CocoaMarkdown
|
||||
|
||||
class PreviewMiddleware {
|
||||
|
||||
let previewTool: PreviewToolMiddleware
|
||||
let buffersList: BuffersListMiddleware
|
||||
let mainWindow: MainWindowMiddleware
|
||||
|
||||
init() {
|
||||
let generator = PreviewGenerator()
|
||||
self.previewTool = PreviewToolMiddleware(generator: generator)
|
||||
self.buffersList = BuffersListMiddleware(generator: generator)
|
||||
self.mainWindow = MainWindowMiddleware(generator: generator)
|
||||
}
|
||||
|
||||
class PreviewGenerator {
|
||||
|
||||
init() {
|
||||
guard let templateUrl = Bundle.main.url(forResource: "template",
|
||||
withExtension: "html",
|
||||
subdirectory: "markdown")
|
||||
else {
|
||||
preconditionFailure("ERROR Cannot load markdown template")
|
||||
}
|
||||
|
||||
guard let template = try? String(contentsOf: templateUrl) else {
|
||||
preconditionFailure("ERROR Cannot load markdown template")
|
||||
}
|
||||
|
||||
self.template = template
|
||||
}
|
||||
|
||||
func apply(_ state: MainWindow.State, uuid: String) {
|
||||
let preview = state.preview
|
||||
guard let buffer = preview.buffer, let html = preview.html else {
|
||||
guard let previewUrl = self.previewFiles[uuid] else {
|
||||
return
|
||||
}
|
||||
|
||||
try? FileManager.default.removeItem(at: previewUrl)
|
||||
self.previewFiles.removeValue(forKey: uuid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fileLog.debug("\(buffer) -> \(html)")
|
||||
do {
|
||||
try self.render(buffer, to: html)
|
||||
self.previewFiles[uuid] = html
|
||||
} catch let error as NSError {
|
||||
// FIXME: error handling!
|
||||
NSLog("ERROR rendering \(buffer) to \(html): \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func render(_ bufferUrl: URL, to htmlUrl: URL) throws {
|
||||
let doc = CMDocument(contentsOfFile: bufferUrl.path, options: .sourcepos)
|
||||
let renderer = CMHTMLRenderer(document: doc)
|
||||
|
||||
guard let body = renderer?.render() else {
|
||||
// FIXME: error handling!
|
||||
return
|
||||
}
|
||||
|
||||
let html = filledTemplate(body: body, title: bufferUrl.lastPathComponent)
|
||||
let htmlFilePath = htmlUrl.path
|
||||
|
||||
try html.write(toFile: htmlFilePath, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func filledTemplate(body: String, title: String) -> String {
|
||||
return self.template
|
||||
.replacingOccurrences(of: "{{ title }}", with: title)
|
||||
.replacingOccurrences(of: "{{ body }}", with: body)
|
||||
}
|
||||
|
||||
private let template: String
|
||||
private var previewFiles = [String: URL]()
|
||||
}
|
||||
|
||||
class PreviewToolMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<PreviewTool.Action>
|
||||
|
||||
init(generator: PreviewGenerator) {
|
||||
self.generator = generator
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .refreshNow = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let generator: PreviewGenerator
|
||||
}
|
||||
|
||||
class BuffersListMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<BuffersList.Action>
|
||||
|
||||
init(generator: PreviewGenerator) {
|
||||
self.generator = generator
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
guard case .open = uuidAction.payload else {
|
||||
return result
|
||||
}
|
||||
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let generator: PreviewGenerator
|
||||
}
|
||||
|
||||
class MainWindowMiddleware: MiddlewareType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
init(generator: PreviewGenerator) {
|
||||
self.generator = generator
|
||||
}
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction {
|
||||
return { tuple in
|
||||
let result = reduce(tuple)
|
||||
|
||||
guard tuple.modified else {
|
||||
return result
|
||||
}
|
||||
|
||||
let uuidAction = tuple.action
|
||||
switch uuidAction.payload {
|
||||
|
||||
case .newCurrentBuffer:
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
|
||||
case .bufferWritten:
|
||||
self.generator.apply(result.state, uuid: uuidAction.uuid)
|
||||
|
||||
default:
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private let generator: PreviewGenerator
|
||||
}
|
||||
}
|
@ -7,124 +7,160 @@ import Foundation
|
||||
|
||||
class MarkdownReducer {
|
||||
|
||||
typealias PreviewToolPair = StateActionPair<UuidState<MainWindow.State>, PreviewTool.Action>
|
||||
typealias BufferListPair = StateActionPair<UuidState<MainWindow.State>, BuffersList.Action>
|
||||
typealias MainWindowPair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
|
||||
static let basePath = "/tools/markdown"
|
||||
static let saveFirstPath = "/tools/markdown/save-first.html"
|
||||
static let errorPath = "/tools/markdown/error.html"
|
||||
static let nonePath = "/tools/markdown/empty.html"
|
||||
|
||||
func reducePreviewTool(_ pair: PreviewToolPair) -> PreviewToolPair {
|
||||
var state = pair.state.payload
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case .refreshNow:
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .reverseSearch(to:position):
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
case let .scroll(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break;
|
||||
}
|
||||
|
||||
guard state.previewTool.isReverseSearchAutomatically && state.preview.lastSearch != .forward else {
|
||||
state.preview.lastSearch = .none
|
||||
state.preview.previewPosition = Marked(mark: state.preview.previewPosition.mark, payload: position)
|
||||
break;
|
||||
}
|
||||
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
default:
|
||||
return pair
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
}
|
||||
|
||||
func reduceBufferList(_ pair: BufferListPair) -> BufferListPair {
|
||||
var state = pair.state.payload
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case let .open(buffer):
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: buffer,
|
||||
editorPosition: Marked(.beginning),
|
||||
previewPosition: Marked(.beginning))
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: pair.state.uuid, state: state), action: pair.action)
|
||||
}
|
||||
|
||||
func reduceMainWindow(_ pair: MainWindowPair) -> MainWindowPair {
|
||||
var state = pair.state.payload
|
||||
|
||||
switch pair.action {
|
||||
|
||||
case let .newCurrentBuffer(buffer):
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid, baseUrl: self.baseServerUrl, buffer: buffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
case .bufferWritten:
|
||||
state.preview = PreviewUtils.state(for: pair.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .setCursor(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
guard state.previewTool.isForwardSearchAutomatically && state.preview.lastSearch != .reverse else {
|
||||
state.preview.editorPosition = Marked(mark: state.preview.editorPosition.mark, payload: position.payload)
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
state.preview.editorPosition = Marked(position.payload)
|
||||
state.preview.lastSearch = .none // .none because the forward search does not invoke .scroll above.
|
||||
|
||||
case .close:
|
||||
state.preview = PreviewUtils.state(for: .none,
|
||||
baseUrl: self.baseServerUrl,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
default:
|
||||
return pair
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: pair.state.uuid, state: state), action: pair.action)
|
||||
}
|
||||
let previewTool: PreviewToolReducer
|
||||
let buffersList: BuffersListReducer
|
||||
let mainWindow: MainWindowReducer
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
self.previewTool = PreviewToolReducer(baseServerUrl: baseServerUrl)
|
||||
self.buffersList = BuffersListReducer(baseServerUrl: baseServerUrl)
|
||||
self.mainWindow = MainWindowReducer(baseServerUrl: baseServerUrl)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
class PreviewToolReducer: ReducerType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<PreviewTool.Action>
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case .refreshNow:
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .reverseSearch(to:position):
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
case let .scroll(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break;
|
||||
}
|
||||
|
||||
guard state.previewTool.isReverseSearchAutomatically && state.preview.lastSearch != .forward else {
|
||||
state.preview.lastSearch = .none
|
||||
state.preview.previewPosition = Marked(mark: state.preview.previewPosition.mark, payload: position)
|
||||
break;
|
||||
}
|
||||
|
||||
state.preview.previewPosition = Marked(position)
|
||||
state.preview.lastSearch = .reverse
|
||||
|
||||
default:
|
||||
return tuple
|
||||
|
||||
}
|
||||
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
}
|
||||
|
||||
class BuffersListReducer: ReducerType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<BuffersList.Action>
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .open(buffer):
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: buffer,
|
||||
editorPosition: Marked(.beginning),
|
||||
previewPosition: Marked(.beginning))
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
}
|
||||
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
}
|
||||
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .newCurrentBuffer(buffer):
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid, baseUrl: self.baseServerUrl, buffer: buffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
case .bufferWritten:
|
||||
state.preview = PreviewUtils.state(for: tuple.state.uuid,
|
||||
baseUrl: self.baseServerUrl,
|
||||
buffer: state.currentBuffer,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .reload
|
||||
|
||||
case let .setCursor(to:position):
|
||||
if state.preview.lastSearch == .reload {
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
guard state.previewTool.isForwardSearchAutomatically && state.preview.lastSearch != .reverse else {
|
||||
state.preview.editorPosition = Marked(mark: state.preview.editorPosition.mark, payload: position.payload)
|
||||
state.preview.lastSearch = .none
|
||||
break
|
||||
}
|
||||
|
||||
state.preview.editorPosition = Marked(position.payload)
|
||||
state.preview.lastSearch = .none // .none because the forward search does not invoke .scroll above.
|
||||
|
||||
case .close:
|
||||
state.preview = PreviewUtils.state(for: .none,
|
||||
baseUrl: self.baseServerUrl,
|
||||
editorPosition: state.preview.editorPosition,
|
||||
previewPosition: state.preview.previewPosition)
|
||||
state.preview.lastSearch = .none
|
||||
|
||||
default:
|
||||
return tuple
|
||||
}
|
||||
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +0,0 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import CocoaMarkdown
|
||||
|
||||
class PreviewService {
|
||||
|
||||
typealias PreviewToolPair = StateActionPair<UuidState<MainWindow.State>, PreviewTool.Action>
|
||||
typealias BufferListPair = StateActionPair<UuidState<MainWindow.State>, BuffersList.Action>
|
||||
typealias MainWindowPair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
|
||||
|
||||
init() {
|
||||
guard let templateUrl = Bundle.main.url(forResource: "template",
|
||||
withExtension: "html",
|
||||
subdirectory: "markdown")
|
||||
else {
|
||||
preconditionFailure("ERROR Cannot load markdown template")
|
||||
}
|
||||
|
||||
guard let template = try? String(contentsOf: templateUrl) else {
|
||||
preconditionFailure("ERROR Cannot load markdown template")
|
||||
}
|
||||
|
||||
self.template = template
|
||||
}
|
||||
|
||||
func applyPreviewTool(_ pair: PreviewToolPair) {
|
||||
guard case .refreshNow = pair.action else {
|
||||
return
|
||||
}
|
||||
|
||||
self.apply(pair.state)
|
||||
}
|
||||
|
||||
func applyBufferList(_ pair: BufferListPair) {
|
||||
guard case .open = pair.action else {
|
||||
return
|
||||
}
|
||||
|
||||
self.apply(pair.state)
|
||||
}
|
||||
|
||||
func applyMainWindow(_ pair: MainWindowPair) {
|
||||
switch pair.action {
|
||||
case .newCurrentBuffer: self.apply(pair.state)
|
||||
case .bufferWritten: self.apply(pair.state)
|
||||
default: return
|
||||
}
|
||||
}
|
||||
|
||||
private func filledTemplate(body: String, title: String) -> String {
|
||||
return self.template
|
||||
.replacingOccurrences(of: "{{ title }}", with: title)
|
||||
.replacingOccurrences(of: "{{ body }}", with: body)
|
||||
}
|
||||
|
||||
private func render(_ bufferUrl: URL, to htmlUrl: URL) throws {
|
||||
let doc = CMDocument(contentsOfFile: bufferUrl.path, options: .sourcepos)
|
||||
let renderer = CMHTMLRenderer(document: doc)
|
||||
|
||||
guard let body = renderer?.render() else {
|
||||
// FIXME: error handling!
|
||||
return
|
||||
}
|
||||
|
||||
let html = filledTemplate(body: body, title: bufferUrl.lastPathComponent)
|
||||
let htmlFilePath = htmlUrl.path
|
||||
|
||||
try html.write(toFile: htmlFilePath, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private func apply(_ state: UuidState<MainWindow.State>) {
|
||||
let uuid = state.uuid
|
||||
|
||||
let preview = state.payload.preview
|
||||
guard let buffer = preview.buffer, let html = preview.html else {
|
||||
guard let previewUrl = self.previewFiles[uuid] else {
|
||||
return
|
||||
}
|
||||
|
||||
try? FileManager.default.removeItem(at: previewUrl)
|
||||
self.previewFiles.removeValue(forKey: uuid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NSLog("\(buffer) -> \(html)")
|
||||
do {
|
||||
try self.render(buffer, to: html)
|
||||
self.previewFiles[uuid] = html
|
||||
} catch let error as NSError {
|
||||
// FIXME: error handling!
|
||||
NSLog("ERROR rendering \(buffer) to \(html): \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private let template: String
|
||||
private let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
private var previewFiles = [String: URL]()
|
||||
}
|
@ -5,18 +5,19 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class PreviewToolReducer {
|
||||
class PreviewToolReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<UuidState<MainWindow.State>, PreviewTool.Action>
|
||||
typealias StateType = MainWindow.State
|
||||
typealias ActionType = UuidAction<PreviewTool.Action>
|
||||
|
||||
init(baseServerUrl: URL) {
|
||||
self.baseServerUrl = baseServerUrl
|
||||
}
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
var state = pair.state.payload
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var state = tuple.state
|
||||
|
||||
switch pair.action {
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .setAutomaticReverseSearch(to:value):
|
||||
state.previewTool.isReverseSearchAutomatically = value
|
||||
@ -28,11 +29,11 @@ class PreviewToolReducer {
|
||||
state.previewTool.isRefreshOnWrite = value
|
||||
|
||||
default:
|
||||
return pair
|
||||
return tuple
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
|
||||
return (state, tuple.action, true)
|
||||
}
|
||||
|
||||
private let baseServerUrl: URL
|
||||
|
215
VimR/VimR/RxRedux.swift
Normal file
215
VimR/VimR/RxRedux.swift
Normal file
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
protocol ReduxContextType {
|
||||
|
||||
/**
|
||||
Type that holds the global app state
|
||||
|
||||
- Important:
|
||||
This type must be typealias'ed in `ReduxTypes` or in an extension thereof.
|
||||
*/
|
||||
associatedtype StateType
|
||||
|
||||
/**
|
||||
"The greatest common divisor" for all actions used in the app: Assuming it is set to `ReduxTypes.ActionType` type,
|
||||
the following must be true for any action
|
||||
```
|
||||
assert(someAction is ReduxTypes.ActionType) // which means
|
||||
let actionWithMinimumType: ReduxTypes.ActionType = anyAction
|
||||
```
|
||||
Most probably this type will be set to `Any`.
|
||||
|
||||
- Important:
|
||||
This type must be typealias'ed in `ReduxTypes` or in an extension thereof.
|
||||
*/
|
||||
associatedtype ActionType
|
||||
|
||||
typealias ReduceTuple = (state: StateType, action: ActionType, modified: Bool)
|
||||
typealias ReduceFunction = (ReduceTuple) -> ReduceTuple
|
||||
}
|
||||
|
||||
/**
|
||||
`typealias` `StateType` and `ActionType` either within the class definition or in an extension.
|
||||
*/
|
||||
class ReduxTypes: ReduxContextType {
|
||||
}
|
||||
|
||||
protocol ReducerType {
|
||||
|
||||
associatedtype StateType
|
||||
associatedtype ActionType
|
||||
|
||||
typealias ReduceTuple = (state: StateType, action: ActionType, modified: Bool)
|
||||
typealias ActionTypeErasedReduceTuple = (state: StateType, action: ReduxTypes.ActionType, modified: Bool)
|
||||
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple
|
||||
}
|
||||
|
||||
extension ReducerType {
|
||||
|
||||
func reduce(_ tuple: ActionTypeErasedReduceTuple) -> ActionTypeErasedReduceTuple {
|
||||
guard let typedTuple = tuple as? ReduceTuple else {
|
||||
return tuple
|
||||
}
|
||||
|
||||
let typedResult = self.typedReduce(typedTuple)
|
||||
return (state: typedResult.state, action: typedResult.action, modified: typedResult.modified)
|
||||
}
|
||||
}
|
||||
|
||||
protocol MiddlewareType {
|
||||
|
||||
associatedtype StateType
|
||||
associatedtype ActionType
|
||||
|
||||
typealias ReduceTuple = (state: StateType, action: ActionType, modified: Bool)
|
||||
typealias ActionTypeErasedReduceTuple = (state: StateType, action: ReduxTypes.ActionType, modified: Bool)
|
||||
|
||||
typealias TypedActionReduceFunction = (ReduceTuple) -> ActionTypeErasedReduceTuple
|
||||
typealias ActionTypeErasedReduceFunction = (ActionTypeErasedReduceTuple) -> ActionTypeErasedReduceTuple
|
||||
|
||||
func typedApply(_ reduce: @escaping TypedActionReduceFunction) -> TypedActionReduceFunction
|
||||
}
|
||||
|
||||
extension MiddlewareType {
|
||||
|
||||
func apply(_ reduce: @escaping ActionTypeErasedReduceFunction) -> ActionTypeErasedReduceFunction {
|
||||
return { tuple in
|
||||
guard let typedTuple = tuple as? ReduceTuple else {
|
||||
return reduce(tuple)
|
||||
}
|
||||
|
||||
let typedReduce: (ReduceTuple) -> ActionTypeErasedReduceTuple = { typedTuple in
|
||||
// We know that we can cast the typed action to ReduxTypes.ActionType
|
||||
return reduce((state: typedTuple.state, action: typedTuple.action, modified: typedTuple.modified))
|
||||
}
|
||||
|
||||
return self.typedApply(typedReduce)(typedTuple)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol UiComponent {
|
||||
|
||||
associatedtype StateType
|
||||
|
||||
init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType)
|
||||
}
|
||||
|
||||
class ActionEmitter {
|
||||
|
||||
let observable: Observable<ReduxTypes.ActionType>
|
||||
|
||||
init() {
|
||||
self.observable = self.subject.asObservable().observeOn(scheduler)
|
||||
}
|
||||
|
||||
func typedEmit<T>() -> ((T) -> Void) {
|
||||
return { (action: T) in
|
||||
self.subject.onNext(action)
|
||||
}
|
||||
}
|
||||
|
||||
func terminate() {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
private let scheduler = SerialDispatchQueueScheduler(qos: .userInteractive)
|
||||
private let subject = PublishSubject<ReduxTypes.ActionType>()
|
||||
}
|
||||
|
||||
class ReduxContext {
|
||||
|
||||
let actionEmitter = ActionEmitter()
|
||||
let stateSource: Observable<ReduxTypes.StateType>
|
||||
|
||||
convenience init(initialState: ReduxTypes.StateType,
|
||||
reducers: [ReduxTypes.ReduceFunction],
|
||||
middlewares: [(@escaping ReduxTypes.ReduceFunction) -> ReduxTypes.ReduceFunction] = []) {
|
||||
|
||||
self.init(initialState: initialState)
|
||||
|
||||
self.actionEmitter.observable
|
||||
.map { (state: self.state, action: $0, modified: false) }
|
||||
.reduce(by: reducers, middlewares: middlewares)
|
||||
.filter { $0.modified }
|
||||
.subscribe(onNext: { tuple in
|
||||
self.state = tuple.state
|
||||
self.stateSubject.onNext(tuple.state)
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
}
|
||||
|
||||
init(initialState: ReduxTypes.StateType) {
|
||||
self.state = initialState
|
||||
self.stateSource = self.stateSubject.asObservable().observeOn(self.stateScheduler)
|
||||
}
|
||||
|
||||
func terminate() {
|
||||
self.actionEmitter.terminate()
|
||||
self.stateSubject.onCompleted()
|
||||
}
|
||||
|
||||
var state: ReduxTypes.StateType
|
||||
|
||||
let stateSubject = PublishSubject<ReduxTypes.StateType>()
|
||||
let stateScheduler = SerialDispatchQueueScheduler(qos: .userInteractive)
|
||||
|
||||
let disposeBag = DisposeBag()
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
func completableSubject() -> CompletableSubject<Element> {
|
||||
return CompletableSubject(source: self)
|
||||
}
|
||||
}
|
||||
|
||||
class CompletableSubject<T> {
|
||||
|
||||
func asObservable() -> Observable<T> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
init(source: Observable<T>) {
|
||||
let subject = PublishSubject<T>()
|
||||
self.subscription = source.subscribe(onNext: { element in subject.onNext(element) })
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
func onCompleted() {
|
||||
self.subject.onCompleted()
|
||||
self.subscription.dispose()
|
||||
}
|
||||
|
||||
private let subject: PublishSubject<T>
|
||||
private let subscription: Disposable
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
func reduce(
|
||||
by reducers: [(Element) -> Element],
|
||||
middlewares: [(@escaping (Element) -> Element) -> (Element) -> Element]
|
||||
) -> Observable<Element> {
|
||||
|
||||
let dispatch = { pair in
|
||||
return reducers.reduce(pair) { result, reduceBody in
|
||||
return reduceBody(result)
|
||||
}
|
||||
}
|
||||
|
||||
let next = middlewares.reversed().reduce(dispatch) { result, middleware in middleware(result) }
|
||||
return self.map(next)
|
||||
}
|
||||
}
|
@ -14,31 +14,6 @@ extension Observable {
|
||||
.filter { $0 != nil }
|
||||
.map { $0! }
|
||||
}
|
||||
|
||||
func completableSubject() -> CompletableSubject<Element> {
|
||||
return CompletableSubject(source: self)
|
||||
}
|
||||
}
|
||||
|
||||
class CompletableSubject<T> {
|
||||
|
||||
func asObservable() -> Observable<T> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
init(source: Observable<T>) {
|
||||
let subject = PublishSubject<T>()
|
||||
self.subscription = source.subscribe(subject)
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
func onCompleted() {
|
||||
self.subject.onCompleted()
|
||||
self.subscription.dispose()
|
||||
}
|
||||
|
||||
private let subject: PublishSubject<T>
|
||||
private let subscription: Disposable
|
||||
}
|
||||
|
||||
extension PrimitiveSequenceType where TraitType == CompletableTrait, ElementType == Never {
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class ToolsPrefReducer {
|
||||
class ToolsPrefReducer: ReducerType {
|
||||
|
||||
typealias Pair = StateActionPair<AppState, ToolsPref.Action>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = ToolsPref.Action
|
||||
|
||||
func reduce(_ pair: Pair) -> Pair {
|
||||
func typedReduce(_ pair: ReduceTuple) -> ReduceTuple {
|
||||
var state = pair.state
|
||||
|
||||
switch pair.action {
|
||||
@ -19,6 +20,6 @@ class ToolsPrefReducer {
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: state, action: pair.action)
|
||||
return (state, pair.action, true)
|
||||
}
|
||||
}
|
||||
|
@ -6,49 +6,19 @@
|
||||
import Foundation
|
||||
import RxSwift
|
||||
|
||||
protocol UiComponent {
|
||||
struct StateActionPair<S, A> {
|
||||
|
||||
associatedtype StateType
|
||||
|
||||
init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType)
|
||||
var state: S
|
||||
var action: A
|
||||
var modified: Bool
|
||||
}
|
||||
|
||||
class ActionEmitter {
|
||||
protocol UuidTagged {
|
||||
|
||||
let observable: Observable<Any>
|
||||
|
||||
init() {
|
||||
self.observable = self.subject.asObservable().observeOn(scheduler)
|
||||
}
|
||||
|
||||
func typedEmit<T>() -> ((T) -> Void) {
|
||||
return { (action: T) in
|
||||
self.subject.onNext(action)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
private let scheduler = SerialDispatchQueueScheduler(qos: .userInteractive)
|
||||
private let subject = PublishSubject<Any>()
|
||||
var uuid: String { get }
|
||||
}
|
||||
|
||||
class StateActionPair<S, A> {
|
||||
|
||||
let modified: Bool
|
||||
let state: S
|
||||
let action: A
|
||||
|
||||
init(state: S, action: A, modified: Bool = true) {
|
||||
self.modified = modified
|
||||
self.state = state
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
class UuidAction<A>: CustomStringConvertible {
|
||||
class UuidAction<A>: UuidTagged, CustomStringConvertible {
|
||||
|
||||
let uuid: String
|
||||
let payload: A
|
||||
@ -63,7 +33,7 @@ class UuidAction<A>: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
class UuidState<S>: CustomStringConvertible {
|
||||
class UuidState<S>: UuidTagged, CustomStringConvertible {
|
||||
|
||||
let uuid: String
|
||||
let payload: S
|
||||
@ -116,23 +86,6 @@ class Marked<T>: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
extension Observable {
|
||||
|
||||
func reduce(by reduce: @escaping (Element) -> Element) -> Observable<Element> {
|
||||
return self.map(reduce)
|
||||
}
|
||||
|
||||
func apply(_ apply: @escaping (Element) -> Void) -> Observable<Element> {
|
||||
return self.do(onNext: apply)
|
||||
}
|
||||
|
||||
func filterMapPair<S, A>() -> Observable<S> where Element == StateActionPair<S, A> {
|
||||
return self
|
||||
.filter { $0.modified }
|
||||
.map { $0.state }
|
||||
}
|
||||
}
|
||||
|
||||
class UiComponentTemplate: UiComponent {
|
||||
|
||||
typealias StateType = State
|
||||
|
@ -5,92 +5,100 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class UiRootReducer {
|
||||
class UiRootReducer: ReducerType {
|
||||
|
||||
typealias UiRootPair = StateActionPair<AppState, UiRoot.Action>
|
||||
typealias MainWindowPair = StateActionPair<AppState, UuidAction<MainWindow.Action>>
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UiRoot.Action
|
||||
|
||||
func reduceUiRoot(_ pair: UiRootPair) -> UiRootPair {
|
||||
var appState = pair.state
|
||||
let mainWindow = MainWindowReducer()
|
||||
|
||||
switch pair.action {
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var appState = tuple.state
|
||||
|
||||
switch tuple.action {
|
||||
|
||||
case .quit:
|
||||
appState.quit = true
|
||||
|
||||
}
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
return (appState, tuple.action, true)
|
||||
}
|
||||
|
||||
func reduceMainWindow(_ pair: MainWindowPair) -> MainWindowPair {
|
||||
var appState = pair.state
|
||||
let uuid = pair.action.uuid
|
||||
class MainWindowReducer: ReducerType {
|
||||
|
||||
switch pair.action.payload {
|
||||
typealias StateType = AppState
|
||||
typealias ActionType = UuidAction<MainWindow.Action>
|
||||
|
||||
case let .becomeKey(isFullScreen):
|
||||
appState.currentMainWindowUuid = uuid
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(
|
||||
from: appState.mainWindowTemplate,
|
||||
new: appState.mainWindows[uuid] ?? appState.mainWindowTemplate,
|
||||
isFullScreen: isFullScreen
|
||||
)
|
||||
func typedReduce(_ tuple: ReduceTuple) -> ReduceTuple {
|
||||
var appState = tuple.state
|
||||
let uuid = tuple.action.uuid
|
||||
|
||||
switch tuple.action.payload {
|
||||
|
||||
case let .becomeKey(isFullScreen):
|
||||
appState.currentMainWindowUuid = uuid
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(
|
||||
from: appState.mainWindowTemplate,
|
||||
new: appState.mainWindows[uuid] ?? appState.mainWindowTemplate,
|
||||
isFullScreen: isFullScreen
|
||||
)
|
||||
|
||||
case let .frameChanged(to:frame):
|
||||
if uuid == appState.currentMainWindowUuid {
|
||||
appState.mainWindowTemplate.frame = frame
|
||||
}
|
||||
|
||||
case let .setToolsState(tools):
|
||||
appState.mainWindowTemplate.orderedTools = tools.map { $0.0 }
|
||||
|
||||
case let .toggleAllTools(value):
|
||||
appState.mainWindowTemplate.isAllToolsVisible = value
|
||||
|
||||
case let .toggleToolButtons(value):
|
||||
appState.mainWindowTemplate.isToolButtonsVisible = value
|
||||
|
||||
case .close:
|
||||
if appState.currentMainWindowUuid == uuid, let mainWindowToClose = appState.mainWindows[uuid] {
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(from: appState.mainWindowTemplate,
|
||||
new: mainWindowToClose,
|
||||
isFullScreen: false)
|
||||
|
||||
appState.currentMainWindowUuid = nil
|
||||
}
|
||||
|
||||
appState.mainWindows.removeValue(forKey: uuid)
|
||||
|
||||
case let .setTheme(theme):
|
||||
appState.mainWindowTemplate.appearance.theme = Marked(theme)
|
||||
|
||||
default:
|
||||
return tuple
|
||||
|
||||
case let .frameChanged(to:frame):
|
||||
if uuid == appState.currentMainWindowUuid {
|
||||
appState.mainWindowTemplate.frame = frame
|
||||
}
|
||||
|
||||
case let .setToolsState(tools):
|
||||
appState.mainWindowTemplate.orderedTools = tools.map { $0.0 }
|
||||
return (appState, tuple.action, true)
|
||||
}
|
||||
|
||||
case let .toggleAllTools(value):
|
||||
appState.mainWindowTemplate.isAllToolsVisible = value
|
||||
private func mainWindowTemplate(from old: MainWindow.State,
|
||||
new: MainWindow.State,
|
||||
isFullScreen: Bool) -> MainWindow.State {
|
||||
|
||||
case let .toggleToolButtons(value):
|
||||
appState.mainWindowTemplate.isToolButtonsVisible = value
|
||||
var result = old
|
||||
|
||||
case .close:
|
||||
if appState.currentMainWindowUuid == uuid, let mainWindowToClose = appState.mainWindows[uuid] {
|
||||
appState.mainWindowTemplate = self.mainWindowTemplate(from: appState.mainWindowTemplate,
|
||||
new: mainWindowToClose,
|
||||
isFullScreen: false)
|
||||
|
||||
appState.currentMainWindowUuid = nil
|
||||
if !isFullScreen {
|
||||
result.frame = new.frame
|
||||
}
|
||||
|
||||
appState.mainWindows.removeValue(forKey: uuid)
|
||||
|
||||
case let .setTheme(theme):
|
||||
appState.mainWindowTemplate.appearance.theme = Marked(theme)
|
||||
|
||||
default:
|
||||
return pair
|
||||
result.isAllToolsVisible = new.isAllToolsVisible
|
||||
result.isToolButtonsVisible = new.isToolButtonsVisible
|
||||
result.tools = new.tools
|
||||
result.orderedTools = new.orderedTools
|
||||
result.previewTool = new.previewTool
|
||||
result.fileBrowserShowHidden = new.fileBrowserShowHidden
|
||||
result.htmlPreview = .default
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return StateActionPair(state: appState, action: pair.action)
|
||||
}
|
||||
|
||||
private func mainWindowTemplate(from old: MainWindow.State,
|
||||
new: MainWindow.State,
|
||||
isFullScreen: Bool) -> MainWindow.State {
|
||||
|
||||
var result = old
|
||||
|
||||
if !isFullScreen {
|
||||
result.frame = new.frame
|
||||
}
|
||||
|
||||
result.isAllToolsVisible = new.isAllToolsVisible
|
||||
result.isToolButtonsVisible = new.isToolButtonsVisible
|
||||
result.tools = new.tools
|
||||
result.orderedTools = new.orderedTools
|
||||
result.previewTool = new.previewTool
|
||||
result.fileBrowserShowHidden = new.fileBrowserShowHidden
|
||||
result.htmlPreview = .default
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user