aboutsummaryrefslogtreecommitdiff
path: root/Map/Views
diff options
context:
space:
mode:
Diffstat (limited to 'Map/Views')
-rw-r--r--Map/Views/ContentView.swift2
-rw-r--r--Map/Views/DefaultMapView.swift2
-rw-r--r--Map/Views/EvolutionPicker.swift48
-rw-r--r--Map/Views/MapDetail.swift15
-rw-r--r--Map/Views/MapRender.swift11
-rw-r--r--Map/Views/MapTextEditor.swift80
-rw-r--r--Map/Views/Stage.swift161
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 }
-}