From 3bacbf6ac85330d35493953d7296e5ba420b7750 Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Mon, 7 Jul 2025 13:51:43 +0200 Subject: Use inertia instead of blockers --- Map.xcodeproj/project.pbxproj | 8 ++-- Map/Business/MapParser/MapParser.swift | 22 ++++----- .../Strategies/BlockerParserStrategy.swift | 40 ---------------- .../Strategies/InertiaParserStrategy.swift | 40 ++++++++++++++++ Map/Localizable.xcstrings | 2 +- .../Base Components/MapRender/MapBlockers.swift | 55 ---------------------- .../Base Components/MapRender/MapIntertias.swift | 55 ++++++++++++++++++++++ .../Base Components/MapTextEditor.swift | 4 +- .../MapRender/MapRenderView.swift | 2 +- Map/Presentation/Theme/Color+theme.swift | 2 +- MapTests/MapParserStrategyTests.swift | 10 ++-- MapTests/MapTests.swift | 18 +++---- 12 files changed, 129 insertions(+), 129 deletions(-) delete mode 100644 Map/Business/MapParser/Strategies/BlockerParserStrategy.swift create mode 100644 Map/Business/MapParser/Strategies/InertiaParserStrategy.swift delete mode 100644 Map/Presentation/Base Components/MapRender/MapBlockers.swift create mode 100644 Map/Presentation/Base Components/MapRender/MapIntertias.swift diff --git a/Map.xcodeproj/project.pbxproj b/Map.xcodeproj/project.pbxproj index 46304ae..77943e2 100644 --- a/Map.xcodeproj/project.pbxproj +++ b/Map.xcodeproj/project.pbxproj @@ -440,7 +440,7 @@ CODE_SIGN_ENTITLEMENTS = Map/Map.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 50; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Map/Preview Content\""; ENABLE_HARDENED_RUNTIME = YES; @@ -454,7 +454,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 3.1.0; + MARKETING_VERSION = 4.0.0; PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -471,7 +471,7 @@ CODE_SIGN_ENTITLEMENTS = Map/Map.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 50; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Map/Preview Content\""; ENABLE_HARDENED_RUNTIME = YES; @@ -485,7 +485,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 3.1.0; + MARKETING_VERSION = 4.0.0; PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Map/Business/MapParser/MapParser.swift b/Map/Business/MapParser/MapParser.swift index e94de77..fc11415 100644 --- a/Map/Business/MapParser/MapParser.swift +++ b/Map/Business/MapParser/MapParser.swift @@ -22,7 +22,7 @@ struct MapParser { AnyMapParserStrategy(NoteParserStrategy()), AnyMapParserStrategy(VertexParserStrategy()), AnyMapParserStrategy(EdgeParserStrategy()), - AnyMapParserStrategy(BlockerParserStrategy()), + AnyMapParserStrategy(InertiaParserStrategy()), AnyMapParserStrategy(OpportunityParserStrategy()), AnyMapParserStrategy(StageParserStrategy()), AnyMapParserStrategy(GroupParserStrategy()), @@ -55,8 +55,8 @@ struct MapParsingPatterns { options: [.caseInsensitive, .anchorsMatchLines]) static let edge = try! NSRegularExpression( pattern: "^(.+?)[\\s]*-([->])[\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) - static let blocker = try! NSRegularExpression( - pattern: "^\\[(Blocker)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) + static let inertia = try! NSRegularExpression( + pattern: "^\\[(Inertia)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) static let opportunity = try! NSRegularExpression( pattern: "^\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)", options: [.caseInsensitive, .anchorsMatchLines]) @@ -74,14 +74,14 @@ struct MapParsingPatterns { struct ParsedMap { let vertices: [Vertex] let edges: [MapEdge] - let blockers: [Blocker] + let inertias: [Inertia] let opportunities: [Opportunity] let notes: [Note] let stages: [CGFloat] let groups: [[Vertex]] static let empty: ParsedMap = ParsedMap( - vertices: [], edges: [], blockers: [], opportunities: [], notes: [], stages: defaultDimensions, + vertices: [], edges: [], inertias: [], opportunities: [], notes: [], stages: defaultDimensions, groups: []) } @@ -112,7 +112,7 @@ struct MapEdge { let arrowhead: Bool } -struct Blocker { +struct Inertia { let id: Int let position: CGPoint } @@ -162,7 +162,7 @@ struct AnyMapParserStrategy: MapParserStrategy { class MapBuilder { var vertices: [String: Vertex] = [:] private var edges: [MapEdge] = [] - private var blockers: [Blocker] = [] + private var inertias: [Inertia] = [] private var opportunities: [Opportunity] = [] private var notes: [Note] = [] private var stages: [CGFloat] = defaultDimensions @@ -179,9 +179,9 @@ class MapBuilder { edges.append(edge) } - if type == Blocker.self { - let blocker = object as! Blocker - blockers.append(blocker) + if type == Inertia.self { + let inertia = object as! Inertia + inertias.append(inertia) } if type == Opportunity.self { @@ -208,7 +208,7 @@ class MapBuilder { func build() -> ParsedMap { let mappedVertices = vertices.map { label, vertex in return vertex } return ParsedMap( - vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities, + vertices: mappedVertices, edges: edges, inertias: inertias, opportunities: opportunities, notes: notes, stages: stages, groups: groups) } diff --git a/Map/Business/MapParser/Strategies/BlockerParserStrategy.swift b/Map/Business/MapParser/Strategies/BlockerParserStrategy.swift deleted file mode 100644 index 4bd2ce4..0000000 --- a/Map/Business/MapParser/Strategies/BlockerParserStrategy.swift +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2024 Rubén Beltrán del Río - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see https://map.tranquil.systems. -import Foundation - -struct BlockerParserStrategy: MapParserStrategy { - private let regex = MapParsingPatterns.blocker - - func canHandle(line: String) -> Bool { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - return matches.count > 0 && matches[0].numberOfRanges == 3 - } - - func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { - let range = NSRange(location: 0, length: line.utf16.count) - let matches = regex.matches(in: String(line), options: [], range: range) - - let match = matches[0] - let vertexA = String(line[Range(match.range(at: 2), in: line)!]) - - if let vertex = vertices[vertexA] { - let blocker = Blocker(id: index, position: vertex.position) - return (Blocker.self, blocker) - } - - return (NSObject.self, NSObject()) // No matching object - } -} diff --git a/Map/Business/MapParser/Strategies/InertiaParserStrategy.swift b/Map/Business/MapParser/Strategies/InertiaParserStrategy.swift new file mode 100644 index 0000000..d5d515e --- /dev/null +++ b/Map/Business/MapParser/Strategies/InertiaParserStrategy.swift @@ -0,0 +1,40 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import Foundation + +struct InertiaParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.inertia + + func canHandle(line: String) -> Bool { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + return matches.count > 0 && matches[0].numberOfRanges == 3 + } + + func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) { + let range = NSRange(location: 0, length: line.utf16.count) + let matches = regex.matches(in: String(line), options: [], range: range) + + let match = matches[0] + let vertexA = String(line[Range(match.range(at: 2), in: line)!]) + + if let vertex = vertices[vertexA] { + let inertia = Inertia(id: index, position: vertex.position) + return (Inertia.self, inertia) + } + + return (NSObject.self, NSObject()) // No matching object + } +} diff --git a/Map/Localizable.xcstrings b/Map/Localizable.xcstrings index a4b07f0..4dc5e1b 100644 --- a/Map/Localizable.xcstrings +++ b/Map/Localizable.xcstrings @@ -165,7 +165,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Anchor (95, 80)\nUsers (90, 20)\nWalking App (70, 20)\nWalking App Database (50, 60)\nUser Interface (80, 50)\nWeb Server (60, 30)\nCRM (30, 70)\n\nUsers -> Walking App\nUsers -> User Interface\nWalking App -> Walking App Database\nWalking App -> Web Server\nUser Interface -> Web Server\nWeb Server -> CRM\n\n[Note] (80, 5) Social walking app for everyone\n\n[Group] Client Side, Walking App, User Interface\n\n[Evolution] Walking App +5\n\n[Blocker] Unreliable Data" + "value" : "Anchor (95, 80)\nUsers (90, 20)\nWalking App (70, 20)\nWalking App Database (50, 60)\nUser Interface (80, 50)\nWeb Server (60, 30)\nCRM (30, 70)\n\nUsers -> Walking App\nUsers -> User Interface\nWalking App -> Walking App Database\nWalking App -> Web Server\nUser Interface -> Web Server\nWeb Server -> CRM\n\n[Note] (80, 5) Social walking app for everyone\n\n[Group] Client Side, Walking App, User Interface\n\n[Evolution] Walking App +5\n\n[Inertia] Unreliable Data" } } } diff --git a/Map/Presentation/Base Components/MapRender/MapBlockers.swift b/Map/Presentation/Base Components/MapRender/MapBlockers.swift deleted file mode 100644 index e550ca9..0000000 --- a/Map/Presentation/Base Components/MapRender/MapBlockers.swift +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2024 Rubén Beltrán del Río - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see https://map.tranquil.systems. -import SwiftUI - -struct MapBlockers: View { - - let mapSize: CGSize - let vertexSize: CGSize - let blockers: [Blocker] - - let cornerSize = CGSize(width: 2.0, height: 2.0) - - var body: some View { - ForEach(blockers, id: \.id) { vertex in - Path { path in - path.addRoundedRect( - in: CGRect( - origin: CGPoint( - x: w(vertex.position.x) + 3 * vertexSize.width, - y: h(vertex.position.y) - vertexSize.height * 2 / 3), - size: CGSize(width: vertexSize.width / 2, height: vertexSize.height * 2) - ), cornerSize: cornerSize) - }.fill(Color.Theme.Map.blockerColor) - } - } - - func h(_ dimension: CGFloat) -> CGFloat { - max(0.0, min(mapSize.height, dimension * mapSize.height / 100.0)) - } - - func w(_ dimension: CGFloat) -> CGFloat { - max(0.0, min(mapSize.width, dimension * mapSize.width / 100.0)) - } -} - -#Preview { - MapBlockers( - mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0), - blockers: [ - Blocker(id: 0, position: CGPoint(x: 50.0, y: 50.0)), - Blocker(id: 1, position: CGPoint(x: 10.0, y: 20.0)), - ]) -} diff --git a/Map/Presentation/Base Components/MapRender/MapIntertias.swift b/Map/Presentation/Base Components/MapRender/MapIntertias.swift new file mode 100644 index 0000000..91ec6f9 --- /dev/null +++ b/Map/Presentation/Base Components/MapRender/MapIntertias.swift @@ -0,0 +1,55 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import SwiftUI + +struct MapIntertias: View { + + let mapSize: CGSize + let vertexSize: CGSize + let inertias: [Inertia] + + let cornerSize = CGSize(width: 2.0, height: 2.0) + + var body: some View { + ForEach(inertias, id: \.id) { vertex in + Path { path in + path.addRoundedRect( + in: CGRect( + origin: CGPoint( + x: w(vertex.position.x) + 3 * vertexSize.width, + y: h(vertex.position.y) - vertexSize.height * 2 / 3), + size: CGSize(width: vertexSize.width / 2, height: vertexSize.height * 2) + ), cornerSize: cornerSize) + }.fill(Color.Theme.Map.inertia) + } + } + + func h(_ dimension: CGFloat) -> CGFloat { + max(0.0, min(mapSize.height, dimension * mapSize.height / 100.0)) + } + + func w(_ dimension: CGFloat) -> CGFloat { + max(0.0, min(mapSize.width, dimension * mapSize.width / 100.0)) + } +} + +#Preview { + MapIntertias( + mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0), + inertias: [ + Inertia(id: 0, position: CGPoint(x: 50.0, y: 50.0)), + Inertia(id: 1, position: CGPoint(x: 10.0, y: 20.0)), + ]) +} diff --git a/Map/Presentation/Base Components/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor.swift index fe4a7d6..b247047 100644 --- a/Map/Presentation/Base Components/MapTextEditor.swift +++ b/Map/Presentation/Base Components/MapTextEditor.swift @@ -36,7 +36,7 @@ class MapTextEditorController: NSViewController { private let vertexRegex = MapParsingPatterns.vertex private let edgeRegex = MapParsingPatterns.edge - private let blockerRegex = MapParsingPatterns.blocker + private let inertiaRegex = MapParsingPatterns.inertia private let opportunityRegex = MapParsingPatterns.opportunity private let noteRegex = MapParsingPatterns.note private let stageRegex = MapParsingPatterns.stage @@ -195,7 +195,7 @@ extension MapTextEditorController: NSTextStorageDelegate { [.foregroundColor: NSColor.Theme.Syntax.number], range: match.range(at: 4)) } - matches = blockerRegex.matches(in: textStorage.string, options: [], range: range) + matches = inertiaRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { textStorage.addAttributes( diff --git a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift index b821634..3b97945 100644 --- a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift +++ b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift @@ -65,7 +65,7 @@ struct MapRenderView: View { MapOpportunities( mapSize: mapSize, lineWidth: lineWidth, vertexSize: vertexSize, opportunities: parsedMap.opportunities) - MapBlockers(mapSize: mapSize, vertexSize: vertexSize, blockers: parsedMap.blockers) + MapIntertias(mapSize: mapSize, vertexSize: vertexSize, inertias: parsedMap.inertias) }.mask { MapMask( mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices) diff --git a/Map/Presentation/Theme/Color+theme.swift b/Map/Presentation/Theme/Color+theme.swift index 22dc18b..4433dc1 100644 --- a/Map/Presentation/Theme/Color+theme.swift +++ b/Map/Presentation/Theme/Color+theme.swift @@ -44,7 +44,7 @@ extension Color { static let labelColor = Color.Theme.darkSlate static let axisColor = Color.Theme.darkSlate static let vertexColor = Color.Theme.darkSlate - static let blockerColor = Color.Theme.jasperRed + static let inertia = Color.Theme.jasperRed static let opportunityColor = Color.Theme.olympicBlue static let stageForeground = Color.Theme.lightNeutralGray static let stageBackground = Color.white diff --git a/MapTests/MapParserStrategyTests.swift b/MapTests/MapParserStrategyTests.swift index c9f92e8..7061d4d 100644 --- a/MapTests/MapParserStrategyTests.swift +++ b/MapTests/MapParserStrategyTests.swift @@ -113,11 +113,11 @@ struct MapParserStrategyTests { #expect(!strategy.canHandle(line: "Invalid line")) } - @Test func testBlockerParserStrategyCanHandle() async throws { - let strategy = BlockerParserStrategy() + @Test func testInertiaParserStrategyCanHandle() async throws { + let strategy = InertiaParserStrategy() - #expect(strategy.canHandle(line: "[Blocker] Node A")) - #expect(strategy.canHandle(line: "[Blocker] Multi Word Node")) + #expect(strategy.canHandle(line: "[Indertia] Node A")) + #expect(strategy.canHandle(line: "[Inertia] Multi Word Node")) #expect(!strategy.canHandle(line: "Node A (10, 20)")) #expect(!strategy.canHandle(line: "[Note] (10, 20) Test")) @@ -132,7 +132,7 @@ struct MapParserStrategyTests { #expect(strategy.canHandle(line: "[Evolution] Multi Word Node +5.5")) #expect(!strategy.canHandle(line: "Node A (10, 20)")) - #expect(!strategy.canHandle(line: "[Blocker] Node A")) + #expect(!strategy.canHandle(line: "[Inertia] Node A")) #expect(!strategy.canHandle(line: "Invalid line")) } diff --git a/MapTests/MapTests.swift b/MapTests/MapTests.swift index 0dbf63b..fd092d0 100644 --- a/MapTests/MapTests.swift +++ b/MapTests/MapTests.swift @@ -10,7 +10,7 @@ struct MapTests { #expect(result.vertices.isEmpty) #expect(result.edges.isEmpty) #expect(result.notes.isEmpty) - #expect(result.blockers.isEmpty) + #expect(result.inertias.isEmpty) #expect(result.opportunities.isEmpty) #expect(result.groups.isEmpty) } @@ -81,17 +81,17 @@ struct MapTests { #expect(note.text == "This is a test note") } - @Test func testMapParserWithBlocker() async throws { + @Test func testMapParserWithInertia() async throws { let content = """ Node A (10, 20) - [Blocker] Node A + [Inertia] Node A """ let result = MapParser.parse(content: content) - #expect(result.blockers.count == 1) - let blocker = result.blockers.first! - #expect(blocker.position.x == 10.0) - #expect(blocker.position.y == 20.0) + #expect(result.inertias.count == 1) + let inertia = result.inertias.first! + #expect(inertia.position.x == 10.0) + #expect(inertia.position.y == 20.0) } @Test func testMapParserWithOpportunity() async throws { @@ -148,7 +148,7 @@ struct MapTests { Node A -> Node B Node B -- Node C [Note] (70, 80) Complex map example - [Blocker] Node A + [Inertia] Node A [Evolution] Node B +10 [Group] Node A, Node C [I] 25 @@ -159,7 +159,7 @@ struct MapTests { #expect(result.vertices.count == 3) #expect(result.edges.count == 2) #expect(result.notes.count == 1) - #expect(result.blockers.count == 1) + #expect(result.inertias.count == 1) #expect(result.opportunities.count == 1) #expect(result.groups.count == 1) #expect(result.stages[0] == 25.0) -- cgit