From: Ruben Beltran del Rio Date: Mon, 16 Sep 2024 09:10:32 +0000 (+0200) Subject: Map 3 first commit: files, groups and layout X-Git-Tag: 3.0.0~3 X-Git-Url: https://git.r.bdr.sh/rbdr/map/commitdiff_plain/e2c37ac1dd2ad562e3d619d39b72a174a2212b67 Map 3 first commit: files, groups and layout --- diff --git a/.gitignore b/.gitignore index c20bac4..996111e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ DerivedData/ ## Gcc Patch /*.gcno - +.DS_Store diff --git a/Makefile b/Makefile index fe56dfc..c02a227 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,6 @@ format: swift format -i -r . lint: - swift format -m lint -r . - -docker-build: - docker build --force-rm --build-arg swift_version=$(swift_version) -t doapp/do:$(swift_version) . - -docker-push: docker-build - docker push doapp/do:$(swift_version) + swift format lint -r . .PHONY: format lint docker-build docker-push diff --git a/Map.xcodeproj/project.pbxproj b/Map.xcodeproj/project.pbxproj index 9cd5d87..15ddee9 100644 --- a/Map.xcodeproj/project.pbxproj +++ b/Map.xcodeproj/project.pbxproj @@ -3,134 +3,131 @@ archiveVersion = 1; classes = { }; - objectVersion = 53; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ - B523C73D25C98D9800C44061 /* NSImage+writePNG.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C73C25C98D9800C44061 /* NSImage+writePNG.swift */; }; - B523C74625C9BD3500C44061 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B523C74525C9BD3500C44061 /* CloudKit.framework */; }; - B523C74B25C9C1BA00C44061 /* EmptyMapDetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C74A25C9C1BA00C44061 /* EmptyMapDetailScreen.swift */; }; - B523C75A25C9FD4900C44061 /* MapAxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C75925C9FD4900C44061 /* MapAxes.swift */; }; - B523C76225CA05A300C44061 /* MapStages.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C76125CA05A300C44061 /* MapStages.swift */; }; - B523C76725CA071B00C44061 /* MapVertices.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C76625CA071B00C44061 /* MapVertices.swift */; }; - B523C76C25CA0DFA00C44061 /* MapEdges.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C76B25CA0DFA00C44061 /* MapEdges.swift */; }; - B523C77125CA121300C44061 /* MapBlockers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C77025CA121300C44061 /* MapBlockers.swift */; }; - B523C77E25CA294C00C44061 /* MapOpportunities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B523C77D25CA294C00C44061 /* MapOpportunities.swift */; }; - B526257225C874F9003E73B7 /* MapApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B526257125C874F9003E73B7 /* MapApp.swift */; }; - B526257425C874F9003E73B7 /* MapEditorWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B526257325C874F9003E73B7 /* MapEditorWindow.swift */; }; - B526257625C874FA003E73B7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B526257525C874FA003E73B7 /* Assets.xcassets */; }; - B526257925C874FA003E73B7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B526257825C874FA003E73B7 /* Preview Assets.xcassets */; }; - B526257B25C874FA003E73B7 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = B526257A25C874FA003E73B7 /* Persistence.swift */; }; - B526257E25C874FA003E73B7 /* Map.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B526257C25C874FA003E73B7 /* Map.xcdatamodeld */; }; - B526258A25C874FA003E73B7 /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B526258925C874FA003E73B7 /* MapTests.swift */; }; - B526259525C874FA003E73B7 /* MapUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B526259425C874FA003E73B7 /* MapUITests.swift */; }; - B52625A625C876C3003E73B7 /* MapDetailScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52625A525C876C3003E73B7 /* MapDetailScreen.swift */; }; - B52625AB25C87909003E73B7 /* Date+format.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52625AA25C87909003E73B7 /* Date+format.swift */; }; - B52625B025C87C14003E73B7 /* MapRenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52625AF25C87C14003E73B7 /* MapRenderView.swift */; }; - B52625BB25C884C2003E73B7 /* Map+parse.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52625BA25C884C2003E73B7 /* Map+parse.swift */; }; - B52625C625C8BD2A003E73B7 /* Stage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52625C525C8BD2A003E73B7 /* Stage.swift */; }; - B539516C25CB0C9300959F72 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539516B25CB0C9200959F72 /* Store.swift */; }; - B539517425CB0CA400959F72 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539517325CB0CA400959F72 /* AppState.swift */; }; - B539518125CB2D7A00959F72 /* MapTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B539518025CB2D7A00959F72 /* MapTextEditor.swift */; }; - B5CF75C925CC19FC003BFF3D /* EvolutionPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75C825CC19FC003BFF3D /* EvolutionPicker.swift */; }; - B5CF75CF25CC7965003BFF3D /* MapParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75CE25CC7965003BFF3D /* MapParser.swift */; }; - B5CF75D825CC79BC003BFF3D /* VertexParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75D725CC79BC003BFF3D /* VertexParserStrategy.swift */; }; - B5CF75DD25CC79D7003BFF3D /* EdgeParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75DC25CC79D7003BFF3D /* EdgeParserStrategy.swift */; }; - B5CF75E225CC79ED003BFF3D /* BlockerParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75E125CC79ED003BFF3D /* BlockerParserStrategy.swift */; }; - B5CF75EA25CC7A13003BFF3D /* OpportunityParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75E925CC7A13003BFF3D /* OpportunityParserStrategy.swift */; }; - B5CF75EF25CC7A4A003BFF3D /* StageParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75EE25CC7A4A003BFF3D /* StageParserStrategy.swift */; }; - B5CF75F725CC97CA003BFF3D /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CF75F625CC97CA003BFF3D /* Debouncer.swift */; }; - B5F8D3082A06DD8C000EEA24 /* Font+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8D3072A06DD8C000EEA24 /* Font+Theme.swift */; }; - B5F8D30B2A06E3E6000EEA24 /* Color+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8D30A2A06E3E6000EEA24 /* Color+Theme.swift */; }; - B5F8D30E2A06E5C2000EEA24 /* Patterns in Frameworks */ = {isa = PBXBuildFile; productRef = B5F8D30D2A06E5C2000EEA24 /* Patterns */; }; - B5F8D3102A07B33D000EEA24 /* NSColor+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8D30F2A07B33D000EEA24 /* NSColor+Theme.swift */; }; - B5F8D3122A07B690000EEA24 /* NoteParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8D3112A07B690000EEA24 /* NoteParserStrategy.swift */; }; - B5F8D3142A07C05F000EEA24 /* MapNotes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F8D3132A07C05F000EEA24 /* MapNotes.swift */; }; + B5012E3F2C96232A00AC4D68 /* EvolutionPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E3E2C96232300AC4D68 /* EvolutionPicker.swift */; }; + B5012E422C96235E00AC4D68 /* Stage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E412C96235B00AC4D68 /* Stage.swift */; }; + B5012E452C9623C700AC4D68 /* Font+theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E442C9623C500AC4D68 /* Font+theme.swift */; }; + B5012E472C96243C00AC4D68 /* MapTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E462C96243500AC4D68 /* MapTextEditor.swift */; }; + B5012E492C96245B00AC4D68 /* Color+theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E482C96245800AC4D68 /* Color+theme.swift */; }; + B5012E4B2C96246F00AC4D68 /* NSColor+theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E4A2C96246D00AC4D68 /* NSColor+theme.swift */; }; + B5012E572C96249400AC4D68 /* NoteParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E4D2C96249400AC4D68 /* NoteParserStrategy.swift */; }; + B5012E582C96249400AC4D68 /* BlockerParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E4F2C96249400AC4D68 /* BlockerParserStrategy.swift */; }; + B5012E592C96249400AC4D68 /* VertexParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E4C2C96249400AC4D68 /* VertexParserStrategy.swift */; }; + B5012E5A2C96249400AC4D68 /* MapParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E532C96249400AC4D68 /* MapParser.swift */; }; + B5012E5B2C96249400AC4D68 /* OpportunityParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E502C96249400AC4D68 /* OpportunityParserStrategy.swift */; }; + B5012E5C2C96249400AC4D68 /* EdgeParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E4E2C96249400AC4D68 /* EdgeParserStrategy.swift */; }; + B5012E5D2C96249400AC4D68 /* StageParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E512C96249400AC4D68 /* StageParserStrategy.swift */; }; + B5012E5E2C96249400AC4D68 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E552C96249400AC4D68 /* Debouncer.swift */; }; + B5012E622C96254700AC4D68 /* MapRenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E5F2C96254700AC4D68 /* MapRenderView.swift */; }; + B5012E6B2C96255A00AC4D68 /* MapAxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E632C96255A00AC4D68 /* MapAxes.swift */; }; + B5012E6C2C96255A00AC4D68 /* MapVertices.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E652C96255A00AC4D68 /* MapVertices.swift */; }; + B5012E6D2C96255A00AC4D68 /* MapStages.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E642C96255A00AC4D68 /* MapStages.swift */; }; + B5012E6E2C96255A00AC4D68 /* MapNotes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E662C96255A00AC4D68 /* MapNotes.swift */; }; + B5012E6F2C96255A00AC4D68 /* MapEdges.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E672C96255A00AC4D68 /* MapEdges.swift */; }; + B5012E702C96255A00AC4D68 /* MapOpportunities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E692C96255A00AC4D68 /* MapOpportunities.swift */; }; + B5012E712C96255A00AC4D68 /* MapBlockers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E682C96255A00AC4D68 /* MapBlockers.swift */; }; + B5012E742C9625E200AC4D68 /* Patterns in Frameworks */ = {isa = PBXBuildFile; productRef = B5012E732C9625E200AC4D68 /* Patterns */; }; + B5012E7A2C96F02F00AC4D68 /* Dimensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E792C96F02E00AC4D68 /* Dimensions.swift */; }; + B5012E7C2C972B6C00AC4D68 /* GroupParserStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E7B2C972B6600AC4D68 /* GroupParserStrategy.swift */; }; + B5012E7F2C97315800AC4D68 /* ConcaveHull in Frameworks */ = {isa = PBXBuildFile; productRef = B5012E7E2C97315800AC4D68 /* ConcaveHull */; }; + B5012E812C97318600AC4D68 /* MapGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E802C97318300AC4D68 /* MapGroups.swift */; }; + B5012E872C97874600AC4D68 /* MapGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E862C97874400AC4D68 /* MapGroup.swift */; }; + B5012E8A2C98235500AC4D68 /* MapCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E892C98235300AC4D68 /* MapCommands.swift */; }; + B5012E8C2C98244000AC4D68 /* ViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E8B2C98243E00AC4D68 /* ViewStyle.swift */; }; + B5012E8E2C9828D000AC4D68 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5012E8D2C9828CE00AC4D68 /* Constants.swift */; }; + B54587102C961E9C0067B788 /* MapApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B545870F2C961E9C0067B788 /* MapApp.swift */; }; + B54587122C961E9C0067B788 /* MapDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54587112C961E9C0067B788 /* MapDocument.swift */; }; + B54587142C961E9C0067B788 /* MapEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54587132C961E9C0067B788 /* MapEditor.swift */; }; + B54587162C961E9E0067B788 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54587152C961E9E0067B788 /* Assets.xcassets */; }; + B54587192C961E9E0067B788 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54587182C961E9E0067B788 /* Preview Assets.xcassets */; }; + B54587252C961E9E0067B788 /* MapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54587242C961E9E0067B788 /* MapTests.swift */; }; + B545872F2C961E9E0067B788 /* MapUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B545872E2C961E9E0067B788 /* MapUITests.swift */; }; + B54587312C961E9E0067B788 /* MapUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54587302C961E9E0067B788 /* MapUITestsLaunchTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - B526258625C874FA003E73B7 /* PBXContainerItemProxy */ = { + B54587212C961E9E0067B788 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = B526256625C874F9003E73B7 /* Project object */; + containerPortal = B54587042C961E9C0067B788 /* Project object */; proxyType = 1; - remoteGlobalIDString = B526256D25C874F9003E73B7; - remoteInfo = Map; + remoteGlobalIDString = B545870B2C961E9C0067B788; + remoteInfo = Map2; }; - B526259125C874FA003E73B7 /* PBXContainerItemProxy */ = { + B545872B2C961E9E0067B788 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = B526256625C874F9003E73B7 /* Project object */; + containerPortal = B54587042C961E9C0067B788 /* Project object */; proxyType = 1; - remoteGlobalIDString = B526256D25C874F9003E73B7; - remoteInfo = Map; + remoteGlobalIDString = B545870B2C961E9C0067B788; + remoteInfo = Map2; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - B523C73C25C98D9800C44061 /* NSImage+writePNG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImage+writePNG.swift"; sourceTree = ""; }; - B523C74525C9BD3500C44061 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; - B523C74A25C9C1BA00C44061 /* EmptyMapDetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyMapDetailScreen.swift; sourceTree = ""; }; - B523C75925C9FD4900C44061 /* MapAxes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapAxes.swift; sourceTree = ""; }; - B523C76125CA05A300C44061 /* MapStages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStages.swift; sourceTree = ""; }; - B523C76625CA071B00C44061 /* MapVertices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapVertices.swift; sourceTree = ""; }; - B523C76B25CA0DFA00C44061 /* MapEdges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapEdges.swift; sourceTree = ""; }; - B523C77025CA121300C44061 /* MapBlockers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapBlockers.swift; sourceTree = ""; }; - B523C77D25CA294C00C44061 /* MapOpportunities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapOpportunities.swift; sourceTree = ""; }; - B526256E25C874F9003E73B7 /* Map.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Map.app; sourceTree = BUILT_PRODUCTS_DIR; }; - B526257125C874F9003E73B7 /* MapApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapApp.swift; sourceTree = ""; }; - B526257325C874F9003E73B7 /* MapEditorWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapEditorWindow.swift; sourceTree = ""; }; - B526257525C874FA003E73B7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - B526257825C874FA003E73B7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - B526257A25C874FA003E73B7 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; - B526257D25C874FA003E73B7 /* Map.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Map.xcdatamodel; sourceTree = ""; }; - B526257F25C874FA003E73B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B526258025C874FA003E73B7 /* Map.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Map.entitlements; sourceTree = ""; }; - B526258525C874FA003E73B7 /* MapTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MapTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B526258925C874FA003E73B7 /* MapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTests.swift; sourceTree = ""; }; - B526258B25C874FA003E73B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B526259025C874FA003E73B7 /* MapUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MapUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B526259425C874FA003E73B7 /* MapUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapUITests.swift; sourceTree = ""; }; - B526259625C874FA003E73B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B52625A525C876C3003E73B7 /* MapDetailScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDetailScreen.swift; sourceTree = ""; }; - B52625AA25C87909003E73B7 /* Date+format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+format.swift"; sourceTree = ""; }; - B52625AF25C87C14003E73B7 /* MapRenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRenderView.swift; sourceTree = ""; }; - B52625BA25C884C2003E73B7 /* Map+parse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Map+parse.swift"; sourceTree = ""; }; - B52625C525C8BD2A003E73B7 /* Stage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stage.swift; sourceTree = ""; }; - B539516B25CB0C9200959F72 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; - B539517325CB0CA400959F72 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; - B539518025CB2D7A00959F72 /* MapTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTextEditor.swift; sourceTree = ""; }; - B5CF75C825CC19FC003BFF3D /* EvolutionPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvolutionPicker.swift; sourceTree = ""; }; - B5CF75CE25CC7965003BFF3D /* MapParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapParser.swift; sourceTree = ""; }; - B5CF75D725CC79BC003BFF3D /* VertexParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VertexParserStrategy.swift; sourceTree = ""; }; - B5CF75DC25CC79D7003BFF3D /* EdgeParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeParserStrategy.swift; sourceTree = ""; }; - B5CF75E125CC79ED003BFF3D /* BlockerParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockerParserStrategy.swift; sourceTree = ""; }; - B5CF75E925CC7A13003BFF3D /* OpportunityParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpportunityParserStrategy.swift; sourceTree = ""; }; - B5CF75EE25CC7A4A003BFF3D /* StageParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageParserStrategy.swift; sourceTree = ""; }; - B5CF75F625CC97CA003BFF3D /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = ""; }; - B5F8D3072A06DD8C000EEA24 /* Font+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Theme.swift"; sourceTree = ""; }; - B5F8D30A2A06E3E6000EEA24 /* Color+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Theme.swift"; sourceTree = ""; }; - B5F8D30F2A07B33D000EEA24 /* NSColor+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+Theme.swift"; sourceTree = ""; }; - B5F8D3112A07B690000EEA24 /* NoteParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteParserStrategy.swift; sourceTree = ""; }; - B5F8D3132A07C05F000EEA24 /* MapNotes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapNotes.swift; sourceTree = ""; }; + B5012E3E2C96232300AC4D68 /* EvolutionPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvolutionPicker.swift; sourceTree = ""; }; + B5012E412C96235B00AC4D68 /* Stage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stage.swift; sourceTree = ""; }; + B5012E442C9623C500AC4D68 /* Font+theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+theme.swift"; sourceTree = ""; }; + B5012E462C96243500AC4D68 /* MapTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTextEditor.swift; sourceTree = ""; }; + B5012E482C96245800AC4D68 /* Color+theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+theme.swift"; sourceTree = ""; }; + B5012E4A2C96246D00AC4D68 /* NSColor+theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+theme.swift"; sourceTree = ""; }; + B5012E4C2C96249400AC4D68 /* VertexParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VertexParserStrategy.swift; sourceTree = ""; }; + B5012E4D2C96249400AC4D68 /* NoteParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteParserStrategy.swift; sourceTree = ""; }; + B5012E4E2C96249400AC4D68 /* EdgeParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeParserStrategy.swift; sourceTree = ""; }; + B5012E4F2C96249400AC4D68 /* BlockerParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockerParserStrategy.swift; sourceTree = ""; }; + B5012E502C96249400AC4D68 /* OpportunityParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpportunityParserStrategy.swift; sourceTree = ""; }; + B5012E512C96249400AC4D68 /* StageParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageParserStrategy.swift; sourceTree = ""; }; + B5012E532C96249400AC4D68 /* MapParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapParser.swift; sourceTree = ""; }; + B5012E552C96249400AC4D68 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = ""; }; + B5012E5F2C96254700AC4D68 /* MapRenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRenderView.swift; sourceTree = ""; }; + B5012E632C96255A00AC4D68 /* MapAxes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapAxes.swift; sourceTree = ""; }; + B5012E642C96255A00AC4D68 /* MapStages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStages.swift; sourceTree = ""; }; + B5012E652C96255A00AC4D68 /* MapVertices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapVertices.swift; sourceTree = ""; }; + B5012E662C96255A00AC4D68 /* MapNotes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapNotes.swift; sourceTree = ""; }; + B5012E672C96255A00AC4D68 /* MapEdges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapEdges.swift; sourceTree = ""; }; + B5012E682C96255A00AC4D68 /* MapBlockers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapBlockers.swift; sourceTree = ""; }; + B5012E692C96255A00AC4D68 /* MapOpportunities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapOpportunities.swift; sourceTree = ""; }; + B5012E792C96F02E00AC4D68 /* Dimensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dimensions.swift; sourceTree = ""; }; + B5012E7B2C972B6600AC4D68 /* GroupParserStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupParserStrategy.swift; sourceTree = ""; }; + B5012E802C97318300AC4D68 /* MapGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapGroups.swift; sourceTree = ""; }; + B5012E862C97874400AC4D68 /* MapGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapGroup.swift; sourceTree = ""; }; + B5012E892C98235300AC4D68 /* MapCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCommands.swift; sourceTree = ""; }; + B5012E8B2C98243E00AC4D68 /* ViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewStyle.swift; sourceTree = ""; }; + B5012E8D2C9828CE00AC4D68 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + B545870C2C961E9C0067B788 /* Map.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Map.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B545870F2C961E9C0067B788 /* MapApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapApp.swift; sourceTree = ""; }; + B54587112C961E9C0067B788 /* MapDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDocument.swift; sourceTree = ""; }; + B54587132C961E9C0067B788 /* MapEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapEditor.swift; sourceTree = ""; }; + B54587152C961E9E0067B788 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + B54587182C961E9E0067B788 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + B545871A2C961E9E0067B788 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B545871B2C961E9E0067B788 /* Map.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Map.entitlements; sourceTree = ""; }; + B54587202C961E9E0067B788 /* MapTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MapTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B54587242C961E9E0067B788 /* MapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTests.swift; sourceTree = ""; }; + B545872A2C961E9E0067B788 /* Map2UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Map2UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B545872E2C961E9E0067B788 /* MapUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapUITests.swift; sourceTree = ""; }; + B54587302C961E9E0067B788 /* MapUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapUITestsLaunchTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - B526256B25C874F9003E73B7 /* Frameworks */ = { + B54587092C961E9C0067B788 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B523C74625C9BD3500C44061 /* CloudKit.framework in Frameworks */, - B5F8D30E2A06E5C2000EEA24 /* Patterns in Frameworks */, + B5012E742C9625E200AC4D68 /* Patterns in Frameworks */, + B5012E7F2C97315800AC4D68 /* ConcaveHull in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - B526258225C874FA003E73B7 /* Frameworks */ = { + B545871D2C961E9E0067B788 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - B526258D25C874FA003E73B7 /* Frameworks */ = { + B54587272C961E9E0067B788 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -140,219 +137,192 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - B523C74425C9BD3500C44061 /* Frameworks */ = { + B5012E3C2C96222E00AC4D68 /* Data */ = { isa = PBXGroup; children = ( - B523C74525C9BD3500C44061 /* CloudKit.framework */, + B5012E412C96235B00AC4D68 /* Stage.swift */, + B54587112C961E9C0067B788 /* MapDocument.swift */, ); - name = Frameworks; - sourceTree = ""; - }; - B523C75825C9FD3A00C44061 /* MapRender */ = { - isa = PBXGroup; - children = ( - B523C75925C9FD4900C44061 /* MapAxes.swift */, - B523C76125CA05A300C44061 /* MapStages.swift */, - B523C76625CA071B00C44061 /* MapVertices.swift */, - B5F8D3132A07C05F000EEA24 /* MapNotes.swift */, - B523C76B25CA0DFA00C44061 /* MapEdges.swift */, - B523C77025CA121300C44061 /* MapBlockers.swift */, - B523C77D25CA294C00C44061 /* MapOpportunities.swift */, - ); - path = MapRender; - sourceTree = ""; - }; - B526256525C874F9003E73B7 = { - isa = PBXGroup; - children = ( - B526257025C874F9003E73B7 /* Map */, - B526258825C874FA003E73B7 /* MapTests */, - B526259325C874FA003E73B7 /* MapUITests */, - B526256F25C874F9003E73B7 /* Products */, - B523C74425C9BD3500C44061 /* Frameworks */, - ); - sourceTree = ""; - }; - B526256F25C874F9003E73B7 /* Products */ = { - isa = PBXGroup; - children = ( - B526256E25C874F9003E73B7 /* Map.app */, - B526258525C874FA003E73B7 /* MapTests.xctest */, - B526259025C874FA003E73B7 /* MapUITests.xctest */, - ); - name = Products; + path = Data; sourceTree = ""; }; - B526257025C874F9003E73B7 /* Map */ = { + B5012E3D2C96223800AC4D68 /* Presentation */ = { isa = PBXGroup; children = ( - B526257125C874F9003E73B7 /* MapApp.swift */, - B5F8D2FE2A06DB3A000EEA24 /* Data */, - B5F8D3092A06DE1A000EEA24 /* Logic */, - B5F8D2FF2A06DB40000EEA24 /* Presentation */, - B52625B425C87D54003E73B7 /* Core Extensions */, - B526257525C874FA003E73B7 /* Assets.xcassets */, - B526257F25C874FA003E73B7 /* Info.plist */, - B526258025C874FA003E73B7 /* Map.entitlements */, - B526257C25C874FA003E73B7 /* Map.xcdatamodeld */, - B526257725C874FA003E73B7 /* Preview Content */, + B5012E8B2C98243E00AC4D68 /* ViewStyle.swift */, + B5012E882C98234F00AC4D68 /* Commands */, + B5012E612C96254700AC4D68 /* Complex Components */, + B5012E432C9623BC00AC4D68 /* Theme */, + B5012E402C96232E00AC4D68 /* Base Components */, + B54587132C961E9C0067B788 /* MapEditor.swift */, ); - path = Map; + path = Presentation; sourceTree = ""; }; - B526257725C874FA003E73B7 /* Preview Content */ = { + B5012E402C96232E00AC4D68 /* Base Components */ = { isa = PBXGroup; children = ( - B526257825C874FA003E73B7 /* Preview Assets.xcassets */, + B5012E6A2C96255A00AC4D68 /* MapRender */, + B5012E462C96243500AC4D68 /* MapTextEditor.swift */, + B5012E3E2C96232300AC4D68 /* EvolutionPicker.swift */, ); - path = "Preview Content"; + path = "Base Components"; sourceTree = ""; }; - B526258825C874FA003E73B7 /* MapTests */ = { + B5012E432C9623BC00AC4D68 /* Theme */ = { isa = PBXGroup; children = ( - B526258925C874FA003E73B7 /* MapTests.swift */, - B526258B25C874FA003E73B7 /* Info.plist */, + B5012E792C96F02E00AC4D68 /* Dimensions.swift */, + B5012E4A2C96246D00AC4D68 /* NSColor+theme.swift */, + B5012E482C96245800AC4D68 /* Color+theme.swift */, + B5012E442C9623C500AC4D68 /* Font+theme.swift */, ); - path = MapTests; + path = Theme; sourceTree = ""; }; - B526259325C874FA003E73B7 /* MapUITests */ = { + B5012E522C96249400AC4D68 /* Strategies */ = { isa = PBXGroup; children = ( - B526259425C874FA003E73B7 /* MapUITests.swift */, - B526259625C874FA003E73B7 /* Info.plist */, + B5012E7B2C972B6600AC4D68 /* GroupParserStrategy.swift */, + B5012E4C2C96249400AC4D68 /* VertexParserStrategy.swift */, + B5012E4D2C96249400AC4D68 /* NoteParserStrategy.swift */, + B5012E4E2C96249400AC4D68 /* EdgeParserStrategy.swift */, + B5012E4F2C96249400AC4D68 /* BlockerParserStrategy.swift */, + B5012E502C96249400AC4D68 /* OpportunityParserStrategy.swift */, + B5012E512C96249400AC4D68 /* StageParserStrategy.swift */, ); - path = MapUITests; + path = Strategies; sourceTree = ""; }; - B52625B425C87D54003E73B7 /* Core Extensions */ = { + B5012E542C96249400AC4D68 /* MapParser */ = { isa = PBXGroup; children = ( - B52625AA25C87909003E73B7 /* Date+format.swift */, - B523C73C25C98D9800C44061 /* NSImage+writePNG.swift */, - B5F8D3072A06DD8C000EEA24 /* Font+Theme.swift */, - B5F8D30F2A07B33D000EEA24 /* NSColor+Theme.swift */, - B5F8D30A2A06E3E6000EEA24 /* Color+Theme.swift */, + B5012E522C96249400AC4D68 /* Strategies */, + B5012E532C96249400AC4D68 /* MapParser.swift */, ); - path = "Core Extensions"; + path = MapParser; sourceTree = ""; }; - B5CF75CD25CC7953003BFF3D /* MapParser */ = { + B5012E562C96249400AC4D68 /* Logic */ = { isa = PBXGroup; children = ( - B5CF75D625CC79A4003BFF3D /* Strategies */, - B5CF75CE25CC7965003BFF3D /* MapParser.swift */, + B5012E8D2C9828CE00AC4D68 /* Constants.swift */, + B5012E542C96249400AC4D68 /* MapParser */, + B5012E552C96249400AC4D68 /* Debouncer.swift */, ); - path = MapParser; + path = Logic; sourceTree = ""; }; - B5CF75D625CC79A4003BFF3D /* Strategies */ = { + B5012E602C96254700AC4D68 /* MapRender */ = { isa = PBXGroup; children = ( - B5CF75D725CC79BC003BFF3D /* VertexParserStrategy.swift */, - B5F8D3112A07B690000EEA24 /* NoteParserStrategy.swift */, - B5CF75DC25CC79D7003BFF3D /* EdgeParserStrategy.swift */, - B5CF75E125CC79ED003BFF3D /* BlockerParserStrategy.swift */, - B5CF75E925CC7A13003BFF3D /* OpportunityParserStrategy.swift */, - B5CF75EE25CC7A4A003BFF3D /* StageParserStrategy.swift */, + B5012E5F2C96254700AC4D68 /* MapRenderView.swift */, ); - path = Strategies; + path = MapRender; sourceTree = ""; }; - B5F8D2FE2A06DB3A000EEA24 /* Data */ = { + B5012E612C96254700AC4D68 /* Complex Components */ = { isa = PBXGroup; children = ( - B5F8D3012A06DB75000EEA24 /* Models */, - B52625C525C8BD2A003E73B7 /* Stage.swift */, - B526257A25C874FA003E73B7 /* Persistence.swift */, - B539516B25CB0C9200959F72 /* Store.swift */, - B539517325CB0CA400959F72 /* AppState.swift */, + B5012E602C96254700AC4D68 /* MapRender */, ); - path = Data; + path = "Complex Components"; sourceTree = ""; }; - B5F8D2FF2A06DB40000EEA24 /* Presentation */ = { + B5012E6A2C96255A00AC4D68 /* MapRender */ = { isa = PBXGroup; children = ( - B5F8D3022A06DBC3000EEA24 /* Windows */, - B5F8D3032A06DC2D000EEA24 /* Screens */, - B5F8D3052A06DCF3000EEA24 /* Complex Components */, - B5F8D3042A06DCC4000EEA24 /* Base Components */, + B5012E802C97318300AC4D68 /* MapGroups.swift */, + B5012E862C97874400AC4D68 /* MapGroup.swift */, + B5012E632C96255A00AC4D68 /* MapAxes.swift */, + B5012E642C96255A00AC4D68 /* MapStages.swift */, + B5012E652C96255A00AC4D68 /* MapVertices.swift */, + B5012E662C96255A00AC4D68 /* MapNotes.swift */, + B5012E672C96255A00AC4D68 /* MapEdges.swift */, + B5012E682C96255A00AC4D68 /* MapBlockers.swift */, + B5012E692C96255A00AC4D68 /* MapOpportunities.swift */, ); - path = Presentation; + path = MapRender; sourceTree = ""; }; - B5F8D3012A06DB75000EEA24 /* Models */ = { + B5012E882C98234F00AC4D68 /* Commands */ = { isa = PBXGroup; children = ( - B52625BA25C884C2003E73B7 /* Map+parse.swift */, + B5012E892C98235300AC4D68 /* MapCommands.swift */, ); - path = Models; + path = Commands; sourceTree = ""; }; - B5F8D3022A06DBC3000EEA24 /* Windows */ = { + B54587032C961E9C0067B788 = { isa = PBXGroup; children = ( - B526257325C874F9003E73B7 /* MapEditorWindow.swift */, + B545870E2C961E9C0067B788 /* Map */, + B54587232C961E9E0067B788 /* MapTests */, + B545872D2C961E9E0067B788 /* MapUITests */, + B545870D2C961E9C0067B788 /* Products */, ); - path = Windows; sourceTree = ""; }; - B5F8D3032A06DC2D000EEA24 /* Screens */ = { + B545870D2C961E9C0067B788 /* Products */ = { isa = PBXGroup; children = ( - B523C74A25C9C1BA00C44061 /* EmptyMapDetailScreen.swift */, - B52625A525C876C3003E73B7 /* MapDetailScreen.swift */, + B545870C2C961E9C0067B788 /* Map.app */, + B54587202C961E9E0067B788 /* MapTests.xctest */, + B545872A2C961E9E0067B788 /* Map2UITests.xctest */, ); - path = Screens; + name = Products; sourceTree = ""; }; - B5F8D3042A06DCC4000EEA24 /* Base Components */ = { + B545870E2C961E9C0067B788 /* Map */ = { isa = PBXGroup; children = ( - B5CF75C825CC19FC003BFF3D /* EvolutionPicker.swift */, - B539518025CB2D7A00959F72 /* MapTextEditor.swift */, - B523C75825C9FD3A00C44061 /* MapRender */, + B5012E562C96249400AC4D68 /* Logic */, + B5012E3C2C96222E00AC4D68 /* Data */, + B5012E3D2C96223800AC4D68 /* Presentation */, + B545870F2C961E9C0067B788 /* MapApp.swift */, + B54587152C961E9E0067B788 /* Assets.xcassets */, + B545871A2C961E9E0067B788 /* Info.plist */, + B545871B2C961E9E0067B788 /* Map.entitlements */, + B54587172C961E9E0067B788 /* Preview Content */, ); - path = "Base Components"; + path = Map; sourceTree = ""; }; - B5F8D3052A06DCF3000EEA24 /* Complex Components */ = { + B54587172C961E9E0067B788 /* Preview Content */ = { isa = PBXGroup; children = ( - B5F8D3062A06DD2A000EEA24 /* MapRender */, + B54587182C961E9E0067B788 /* Preview Assets.xcassets */, ); - path = "Complex Components"; + path = "Preview Content"; sourceTree = ""; }; - B5F8D3062A06DD2A000EEA24 /* MapRender */ = { + B54587232C961E9E0067B788 /* MapTests */ = { isa = PBXGroup; children = ( - B52625AF25C87C14003E73B7 /* MapRenderView.swift */, + B54587242C961E9E0067B788 /* MapTests.swift */, ); - path = MapRender; + path = MapTests; sourceTree = ""; }; - B5F8D3092A06DE1A000EEA24 /* Logic */ = { + B545872D2C961E9E0067B788 /* MapUITests */ = { isa = PBXGroup; children = ( - B5CF75CD25CC7953003BFF3D /* MapParser */, - B5CF75F625CC97CA003BFF3D /* Debouncer.swift */, + B545872E2C961E9E0067B788 /* MapUITests.swift */, + B54587302C961E9E0067B788 /* MapUITestsLaunchTests.swift */, ); - path = Logic; + path = MapUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - B526256D25C874F9003E73B7 /* Map */ = { + B545870B2C961E9C0067B788 /* Map */ = { isa = PBXNativeTarget; - buildConfigurationList = B526259925C874FA003E73B7 /* Build configuration list for PBXNativeTarget "Map" */; + buildConfigurationList = B54587342C961E9E0067B788 /* Build configuration list for PBXNativeTarget "Map" */; buildPhases = ( - B526256A25C874F9003E73B7 /* Sources */, - B526256B25C874F9003E73B7 /* Frameworks */, - B526256C25C874F9003E73B7 /* Resources */, + B54587082C961E9C0067B788 /* Sources */, + B54587092C961E9C0067B788 /* Frameworks */, + B545870A2C961E9C0067B788 /* Resources */, ); buildRules = ( ); @@ -360,112 +330,114 @@ ); name = Map; packageProductDependencies = ( - B5F8D30D2A06E5C2000EEA24 /* Patterns */, + B5012E732C9625E200AC4D68 /* Patterns */, + B5012E7E2C97315800AC4D68 /* ConcaveHull */, ); - productName = Map; - productReference = B526256E25C874F9003E73B7 /* Map.app */; + productName = Map2; + productReference = B545870C2C961E9C0067B788 /* Map.app */; productType = "com.apple.product-type.application"; }; - B526258425C874FA003E73B7 /* MapTests */ = { + B545871F2C961E9E0067B788 /* MapTests */ = { isa = PBXNativeTarget; - buildConfigurationList = B526259C25C874FA003E73B7 /* Build configuration list for PBXNativeTarget "MapTests" */; + buildConfigurationList = B54587372C961E9E0067B788 /* Build configuration list for PBXNativeTarget "MapTests" */; buildPhases = ( - B526258125C874FA003E73B7 /* Sources */, - B526258225C874FA003E73B7 /* Frameworks */, - B526258325C874FA003E73B7 /* Resources */, + B545871C2C961E9E0067B788 /* Sources */, + B545871D2C961E9E0067B788 /* Frameworks */, + B545871E2C961E9E0067B788 /* Resources */, ); buildRules = ( ); dependencies = ( - B526258725C874FA003E73B7 /* PBXTargetDependency */, + B54587222C961E9E0067B788 /* PBXTargetDependency */, ); name = MapTests; - productName = MapTests; - productReference = B526258525C874FA003E73B7 /* MapTests.xctest */; + productName = Map2Tests; + productReference = B54587202C961E9E0067B788 /* MapTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - B526258F25C874FA003E73B7 /* MapUITests */ = { + B54587292C961E9E0067B788 /* Map2UITests */ = { isa = PBXNativeTarget; - buildConfigurationList = B526259F25C874FA003E73B7 /* Build configuration list for PBXNativeTarget "MapUITests" */; + buildConfigurationList = B545873A2C961E9E0067B788 /* Build configuration list for PBXNativeTarget "Map2UITests" */; buildPhases = ( - B526258C25C874FA003E73B7 /* Sources */, - B526258D25C874FA003E73B7 /* Frameworks */, - B526258E25C874FA003E73B7 /* Resources */, + B54587262C961E9E0067B788 /* Sources */, + B54587272C961E9E0067B788 /* Frameworks */, + B54587282C961E9E0067B788 /* Resources */, ); buildRules = ( ); dependencies = ( - B526259225C874FA003E73B7 /* PBXTargetDependency */, + B545872C2C961E9E0067B788 /* PBXTargetDependency */, ); - name = MapUITests; - productName = MapUITests; - productReference = B526259025C874FA003E73B7 /* MapUITests.xctest */; + name = Map2UITests; + productName = Map2UITests; + productReference = B545872A2C961E9E0067B788 /* Map2UITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - B526256625C874F9003E73B7 /* Project object */ = { + B54587042C961E9C0067B788 /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1430; + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; TargetAttributes = { - B526256D25C874F9003E73B7 = { - CreatedOnToolsVersion = 12.4; + B545870B2C961E9C0067B788 = { + CreatedOnToolsVersion = 16.0; }; - B526258425C874FA003E73B7 = { - CreatedOnToolsVersion = 12.4; - TestTargetID = B526256D25C874F9003E73B7; + B545871F2C961E9E0067B788 = { + CreatedOnToolsVersion = 16.0; + TestTargetID = B545870B2C961E9C0067B788; }; - B526258F25C874FA003E73B7 = { - CreatedOnToolsVersion = 12.4; - TestTargetID = B526256D25C874F9003E73B7; + B54587292C961E9E0067B788 = { + CreatedOnToolsVersion = 16.0; + TestTargetID = B545870B2C961E9C0067B788; }; }; }; - buildConfigurationList = B526256925C874F9003E73B7 /* Build configuration list for PBXProject "Map" */; - compatibilityVersion = "Xcode 9.3"; + buildConfigurationList = B54587072C961E9C0067B788 /* Build configuration list for PBXProject "Map" */; + compatibilityVersion = "Xcode 15.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); - mainGroup = B526256525C874F9003E73B7; + mainGroup = B54587032C961E9C0067B788; packageReferences = ( - B5F8D30C2A06E5C2000EEA24 /* XCRemoteSwiftPackageReference "patterns" */, + B5012E722C9625E200AC4D68 /* XCRemoteSwiftPackageReference "patterns" */, + B5012E7D2C97315800AC4D68 /* XCRemoteSwiftPackageReference "ConcaveHull" */, ); - productRefGroup = B526256F25C874F9003E73B7 /* Products */; + productRefGroup = B545870D2C961E9C0067B788 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - B526256D25C874F9003E73B7 /* Map */, - B526258425C874FA003E73B7 /* MapTests */, - B526258F25C874FA003E73B7 /* MapUITests */, + B545870B2C961E9C0067B788 /* Map */, + B545871F2C961E9E0067B788 /* MapTests */, + B54587292C961E9E0067B788 /* Map2UITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - B526256C25C874F9003E73B7 /* Resources */ = { + B545870A2C961E9C0067B788 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - B526257925C874FA003E73B7 /* Preview Assets.xcassets in Resources */, - B526257625C874FA003E73B7 /* Assets.xcassets in Resources */, + B54587192C961E9E0067B788 /* Preview Assets.xcassets in Resources */, + B54587162C961E9E0067B788 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - B526258325C874FA003E73B7 /* Resources */ = { + B545871E2C961E9E0067B788 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - B526258E25C874FA003E73B7 /* Resources */ = { + B54587282C961E9E0067B788 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -475,86 +447,86 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - B526256A25C874F9003E73B7 /* Sources */ = { + B54587082C961E9C0067B788 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B52625B025C87C14003E73B7 /* MapRenderView.swift in Sources */, - B523C77E25CA294C00C44061 /* MapOpportunities.swift in Sources */, - B52625AB25C87909003E73B7 /* Date+format.swift in Sources */, - B5CF75D825CC79BC003BFF3D /* VertexParserStrategy.swift in Sources */, - B5CF75EA25CC7A13003BFF3D /* OpportunityParserStrategy.swift in Sources */, - B5F8D3142A07C05F000EEA24 /* MapNotes.swift in Sources */, - B523C77125CA121300C44061 /* MapBlockers.swift in Sources */, - B523C76725CA071B00C44061 /* MapVertices.swift in Sources */, - B5CF75CF25CC7965003BFF3D /* MapParser.swift in Sources */, - B5CF75E225CC79ED003BFF3D /* BlockerParserStrategy.swift in Sources */, - B5F8D3102A07B33D000EEA24 /* NSColor+Theme.swift in Sources */, - B5F8D30B2A06E3E6000EEA24 /* Color+Theme.swift in Sources */, - B539517425CB0CA400959F72 /* AppState.swift in Sources */, - B523C75A25C9FD4900C44061 /* MapAxes.swift in Sources */, - B539518125CB2D7A00959F72 /* MapTextEditor.swift in Sources */, - B52625BB25C884C2003E73B7 /* Map+parse.swift in Sources */, - B52625C625C8BD2A003E73B7 /* Stage.swift in Sources */, - B523C73D25C98D9800C44061 /* NSImage+writePNG.swift in Sources */, - B5F8D3122A07B690000EEA24 /* NoteParserStrategy.swift in Sources */, - B526257B25C874FA003E73B7 /* Persistence.swift in Sources */, - B5F8D3082A06DD8C000EEA24 /* Font+Theme.swift in Sources */, - B5CF75EF25CC7A4A003BFF3D /* StageParserStrategy.swift in Sources */, - B5CF75DD25CC79D7003BFF3D /* EdgeParserStrategy.swift in Sources */, - B526257425C874F9003E73B7 /* MapEditorWindow.swift in Sources */, - B526257E25C874FA003E73B7 /* Map.xcdatamodeld in Sources */, - B5CF75C925CC19FC003BFF3D /* EvolutionPicker.swift in Sources */, - B523C74B25C9C1BA00C44061 /* EmptyMapDetailScreen.swift in Sources */, - B539516C25CB0C9300959F72 /* Store.swift in Sources */, - B526257225C874F9003E73B7 /* MapApp.swift in Sources */, - B52625A625C876C3003E73B7 /* MapDetailScreen.swift in Sources */, - B523C76225CA05A300C44061 /* MapStages.swift in Sources */, - B5CF75F725CC97CA003BFF3D /* Debouncer.swift in Sources */, - B523C76C25CA0DFA00C44061 /* MapEdges.swift in Sources */, + B5012E872C97874600AC4D68 /* MapGroup.swift in Sources */, + B5012E472C96243C00AC4D68 /* MapTextEditor.swift in Sources */, + B5012E622C96254700AC4D68 /* MapRenderView.swift in Sources */, + B5012E492C96245B00AC4D68 /* Color+theme.swift in Sources */, + B54587122C961E9C0067B788 /* MapDocument.swift in Sources */, + B54587102C961E9C0067B788 /* MapApp.swift in Sources */, + B5012E8C2C98244000AC4D68 /* ViewStyle.swift in Sources */, + B5012E8E2C9828D000AC4D68 /* Constants.swift in Sources */, + B5012E7C2C972B6C00AC4D68 /* GroupParserStrategy.swift in Sources */, + B5012E6B2C96255A00AC4D68 /* MapAxes.swift in Sources */, + B5012E6C2C96255A00AC4D68 /* MapVertices.swift in Sources */, + B5012E6D2C96255A00AC4D68 /* MapStages.swift in Sources */, + B5012E7A2C96F02F00AC4D68 /* Dimensions.swift in Sources */, + B5012E6E2C96255A00AC4D68 /* MapNotes.swift in Sources */, + B5012E6F2C96255A00AC4D68 /* MapEdges.swift in Sources */, + B5012E702C96255A00AC4D68 /* MapOpportunities.swift in Sources */, + B5012E712C96255A00AC4D68 /* MapBlockers.swift in Sources */, + B5012E4B2C96246F00AC4D68 /* NSColor+theme.swift in Sources */, + B54587142C961E9C0067B788 /* MapEditor.swift in Sources */, + B5012E3F2C96232A00AC4D68 /* EvolutionPicker.swift in Sources */, + B5012E572C96249400AC4D68 /* NoteParserStrategy.swift in Sources */, + B5012E582C96249400AC4D68 /* BlockerParserStrategy.swift in Sources */, + B5012E592C96249400AC4D68 /* VertexParserStrategy.swift in Sources */, + B5012E5A2C96249400AC4D68 /* MapParser.swift in Sources */, + B5012E5B2C96249400AC4D68 /* OpportunityParserStrategy.swift in Sources */, + B5012E5C2C96249400AC4D68 /* EdgeParserStrategy.swift in Sources */, + B5012E5D2C96249400AC4D68 /* StageParserStrategy.swift in Sources */, + B5012E812C97318600AC4D68 /* MapGroups.swift in Sources */, + B5012E8A2C98235500AC4D68 /* MapCommands.swift in Sources */, + B5012E5E2C96249400AC4D68 /* Debouncer.swift in Sources */, + B5012E452C9623C700AC4D68 /* Font+theme.swift in Sources */, + B5012E422C96235E00AC4D68 /* Stage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - B526258125C874FA003E73B7 /* Sources */ = { + B545871C2C961E9E0067B788 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B526258A25C874FA003E73B7 /* MapTests.swift in Sources */, + B54587252C961E9E0067B788 /* MapTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - B526258C25C874FA003E73B7 /* Sources */ = { + B54587262C961E9E0067B788 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B526259525C874FA003E73B7 /* MapUITests.swift in Sources */, + B54587312C961E9E0067B788 /* MapUITestsLaunchTests.swift in Sources */, + B545872F2C961E9E0067B788 /* MapUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - B526258725C874FA003E73B7 /* PBXTargetDependency */ = { + B54587222C961E9E0067B788 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = B526256D25C874F9003E73B7 /* Map */; - targetProxy = B526258625C874FA003E73B7 /* PBXContainerItemProxy */; + target = B545870B2C961E9C0067B788 /* Map */; + targetProxy = B54587212C961E9E0067B788 /* PBXContainerItemProxy */; }; - B526259225C874FA003E73B7 /* PBXTargetDependency */ = { + B545872C2C961E9E0067B788 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = B526256D25C874F9003E73B7 /* Map */; - targetProxy = B526259125C874FA003E73B7 /* PBXContainerItemProxy */; + target = B545870B2C961E9C0067B788 /* Map */; + targetProxy = B545872B2C961E9E0067B788 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - B526259725C874FA003E73B7 /* Debug */ = { + B54587322C961E9E0067B788 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -581,11 +553,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -599,24 +571,25 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - B526259825C874FA003E73B7 /* Release */ = { + B54587332C961E9E0067B788 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -643,11 +616,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -655,16 +628,16 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; - B526259A25C874FA003E73B7 /* Debug */ = { + B54587352C961E9E0067B788 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -672,26 +645,29 @@ CODE_SIGN_ENTITLEMENTS = Map/Map.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 4; - DEAD_CODE_STRIPPING = YES; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"Map/Preview Content\""; DEVELOPMENT_TEAM = S68NHQVJXW; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Map/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.0.0; - PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.Map; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 3.0.0; + PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; name = Debug; }; - B526259B25C874FA003E73B7 /* Release */ = { + B54587362C961E9E0067B788 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -699,148 +675,131 @@ CODE_SIGN_ENTITLEMENTS = Map/Map.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 4; - DEAD_CODE_STRIPPING = YES; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"Map/Preview Content\""; DEVELOPMENT_TEAM = S68NHQVJXW; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Map/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.0.0; - PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.Map; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 3.0.0; + PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; name = Release; }; - B526259D25C874FA003E73B7 /* Debug */ = { + B54587382C961E9E0067B788 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEAD_CODE_STRIPPING = YES; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = S68NHQVJXW; - INFOPLIST_FILE = MapTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.MapTests; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map2Tests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Map.app/Contents/MacOS/Map"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Map2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Map2"; }; name = Debug; }; - B526259E25C874FA003E73B7 /* Release */ = { + B54587392C961E9E0067B788 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEAD_CODE_STRIPPING = YES; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = S68NHQVJXW; - INFOPLIST_FILE = MapTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.MapTests; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map2Tests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Map.app/Contents/MacOS/Map"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Map2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Map2"; }; name = Release; }; - B52625A025C874FA003E73B7 /* Debug */ = { + B545873B2C961E9E0067B788 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEAD_CODE_STRIPPING = YES; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = S68NHQVJXW; - INFOPLIST_FILE = MapUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.MapUITests; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map2UITests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = Map; + TEST_TARGET_NAME = Map2; }; name = Debug; }; - B52625A125C874FA003E73B7 /* Release */ = { + B545873C2C961E9E0067B788 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEAD_CODE_STRIPPING = YES; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = S68NHQVJXW; - INFOPLIST_FILE = MapUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = pizza.unlimited.MapUITests; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = systems.tranquil.Map2UITests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = Map; + TEST_TARGET_NAME = Map2; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - B526256925C874F9003E73B7 /* Build configuration list for PBXProject "Map" */ = { + B54587072C961E9C0067B788 /* Build configuration list for PBXProject "Map" */ = { isa = XCConfigurationList; buildConfigurations = ( - B526259725C874FA003E73B7 /* Debug */, - B526259825C874FA003E73B7 /* Release */, + B54587322C961E9E0067B788 /* Debug */, + B54587332C961E9E0067B788 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B526259925C874FA003E73B7 /* Build configuration list for PBXNativeTarget "Map" */ = { + B54587342C961E9E0067B788 /* Build configuration list for PBXNativeTarget "Map" */ = { isa = XCConfigurationList; buildConfigurations = ( - B526259A25C874FA003E73B7 /* Debug */, - B526259B25C874FA003E73B7 /* Release */, + B54587352C961E9E0067B788 /* Debug */, + B54587362C961E9E0067B788 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B526259C25C874FA003E73B7 /* Build configuration list for PBXNativeTarget "MapTests" */ = { + B54587372C961E9E0067B788 /* Build configuration list for PBXNativeTarget "MapTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - B526259D25C874FA003E73B7 /* Debug */, - B526259E25C874FA003E73B7 /* Release */, + B54587382C961E9E0067B788 /* Debug */, + B54587392C961E9E0067B788 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B526259F25C874FA003E73B7 /* Build configuration list for PBXNativeTarget "MapUITests" */ = { + B545873A2C961E9E0067B788 /* Build configuration list for PBXNativeTarget "Map2UITests" */ = { isa = XCConfigurationList; buildConfigurations = ( - B52625A025C874FA003E73B7 /* Debug */, - B52625A125C874FA003E73B7 /* Release */, + B545873B2C961E9E0067B788 /* Debug */, + B545873C2C961E9E0067B788 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -848,7 +807,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - B5F8D30C2A06E5C2000EEA24 /* XCRemoteSwiftPackageReference "patterns" */ = { + B5012E722C9625E200AC4D68 /* XCRemoteSwiftPackageReference "patterns" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://git.sr.ht/~rbdr/patterns"; requirement = { @@ -856,28 +815,28 @@ minimumVersion = 2.0.0; }; }; + B5012E7D2C97315800AC4D68 /* XCRemoteSwiftPackageReference "ConcaveHull" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Syncheo/ConcaveHull"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - B5F8D30D2A06E5C2000EEA24 /* Patterns */ = { + B5012E732C9625E200AC4D68 /* Patterns */ = { isa = XCSwiftPackageProductDependency; - package = B5F8D30C2A06E5C2000EEA24 /* XCRemoteSwiftPackageReference "patterns" */; + package = B5012E722C9625E200AC4D68 /* XCRemoteSwiftPackageReference "patterns" */; productName = Patterns; }; -/* End XCSwiftPackageProductDependency section */ - -/* Begin XCVersionGroup section */ - B526257C25C874FA003E73B7 /* Map.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - B526257D25C874FA003E73B7 /* Map.xcdatamodel */, - ); - currentVersion = B526257D25C874FA003E73B7 /* Map.xcdatamodel */; - path = Map.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; + B5012E7E2C97315800AC4D68 /* ConcaveHull */ = { + isa = XCSwiftPackageProductDependency; + package = B5012E7D2C97315800AC4D68 /* XCRemoteSwiftPackageReference "ConcaveHull" */; + productName = ConcaveHull; }; -/* End XCVersionGroup section */ +/* End XCSwiftPackageProductDependency section */ }; - rootObject = B526256625C874F9003E73B7 /* Project object */; + rootObject = B54587042C961E9C0067B788 /* Project object */; } diff --git a/Map.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Map.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9606e6c..887d9ee 100644 --- a/Map.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Map.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,15 @@ { + "originHash" : "02d7b701c0ab6f32b55ad490056b5cb87cdec5003f477a828ca9a07b09a2d7b3", "pins" : [ + { + "identity" : "concavehull", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Syncheo/ConcaveHull", + "state" : { + "branch" : "master", + "revision" : "2583fea7f4b2a34830e47b64fc076cd3fbd4038c" + } + }, { "identity" : "patterns", "kind" : "remoteSourceControl", @@ -10,5 +20,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/Map/Assets.xcassets/Colors/Theme/Darker Neutral Gray.colorset/Contents.json b/Map/Assets.xcassets/Colors/Theme/Darker Neutral Gray.colorset/Contents.json new file mode 100644 index 0000000..e890acd --- /dev/null +++ b/Map/Assets.xcassets/Colors/Theme/Darker Neutral Gray.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.488", + "green" : "0.500", + "red" : "0.434" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Assets.xcassets/Colors/Theme/Hermosa Pink.colorset/Contents.json b/Map/Assets.xcassets/Colors/Theme/Hermosa Pink.colorset/Contents.json new file mode 100644 index 0000000..b90aa6c --- /dev/null +++ b/Map/Assets.xcassets/Colors/Theme/Hermosa Pink.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0xB3", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Assets.xcassets/Colors/Theme/Light Porcelain Green.colorset/Contents.json b/Map/Assets.xcassets/Colors/Theme/Light Porcelain Green.colorset/Contents.json new file mode 100644 index 0000000..13a53a1 --- /dev/null +++ b/Map/Assets.xcassets/Colors/Theme/Light Porcelain Green.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7C", + "green" : "0xC1", + "red" : "0x23" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Assets.xcassets/Colors/Theme/Naples Yellow.colorset/Contents.json b/Map/Assets.xcassets/Colors/Theme/Naples Yellow.colorset/Contents.json new file mode 100644 index 0000000..1c11b82 --- /dev/null +++ b/Map/Assets.xcassets/Colors/Theme/Naples Yellow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x8F", + "green" : "0xED", + "red" : "0xFA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Map/Core Extensions/Date+format.swift b/Map/Core Extensions/Date+format.swift deleted file mode 100644 index d29ed0f..0000000 --- a/Map/Core Extensions/Date+format.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Date+format.swift -// Map -// -// Created by Ruben Beltran del Rio on 2/1/21. -// - -import Foundation - -extension Date { - func format() -> String { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .medium - return formatter.string(from: self) - } -} diff --git a/Map/Core Extensions/Font+Theme.swift b/Map/Core Extensions/Font+Theme.swift deleted file mode 100644 index 30bc6f9..0000000 --- a/Map/Core Extensions/Font+Theme.swift +++ /dev/null @@ -1,8 +0,0 @@ -import SwiftUI - -public extension Font { - struct theme { - static let axisLabel = Font.custom("Hiragino Mincho ProN", size: 14) - static let vertexLabel = Font.custom("Hiragino Mincho ProN", size: 12) - } -} diff --git a/Map/Core Extensions/NSImage+writePNG.swift b/Map/Core Extensions/NSImage+writePNG.swift deleted file mode 100644 index c24c0cf..0000000 --- a/Map/Core Extensions/NSImage+writePNG.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Cocoa - -extension NSImage { - public func writePNG(toURL url: URL) { - - guard let data = tiffRepresentation, - let rep = NSBitmapImageRep(data: data), - let imgData = rep.representation( - using: .png, properties: [.compressionFactor: NSNumber(floatLiteral: 1.0)]) - else { - - print( - "\(self.self) Error Function '\(#function)' Line: \(#line) No tiff rep found for image writing to \(url)" - ) - return - } - - do { - try imgData.write(to: url) - } catch let error { - print( - "\(self.self) Error Function '\(#function)' Line: \(#line) \(error.localizedDescription)") - } - } -} diff --git a/Map/Data/AppState.swift b/Map/Data/AppState.swift deleted file mode 100644 index d0d5670..0000000 --- a/Map/Data/AppState.swift +++ /dev/null @@ -1,103 +0,0 @@ -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 diff --git a/Map/Data/MapDocument.swift b/Map/Data/MapDocument.swift new file mode 100644 index 0000000..9340684 --- /dev/null +++ b/Map/Data/MapDocument.swift @@ -0,0 +1,42 @@ +import SwiftUI +import UniformTypeIdentifiers + +extension UTType { + static var exampleText: UTType { + UTType(importedAs: "systems.tranquil.map.wmap") + } +} + +struct MapDocument: FileDocument { + var text: String + + init(text: String = "Hello, world!") { + self.text = text + } + + static var readableContentTypes: [UTType] { [.exampleText] } + + init(configuration: ReadConfiguration) throws { + guard let data = configuration.file.regularFileContents, + let string = String(data: data, encoding: .utf8) + else { + throw CocoaError(.fileReadCorruptFile) + } + text = string + } + + func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { + let data = text.data(using: .utf8)! + return .init(regularFileWithContents: data) + } + + @MainActor + func exportAsImage(withEvolution selectedEvolution: StageType) -> NSImage? { + let renderView = MapRenderView( + document: .constant(self), + evolution: .constant(selectedEvolution)) + let renderer = ImageRenderer(content: renderView) + + return renderer.nsImage + } +} diff --git a/Map/Data/Models/Map+parse.swift b/Map/Data/Models/Map+parse.swift deleted file mode 100644 index 5181daf..0000000 --- a/Map/Data/Models/Map+parse.swift +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 1eb09e8..0000000 --- a/Map/Data/Persistence.swift +++ /dev/null @@ -1,42 +0,0 @@ -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/Store.swift b/Map/Data/Store.swift deleted file mode 100644 index 7860f33..0000000 --- a/Map/Data/Store.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -final class Store: ObservableObject { - @Published private(set) var state: State - - private let reducer: Reducer - - init(initialState: State, reducer: @escaping Reducer) { - self.state = initialState - self.reducer = reducer - } - - func send(_ action: Action) { - reducer(&state, action) - } -} - -typealias Reducer = (inout State, Action) -> Void diff --git a/Map/Info.plist b/Map/Info.plist index 6ba8cf0..61150cf 100644 --- a/Map/Info.plist +++ b/Map/Info.plist @@ -2,25 +2,42 @@ - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSApplicationCategoryType - public.app-category.productivity - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) + CFBundleDocumentTypes + + + CFBundleTypeRole + Editor + LSHandlerRank + Default + LSItemContentTypes + + systems.tranquil.map.wmap + + NSUbiquitousDocumentUserActivityType + $(PRODUCT_BUNDLE_IDENTIFIER).exampledocument + + + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.plain-text + + UTTypeDescription + Wardley Map written in Map's wmap syntax + UTTypeIcons + + UTTypeIdentifier + systems.tranquil.map.wmap + UTTypeTagSpecification + + public.filename-extension + + wmap + + + + diff --git a/Map/Logic/Constants.swift b/Map/Logic/Constants.swift new file mode 100644 index 0000000..8cb95e5 --- /dev/null +++ b/Map/Logic/Constants.swift @@ -0,0 +1,4 @@ +struct Constants { + static let kMaxZoom = 2.0 + static let kMinZoom = 0.1 +} diff --git a/Map/Logic/MapParser/MapParser.swift b/Map/Logic/MapParser/MapParser.swift index 5f78d5d..1a66f9a 100644 --- a/Map/Logic/MapParser/MapParser.swift +++ b/Map/Logic/MapParser/MapParser.swift @@ -1,25 +1,60 @@ import CoreGraphics import Foundation +struct MapParser { + static func parse(content: String) -> ParsedMap { + + let parsers = [ + AnyMapParserStrategy(NoteParserStrategy()), + AnyMapParserStrategy(VertexParserStrategy()), + AnyMapParserStrategy(EdgeParserStrategy()), + AnyMapParserStrategy(BlockerParserStrategy()), + AnyMapParserStrategy(OpportunityParserStrategy()), + AnyMapParserStrategy(StageParserStrategy()), + AnyMapParserStrategy(GroupParserStrategy()), + ] + 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() + } +} + // MARK: - Types struct MapParsingPatterns { static let vertex = try! NSRegularExpression( pattern: - "([^\\(\\[\\]]*?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(?:\\[(.*?)\\])?", - options: .caseInsensitive) + "^([^\\(\\[\\]]*?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(?:\\[(.*?)\\])?[\\s]*$", + options: [.caseInsensitive, .anchorsMatchLines]) static let edge = try! NSRegularExpression( - pattern: "(.+?)[\\s]*-([->])[\\s]*(.+)", options: .caseInsensitive) + pattern: "^(.+?)[\\s]*-([->])[\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) static let blocker = try! NSRegularExpression( - pattern: "\\[(Blocker)\\][\\s]*(.+)", options: .caseInsensitive) + pattern: "^\\[(Blocker)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) static let opportunity = try! NSRegularExpression( - pattern: "\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)", - options: .caseInsensitive) + pattern: "^\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)", + options: [.caseInsensitive, .anchorsMatchLines]) static let note = try! NSRegularExpression( - pattern: "\\[(Note)\\][\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(.*)", - options: .caseInsensitive) + pattern: + "^\\[(Note)\\][\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(.*)", + options: [.caseInsensitive, .anchorsMatchLines]) static let stage = try! NSRegularExpression( - pattern: "\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)", options: .caseInsensitive) + pattern: "^\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)", + options: [.caseInsensitive, .anchorsMatchLines]) + static let group = try! NSRegularExpression( + pattern: "^\\[(Group)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines]) } struct ParsedMap { @@ -29,12 +64,14 @@ struct ParsedMap { 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: [], blockers: [], opportunities: [], notes: [], stages: defaultDimensions, + groups: []) } -struct Vertex { +struct Vertex: Identifiable, Hashable { let id: Int let label: String let position: CGPoint @@ -115,6 +152,7 @@ class MapBuilder { private var opportunities: [Opportunity] = [] private var notes: [Note] = [] private var stages: [CGFloat] = defaultDimensions + private var groups: [[Vertex]] = [] func addObjectToMap(type: Any.Type, object: Any) { if type == Vertex.self { @@ -136,7 +174,7 @@ class MapBuilder { let opportunity = object as! Opportunity opportunities.append(opportunity) } - + if type == Note.self { let note = object as! Note notes.append(note) @@ -146,12 +184,18 @@ class MapBuilder { let stageDimensions = object as! StageDimensions stages[stageDimensions.index] = stageDimensions.dimensions } + + if type == [Vertex].self { + let group = object as! [Vertex] + groups.append(group) + } } func build() -> ParsedMap { let mappedVertices = vertices.map { label, vertex in return vertex } return ParsedMap( - vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities, notes: notes, - stages: stages) + vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities, + notes: notes, + stages: stages, groups: groups) } } diff --git a/Map/Logic/MapParser/Strategies/GroupParserStrategy.swift b/Map/Logic/MapParser/Strategies/GroupParserStrategy.swift new file mode 100644 index 0000000..7924935 --- /dev/null +++ b/Map/Logic/MapParser/Strategies/GroupParserStrategy.swift @@ -0,0 +1,34 @@ +import Foundation + +struct GroupParserStrategy: MapParserStrategy { + private let regex = MapParsingPatterns.group + + 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] + var groupVertices: [Vertex] = [] + let vertexIdString = String(line[Range(match.range(at: 2), in: line)!]) + let vertexIds = vertexIdString.split(separator: " ", omittingEmptySubsequences: true).map( + String.init) + + for vertexId in vertexIds { + if let vertex = vertices[vertexId] { + groupVertices.append(vertex) + } + } + + if groupVertices.count > 0 { + return ([Vertex].self, groupVertices) + } + + return (NSObject.self, NSObject()) + } +} diff --git a/Map/Map.entitlements b/Map/Map.entitlements index 6060301..6d968ed 100644 --- a/Map/Map.entitlements +++ b/Map/Map.entitlements @@ -2,19 +2,9 @@ - com.apple.developer.aps-environment - development - com.apple.developer.icloud-container-identifiers - - iCloud.pizza.unlimited.map - - com.apple.developer.icloud-services - - CloudKit - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-write - + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + diff --git a/Map/Map.xcdatamodeld/.xccurrentversion b/Map/Map.xcdatamodeld/.xccurrentversion deleted file mode 100644 index ee72e60..0000000 --- a/Map/Map.xcdatamodeld/.xccurrentversion +++ /dev/null @@ -1,8 +0,0 @@ - - - - - _XCCurrentVersionName - Map.xcdatamodel - - diff --git a/Map/Map.xcdatamodeld/Map.xcdatamodel/contents b/Map/Map.xcdatamodeld/Map.xcdatamodel/contents deleted file mode 100644 index ea8b276..0000000 --- a/Map/Map.xcdatamodeld/Map.xcdatamodel/contents +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Map/MapApp.swift b/Map/MapApp.swift index 14e5605..9aff010 100644 --- a/Map/MapApp.swift +++ b/Map/MapApp.swift @@ -1,23 +1,12 @@ -// -// MapApp.swift -// Map -// -// Created by Ruben Beltran del Rio on 2/1/21. -// - import SwiftUI @main struct MapApp: App { - let persistenceController = PersistenceController.shared - var body: some Scene { - WindowGroup { - MapEditorWindow() - .environment(\.managedObjectContext, persistenceController.container.viewContext) - .environmentObject(AppStore(initialState: AppState(), reducer: appStateReducer)) - }.windowStyle(HiddenTitleBarWindowStyle()).commands { - SidebarCommands() + DocumentGroup(newDocument: MapDocument()) { file in + MapEditor(document: file.$document, url: file.fileURL) + }.commands { + MapCommands() } } } diff --git a/Map/Presentation/Base Components/EvolutionPicker.swift b/Map/Presentation/Base Components/EvolutionPicker.swift index c68e90c..c30798a 100644 --- a/Map/Presentation/Base Components/EvolutionPicker.swift +++ b/Map/Presentation/Base Components/EvolutionPicker.swift @@ -2,40 +2,30 @@ import SwiftUI struct EvolutionPicker: View { - @EnvironmentObject private var store: AppStore - - private var selectedEvolution: Binding { - Binding( - get: { store.state.selectedEvolution }, - set: { evolution in - store.send(.selectEvolution(evolution: evolution)) - } - ) - } + @Binding var selectedEvolution: StageType var body: some View { - Picker("Evolution", selection: selectedEvolution) { + Picker("Evolution", selection: $selectedEvolution) { ForEach(StageType.types) { stage in - Text(Stage.title(stage)).tag(stage).padding(4.0) + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0) } Divider() ForEach(StageType.characteristics) { stage in - Text(Stage.title(stage)).tag(stage).padding(4.0) + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0).font(.theme.body) } Divider() ForEach(StageType.properties) { stage in - Text(Stage.title(stage)).tag(stage).padding(4.0) + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0) } Divider() ForEach(StageType.custom) { stage in - Text(Stage.title(stage)).tag(stage).padding(4.0) + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0) } - }.padding(.horizontal, 8.0).padding(.vertical, 4.0) + }.font(.theme.body).padding(.horizontal, 8.0).padding(.vertical, 4.0) } } -struct EvolutionPicker_Previews: PreviewProvider { - static var previews: some View { - EvolutionPicker() - } +#Preview { + let selectedEvolution: StageType = .behavior + EvolutionPicker(selectedEvolution: .constant(selectedEvolution)) } diff --git a/Map/Presentation/Base Components/MapRender/MapAxes.swift b/Map/Presentation/Base Components/MapRender/MapAxes.swift index 6ba758f..1b9c226 100644 --- a/Map/Presentation/Base Components/MapRender/MapAxes.swift +++ b/Map/Presentation/Base Components/MapRender/MapAxes.swift @@ -22,10 +22,14 @@ struct MapAxes: View { }.stroke(Color.map.axisColor, lineWidth: lineWidth * 2) // Y Labels - Text("Visible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect(Angle(degrees: -90.0)) - .offset(CGSize(width: -35.0, height: 0.0)) - Text("Invisible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect(Angle(degrees: -90.0)) - .offset(CGSize(width: -40.0, height: mapSize.height - 20)) + Text("Visible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect( + Angle(degrees: -90.0) + ) + .offset(CGSize(width: -35.0, height: 0.0)) + Text("Invisible").font(.theme.axisLabel).foregroundColor(.map.labelColor).rotationEffect( + Angle(degrees: -90.0) + ) + .offset(CGSize(width: -40.0, height: mapSize.height - 20)) // X Labels @@ -71,11 +75,9 @@ struct MapAxes: View { } } -struct MapAxes_Previews: PreviewProvider { - static var previews: some View { - MapAxes( - mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(1.0), - evolution: Stage.stages(.general), stages: [25.0, 50.0, 75.0] - ).padding(50.0) - } +#Preview { + MapAxes( + mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(1.0), + evolution: Stage.stages(.general), stages: [25.0, 50.0, 75.0] + ).padding(50.0) } diff --git a/Map/Presentation/Base Components/MapRender/MapBlockers.swift b/Map/Presentation/Base Components/MapRender/MapBlockers.swift index d943ba8..efd84fa 100644 --- a/Map/Presentation/Base Components/MapRender/MapBlockers.swift +++ b/Map/Presentation/Base Components/MapRender/MapBlockers.swift @@ -31,13 +31,11 @@ struct MapBlockers: View { } } -struct MapBlockers_Previews: PreviewProvider { - static var previews: some View { - 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)), - ]) - } +#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/MapEdges.swift b/Map/Presentation/Base Components/MapRender/MapEdges.swift index 3814495..2af7089 100644 --- a/Map/Presentation/Base Components/MapRender/MapEdges.swift +++ b/Map/Presentation/Base Components/MapRender/MapEdges.swift @@ -65,15 +65,13 @@ struct MapEdges: View { } } -struct MapEdges_Previews: PreviewProvider { - static var previews: some View { - MapEdges( - mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0, - vertexSize: CGSize(width: 25.0, height: 25.0), - edges: [ - MapEdge( - id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2), - arrowhead: true) - ]) - } +#Preview { + MapEdges( + mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0, + vertexSize: CGSize(width: 25.0, height: 25.0), + edges: [ + MapEdge( + id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2), + arrowhead: true) + ]) } diff --git a/Map/Presentation/Base Components/MapRender/MapGroup.swift b/Map/Presentation/Base Components/MapRender/MapGroup.swift new file mode 100644 index 0000000..9df3338 --- /dev/null +++ b/Map/Presentation/Base Components/MapRender/MapGroup.swift @@ -0,0 +1,83 @@ +import ConcaveHull +import SwiftUI + +struct MapGroup: View { + + let mapSize: CGSize + let vertexSize: CGSize + let group: [Vertex] + let color: Color + + let cornerSize = CGSize(width: 2.0, height: 2.0) + var strokeSize: CGFloat { 1.75 * vertexSize.width } + + var hull: [CGPoint] { + let groupList = group.map({ vertex in + return [Double(vertex.position.x), Double(vertex.position.y)] + }) + let hull = Hull() + let hullPoints = hull.hull(groupList, nil) + return hullPoints.compactMap({ object in + if let point = object as? [Double] { + return CGPoint(x: point[0], y: point[1]) + } + return nil + }) + } + + var body: some View { + Path { path in + var initialMove: CGPoint? + + for point in hull { + let offsetPoint = CGPoint(x: w(point.x), y: h(point.y)) + + if initialMove == nil { + path.move(to: offsetPoint) + initialMove = offsetPoint + } else { + path.addLine(to: offsetPoint) + } + } + + if let initialMove = initialMove { + path.addLine(to: initialMove) + } + + } + .applying( + CGAffineTransform(translationX: vertexSize.width / 2.0, y: vertexSize.height / 2.0) + ) + .fill(color) + .stroke( + color, + style: StrokeStyle( + lineWidth: strokeSize, + lineCap: .round, + lineJoin: .round, + miterLimit: 0, + dash: [], + dashPhase: 0 + ) + ) + } + + 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 { + MapGroup( + mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0), + group: [ + Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), + Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), + Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + ], color: .red) +} diff --git a/Map/Presentation/Base Components/MapRender/MapGroups.swift b/Map/Presentation/Base Components/MapRender/MapGroups.swift new file mode 100644 index 0000000..84f7fb2 --- /dev/null +++ b/Map/Presentation/Base Components/MapRender/MapGroups.swift @@ -0,0 +1,32 @@ +import ConcaveHull +import SwiftUI + +struct MapGroups: View { + + let mapSize: CGSize + let vertexSize: CGSize + let groups: [[Vertex]] + + var body: some View { + ForEach(Array(groups.enumerated()), id: \.element) { index, group in + MapGroup(mapSize: mapSize, vertexSize: vertexSize, group: group, color: color(index)) + } + } + + private func color(_ index: Int) -> Color { + return .map.groupColors[index % Color.map.groupColors.count] + } +} + +#Preview { + MapGroups( + mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0), + groups: [ + [ + Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), + Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), + Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + ] + ]) +} diff --git a/Map/Presentation/Base Components/MapRender/MapNotes.swift b/Map/Presentation/Base Components/MapRender/MapNotes.swift index f35b3fe..bbb1aba 100644 --- a/Map/Presentation/Base Components/MapRender/MapNotes.swift +++ b/Map/Presentation/Base Components/MapRender/MapNotes.swift @@ -5,13 +5,12 @@ struct MapNotes: View { let mapSize: CGSize let lineWidth: CGFloat let notes: [Note] - + let maxWidth = 400.0 - var body: some View { ForEach(notes, id: \.id) { note in - Text(note.text.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.axisLabel) + Text(note.text.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.note) .padding(2.0) .background(.white) .foregroundColor(.map.labelColor) @@ -35,12 +34,13 @@ struct MapNotes: View { } } -struct MapNotes_Previews: PreviewProvider { - static var previews: some View { - MapNotes( - mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0, - notes: [ - Note(id: 0, position: CGPoint(x: 50.0, y: 50.0), text: "Notes can have a lot more text, so we need to make sure that they're resized correctly"), - ]) - } +#Preview { + MapNotes( + mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0, + notes: [ + Note( + id: 0, position: CGPoint(x: 50.0, y: 50.0), + text: + "Notes can have a lot more text, so we need to make sure that they're resized correctly") + ]) } diff --git a/Map/Presentation/Base Components/MapRender/MapOpportunities.swift b/Map/Presentation/Base Components/MapRender/MapOpportunities.swift index 7fcadff..b3051ee 100644 --- a/Map/Presentation/Base Components/MapRender/MapOpportunities.swift +++ b/Map/Presentation/Base Components/MapRender/MapOpportunities.swift @@ -46,7 +46,8 @@ struct MapOpportunities: View { path.closeSubpath() }.applying( CGAffineTransform(translationX: vertexSize.width / 2.0, y: vertexSize.height / 2.0) - ).strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0])).stroke(Color.map.opportunityColor) + ).strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0])).stroke( + Color.map.opportunityColor) } } @@ -59,13 +60,11 @@ struct MapOpportunities: View { } } -struct MapOpportunities_Previews: PreviewProvider { - static var previews: some View { - MapOpportunities( - mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0, - vertexSize: CGSize(width: 25.0, height: 25.0), - opportunities: [ - Opportunity(id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2)) - ]) - } +#Preview { + MapOpportunities( + mapSize: CGSize(width: 400.0, height: 400.0), lineWidth: 1.0, + vertexSize: CGSize(width: 25.0, height: 25.0), + opportunities: [ + Opportunity(id: 1, origin: CGPoint(x: 2.0, y: 34.0), destination: CGPoint(x: 23.0, y: 76.2)) + ]) } diff --git a/Map/Presentation/Base Components/MapRender/MapStages.swift b/Map/Presentation/Base Components/MapRender/MapStages.swift index 0fc8f48..fc3bfa1 100644 --- a/Map/Presentation/Base Components/MapRender/MapStages.swift +++ b/Map/Presentation/Base Components/MapRender/MapStages.swift @@ -1,5 +1,5 @@ -import SwiftUI import Patterns +import SwiftUI struct MapStages: View { @@ -10,17 +10,29 @@ struct MapStages: View { var body: some View { ZStack(alignment: .topLeading) { - PatternView(design: .constant(.stitch), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground) - .frame(width: w(stages[0]), height: mapSize.height) - PatternView(design: .constant(.shingles), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground) - .offset(CGSize(width: w(stages[0]), height: 0)) - .frame(width: w(stages[1]) - w(stages[0]), height: mapSize.height) - PatternView(design: .constant(.shadowGrid), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground) - .offset(CGSize(width: w(stages[1]), height: 0)) - .frame(width: w(stages[2]) - w(stages[1]), height: mapSize.height) - PatternView(design: .constant(.wicker), pixelSize: 1.0, foregroundColor: .map.stageForeground, backgroundColor: .map.stageBackground) - .offset(CGSize(width: w(stages[2]), height: 0)) - .frame(width: mapSize.width - w(stages[2]), height: mapSize.height) + PatternView( + design: .constant(.stitch), pixelSize: 1.0, foregroundColor: .map.stageForeground, + backgroundColor: .map.stageBackground + ) + .frame(width: w(stages[0]), height: mapSize.height) + PatternView( + design: .constant(.shingles), pixelSize: 1.0, foregroundColor: .map.stageForeground, + backgroundColor: .map.stageBackground + ) + .offset(CGSize(width: w(stages[0]), height: 0)) + .frame(width: w(stages[1]) - w(stages[0]), height: mapSize.height) + PatternView( + design: .constant(.shadowGrid), pixelSize: 1.0, foregroundColor: .map.stageForeground, + backgroundColor: .map.stageBackground + ) + .offset(CGSize(width: w(stages[1]), height: 0)) + .frame(width: w(stages[2]) - w(stages[1]), height: mapSize.height) + PatternView( + design: .constant(.wicker), pixelSize: 1.0, foregroundColor: .map.stageForeground, + backgroundColor: .map.stageBackground + ) + .offset(CGSize(width: w(stages[2]), height: 0)) + .frame(width: mapSize.width - w(stages[2]), height: mapSize.height) Path { path in path.move(to: CGPoint(x: w(stages[0]), y: 0)) @@ -34,7 +46,8 @@ struct MapStages: View { path.closeSubpath() path.move(to: CGPoint(x: w(stages[0]), y: 0)) path.closeSubpath() - }.strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0, 18.0])).stroke(Color.map.axisColor) + }.strokedPath(StrokeStyle(lineWidth: lineWidth / 4, dash: [10.0, 18.0])).stroke( + Color.map.axisColor) } } @@ -43,10 +56,8 @@ struct MapStages: View { } } -struct MapStages_Previews: PreviewProvider { - static var previews: some View { - MapStages( - mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(0.5), - stages: [25.0, 50.0, 75.0]) - } +#Preview { + MapStages( + mapSize: CGSize(width: 200.0, height: 200.0), lineWidth: CGFloat(0.5), + stages: [25.0, 50.0, 75.0]) } diff --git a/Map/Presentation/Base Components/MapRender/MapVertices.swift b/Map/Presentation/Base Components/MapRender/MapVertices.swift index 74cac6d..5bc0a96 100644 --- a/Map/Presentation/Base Components/MapRender/MapVertices.swift +++ b/Map/Presentation/Base Components/MapRender/MapVertices.swift @@ -7,18 +7,29 @@ struct MapVertices: View { let vertices: [Vertex] let padding = CGFloat(5.0) + var onDragVertex: (Vertex, CGFloat, CGFloat) -> Void = { _, _, _ in } + var body: some View { ZStack(alignment: .topLeading) { ForEach(vertices, id: \.id) { vertex in - getVertexShape(vertex).fill(Color.map.vertexColor) - Text(vertex.label.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.vertexLabel) + ZStack(alignment: .topLeading) { + getVertexShape(vertex).fill(Color.map.vertexColor) + Text(vertex.label.replacingOccurrences(of: "\\n", with: "\n")).font(.theme.vertexLabel) .foregroundColor(.map.labelColor) .shadow(color: .white, radius: 0, x: -0.5, y: -0.5) .shadow(color: .white, radius: 0, x: 0.5, y: 0.5) .offset( - CGSize( - width: w(vertex.position.x) + vertexSize.width + padding, - height: h(vertex.position.y) + 7.0)) + CGSize( + width: w(vertex.position.x) + vertexSize.width + padding, + height: h(vertex.position.y) + 7.0)) + }.gesture( + DragGesture() + .onChanged { value in + let deltaX = value.startLocation.x - value.location.x + let deltaY = value.startLocation.y - value.location.y + onDragVertex(vertex, deltaX, deltaY) + } + ) } } } @@ -78,15 +89,13 @@ struct MapVertices: View { } } -struct MapVertices_Previews: PreviewProvider { - static var previews: some View { - MapVertices( - mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0), - vertices: [ - Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), - Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), - Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), - Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), - ]) - } +#Preview { + MapVertices( + mapSize: CGSize(width: 400.0, height: 400.0), vertexSize: CGSize(width: 25.0, height: 25.0), + vertices: [ + Vertex(id: 0, label: "A Circle", position: CGPoint(x: 50.0, y: 50.0)), + Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), + Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), + Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), + ]) } diff --git a/Map/Presentation/Base Components/MapTextEditor.swift b/Map/Presentation/Base Components/MapTextEditor.swift index f3838a6..d7aa9f8 100644 --- a/Map/Presentation/Base Components/MapTextEditor.swift +++ b/Map/Presentation/Base Components/MapTextEditor.swift @@ -3,7 +3,7 @@ import SwiftUI class MapTextEditorController: NSViewController { - @Binding var text: String + @Binding var document: MapDocument let onChange: () -> Void private let vertexRegex = MapParsingPatterns.vertex @@ -12,11 +12,12 @@ class MapTextEditorController: NSViewController { private let opportunityRegex = MapParsingPatterns.opportunity private let noteRegex = MapParsingPatterns.note private let stageRegex = MapParsingPatterns.stage + private let groupRegex = MapParsingPatterns.group private let changeDebouncer: Debouncer = Debouncer(seconds: 1) - init(text: Binding, onChange: @escaping () -> Void) { - self._text = text + init(document: Binding, onChange: @escaping () -> Void) { + self._document = document self.onChange = onChange super.init(nibName: nil, bundle: nil) } @@ -31,10 +32,11 @@ class MapTextEditorController: NSViewController { scrollView.translatesAutoresizingMaskIntoConstraints = false + textView.backgroundColor = .ui.background textView.allowsUndo = true textView.delegate = self textView.textStorage?.delegate = self - textView.string = self.text + textView.string = self.document.text textView.isEditable = true textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular) self.view = scrollView @@ -49,14 +51,13 @@ extension MapTextEditorController: NSTextViewDelegate { func textDidChange(_ obj: Notification) { if let textField = obj.object as? NSTextView { - self.text = textField.string - - - changeDebouncer.debounce { - DispatchQueue.main.async { - self.onChange() - } + self.document.text = textField.string + + changeDebouncer.debounce { + DispatchQueue.main.async { + self.onChange() } + } } } @@ -86,65 +87,91 @@ extension MapTextEditorController: NSTextStorageDelegate { var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { - textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 4)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.option], range: match.range(at: 4)) } matches = edgeRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { - textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1)) let arrowRange = match.range(at: 2) textStorage.addAttributes( [.foregroundColor: NSColor.syntax.symbol], range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3)) } matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { - textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 4)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.number], range: match.range(at: 4)) } matches = blockerRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { - textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) } - + matches = noteRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { - textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.number], range: match.range(at: 3)) } matches = stageRegex.matches(in: textStorage.string, options: [], range: range) for match in matches { - textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) - textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.number], range: match.range(at: 2)) + } + + matches = groupRegex.matches(in: textStorage.string, options: [], range: range) + + for match in matches { + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.option], range: match.range(at: 1)) + textStorage.addAttributes( + [.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2)) } } } struct MapTextEditor: NSViewControllerRepresentable { - @Binding var text: String + @Binding var document: MapDocument var onChange: () -> Void = {} func makeNSViewController( context: NSViewControllerRepresentableContext ) -> MapTextEditorController { - return MapTextEditorController(text: $text, onChange: onChange) + return MapTextEditorController(document: $document, onChange: onChange) } func updateNSViewController( diff --git a/Map/Presentation/Commands/MapCommands.swift b/Map/Presentation/Commands/MapCommands.swift new file mode 100644 index 0000000..32b4958 --- /dev/null +++ b/Map/Presentation/Commands/MapCommands.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct MapCommands: Commands { + + @AppStorage("viewStyle") var viewStyle: ViewStyle = .horizontal + @AppStorage("zoom") var zoom = 1.0 + + var body: some Commands { + + // View + + CommandGroup(after: CommandGroupPlacement.toolbar) { + if viewStyle == .horizontal { + Button("Use Vertical Layout") { + viewStyle = .vertical + }.keyboardShortcut( + "l", modifiers: EventModifiers([.command]) + ) + } else { + Button("Use Horizontal Layout") { + viewStyle = .horizontal + }.keyboardShortcut( + "l", modifiers: EventModifiers([.command]) + ) + } + Divider() + Button("Zoom In") { + zoom = min(Constants.kMaxZoom, zoom + 0.1) + }.keyboardShortcut( + "+", modifiers: EventModifiers([.command]) + ) + Button("Zoom Out") { + zoom = max(Constants.kMinZoom, zoom - 0.1) + }.keyboardShortcut( + "-", modifiers: EventModifiers([.command]) + ) + Divider() + } + + CommandGroup(replacing: CommandGroupPlacement.help) { + Button("Map Help") { NSWorkspace.shared.open(URL(string: "https://map.tranquil.systems")!) } + } + } +} diff --git a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift index b12dabc..b256861 100644 --- a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift +++ b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift @@ -5,20 +5,24 @@ import SwiftUI struct MapRenderView: View { - @Binding var content: String + @Binding var document: MapDocument @Binding var evolution: StageType - + var stage: Stage { Stage.stages(evolution) } - @State var parsedMap: ParsedMap = ParsedMap.empty + var parsedMap: ParsedMap { + MapParser.parse(content: document.text) + } - let mapSize = CGSize(width: 1300.0, height: 1000.0) + let mapSize = Dimensions.mapSize + let padding = Dimensions.mapPadding let lineWidth = CGFloat(0.5) let vertexSize = CGSize(width: 25.0, height: 25.0) - let padding = CGFloat(30.0) + + var onDragVertex: (Vertex, CGFloat, CGFloat) -> Void = { _, _, _ in } var body: some View { ZStack(alignment: .topLeading) { @@ -36,28 +40,27 @@ struct MapRenderView: View { MapEdges( mapSize: mapSize, lineWidth: lineWidth, vertexSize: vertexSize, edges: parsedMap.edges) MapBlockers(mapSize: mapSize, vertexSize: vertexSize, blockers: parsedMap.blockers) - MapVertices(mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices) + MapVertices( + mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices, + onDragVertex: onDragVertex) MapOpportunities( mapSize: mapSize, lineWidth: lineWidth, vertexSize: vertexSize, opportunities: parsedMap.opportunities) + MapGroups(mapSize: mapSize, vertexSize: vertexSize, groups: parsedMap.groups).drawingGroup( + opaque: true + ).opacity(0.1) MapNotes( mapSize: mapSize, lineWidth: lineWidth, notes: parsedMap.notes) - }.frame( - width: mapSize.width, + }.offset(x: padding, y: padding).frame( + width: mapSize.width + 2 * padding, height: mapSize.height + 2 * padding, alignment: .topLeading - ).onAppear { - self.parsedMap = Map.parse(content: content) - }.padding(padding).onChange(of: content) { newState in - self.parsedMap = Map.parse(content: newState) - } + ) } } -struct MapRenderView_Previews: PreviewProvider { - static var previews: some View { - MapRenderView( - content: Binding.constant(""), evolution: Binding.constant(StageType.general) - ).environment( - \.managedObjectContext, PersistenceController.preview.container.viewContext) - } +#Preview { + MapRenderView( + document: Binding.constant(MapDocument(text: "")), + evolution: Binding.constant(StageType.general) + ) } diff --git a/Map/Presentation/EvolutionPicker.swift b/Map/Presentation/EvolutionPicker.swift new file mode 100644 index 0000000..0f4c954 --- /dev/null +++ b/Map/Presentation/EvolutionPicker.swift @@ -0,0 +1,41 @@ +import SwiftUI + +struct EvolutionPicker: View { + + @EnvironmentObject private var store: AppStore + + private var selectedEvolution: Binding { + 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)).font(.theme.body).tag(stage).padding(4.0) + } + Divider() + ForEach(StageType.characteristics) { stage in + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0).font(.theme.body) + } + Divider() + ForEach(StageType.properties) { stage in + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0) + } + Divider() + ForEach(StageType.custom) { stage in + Text(Stage.title(stage)).font(.theme.body).tag(stage).padding(4.0) + } + }.font(.theme.body).padding(.horizontal, 8.0).padding(.vertical, 4.0) + } +} + +struct EvolutionPicker_Previews: PreviewProvider { + static var previews: some View { + EvolutionPicker() + } +} diff --git a/Map/Presentation/MapEditor.swift b/Map/Presentation/MapEditor.swift new file mode 100644 index 0000000..bf33f75 --- /dev/null +++ b/Map/Presentation/MapEditor.swift @@ -0,0 +1,134 @@ +import SwiftUI + +struct MapEditor: View { + @Binding var document: MapDocument + var url: URL? + @State var selectedEvolution: StageType = .behavior + + @AppStorage("viewStyle") var viewStyle: ViewStyle = .horizontal + + let zoomRange = Constants.kMinZoom...Constants.kMaxZoom + @AppStorage("zoom") var zoom = 1.0 + @State var lastZoom = 1.0 + + var body: some View { + VStack(spacing: 0) { + adaptiveStack { + ZStack(alignment: .topLeading) { + MapTextEditor(document: $document) + .background(Color.ui.background) + .foregroundColor(Color.ui.foreground) + .frame(minHeight: 96.0) + }.padding(.top, 8.0).padding(.leading, 8.0).background(Color.ui.background).cornerRadius( + 5.0) + GeometryReader { geometry in + ScrollView([.horizontal, .vertical]) { + MapRenderView( + document: $document, evolution: $selectedEvolution, onDragVertex: onDragVertex + ).scaleEffect(zoom, anchor: .center).frame( + width: (Dimensions.mapSize.width + 2 * Dimensions.mapPadding) * zoom, + height: (Dimensions.mapSize.height + 2 * Dimensions.mapPadding) * zoom) + }.background(Color.ui.background) + .gesture( + MagnificationGesture() + .onChanged { value in + let delta = value / lastZoom + lastZoom = value + zoom = min(max(zoom * delta, zoomRange.lowerBound), zoomRange.upperBound) + } + .onEnded { _ in + lastZoom = 1.0 + } + ) + } + } + Divider() + HStack { + Spacer() + Slider( + value: $zoom, in: zoomRange, step: 0.1, + label: { + Text(formatZoom(zoom)) + .font(.theme.smallControl) + }, + minimumValueLabel: { + Image(systemName: "minus.magnifyingglass") + .font(.theme.smallControl) + .help("Zoom Out (⌘-)") + }, + maximumValueLabel: { + Image(systemName: "plus.magnifyingglass") + .font(.theme.smallControl) + .help("Zoom In (⌘+)") + } + ).frame(width: 200).padding(.trailing, 10.0) + }.padding(4.0) + }.toolbar { + HStack { + Button(action: saveImage) { + Image(systemName: "photo") + } + .help("Export Image (⌘E)") + .padding(.vertical, 4.0).padding(.leading, 4.0).padding(.trailing, 8.0) + } + EvolutionPicker(selectedEvolution: $selectedEvolution) + } + } + + @ViewBuilder + func adaptiveStack(@ViewBuilder content: () -> Content) -> some View { + if viewStyle == .horizontal { + VSplitView { + content() + } + } else { + HSplitView { + content() + } + } + } + + private func formatZoom(_ number: CGFloat) -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 1 + formatter.minimumFractionDigits = 1 + return (formatter.string(from: NSNumber(value: number)) ?? "") + "x" + } + + private func onDragVertex(vertex: Vertex, x: CGFloat, y: CGFloat) { + print("Dragging: \(vertex), \(x), \(y)") + } + + private func saveImage() { + if let image = document.exportAsImage(withEvolution: selectedEvolution) { + + let filename = url?.deletingPathExtension().lastPathComponent ?? "Untitled" + + let savePanel = NSSavePanel() + savePanel.allowedContentTypes = [.png] + savePanel.canCreateDirectories = true + savePanel.isExtensionHidden = false + savePanel.title = "Save \(filename) as image" + savePanel.message = "Choose a location to save the image" + savePanel.nameFieldStringValue = "\(filename).png" + savePanel.begin { result in + if result == .OK, let url = savePanel.url { + if let tiffRepresentation = image.tiffRepresentation { + let bitmapImage = NSBitmapImageRep(data: tiffRepresentation) + let pngData = bitmapImage?.representation(using: .png, properties: [:]) + do { + try pngData?.write(to: url) + } catch { + return + } + } + } + } + } + } +} + +#Preview { + MapEditor(document: .constant(MapDocument()), url: URL(filePath: "test.png")!) +} diff --git a/Map/Presentation/Screens/EmptyMapDetailScreen.swift b/Map/Presentation/Screens/EmptyMapDetailScreen.swift deleted file mode 100644 index f3c6d75..0000000 --- a/Map/Presentation/Screens/EmptyMapDetailScreen.swift +++ /dev/null @@ -1,16 +0,0 @@ -import CoreData -import SwiftUI - -struct EmptyMapDetailScreen: View { - - var body: some View { - Text("Select a map from the left hand side, or click on + to create one.") - } -} - -struct DefaultMapView_Previews: PreviewProvider { - static var previews: some View { - EmptyMapDetailScreen().environment( - \.managedObjectContext, PersistenceController.preview.container.viewContext) - } -} diff --git a/Map/Presentation/Screens/MapDetailScreen.swift b/Map/Presentation/Screens/MapDetailScreen.swift deleted file mode 100644 index a5c32fd..0000000 --- a/Map/Presentation/Screens/MapDetailScreen.swift +++ /dev/null @@ -1,72 +0,0 @@ -import Combine -import CoreData -import SwiftUI - -struct MapDetailScreen: View { - @Environment(\.managedObjectContext) private var viewContext - - @EnvironmentObject var store: AppStore - - @ObservedObject var map: Map - - @State var title: String - @State var content: String - - var body: some View { - if map.uuid != nil { - VSplitView { - VStack { - HStack { - TextField( - "Title", text: $title, onCommit: saveModel - ).font(.title2).textFieldStyle(PlainTextFieldStyle()).padding(.vertical, 4.0).padding( - .leading, 4.0) - Button(action: saveText) { - Image(systemName: "doc.text") - }.padding(.vertical, 4.0).padding(.leading, 4.0) - Button(action: saveImage) { - Image(systemName: "photo") - }.padding(.vertical, 4.0).padding(.leading, 4.0).padding(.trailing, 8.0) - } - EvolutionPicker() - - ZStack(alignment: .topLeading) { - MapTextEditor(text: $content, onChange: saveModel) - .background(Color.ui.background) - .foregroundColor(Color.ui.foreground) - .frame(minHeight: 96.0) - }.padding(.top, 8.0).padding(.leading, 8.0).background(Color.ui.background).cornerRadius( - 5.0) - }.padding(.horizontal, 8.0) - ScrollView([.horizontal, .vertical]) { - MapRenderView(content: $content, evolution: .constant(store.state.selectedEvolution)) - } - }.onDisappear { - saveModel() - } - } else { - EmptyMapDetailScreen() - } - } - - private func saveModel() { - map.content = content - map.title = title - try? viewContext.save() - } - - private func saveText() { - store.send(.exportMapAsText(map: map)) - } - - private func saveImage() { - store.send(.exportMapAsImage(map: map)) - } -} - -struct MapDetailView_Previews: PreviewProvider { - static var previews: some View { - MapDetailScreen(map: Map(), title: "", content: "").environment( - \.managedObjectContext, PersistenceController.preview.container.viewContext) - } -} diff --git a/Map/Core Extensions/Color+Theme.swift b/Map/Presentation/Theme/Color+theme.swift similarity index 66% rename from Map/Core Extensions/Color+Theme.swift rename to Map/Presentation/Theme/Color+theme.swift index ffbd224..1bd9b8e 100644 --- a/Map/Core Extensions/Color+Theme.swift +++ b/Map/Presentation/Theme/Color+theme.swift @@ -5,11 +5,15 @@ extension Color { static let darkSlate = Color("Dark Slate") static let jasperRed = Color("Jasper Red") static let olympicBlue = Color("Olympic Blue") + static let lightPorcelainGreen = Color("Light Porcelain Green") + static let naplesYellow = Color("Naples Yellow") + static let hermosaPink = Color("Hermosa Pink") static let neutralGray = Color("Neutral Gray") static let lightNeutralGray = Color("Light Neutral Gray") static let darkNeutralGray = Color("Dark Neutral Gray") + static let darkerNeutralGray = Color("Darker Neutral Gray") } - + struct map { static let labelColor = Color.theme.darkSlate static let axisColor = Color.theme.darkSlate @@ -18,8 +22,15 @@ extension Color { static let opportunityColor = Color.theme.olympicBlue static let stageForeground = Color.theme.lightNeutralGray static let stageBackground = Color.white + static let groupColors = [ + Color.theme.olympicBlue, + Color.theme.jasperRed, + Color.theme.lightPorcelainGreen, + Color.theme.naplesYellow, + Color.theme.hermosaPink, + ] } - + struct ui { static let foreground = Color("Foreground") static let background = Color("Background") diff --git a/Map/Presentation/Theme/Dimensions.swift b/Map/Presentation/Theme/Dimensions.swift new file mode 100644 index 0000000..6a58d58 --- /dev/null +++ b/Map/Presentation/Theme/Dimensions.swift @@ -0,0 +1,6 @@ +import Foundation + +struct Dimensions { + static let mapSize = CGSize(width: 1300.0, height: 1000.0) + static let mapPadding: CGFloat = 42.0 +} diff --git a/Map/Presentation/Theme/Font+theme.swift b/Map/Presentation/Theme/Font+theme.swift new file mode 100644 index 0000000..07e3b9b --- /dev/null +++ b/Map/Presentation/Theme/Font+theme.swift @@ -0,0 +1,15 @@ +import SwiftUI + +extension Font { + public struct theme { + + // Map + static let note = Font.system(size: 12, design: .serif) + static let axisLabel = Font.system(size: 14, design: .serif) + static let vertexLabel = Font.system(size: 12, design: .serif) + + // UI + static let smallControl = Font.system(size: 9) + static let body = Font.system(size: 13) + } +} diff --git a/Map/Core Extensions/NSColor+Theme.swift b/Map/Presentation/Theme/NSColor+theme.swift similarity index 75% rename from Map/Core Extensions/NSColor+Theme.swift rename to Map/Presentation/Theme/NSColor+theme.swift index 0ef1094..d30332a 100644 --- a/Map/Core Extensions/NSColor+Theme.swift +++ b/Map/Presentation/Theme/NSColor+theme.swift @@ -7,4 +7,8 @@ extension NSColor { static let option = NSColor(named: "Option") ?? .textColor static let symbol = NSColor(named: "Symbol") ?? .textColor } + + struct ui { + static let background = NSColor(named: "Background") ?? .windowBackgroundColor + } } diff --git a/Map/Presentation/ViewStyle.swift b/Map/Presentation/ViewStyle.swift new file mode 100644 index 0000000..cea3113 --- /dev/null +++ b/Map/Presentation/ViewStyle.swift @@ -0,0 +1,3 @@ +enum ViewStyle: String { + case vertical, horizontal +} diff --git a/Map/Presentation/Windows/MapEditorWindow.swift b/Map/Presentation/Windows/MapEditorWindow.swift deleted file mode 100644 index 65316cc..0000000 --- a/Map/Presentation/Windows/MapEditorWindow.swift +++ /dev/null @@ -1,109 +0,0 @@ -import CoreData -import SwiftUI - -struct MapEditorWindow: View { - @Environment(\.managedObjectContext) private var viewContext - - @EnvironmentObject var store: AppStore - - @FetchRequest( - sortDescriptors: [NSSortDescriptor(keyPath: \Map.createdAt, ascending: true)], - animation: .default) - private var maps: FetchedResults - - var body: some View { - NavigationView { - List { - if maps.count == 0 { - EmptyMapDetailScreen() - } - ForEach(maps) { map in - NavigationLink( - destination: MapDetailScreen(map: map, title: map.title ?? "", content: map.content ?? "") - ) { - HStack { - Text(map.title ?? "Untitled Map") - Spacer() - Text(mapFormatter.string(from: (map.createdAt ?? Date()))) - .font(.caption) - .padding(.vertical, 2.0) - .padding(.horizontal, 4.0) - .background(Color.accentColor) - .foregroundColor(Color.black) - .cornerRadius(2.0) - }.padding(.leading, 8.0) - }.contextMenu { - Button( - action: { store.send(.deleteMap(map: map)) }, - label: { - Image(systemName: "trash") - Text("Delete") - }) - } - } - .onDelete(perform: deleteMaps) - }.frame(minWidth: 250.0, alignment: .leading) - .toolbar { - HStack { - Button(action: toggleSidebar) { - Label("Toggle Sidebar", systemImage: "sidebar.left") - } - Button(action: addMap) { - Label("Add Map", systemImage: "plus") - } - } - } - EmptyMapDetailScreen() - } - } - - private func toggleSidebar() { - NSApp.keyWindow?.firstResponder?.tryToPerform( - #selector(NSSplitViewController.toggleSidebar(_:)), with: nil) - } - - private func addMap() { - withAnimation { - 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)") - } - } - } - - private func deleteMaps(offsets: IndexSet) { - - withAnimation { - offsets.map { maps[$0] }.forEach(viewContext.delete) - - do { - try viewContext.save() - } catch { - let nsError = error as NSError - fatalError("Unresolved error \(nsError), \(nsError.userInfo)") - } - } - } -} - -private let mapFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .none - return formatter -}() - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - MapEditorWindow().environment( - \.managedObjectContext, PersistenceController.preview.container.viewContext) - } -} diff --git a/MapTests/Info.plist b/MapTests/Info.plist deleted file mode 100644 index 64d65ca..0000000 --- a/MapTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/MapTests/MapTests.swift b/MapTests/MapTests.swift index e425c00..5a8f781 100644 --- a/MapTests/MapTests.swift +++ b/MapTests/MapTests.swift @@ -1,34 +1,11 @@ -// -// MapTests.swift -// MapTests -// -// Created by Ruben Beltran del Rio on 2/1/21. -// - -import XCTest +import Testing @testable import Map -class MapTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } +struct MapTests { - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } + @Test func testExample() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. } } diff --git a/MapUITests/Info.plist b/MapUITests/Info.plist deleted file mode 100644 index 64d65ca..0000000 --- a/MapUITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/MapUITests/MapUITests.swift b/MapUITests/MapUITests.swift index d2d523d..2f6c35d 100644 --- a/MapUITests/MapUITests.swift +++ b/MapUITests/MapUITests.swift @@ -1,13 +1,6 @@ -// -// MapUITests.swift -// MapUITests -// -// Created by Ruben Beltran del Rio on 2/1/21. -// - import XCTest -class MapUITests: XCTestCase { +final class MapUITests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. @@ -22,17 +15,18 @@ class MapUITests: XCTestCase { // Put teardown code here. This method is called after the invocation of each test method in the class. } + @MainActor func testExample() throws { // UI tests must launch the application that they test. let app = XCUIApplication() app.launch() - // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } + @MainActor func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { // This measures how long it takes to launch your application. measure(metrics: [XCTApplicationLaunchMetric()]) { XCUIApplication().launch() diff --git a/MapUITests/MapUITestsLaunchTests.swift b/MapUITests/MapUITestsLaunchTests.swift new file mode 100644 index 0000000..cdcd1b9 --- /dev/null +++ b/MapUITests/MapUITestsLaunchTests.swift @@ -0,0 +1,26 @@ +import XCTest + +final class MapUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +}