diff options
| author | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-02-04 23:46:55 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <ruben@unlimited.pizza> | 2021-02-04 23:46:55 +0100 |
| commit | 77d0155b661e813a85a7312ed809fc7e5a9f7440 (patch) | |
| tree | e9b99a94e9b5937c70d9d768d56e8092686f01aa /Map/Views | |
| parent | 491463f421c86d3a7a5482108b818aa5b5e441ea (diff) | |
Release 1.1.01.1.0
Diffstat (limited to 'Map/Views')
| -rw-r--r-- | Map/Views/ContentView.swift | 2 | ||||
| -rw-r--r-- | Map/Views/DefaultMapView.swift | 2 | ||||
| -rw-r--r-- | Map/Views/EvolutionPicker.swift | 48 | ||||
| -rw-r--r-- | Map/Views/MapDetail.swift | 15 | ||||
| -rw-r--r-- | Map/Views/MapRender.swift | 11 | ||||
| -rw-r--r-- | Map/Views/MapTextEditor.swift | 80 | ||||
| -rw-r--r-- | Map/Views/Stage.swift | 161 |
7 files changed, 140 insertions, 179 deletions
diff --git a/Map/Views/ContentView.swift b/Map/Views/ContentView.swift index 4f55c27..cf1f145 100644 --- a/Map/Views/ContentView.swift +++ b/Map/Views/ContentView.swift @@ -11,6 +11,8 @@ import SwiftUI struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext + @EnvironmentObject var store: AppStore + @FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \Map.createdAt, ascending: true)], animation: .default) diff --git a/Map/Views/DefaultMapView.swift b/Map/Views/DefaultMapView.swift index 66914f1..1f624d2 100644 --- a/Map/Views/DefaultMapView.swift +++ b/Map/Views/DefaultMapView.swift @@ -17,7 +17,7 @@ struct DefaultMapView: View { struct DefaultMapView_Previews: PreviewProvider { static var previews: some View { - MapDetailView(map: Map()).environment( + DefaultMapView().environment( \.managedObjectContext, PersistenceController.preview.container.viewContext) } } diff --git a/Map/Views/EvolutionPicker.swift b/Map/Views/EvolutionPicker.swift new file mode 100644 index 0000000..815f575 --- /dev/null +++ b/Map/Views/EvolutionPicker.swift @@ -0,0 +1,48 @@ +// +// EvolutionPicker.swift +// Map +// +// Created by Ruben Beltran del Rio on 2/4/21. +// + +import SwiftUI + +struct EvolutionPicker: View { + + @EnvironmentObject private var store: AppStore + + private var selectedEvolution: Binding<StageType> { + Binding( + get: { store.state.selectedEvolution }, + set: { evolution in + store.send(.selectEvolution(evolution: evolution)) + } + ) + } + + var body: some View { + Picker("Evolution", selection: selectedEvolution) { + ForEach(StageType.types) { stage in + Text(Stage.title(stage)).tag(stage).padding(4.0) + } + Divider() + ForEach(StageType.characteristics) { stage in + Text(Stage.title(stage)).tag(stage).padding(4.0) + } + Divider() + ForEach(StageType.properties) { stage in + Text(Stage.title(stage)).tag(stage).padding(4.0) + } + Divider() + ForEach(StageType.custom) { stage in + Text(Stage.title(stage)).tag(stage).padding(4.0) + } + }.padding(.horizontal, 8.0).padding(.vertical, 4.0) + } +} + +struct EvolutionPicker_Previews: PreviewProvider { + static var previews: some View { + EvolutionPicker() + } +} diff --git a/Map/Views/MapDetail.swift b/Map/Views/MapDetail.swift index a8ea3d1..6ba92bb 100644 --- a/Map/Views/MapDetail.swift +++ b/Map/Views/MapDetail.swift @@ -38,8 +38,6 @@ struct MapDetailView: View { ) } - @State private var selectedEvolution = StageType.general - var body: some View { if map.uuid != nil { VSplitView { @@ -59,14 +57,10 @@ struct MapDetailView: View { Image(systemName: "photo") }.padding(.vertical, 4.0).padding(.leading, 4.0).padding(.trailing, 8.0) } - Picker("Evolution", selection: $selectedEvolution) { - ForEach(StageType.allCases) { stage in - Text(Stage.title(stage)).tag(stage).padding(4.0) - } - }.padding(.horizontal, 8.0).padding(.vertical, 4.0) + EvolutionPicker() ZStack(alignment: .topLeading) { - MapTextEditor(text: content).onChange(of: map.content) { _ in + MapTextEditor(text: content, colorScheme: colorScheme).onChange(of: map.content) { _ in try? viewContext.save() } .background(mapColor.background) @@ -76,7 +70,8 @@ struct MapDetailView: View { 5.0) }.padding(.horizontal, 8.0) ScrollView([.horizontal, .vertical]) { - MapRenderView(map: map, evolution: Stage.stages(selectedEvolution)) + MapRenderView( + content: content.wrappedValue, evolution: Stage.stages(store.state.selectedEvolution)) }.background(mapColor.background) } } else { @@ -89,7 +84,7 @@ struct MapDetailView: View { } private func saveImage() { - store.send(.exportMapAsImage(map: map, evolution: selectedEvolution)) + store.send(.exportMapAsImage(map: map)) } } diff --git a/Map/Views/MapRender.swift b/Map/Views/MapRender.swift index b35b724..7623b61 100644 --- a/Map/Views/MapRender.swift +++ b/Map/Views/MapRender.swift @@ -5,6 +5,7 @@ // Created by Ruben Beltran del Rio on 2/1/21. // +import Combine import CoreData import CoreGraphics import SwiftUI @@ -13,7 +14,7 @@ struct MapRenderView: View { @Environment(\.colorScheme) var colorScheme - @ObservedObject var map: Map + var content: String let evolution: Stage let mapSize = CGSize(width: 1300.0, height: 1000.0) @@ -23,7 +24,7 @@ struct MapRenderView: View { let padding = CGFloat(30.0) var parsedMap: ParsedMap { - return map.parse() + return Map.parse(content: content) } var body: some View { @@ -33,7 +34,7 @@ struct MapRenderView: View { path.addRect( CGRect( x: -padding, y: -padding, width: mapSize.width + padding * 2, - height: mapSize.height + padding * 2)) + height: mapSize.height + padding * 4)) }.fill(MapColor.colorForScheme(colorScheme).background) MapStages(mapSize: mapSize, lineWidth: lineWidth, stages: parsedMap.stages) @@ -48,14 +49,14 @@ struct MapRenderView: View { mapSize: mapSize, lineWidth: lineWidth, vertexSize: vertexSize, edges: parsedMap.edges) }.frame( width: mapSize.width, - height: mapSize.height, alignment: .topLeading + height: mapSize.height + 2 * padding, alignment: .topLeading ).padding(padding) } } struct MapRenderView_Previews: PreviewProvider { static var previews: some View { - MapDetailView(map: Map()).environment( + MapRenderView(content: "", evolution: Stage.stages(.general)).environment( \.managedObjectContext, PersistenceController.preview.container.viewContext) } } diff --git a/Map/Views/MapTextEditor.swift b/Map/Views/MapTextEditor.swift index 7251c59..e030d64 100644 --- a/Map/Views/MapTextEditor.swift +++ b/Map/Views/MapTextEditor.swift @@ -4,9 +4,19 @@ import SwiftUI class MapTextEditorController: NSViewController { @Binding var text: String + var colorScheme: ColorScheme - init(text: Binding<String>) { + private let vertexRegex = MapParsingPatterns.vertex + private let edgeRegex = MapParsingPatterns.edge + private let blockerRegex = MapParsingPatterns.blocker + private let opportunityRegex = MapParsingPatterns.opportunity + private let stageRegex = MapParsingPatterns.stage + + private let debouncer: Debouncer = Debouncer(seconds: 0.2) + + init(text: Binding<String>, colorScheme: ColorScheme) { self._text = text + self.colorScheme = colorScheme super.init(nibName: nil, bundle: nil) } @@ -21,6 +31,7 @@ class MapTextEditorController: NSViewController { scrollView.translatesAutoresizingMaskIntoConstraints = false textView.delegate = self + textView.textStorage?.delegate = self textView.string = self.text textView.isEditable = true textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular) @@ -30,6 +41,10 @@ class MapTextEditorController: NSViewController { override func viewDidAppear() { self.view.window?.makeFirstResponder(self.view) } + + func updateColorScheme(_ colorScheme: ColorScheme) { + self.colorScheme = colorScheme + } } extension MapTextEditorController: NSTextViewDelegate { @@ -53,19 +68,80 @@ extension MapTextEditorController: NSTextViewDelegate { } } +extension MapTextEditorController: NSTextStorageDelegate { + override func textStorageDidProcessEditing(_ obj: Notification) { + if let textStorage = obj.object as? NSTextStorage { + debouncer.debounce { + DispatchQueue.main.async { + self.colorizeText(textStorage: textStorage) + } + } + } + } + + private func colorizeText(textStorage: NSTextStorage) { + let range = NSMakeRange(0, textStorage.length) + var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range) + let colors = MapColor.colorForScheme(colorScheme) + + for match in matches { + textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2)) + textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 3)) + textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 4)) + } + + matches = edgeRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 1)) + let arrowRange = match.range(at: 2) + textStorage.addAttributes( + [.foregroundColor: colors.syntax.symbol], + range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1)) + textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 3)) + } + + matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2)) + textStorage.addAttributes([.foregroundColor: colors.syntax.symbol], range: match.range(at: 3)) + textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 4)) + } + + matches = blockerRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: colors.syntax.vertex], range: match.range(at: 2)) + } + + matches = stageRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes([.foregroundColor: colors.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes([.foregroundColor: colors.syntax.number], range: match.range(at: 2)) + } + } +} + struct MapTextEditor: NSViewControllerRepresentable { @Binding var text: String + let colorScheme: ColorScheme func makeNSViewController( context: NSViewControllerRepresentableContext<MapTextEditor> ) -> MapTextEditorController { - return MapTextEditorController(text: $text) + return MapTextEditorController(text: $text, colorScheme: colorScheme) } func updateNSViewController( _ nsViewController: MapTextEditorController, context: NSViewControllerRepresentableContext<MapTextEditor> ) { + nsViewController.updateColorScheme(context.environment.colorScheme) } } diff --git a/Map/Views/Stage.swift b/Map/Views/Stage.swift deleted file mode 100644 index 23432ad..0000000 --- a/Map/Views/Stage.swift +++ /dev/null @@ -1,161 +0,0 @@ -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 Built", 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 Consumption", iii: "Rapidly Increasing Consumption", - iv: "Widespread and stabilising") - case .certainty: - return Stage( - i: "Poorly Understood", ii: "Rapid Increase In Learning", - iii: "Rapid Increase in Use / fit for purpose", iv: "Commonly understood (in terms of use)") - case .publicationTypes: - return Stage( - i: "Normally describing the wonder of the thing", - ii: "Build / construct / awareness and learning", - iii: "Maintenance / operations / installation / feature", iv: "Focused on use") - case .market: - return Stage( - i: "Undefined Market", ii: "Forming Market", iii: "Growing Market", iv: "Mature Market") - case .knowledgeManagement: - return Stage( - i: "Uncertain", ii: "Learning on use", iii: "Learning on operation", iv: "Known / accepted") - case .marketPerception: - return Stage( - i: "Chaotic (non-linear)", ii: "Domain of experts", iii: "Increasing expectation of use", - iv: "Ordered (appearance of being trivial) / trivial") - case .userPerception: - return Stage( - i: "Different / confusing / exciting / surprising", ii: "Leading edge / emerging", - iii: "Increasingly common / disappointed if not used", iv: "Standard / expected") - case .perceptionInIndustry: - return Stage( - i: "Competitive advantage / unpredictable / unknown", - ii: "Competitive advantage / ROI / case examples", - iii: "Advantage through implementation / features", iv: "Cost of doing business") - case .focusOfValue: - return Stage( - i: "High future worth", ii: "Seeking profit / ROI", iii: "High profitability", - iv: "High volume / reducing margin") - 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: "Feature difference", iv: "Essential / operational advantage") - case .failure: - return Stage( - i: "High / tolerated / assumed", ii: "Moderate / unsurprising but disappointed", - iii: "Not tolerated, focus on constant improvement", - iv: "Operational efficiency and surprised by failure") - 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 } -} |