diff options
Diffstat (limited to 'Map/Data')
| -rw-r--r-- | Map/Data/AppState.swift | 103 | ||||
| -rw-r--r-- | Map/Data/Models/Map+parse.swift | 29 | ||||
| -rw-r--r-- | Map/Data/Persistence.swift | 42 | ||||
| -rw-r--r-- | Map/Data/Stage.swift | 195 | ||||
| -rw-r--r-- | Map/Data/Store.swift | 18 |
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 |