aboutsummaryrefslogtreecommitdiff
path: root/Map/Data
diff options
context:
space:
mode:
authorRuben Beltran del Rio <ruben@unlimited.pizza>2023-05-07 15:22:54 +0200
committerRuben Beltran del Rio <ruben@unlimited.pizza>2023-05-07 15:22:54 +0200
commitfdb4633d3e9158e457d57e820df6e1efb4df39c2 (patch)
tree176cad99cb9befc0a65a7af02561019e811814de /Map/Data
parent75a0e4509a70055851b085f3f7293ae1cf48164c (diff)
Update to support notes + new style2.0.0
Diffstat (limited to 'Map/Data')
-rw-r--r--Map/Data/AppState.swift103
-rw-r--r--Map/Data/Models/Map+parse.swift29
-rw-r--r--Map/Data/Persistence.swift42
-rw-r--r--Map/Data/Stage.swift195
-rw-r--r--Map/Data/Store.swift18
5 files changed, 387 insertions, 0 deletions
diff --git a/Map/Data/AppState.swift b/Map/Data/AppState.swift
new file mode 100644
index 0000000..d0d5670
--- /dev/null
+++ b/Map/Data/AppState.swift
@@ -0,0 +1,103 @@
+import Cocoa
+import Foundation
+import SwiftUI
+
+struct AppState {
+ var selectedEvolution: StageType = .general
+}
+
+enum AppAction {
+ case selectEvolution(evolution: StageType)
+ case exportMapAsImage(map: Map)
+ case exportMapAsText(map: Map)
+ case deleteMap(map: Map)
+}
+
+func appStateReducer(state: inout AppState, action: AppAction) {
+
+ switch action {
+
+ case .selectEvolution(let evolution):
+ state.selectedEvolution = evolution
+
+ case .exportMapAsImage(let map):
+ let window = NSWindow(
+ contentRect: .init(
+ origin: .zero,
+ size: .init(
+ width: NSScreen.main!.frame.width,
+ height: NSScreen.main!.frame.height)),
+ styleMask: [.closable],
+ backing: .buffered,
+ defer: false)
+
+ window.title = map.title ?? "Untitled Map"
+ window.isOpaque = true
+ window.center()
+ window.isMovableByWindowBackground = true
+ window.makeKeyAndOrderFront(nil)
+
+ let renderView = MapRenderView(
+ content: Binding.constant(map.content ?? ""),
+ evolution: Binding.constant(state.selectedEvolution))
+
+ let view = NSHostingView(rootView: renderView)
+ window.contentView = view
+
+ let imageRepresentation = view.bitmapImageRepForCachingDisplay(in: view.bounds)!
+ view.cacheDisplay(in: view.bounds, to: imageRepresentation)
+ let image = NSImage(cgImage: imageRepresentation.cgImage!, size: view.bounds.size)
+
+ let dialog = NSSavePanel()
+
+ dialog.title = "Save Map"
+ dialog.showsResizeIndicator = false
+ dialog.canCreateDirectories = true
+ dialog.showsHiddenFiles = false
+ dialog.allowedContentTypes = [.png]
+ dialog.nameFieldStringValue = map.title ?? "Untitled Map"
+
+ if dialog.runModal() == NSApplication.ModalResponse.OK {
+ let result = dialog.url
+
+ if result != nil {
+
+ image.writePNG(toURL: result!)
+ print("saved at \(result!)")
+ }
+ } else {
+ print("Cancel")
+ }
+ window.orderOut(nil)
+
+ case .exportMapAsText(let map):
+ let dialog = NSSavePanel()
+
+ dialog.title = "Save Map Text"
+ dialog.showsResizeIndicator = false
+ dialog.canCreateDirectories = true
+ dialog.showsHiddenFiles = false
+ dialog.allowedContentTypes = [.text]
+ dialog.nameFieldStringValue = map.title ?? "Untitled Map"
+
+ if let content = map.content {
+
+ if dialog.runModal() == NSApplication.ModalResponse.OK {
+ let result = dialog.url
+
+ if let result = result {
+ try? content.write(to: result, atomically: true, encoding: String.Encoding.utf8)
+ }
+ } else {
+ print("Cancel")
+ }
+ }
+ case .deleteMap(let map):
+ let context = PersistenceController.shared.container.viewContext
+ context.delete(map)
+
+ try? context.save()
+ }
+}
+
+typealias AppStore = Store<AppState, AppAction>
diff --git a/Map/Data/Models/Map+parse.swift b/Map/Data/Models/Map+parse.swift
new file mode 100644
index 0000000..5181daf
--- /dev/null
+++ b/Map/Data/Models/Map+parse.swift
@@ -0,0 +1,29 @@
+extension Map {
+ static func parse(content: String) -> ParsedMap {
+
+ let parsers = [
+ AnyMapParserStrategy(NoteParserStrategy()),
+ AnyMapParserStrategy(VertexParserStrategy()),
+ AnyMapParserStrategy(EdgeParserStrategy()),
+ AnyMapParserStrategy(BlockerParserStrategy()),
+ AnyMapParserStrategy(OpportunityParserStrategy()),
+ AnyMapParserStrategy(StageParserStrategy()),
+ ]
+ let builder = MapBuilder()
+
+ let lines = content.split(whereSeparator: \.isNewline)
+
+ for (index, line) in lines.enumerated() {
+ for parser in parsers {
+ if parser.canHandle(line: String(line)) {
+ let (type, object) = parser.handle(
+ index: index, line: String(line), vertices: builder.vertices)
+ builder.addObjectToMap(type: type, object: object)
+ break
+ }
+ }
+ }
+
+ return builder.build()
+ }
+}
diff --git a/Map/Data/Persistence.swift b/Map/Data/Persistence.swift
new file mode 100644
index 0000000..1eb09e8
--- /dev/null
+++ b/Map/Data/Persistence.swift
@@ -0,0 +1,42 @@
+import CoreData
+
+struct PersistenceController {
+ static let shared = PersistenceController()
+
+ static var preview: PersistenceController = {
+ let result = PersistenceController(inMemory: true)
+ let viewContext = result.container.viewContext
+ for _ in 0..<10 {
+ let newMap = Map(context: viewContext)
+ newMap.uuid = UUID()
+ newMap.createdAt = Date()
+ newMap.title = "Map \(newMap.createdAt!.format())"
+ newMap.content = ""
+ }
+ do {
+ try viewContext.save()
+ } catch {
+ let nsError = error as NSError
+ fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
+ }
+ return result
+ }()
+
+ let container: NSPersistentCloudKitContainer
+
+ init(inMemory: Bool = false) {
+ container = NSPersistentCloudKitContainer(name: "Map")
+
+ container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
+ container.viewContext.automaticallyMergesChangesFromParent = true
+
+ if inMemory {
+ container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
+ }
+ container.loadPersistentStores(completionHandler: { (storeDescription, error) in
+ if let error = error as NSError? {
+ fatalError("Unresolved error \(error), \(error.userInfo)")
+ }
+ })
+ }
+}
diff --git a/Map/Data/Stage.swift b/Map/Data/Stage.swift
new file mode 100644
index 0000000..26b1929
--- /dev/null
+++ b/Map/Data/Stage.swift
@@ -0,0 +1,195 @@
+struct Stage {
+ let i: String
+ let ii: String
+ let iii: String
+ let iv: String
+
+ static func stages(_ type: StageType) -> Stage {
+ switch type {
+ case .general:
+ return Stage(
+ i: "Genesis", ii: "Custom", iii: "Product (+rental)", iv: "Commodity (+utility)")
+ case .practice:
+ return Stage(
+ i: "Novel", ii: "Emerging", iii: "Good", iv: "Best")
+ case .data:
+ return Stage(
+ i: "Unmodelled", ii: "Divergent", iii: "Convergent", iv: "Modelled")
+ case .knowledge:
+ return Stage(
+ i: "Concept", ii: "Hypothesis", iii: "Theory", iv: "Accepted")
+ case .ubiquity:
+ return Stage(
+ i: "Rare", ii: "Slowly Increasing", iii: "Rapidly Increasing",
+ iv: "Widespread in the applicable market / ecosystem")
+ case .certainty:
+ return Stage(
+ i: "Poorly Understood / exploring the unknown",
+ ii: "Rapid Increase In Learning / discovery becomes refining",
+ iii: "Rapid increase in use / increasing fit for purpose",
+ iv: "Commonly understood (in terms of use)")
+ case .publicationTypes:
+ return Stage(
+ i:
+ "Describe the wonder of the thing / the discovery of some marvel / a new land / an unknown frontier",
+ ii:
+ "Focused on build / construct / awareness and learning / many models of explanation / no accepted forms / a wild west",
+ iii:
+ "Maintenance / operations / installation / comparison between competing forms / feature analysis",
+ iv: "Focused on use / increasingly an accepted, almost invisible component")
+ case .market:
+ return Stage(
+ i: "Undefined Market",
+ ii: "Forming Market / an array of competing forms and models of understanding",
+ iii: "Growing Market / consolidation to a few competing but more accepted forms",
+ iv: "Mature Market / stabilised to an accepted form")
+ case .knowledgeManagement:
+ return Stage(
+ i: "Uncertain", ii: "Learning on use / focused on testing prediction",
+ iii: "Learning on operation / using prediction / verification", iv: "Known / accepted")
+ case .marketPerception:
+ return Stage(
+ i: "Chaotic (non-linear) / domain of the \"crazy\"", ii: "Domain of \"experts\"",
+ iii: "Increasing expectation of use / domain of \"professionals\"",
+ iv: "Ordered (appearance of being linear) / trivial / formula to be applied")
+ case .userPerception:
+ return Stage(
+ i: "Different / confusing / exciting / surprising / dangerous",
+ ii: "Leading edge / emerging / unceirtanty over results",
+ iii: "Increasingly common / disappointed if not used or available / feeling left behind",
+ iv: "Standard / expected / feeling of shock if not used")
+ case .perceptionInIndustry:
+ return Stage(
+ i: "Future source of competitive advantage / unpredictable / unknown",
+ ii: "Seen as a scompetitive advantage / a differential / ROI / case examples",
+ iii: "Advantage through implementation / features / this model is better than that",
+ iv: "Cost of doing business / accepted / specific defined models")
+ case .focusOfValue:
+ return Stage(
+ i: "High future worth but immediate investment",
+ ii: "Seeking ways to profit and a ROI / seeking confirmation of value",
+ iii:
+ "High profitability per unit / a valuable model / a feeling of understanding / focus on exploitation",
+ iv:
+ "High volume / reducing margin / important but invisible / an essential component of something more complex"
+ )
+ case .understanding:
+ return Stage(
+ i: "Poorly Understood / unpredictable",
+ ii: "Increasing understanding / development of measures",
+ iii: "Increasing education / constant refinement of needs / measures",
+ iv: "Believed to be well defined / stable / measurable")
+ case .comparison:
+ return Stage(
+ i: "Constantly changing / a differential / unstable",
+ ii: "Learning from others / testing the water / some evidential support",
+ iii: "Competing models / feature difference / evidential support",
+ iv: "Essential / any advantage is operational / accepted norm")
+ case .failure:
+ return Stage(
+ i: "High / tolerated / assumed to be wrong",
+ ii: "Moderate / unsurprising if wrong but disappointed",
+ iii:
+ "Not tolerated / focus on constant improvement / assumed to be in the right direction / resistance to changing the model",
+ iv: "Surprised by failure / focus on operational efficiency")
+ case .marketAction:
+ return Stage(
+ i: "Gambling / driven by gut", ii: "Exploring a \"found\" value",
+ iii: "Market analysis / listening to customers", iv: "Metric driven / build what is needed")
+ case .efficiency:
+ return Stage(
+ i: "Reducing the cost of change (experimentation)", ii: "Reducing cost of waste (Learning)",
+ iii: "Reducing cost of waste (Learning)", iv: "Reducing cost of deviation (Volume)")
+ case .decisionDrivers:
+ return Stage(
+ i: "Heritage / culture", ii: "Analyses & synthesis", iii: "Analyses & synthesis",
+ iv: "Previous Experience")
+ case .behavior:
+ return Stage(
+ i: "Uncertain when to use", ii: "Learning when to use", iii: "Learning through use",
+ iv: "Known / common usage")
+ }
+ }
+
+ static func title(_ type: StageType) -> String {
+ switch type {
+ case .general:
+ return "Activities"
+ case .practice:
+ return "Practice"
+ case .data:
+ return "Data"
+ case .knowledge:
+ return "Knowledge"
+ case .ubiquity:
+ return "Ubiquity"
+ case .certainty:
+ return "Certainty"
+ case .publicationTypes:
+ return "Publication Types"
+ case .market:
+ return "Market"
+ case .knowledgeManagement:
+ return "Knowledge Management"
+ case .marketPerception:
+ return "Market Perception"
+ case .userPerception:
+ return "User Perception"
+ case .perceptionInIndustry:
+ return "Perception In Industry"
+ case .focusOfValue:
+ return "Focus Of Value"
+ case .understanding:
+ return "Understanding"
+ case .comparison:
+ return "Comparison"
+ case .failure:
+ return "Failure"
+ case .marketAction:
+ return "Market Action"
+ case .efficiency:
+ return "Efficiency"
+ case .decisionDrivers:
+ return "Decision Drivers"
+ case .behavior:
+ return "Behavior"
+ }
+ }
+}
+
+enum StageType: String, CaseIterable, Identifiable {
+ case general
+ case practice
+ case data
+ case knowledge
+
+ case ubiquity
+ case certainty
+ case publicationTypes
+
+ case market
+ case knowledgeManagement
+ case marketPerception
+ case userPerception
+ case perceptionInIndustry
+ case focusOfValue
+ case understanding
+ case comparison
+ case failure
+ case marketAction
+ case efficiency
+ case decisionDrivers
+
+ case behavior
+
+ var id: String { self.rawValue }
+
+ static let types: [StageType] = [.general, .practice, .data, .knowledge]
+ static let characteristics: [StageType] = [.ubiquity, .certainty, .publicationTypes]
+ static let properties: [StageType] = [
+ .market, .knowledgeManagement, .marketPerception, .userPerception,
+ .perceptionInIndustry, .focusOfValue, .understanding, .comparison, .failure,
+ .marketAction, .efficiency, .decisionDrivers,
+ ]
+ static let custom: [StageType] = [.behavior]
+}
diff --git a/Map/Data/Store.swift b/Map/Data/Store.swift
new file mode 100644
index 0000000..7860f33
--- /dev/null
+++ b/Map/Data/Store.swift
@@ -0,0 +1,18 @@
+import Foundation
+
+final class Store<State, Action>: ObservableObject {
+ @Published private(set) var state: State
+
+ private let reducer: Reducer<State, Action>
+
+ init(initialState: State, reducer: @escaping Reducer<State, Action>) {
+ self.state = initialState
+ self.reducer = reducer
+ }
+
+ func send(_ action: Action) {
+ reducer(&state, action)
+ }
+}
+
+typealias Reducer<State, Action> = (inout State, Action) -> Void