mirror of
https://github.com/somegeekintn/SimDirs.git
synced 2026-03-25 08:55:54 +00:00
Complete re-think on model, presentation. Pretty much everything
This commit is contained in:
parent
afcb810a4f
commit
9bf4d8d62e
44 changed files with 1182 additions and 740 deletions
|
|
@ -7,15 +7,16 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE432861D3C500C2EF35 /* DeviceContent.swift */; };
|
||||||
|
C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE452861D57100C2EF35 /* DeviceHeader.swift */; };
|
||||||
|
C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE472861D70500C2EF35 /* ErrorView.swift */; };
|
||||||
|
C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */; };
|
||||||
|
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4B2861E37900C2EF35 /* AppHeader.swift */; };
|
||||||
|
C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4D2861E4E400C2EF35 /* AppContent.swift */; };
|
||||||
|
C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4F2861E9D000C2EF35 /* SourceState.swift */; };
|
||||||
|
C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */; };
|
||||||
C927A0D92846414900533D66 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0D82846414900533D66 /* Helpers.swift */; };
|
C927A0D92846414900533D66 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0D82846414900533D66 /* Helpers.swift */; };
|
||||||
C927A0DB2846502300533D66 /* PathActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0DA2846502300533D66 /* PathActions.swift */; };
|
C927A0DB2846502300533D66 /* PathActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0DA2846502300533D66 /* PathActions.swift */; };
|
||||||
C94C52C72844E80A00E2129E /* SimItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94C52C62844E80A00E2129E /* SimItemRow.swift */; };
|
|
||||||
C94C52C92844E99B00E2129E /* SimItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94C52C82844E99B00E2129E /* SimItemContent.swift */; };
|
|
||||||
C94C52CB2844EAAC00E2129E /* RuntimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94C52CA2844EAAC00E2129E /* RuntimeView.swift */; };
|
|
||||||
C95E5AB6284B6DDE00A2124E /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95E5AB5284B6DDE00A2124E /* AppView.swift */; };
|
|
||||||
C977973C284F58A900706DFB /* PresentationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C977973B284F58A900706DFB /* PresentationState.swift */; };
|
|
||||||
C977973E284F5AE100706DFB /* SimItemNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C977973D284F5AE100706DFB /* SimItemNavLink.swift */; };
|
|
||||||
C9779740284F5CBB00706DFB /* SimItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C977973F284F5CBB00706DFB /* SimItemGroup.swift */; };
|
|
||||||
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9779741284F6DE000706DFB /* ToolbarMenu.swift */; };
|
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9779741284F6DE000706DFB /* ToolbarMenu.swift */; };
|
||||||
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.swift */; };
|
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.swift */; };
|
||||||
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F85A283B9F9000D491F4 /* ContentView.swift */; };
|
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F85A283B9F9000D491F4 /* ContentView.swift */; };
|
||||||
|
|
@ -28,23 +29,31 @@
|
||||||
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F876283D020C00D491F4 /* SimProductFamily.swift */; };
|
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F876283D020C00D491F4 /* SimProductFamily.swift */; };
|
||||||
C982F879283D042E00D491F4 /* SimModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F878283D042E00D491F4 /* SimModel.swift */; };
|
C982F879283D042E00D491F4 /* SimModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F878283D042E00D491F4 /* SimModel.swift */; };
|
||||||
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87A283E40C800D491F4 /* SimCtl.swift */; };
|
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87A283E40C800D491F4 /* SimCtl.swift */; };
|
||||||
C982F880283E57E600D491F4 /* PresentationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87F283E57E600D491F4 /* PresentationItem.swift */; };
|
C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C24285C8C0C0044A279 /* SourceItemData.swift */; };
|
||||||
C982F883283E813F00D491F4 /* DeviceTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F882283E813F00D491F4 /* DeviceTypeView.swift */; };
|
C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C28285C8C4B0044A279 /* SourceItem.swift */; };
|
||||||
C9D729F128478AB00064152D /* DeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D729F028478AB00064152D /* DeviceView.swift */; };
|
C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */; };
|
||||||
|
C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */; };
|
||||||
|
C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */; };
|
||||||
|
C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */; };
|
||||||
|
C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */; };
|
||||||
|
C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */; };
|
||||||
|
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */; };
|
||||||
|
C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */; };
|
||||||
C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD128478FDB00E9B97A /* PathRow.swift */; };
|
C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD128478FDB00E9B97A /* PathRow.swift */; };
|
||||||
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD32847B79E00E9B97A /* SimApp.swift */; };
|
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD32847B79E00E9B97A /* SimApp.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
C90BCE432861D3C500C2EF35 /* DeviceContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContent.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE452861D57100C2EF35 /* DeviceHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHeader.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE472861D70500C2EF35 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeHeader.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE4B2861E37900C2EF35 /* AppHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHeader.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE4D2861E4E400C2EF35 /* AppContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContent.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE4F2861E9D000C2EF35 /* SourceState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceState.swift; sourceTree = "<group>"; };
|
||||||
|
C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFilter.swift; sourceTree = "<group>"; };
|
||||||
C927A0D82846414900533D66 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
C927A0D82846414900533D66 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||||
C927A0DA2846502300533D66 /* PathActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathActions.swift; sourceTree = "<group>"; };
|
C927A0DA2846502300533D66 /* PathActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathActions.swift; sourceTree = "<group>"; };
|
||||||
C94C52C62844E80A00E2129E /* SimItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemRow.swift; sourceTree = "<group>"; };
|
|
||||||
C94C52C82844E99B00E2129E /* SimItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemContent.swift; sourceTree = "<group>"; };
|
|
||||||
C94C52CA2844EAAC00E2129E /* RuntimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeView.swift; sourceTree = "<group>"; };
|
|
||||||
C95E5AB5284B6DDE00A2124E /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = "<group>"; };
|
|
||||||
C977973B284F58A900706DFB /* PresentationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationState.swift; sourceTree = "<group>"; };
|
|
||||||
C977973D284F5AE100706DFB /* SimItemNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemNavLink.swift; sourceTree = "<group>"; };
|
|
||||||
C977973F284F5CBB00706DFB /* SimItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemGroup.swift; sourceTree = "<group>"; };
|
|
||||||
C9779741284F6DE000706DFB /* ToolbarMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarMenu.swift; sourceTree = "<group>"; };
|
C9779741284F6DE000706DFB /* ToolbarMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarMenu.swift; sourceTree = "<group>"; };
|
||||||
C982F855283B9F9000D491F4 /* SimDirs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimDirs.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
C982F855283B9F9000D491F4 /* SimDirs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimDirs.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
C982F858283B9F9000D491F4 /* SimDirsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDirsApp.swift; sourceTree = "<group>"; };
|
C982F858283B9F9000D491F4 /* SimDirsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDirsApp.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -59,9 +68,16 @@
|
||||||
C982F876283D020C00D491F4 /* SimProductFamily.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimProductFamily.swift; sourceTree = "<group>"; };
|
C982F876283D020C00D491F4 /* SimProductFamily.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimProductFamily.swift; sourceTree = "<group>"; };
|
||||||
C982F878283D042E00D491F4 /* SimModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimModel.swift; sourceTree = "<group>"; };
|
C982F878283D042E00D491F4 /* SimModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimModel.swift; sourceTree = "<group>"; };
|
||||||
C982F87A283E40C800D491F4 /* SimCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimCtl.swift; sourceTree = "<group>"; };
|
C982F87A283E40C800D491F4 /* SimCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimCtl.swift; sourceTree = "<group>"; };
|
||||||
C982F87F283E57E600D491F4 /* PresentationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationItem.swift; sourceTree = "<group>"; };
|
C9D73C24285C8C0C0044A279 /* SourceItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemData.swift; sourceTree = "<group>"; };
|
||||||
C982F882283E813F00D491F4 /* DeviceTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeView.swift; sourceTree = "<group>"; };
|
C9D73C28285C8C4B0044A279 /* SourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItem.swift; sourceTree = "<group>"; };
|
||||||
C9D729F028478AB00064152D /* DeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceView.swift; sourceTree = "<group>"; };
|
C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemGroup.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLink.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemContent.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemImage.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLabel.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeContent.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeHeader.swift; sourceTree = "<group>"; };
|
||||||
|
C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeContent.swift; sourceTree = "<group>"; };
|
||||||
C9EE0CD128478FDB00E9B97A /* PathRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathRow.swift; sourceTree = "<group>"; };
|
C9EE0CD128478FDB00E9B97A /* PathRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathRow.swift; sourceTree = "<group>"; };
|
||||||
C9EE0CD32847B79E00E9B97A /* SimApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimApp.swift; sourceTree = "<group>"; };
|
C9EE0CD32847B79E00E9B97A /* SimApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimApp.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
@ -100,6 +116,7 @@
|
||||||
C982F85A283B9F9000D491F4 /* ContentView.swift */,
|
C982F85A283B9F9000D491F4 /* ContentView.swift */,
|
||||||
C927A0D82846414900533D66 /* Helpers.swift */,
|
C927A0D82846414900533D66 /* Helpers.swift */,
|
||||||
C982F867283BA09B00D491F4 /* Model */,
|
C982F867283BA09B00D491F4 /* Model */,
|
||||||
|
C9D73C23285C8B3B0044A279 /* Presentation */,
|
||||||
C982F881283E7F0400D491F4 /* Views */,
|
C982F881283E7F0400D491F4 /* Views */,
|
||||||
C982F85C283B9F9200D491F4 /* Assets.xcassets */,
|
C982F85C283B9F9200D491F4 /* Assets.xcassets */,
|
||||||
C982F861283B9F9200D491F4 /* SimDirs.entitlements */,
|
C982F861283B9F9200D491F4 /* SimDirs.entitlements */,
|
||||||
|
|
@ -119,7 +136,6 @@
|
||||||
C982F867283BA09B00D491F4 /* Model */ = {
|
C982F867283BA09B00D491F4 /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C982F87C283E579900D491F4 /* Presentation */,
|
|
||||||
C982F87A283E40C800D491F4 /* SimCtl.swift */,
|
C982F87A283E40C800D491F4 /* SimCtl.swift */,
|
||||||
C982F878283D042E00D491F4 /* SimModel.swift */,
|
C982F878283D042E00D491F4 /* SimModel.swift */,
|
||||||
C9EE0CD32847B79E00E9B97A /* SimApp.swift */,
|
C9EE0CD32847B79E00E9B97A /* SimApp.swift */,
|
||||||
|
|
@ -132,31 +148,55 @@
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
C982F87C283E579900D491F4 /* Presentation */ = {
|
C982F881283E7F0400D491F4 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C982F87F283E57E600D491F4 /* PresentationItem.swift */,
|
C9DD54C12860935300D46AB3 /* SourceItem Views */,
|
||||||
C977973B284F58A900706DFB /* PresentationState.swift */,
|
C9DD54CC2860992200D46AB3 /* Model Views */,
|
||||||
|
C90BCE472861D70500C2EF35 /* ErrorView.swift */,
|
||||||
|
C927A0DA2846502300533D66 /* PathActions.swift */,
|
||||||
|
C9EE0CD128478FDB00E9B97A /* PathRow.swift */,
|
||||||
|
C9779741284F6DE000706DFB /* ToolbarMenu.swift */,
|
||||||
|
);
|
||||||
|
path = Views;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C9D73C23285C8B3B0044A279 /* Presentation */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C9D73C28285C8C4B0044A279 /* SourceItem.swift */,
|
||||||
|
C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */,
|
||||||
|
C9D73C24285C8C0C0044A279 /* SourceItemData.swift */,
|
||||||
|
C90BCE4F2861E9D000C2EF35 /* SourceState.swift */,
|
||||||
);
|
);
|
||||||
path = Presentation;
|
path = Presentation;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
C982F881283E7F0400D491F4 /* Views */ = {
|
C9DD54C12860935300D46AB3 /* SourceItem Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
C95E5AB5284B6DDE00A2124E /* AppView.swift */,
|
C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */,
|
||||||
C982F882283E813F00D491F4 /* DeviceTypeView.swift */,
|
C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */,
|
||||||
C9D729F028478AB00064152D /* DeviceView.swift */,
|
C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */,
|
||||||
C927A0DA2846502300533D66 /* PathActions.swift */,
|
C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */,
|
||||||
C9EE0CD128478FDB00E9B97A /* PathRow.swift */,
|
C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */,
|
||||||
C94C52CA2844EAAC00E2129E /* RuntimeView.swift */,
|
|
||||||
C94C52C82844E99B00E2129E /* SimItemContent.swift */,
|
|
||||||
C977973F284F5CBB00706DFB /* SimItemGroup.swift */,
|
|
||||||
C977973D284F5AE100706DFB /* SimItemNavLink.swift */,
|
|
||||||
C94C52C62844E80A00E2129E /* SimItemRow.swift */,
|
|
||||||
C9779741284F6DE000706DFB /* ToolbarMenu.swift */,
|
|
||||||
);
|
);
|
||||||
path = Views;
|
path = "SourceItem Views";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
C9DD54CC2860992200D46AB3 /* Model Views */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
C90BCE4D2861E4E400C2EF35 /* AppContent.swift */,
|
||||||
|
C90BCE4B2861E37900C2EF35 /* AppHeader.swift */,
|
||||||
|
C90BCE432861D3C500C2EF35 /* DeviceContent.swift */,
|
||||||
|
C90BCE452861D57100C2EF35 /* DeviceHeader.swift */,
|
||||||
|
C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */,
|
||||||
|
C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */,
|
||||||
|
C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */,
|
||||||
|
C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */,
|
||||||
|
);
|
||||||
|
path = "Model Views";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
@ -229,29 +269,37 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
C982F883283E813F00D491F4 /* DeviceTypeView.swift in Sources */,
|
C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */,
|
||||||
C94C52C92844E99B00E2129E /* SimItemContent.swift in Sources */,
|
C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */,
|
||||||
|
C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */,
|
||||||
C927A0DB2846502300533D66 /* PathActions.swift in Sources */,
|
C927A0DB2846502300533D66 /* PathActions.swift in Sources */,
|
||||||
|
C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */,
|
||||||
|
C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */,
|
||||||
|
C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */,
|
||||||
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */,
|
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */,
|
||||||
|
C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */,
|
||||||
|
C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */,
|
||||||
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */,
|
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */,
|
||||||
C9779740284F5CBB00706DFB /* SimItemGroup.swift in Sources */,
|
C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */,
|
||||||
C95E5AB6284B6DDE00A2124E /* AppView.swift in Sources */,
|
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */,
|
||||||
|
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */,
|
||||||
|
C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */,
|
||||||
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */,
|
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */,
|
||||||
C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */,
|
C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */,
|
||||||
C977973C284F58A900706DFB /* PresentationState.swift in Sources */,
|
C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */,
|
||||||
C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */,
|
C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */,
|
||||||
C977973E284F5AE100706DFB /* SimItemNavLink.swift in Sources */,
|
C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */,
|
||||||
|
C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */,
|
||||||
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */,
|
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */,
|
||||||
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */,
|
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */,
|
||||||
C982F871283CE7B800D491F4 /* SimRuntime.swift in Sources */,
|
C982F871283CE7B800D491F4 /* SimRuntime.swift in Sources */,
|
||||||
C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */,
|
C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */,
|
||||||
C94C52C72844E80A00E2129E /* SimItemRow.swift in Sources */,
|
|
||||||
C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */,
|
C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */,
|
||||||
C927A0D92846414900533D66 /* Helpers.swift in Sources */,
|
C927A0D92846414900533D66 /* Helpers.swift in Sources */,
|
||||||
C94C52CB2844EAAC00E2129E /* RuntimeView.swift in Sources */,
|
C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */,
|
||||||
C982F880283E57E600D491F4 /* PresentationItem.swift in Sources */,
|
C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */,
|
||||||
|
C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */,
|
||||||
C982F879283D042E00D491F4 /* SimModel.swift in Sources */,
|
C982F879283D042E00D491F4 /* SimModel.swift in Sources */,
|
||||||
C9D729F128478AB00064152D /* DeviceView.swift in Sources */,
|
|
||||||
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */,
|
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
||||||
38
SimDirs/Assets.xcassets/HeaderEdge.colorset/Contents.json
Normal file
38
SimDirs/Assets.xcassets/HeaderEdge.colorset/Contents.json
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.900",
|
||||||
|
"green" : "0.900",
|
||||||
|
"red" : "0.900"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.200",
|
||||||
|
"green" : "0.200",
|
||||||
|
"red" : "0.200"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,33 +8,52 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject var model : SimModel
|
@ObservedObject var state : SourceState
|
||||||
@State private var state = PresentationState(filter: [])
|
|
||||||
|
init(model: SimModel) {
|
||||||
|
state = SourceState(model: model)
|
||||||
|
}
|
||||||
|
|
||||||
var rootItems : [PresentationItem] { state.presentationItems(from: model) }
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
VStack {
|
||||||
List {
|
NavigationView {
|
||||||
ForEach(rootItems) { item in
|
List {
|
||||||
SimItemGroup(item: item, state: $state)
|
Divider()
|
||||||
|
|
||||||
|
switch state.filteredRoot {
|
||||||
|
case .placeholder:
|
||||||
|
Text("Placeholder")
|
||||||
|
|
||||||
|
case let .device(_, root):
|
||||||
|
ForEach(root.items) {
|
||||||
|
SourceItemGroup(selection: $state.selection, item: $0)
|
||||||
|
}
|
||||||
|
|
||||||
|
case let .runtime(_, root):
|
||||||
|
ForEach(root.items) {
|
||||||
|
SourceItemGroup(selection: $state.selection, item: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.leading, 2.0)
|
.toolbar {
|
||||||
|
ToolbarItem { ToolbarMenu(state: state) }
|
||||||
|
}
|
||||||
|
.frame(minWidth: 200)
|
||||||
|
|
||||||
|
Image("Icon-256") // Initial View
|
||||||
}
|
}
|
||||||
.frame(minWidth: 200)
|
.searchable(text: $state.filter.searchTerm, placement: .sidebar)
|
||||||
.toolbar {
|
|
||||||
ToolbarItem { ToolbarMenu(state: $state) }
|
|
||||||
}
|
|
||||||
Image("Icon-256")
|
|
||||||
}
|
}
|
||||||
.searchable(text: $state.searchTerm, placement: .sidebar)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var simModel = SimModel()
|
static var model = SimModel()
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView(model: simModel)
|
ContentView(model: model)
|
||||||
|
.preferredColorScheme(.dark)
|
||||||
|
ContentView(model: model)
|
||||||
|
.preferredColorScheme(.light)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Casey Fleser on 5/31/22.
|
// Created by Casey Fleser on 5/31/22.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
import SwiftUI
|
||||||
|
|
||||||
extension NSPasteboard {
|
extension NSPasteboard {
|
||||||
static func copy(text: String) {
|
static func copy(text: String) {
|
||||||
|
|
@ -23,12 +23,21 @@ extension NSWorkspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OptionSet where Self == Self.Element {
|
extension OptionSet where Self == Self.Element {
|
||||||
|
func settingBool(_ value: Bool, options: Self) -> Self {
|
||||||
|
if value { return union(options) }
|
||||||
|
else { return subtracting (options) }
|
||||||
|
}
|
||||||
|
|
||||||
mutating func booleanSet(_ value: Bool, options: Self) {
|
mutating func booleanSet(_ value: Bool, options: Self) {
|
||||||
if value { update(with: options) }
|
if value { update(with: options) }
|
||||||
else { subtract(options) }
|
else { subtract(options) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ProcessInfo {
|
||||||
|
var isPreviewing : Bool { environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" }
|
||||||
|
}
|
||||||
|
|
||||||
extension PropertyListSerialization {
|
extension PropertyListSerialization {
|
||||||
class func propertyList(from url: URL) -> [String : AnyObject]? {
|
class func propertyList(from url: URL) -> [String : AnyObject]? {
|
||||||
guard let plistData = try? Data(contentsOf: url) else { return nil }
|
guard let plistData = try? Data(contentsOf: url) else { return nil }
|
||||||
|
|
@ -36,3 +45,15 @@ extension PropertyListSerialization {
|
||||||
return try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String : AnyObject]
|
return try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String : AnyObject]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func evalIf<V: View>(_ test: Bool, then transform: (Self) -> V) -> some View {
|
||||||
|
if test {
|
||||||
|
transform(self)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
//
|
|
||||||
// PresentationItem.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 5/25/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
protocol PresentableItem {
|
|
||||||
var title : String { get }
|
|
||||||
var id : String { get }
|
|
||||||
var icon : NSImage? { get }
|
|
||||||
var imageName : String { get }
|
|
||||||
var imageColor : Color? { get }
|
|
||||||
var contentView : AnyView? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PresentableItem {
|
|
||||||
var imageColor : Color? { return nil }
|
|
||||||
var icon : NSImage? { return nil }
|
|
||||||
var contentView : AnyView? { return nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PresentationItem: Identifiable {
|
|
||||||
let underlying : PresentableItem
|
|
||||||
var children : [PresentationItem]?
|
|
||||||
var id : String
|
|
||||||
var customImage : String?
|
|
||||||
|
|
||||||
var title : String { return underlying.title }
|
|
||||||
var navTitle : String { return "\(typeName): \(underlying.title)" }
|
|
||||||
var icon : NSImage? { return underlying.icon }
|
|
||||||
var imageName : String { return customImage ?? underlying.imageName }
|
|
||||||
var imageColor : Color { return underlying.imageColor ?? .white }
|
|
||||||
var contentView : AnyView { return underlying.contentView ?? AnyView(Text(title)) }
|
|
||||||
var flattened : [PresentationItem] { return [self] + (self.children?.flatMap { $0.flattened } ?? []) }
|
|
||||||
var typeName : String {
|
|
||||||
switch underlying {
|
|
||||||
case is SimPlatform: return "Platform"
|
|
||||||
case is SimProductFamily: return "Product Family"
|
|
||||||
case is SimRuntime: return "Runtime"
|
|
||||||
case is SimDeviceType: return "Device Type"
|
|
||||||
case is SimDevice: return "Device"
|
|
||||||
case is SimApp: return "App"
|
|
||||||
default: return "Item"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(_ presentable: PresentableItem, image: String? = nil, identifier: String? = nil) {
|
|
||||||
underlying = presentable
|
|
||||||
id = identifier ?? underlying.id
|
|
||||||
customImage = image
|
|
||||||
}
|
|
||||||
|
|
||||||
func titlesContain(_ searchTerm: String) -> Bool {
|
|
||||||
return title.contains(searchTerm) || children?.contains(where: { $0.titlesContain(searchTerm)}) ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsType<T>(_ type: T.Type) -> Bool {
|
|
||||||
return underlying is T || children?.contains(where: { $0.containsType(type)}) ?? false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Array where Element == PresentationItem {
|
|
||||||
var flatItems : [PresentationItem] { self.flatMap { $0.flattened } }
|
|
||||||
|
|
||||||
func itemsOf<T> (type: T.Type) -> [T] {
|
|
||||||
return flatItems.compactMap { $0.underlying as? T }
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateItems() {
|
|
||||||
let allIDs = flatItems.map { $0.id }
|
|
||||||
var idSet = Set<String>()
|
|
||||||
|
|
||||||
print("Validating \(allIDs.count) items")
|
|
||||||
for id in allIDs {
|
|
||||||
if !idSet.insert(id).inserted {
|
|
||||||
print("Duplicate PresentationItem.id: \(id)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dumpPresentation(level: Int = 0) {
|
|
||||||
let ident = Array<String>(repeating: "\t", count: level).joined()
|
|
||||||
|
|
||||||
for item in self {
|
|
||||||
print("\(ident)\(item.title) [\(item.id)]")
|
|
||||||
|
|
||||||
if let children = item.children {
|
|
||||||
children.dumpPresentation(level: level + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
//
|
|
||||||
// PresentationState.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 6/7/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct PresentationState {
|
|
||||||
enum Organization: String, CaseIterable, Identifiable {
|
|
||||||
case byDevice = "By Device"
|
|
||||||
case byRuntime = "By Runtime"
|
|
||||||
|
|
||||||
var id: Organization { self }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Filter: OptionSet, CaseIterable {
|
|
||||||
let rawValue: Int
|
|
||||||
|
|
||||||
static let withApps = Filter(rawValue: 1 << 0)
|
|
||||||
static let runtimeInstalled = Filter(rawValue: 1 << 1)
|
|
||||||
|
|
||||||
static var allCases : [Filter] = [.withApps, .runtimeInstalled]
|
|
||||||
}
|
|
||||||
|
|
||||||
var organization = Organization.byRuntime
|
|
||||||
var filter = Filter()
|
|
||||||
var searchTerm = ""
|
|
||||||
|
|
||||||
static func testItemsOf<T>(type: T.Type) -> [T] {
|
|
||||||
let flatItems = PresentationState().presentationItems(from: SimModel()).flatItems
|
|
||||||
|
|
||||||
return flatItems.itemsOf(type: type)
|
|
||||||
}
|
|
||||||
|
|
||||||
func presentationItems(from model: SimModel) -> [PresentationItem] {
|
|
||||||
switch organization {
|
|
||||||
case .byDevice: return itemsForDeviceStyle(from: model)
|
|
||||||
case .byRuntime: return itemsForRuntimeStyle(from: model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func itemsForDeviceStyle(from model: SimModel) -> [PresentationItem] {
|
|
||||||
return SimProductFamily.presentation.map{ family in
|
|
||||||
var familyItem = PresentationItem(family)
|
|
||||||
|
|
||||||
familyItem.children = model.deviceTypes.filter({ $0.supports(productFamily: family) }).map { deviceType in
|
|
||||||
var deviceTypeItem = PresentationItem(deviceType, identifier: deviceType.id)
|
|
||||||
let deviceTypeChildren : [PresentationItem] = model.runtimes.filter({ $0.supports(deviceType: deviceType) }).map { runtime in
|
|
||||||
var runtimeItem = PresentationItem(runtime, identifier: "\(deviceType.id) - \(runtime.id)")
|
|
||||||
let runtimeItemChildren = runtime.devices.filter({ $0.isDeviceOfType(deviceType) }).map { device -> PresentationItem in
|
|
||||||
var deviceItem = PresentationItem(device, image: family.imageName)
|
|
||||||
let deviceItemChildren = device.apps.map { PresentationItem($0) }
|
|
||||||
|
|
||||||
if !deviceItemChildren.isEmpty {
|
|
||||||
deviceItem.children = deviceItemChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceItem
|
|
||||||
}
|
|
||||||
|
|
||||||
if !runtimeItemChildren.isEmpty {
|
|
||||||
runtimeItem.children = runtimeItemChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
return runtimeItem
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deviceTypeChildren.isEmpty {
|
|
||||||
deviceTypeItem.children = deviceTypeChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceTypeItem
|
|
||||||
}
|
|
||||||
|
|
||||||
return familyItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func itemsForRuntimeStyle(from model: SimModel) -> [PresentationItem] {
|
|
||||||
return SimPlatform.presentation.map{ platform in
|
|
||||||
var platformItem = PresentationItem(platform)
|
|
||||||
|
|
||||||
platformItem.children = model.runtimes.filter({ $0.supports(platform: platform) }).map { runtime in
|
|
||||||
var runtimeItem = PresentationItem(runtime)
|
|
||||||
let runtimeItemChildren : [PresentationItem] = model.deviceTypes.filter({ $0.supports(runtime: runtime) }).map { deviceType in
|
|
||||||
var deviceTypeItem = PresentationItem(deviceType, identifier: "\(runtime.id) - \(deviceType.id)")
|
|
||||||
let deviceTypeChildren = runtime.devices.filter({ $0.isDeviceOfType(deviceType) }).map { device -> PresentationItem in
|
|
||||||
var deviceItem = PresentationItem(device, image: deviceType.imageName)
|
|
||||||
let deviceItemChildren = device.apps.map { PresentationItem($0) }
|
|
||||||
|
|
||||||
if !deviceItemChildren.isEmpty {
|
|
||||||
deviceItem.children = deviceItemChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceItem
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deviceTypeChildren.isEmpty {
|
|
||||||
deviceTypeItem.children = deviceTypeChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceTypeItem
|
|
||||||
}
|
|
||||||
|
|
||||||
if !runtimeItemChildren.isEmpty {
|
|
||||||
runtimeItem.children = runtimeItemChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
return runtimeItem
|
|
||||||
}
|
|
||||||
|
|
||||||
return platformItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -71,10 +71,10 @@ struct SimApp: Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SimApp: PresentableItem, Identifiable {
|
extension SimApp: SourceItemData {
|
||||||
var title : String { return displayName }
|
var title : String { return displayName }
|
||||||
var id : String { return identifier }
|
var headerTitle : String { "App: \(title)" }
|
||||||
|
var imageDesc : SourceImageDesc { nsIcon.map { .icon(nsImage: $0) } ?? .symbol(systemName: "questionmark.app.dashed") }
|
||||||
|
|
||||||
var imageName : String { return "questionmark.app.dashed" }
|
var optionTrait : SourceFilter.Options { .withApps }
|
||||||
var icon : NSImage? { return nsIcon }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,14 @@ struct SimDevice: Decodable, Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SimDevice: PresentableItem, Identifiable {
|
extension SimDevice: SourceItemData {
|
||||||
var title : String { return name }
|
var title : String { return name }
|
||||||
var id : String { return udid }
|
var headerTitle : String { "Device: \(title)" }
|
||||||
|
var imageDesc : SourceImageDesc { .symbol(systemName: "questionmark.circle", color: isAvailable ? .green : .red) }
|
||||||
var imageName : String { return "shippingbox" }
|
|
||||||
var imageColor : Color? { return isAvailable ? .green : .red }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Array where Element == SimDevice {
|
||||||
|
func of(deviceType: SimDeviceType) -> Self {
|
||||||
|
filter { $0.isDeviceOfType(deviceType) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,19 @@ struct SimDeviceType: Decodable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SimDeviceType: PresentableItem, Identifiable {
|
extension SimDeviceType: SourceItemData {
|
||||||
var title : String { return name }
|
var title : String { return name }
|
||||||
var id : String { return identifier }
|
var headerTitle : String { "Device Type: \(title)" }
|
||||||
var imageName : String { return productFamily.imageName }
|
var imageDesc : SourceImageDesc { .symbol(systemName: productFamily.symbolName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Array where Element == SimDeviceType {
|
||||||
|
func supporting(productFamily: SimProductFamily) -> Self {
|
||||||
|
filter { $0.supports(productFamily: productFamily) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func supporting(runtime: SimRuntime) -> Self {
|
||||||
|
filter { $0.supports(runtime: runtime) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@ enum SimError: Error {
|
||||||
class SimModel: ObservableObject {
|
class SimModel: ObservableObject {
|
||||||
var deviceTypes : [SimDeviceType]
|
var deviceTypes : [SimDeviceType]
|
||||||
@Published var runtimes : [SimRuntime]
|
@Published var runtimes : [SimRuntime]
|
||||||
let timer = DispatchSource.makeTimerSource()
|
var monitorSource : DispatchSourceTimer?
|
||||||
let updateInterval = 1.0
|
let updateInterval = 1.0
|
||||||
|
|
||||||
|
var devices : [SimDevice] { runtimes.flatMap { $0.devices } }
|
||||||
|
var apps : [SimApp] { devices.flatMap { $0.apps } }
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let simctl = SimCtl()
|
let simctl = SimCtl()
|
||||||
|
|
||||||
|
|
@ -38,7 +41,9 @@ class SimModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runtimes.sort()
|
runtimes.sort()
|
||||||
beginMonitor()
|
if !ProcessInfo.processInfo.isPreviewing {
|
||||||
|
beginMonitor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
fatalError("Failed to initialize data model:\n\(error)")
|
fatalError("Failed to initialize data model:\n\(error)")
|
||||||
|
|
@ -46,6 +51,8 @@ class SimModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func beginMonitor() {
|
func beginMonitor() {
|
||||||
|
let timer = DispatchSource.makeTimerSource()
|
||||||
|
|
||||||
timer.setEventHandler {
|
timer.setEventHandler {
|
||||||
guard let runtimeDevs : [String : [SimDevice]] = try? SimCtl().readAllRuntimeDevices() else { return }
|
guard let runtimeDevs : [String : [SimDevice]] = try? SimCtl().readAllRuntimeDevices() else { return }
|
||||||
|
|
||||||
|
|
@ -78,5 +85,6 @@ class SimModel: ObservableObject {
|
||||||
}
|
}
|
||||||
timer.schedule(deadline: DispatchTime.now(), repeating: updateInterval)
|
timer.schedule(deadline: DispatchTime.now(), repeating: updateInterval)
|
||||||
timer.resume()
|
timer.resume()
|
||||||
|
monitorSource = timer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,8 @@ enum SimPlatform: String, Decodable {
|
||||||
case watchOS
|
case watchOS
|
||||||
|
|
||||||
static let presentation : [SimPlatform] = [.iOS, .watchOS, .tvOS]
|
static let presentation : [SimPlatform] = [.iOS, .watchOS, .tvOS]
|
||||||
}
|
|
||||||
|
var symbolName : String {
|
||||||
extension SimPlatform: PresentableItem {
|
|
||||||
var title : String { return self.rawValue }
|
|
||||||
var id : String { return self.rawValue }
|
|
||||||
var imageName : String {
|
|
||||||
switch self {
|
switch self {
|
||||||
case .iOS: return "iphone"
|
case .iOS: return "iphone"
|
||||||
case .tvOS: return "appletv"
|
case .tvOS: return "appletv"
|
||||||
|
|
@ -26,3 +22,9 @@ extension SimPlatform: PresentableItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SimPlatform: SourceItemData {
|
||||||
|
var title : String { self.rawValue }
|
||||||
|
var headerTitle : String { "Platform: \(title)" }
|
||||||
|
var imageDesc : SourceImageDesc { .symbol(systemName: symbolName) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,8 @@ enum SimProductFamily: String, Decodable {
|
||||||
case iPhone
|
case iPhone
|
||||||
|
|
||||||
static let presentation : [SimProductFamily] = [.iPhone, .iPad, .appleWatch, .appleTV]
|
static let presentation : [SimProductFamily] = [.iPhone, .iPad, .appleWatch, .appleTV]
|
||||||
}
|
|
||||||
|
|
||||||
extension SimProductFamily: PresentableItem {
|
var symbolName : String {
|
||||||
var title : String { return self.rawValue }
|
|
||||||
var id : String { return self.rawValue }
|
|
||||||
|
|
||||||
var imageName : String {
|
|
||||||
switch self {
|
switch self {
|
||||||
case .iPad: return "ipad"
|
case .iPad: return "ipad"
|
||||||
case .iPhone: return "iphone"
|
case .iPhone: return "iphone"
|
||||||
|
|
@ -29,3 +24,9 @@ extension SimProductFamily: PresentableItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SimProductFamily: SourceItemData {
|
||||||
|
var title : String { self.rawValue }
|
||||||
|
var headerTitle : String { "Product Family: \(title)" }
|
||||||
|
var imageDesc : SourceImageDesc { .symbol(systemName: symbolName) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,11 +127,12 @@ struct SimRuntime: Comparable, Decodable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SimRuntime: PresentableItem, Identifiable {
|
extension SimRuntime: SourceItemData {
|
||||||
var title : String { return name }
|
var title : String { return name }
|
||||||
var id : String { return identifier }
|
var headerTitle : String { "Runtime: \(title)" }
|
||||||
var imageName : String { return "v.circle" }
|
var imageDesc : SourceImageDesc { .symbol(systemName: "shippingbox", color: isAvailable ? .green : .red) }
|
||||||
var imageColor : Color? { return isAvailable ? .green : .red }
|
|
||||||
|
var optionTrait : SourceFilter.Options { isAvailable ? .runtimeInstalled : [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element == SimRuntime {
|
extension Array where Element == SimRuntime {
|
||||||
|
|
@ -142,4 +143,12 @@ extension Array where Element == SimRuntime {
|
||||||
return self.endIndex - 1
|
return self.endIndex - 1
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func supporting(deviceType: SimDeviceType) -> Self {
|
||||||
|
filter { $0.supports(deviceType: deviceType) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func supporting(platform: SimPlatform) -> Self {
|
||||||
|
filter { $0.supports(platform: platform) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
89
SimDirs/Presentation/SourceFilter.swift
Normal file
89
SimDirs/Presentation/SourceFilter.swift
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// SourceFilter.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SourceFilter {
|
||||||
|
struct Options: OptionSet, CaseIterable {
|
||||||
|
let rawValue: Int
|
||||||
|
|
||||||
|
static let withApps = Options(rawValue: 1 << 0)
|
||||||
|
static let runtimeInstalled = Options(rawValue: 1 << 1)
|
||||||
|
|
||||||
|
static var allCases : [Options] = [.withApps, .runtimeInstalled]
|
||||||
|
|
||||||
|
func search<T: SourceItem>(item: T, progress: Options) -> Self {
|
||||||
|
var foundOptions = progress.union(item.data.optionTrait)
|
||||||
|
|
||||||
|
if let items = item.children, !subtracting(foundOptions).isEmpty {
|
||||||
|
for child in items {
|
||||||
|
foundOptions = search(item: child, progress: foundOptions)
|
||||||
|
|
||||||
|
if isSubset(of: foundOptions) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchTerm = ""
|
||||||
|
var options = Options()
|
||||||
|
|
||||||
|
func filtered<T>(root: SourceRoot<T>) -> SourceRoot<T> {
|
||||||
|
if !searchTerm.isEmpty || !options.isEmpty {
|
||||||
|
var fRoot = root
|
||||||
|
|
||||||
|
fRoot.items = root.items.compactMap { item in
|
||||||
|
let result = filtered(item: item)
|
||||||
|
|
||||||
|
return result.match ? result.fItem : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fRoot
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filtered<T: SourceItem>(item: T, inheritedOptions: Options = []) -> (fItem: T, match: Bool) {
|
||||||
|
var fItem = item
|
||||||
|
var match = true
|
||||||
|
var childMatch = false
|
||||||
|
let optProgress = inheritedOptions.union(fItem.data.optionTrait) // options inherited by children
|
||||||
|
|
||||||
|
// If there are options to match then do that first passing inherited options along
|
||||||
|
// and consider a match fulfilled if any child contains all the desired options.
|
||||||
|
|
||||||
|
if !options.isEmpty {
|
||||||
|
var foundOptions = optProgress
|
||||||
|
|
||||||
|
if !options.isSubset(of: foundOptions) {
|
||||||
|
foundOptions = options.search(item: fItem, progress: foundOptions)
|
||||||
|
}
|
||||||
|
match = options.isSubset(of: foundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !searchTerm.isEmpty && match {
|
||||||
|
match = fItem.title.uppercased().contains(searchTerm.uppercased())
|
||||||
|
}
|
||||||
|
|
||||||
|
if let srcChildren = fItem.children {
|
||||||
|
fItem.children = srcChildren.compactMap { child -> T.Child? in
|
||||||
|
let result = filtered(item: child, inheritedOptions: optProgress)
|
||||||
|
|
||||||
|
return result.match ? result.fItem : nil
|
||||||
|
}
|
||||||
|
childMatch = fItem.children?.isEmpty == false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (fItem, match || childMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
44
SimDirs/Presentation/SourceItem.swift
Normal file
44
SimDirs/Presentation/SourceItem.swift
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// SourceItem.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/14/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
protocol SourceItem: Identifiable {
|
||||||
|
associatedtype Model : SourceItemData
|
||||||
|
associatedtype Child : SourceItem
|
||||||
|
|
||||||
|
var id : UUID { get }
|
||||||
|
var data : Model { get }
|
||||||
|
var children : [Child]? { get set }
|
||||||
|
var customImgDesc : SourceImageDesc? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SourceItem {
|
||||||
|
var title : String { data.title }
|
||||||
|
var headerTitle : String { data.headerTitle }
|
||||||
|
var header : some View { data.header }
|
||||||
|
var content : some View { data.content }
|
||||||
|
var imageDesc : SourceImageDesc { customImgDesc ?? data.imageDesc }
|
||||||
|
var customImgDesc : SourceImageDesc? { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceItemVal<Model: SourceItemData, Child: SourceItem>: SourceItem {
|
||||||
|
var id = UUID()
|
||||||
|
var data : Model
|
||||||
|
var children : [Child]?
|
||||||
|
var customImgDesc : SourceImageDesc?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceRoot<Item: SourceItem> {
|
||||||
|
var items : [Item]
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Never: SourceItem {
|
||||||
|
public var id : UUID { fatalError() }
|
||||||
|
public var data : Never { fatalError() }
|
||||||
|
public var children : [Never]? { get { fatalError() } set { } }
|
||||||
|
}
|
||||||
51
SimDirs/Presentation/SourceItemData.swift
Normal file
51
SimDirs/Presentation/SourceItemData.swift
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// SourceItemData.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/15/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
protocol SourceItemData {
|
||||||
|
associatedtype Content : View
|
||||||
|
associatedtype Header : View
|
||||||
|
|
||||||
|
@ViewBuilder var header : Self.Header { get }
|
||||||
|
@ViewBuilder var content : Self.Content { get }
|
||||||
|
|
||||||
|
var title : String { get }
|
||||||
|
var headerTitle : String { get }
|
||||||
|
var imageDesc : SourceImageDesc { get }
|
||||||
|
|
||||||
|
var optionTrait : SourceFilter.Options { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SourceItemData {
|
||||||
|
var headerTitle : String { title }
|
||||||
|
var imageDesc : SourceImageDesc { .symbol() }
|
||||||
|
|
||||||
|
var header : some View { get { EmptyView() } }
|
||||||
|
var content : some View { get { EmptyView() } }
|
||||||
|
|
||||||
|
var optionTrait : SourceFilter.Options { [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SourceImageDesc {
|
||||||
|
case icon(nsImage: NSImage)
|
||||||
|
case symbol(systemName: String = "questionmark.circle", color: Color = .primary)
|
||||||
|
|
||||||
|
func withColor(_ color: Color) -> Self {
|
||||||
|
switch self {
|
||||||
|
case .icon: return self
|
||||||
|
case let .symbol(name, _): return .symbol(systemName: name, color: color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Never: SourceItemData {
|
||||||
|
var headerTitle : String { fatalError () }
|
||||||
|
var imageDesc : SourceImageDesc { SourceImageDesc.symbol(systemName: "exclamationmark.octagon", color: .red) }
|
||||||
|
var header : some View { Text("Error: Never SourceItemData type cannot provide header") }
|
||||||
|
var content : some View { Text("Error: Never SourceItemData type cannot provide content") }
|
||||||
|
}
|
||||||
140
SimDirs/Presentation/SourceState.swift
Normal file
140
SimDirs/Presentation/SourceState.swift
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
//
|
||||||
|
// SourceState.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class SourceState: ObservableObject {
|
||||||
|
typealias ProductFamily = SourceItemVal<SimProductFamily, DeviceType_DS>
|
||||||
|
typealias Platform = SourceItemVal<SimPlatform, Runtime_RT>
|
||||||
|
typealias DeviceType_DS = SourceItemVal<SimDeviceType, Runtime_DS>
|
||||||
|
typealias DeviceType_RT = SourceItemVal<SimDeviceType, Device>
|
||||||
|
typealias Runtime_DS = SourceItemVal<SimRuntime, Device>
|
||||||
|
typealias Runtime_RT = SourceItemVal<SimRuntime, DeviceType_RT>
|
||||||
|
typealias Device = SourceItemVal<SimDevice, App>
|
||||||
|
typealias App = SourceItemVal<SimApp, Never>
|
||||||
|
|
||||||
|
enum Root: Identifiable {
|
||||||
|
case placeholder(id: UUID = UUID())
|
||||||
|
case device(id: UUID = UUID(), SourceRoot<ProductFamily>)
|
||||||
|
case runtime(id: UUID = UUID(), SourceRoot<Platform>)
|
||||||
|
|
||||||
|
var id : UUID {
|
||||||
|
switch self {
|
||||||
|
case let .placeholder(id): return id
|
||||||
|
case let .device(id, _): return id
|
||||||
|
case let .runtime(id, _): return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Style: Int, CaseIterable, Identifiable {
|
||||||
|
case placeholder
|
||||||
|
case byDevice
|
||||||
|
case byRuntime
|
||||||
|
|
||||||
|
var id : Int { rawValue }
|
||||||
|
var title : String {
|
||||||
|
switch self {
|
||||||
|
case .placeholder: return "Placeholder"
|
||||||
|
case .byDevice: return "By Device"
|
||||||
|
case .byRuntime: return "By Runtime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var visible : Bool {
|
||||||
|
switch self {
|
||||||
|
case .placeholder: return false
|
||||||
|
default: return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var style = Style.placeholder { didSet { baseRoot = rootFor(style: style) } }
|
||||||
|
@Published var filter = SourceFilter()
|
||||||
|
@Published var selection : UUID?
|
||||||
|
|
||||||
|
var model : SimModel
|
||||||
|
var baseRoot = Root.placeholder()
|
||||||
|
|
||||||
|
var filteredRoot : Root {
|
||||||
|
switch baseRoot {
|
||||||
|
case .placeholder: return baseRoot
|
||||||
|
case let .device(_, root): return .device(id: baseRoot.id, filter.filtered(root: root))
|
||||||
|
case let .runtime(_, root): return .runtime(id: baseRoot.id, filter.filtered(root: root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterApps : Bool {
|
||||||
|
get { filter.options.contains(.withApps) }
|
||||||
|
set { filter.options.booleanSet(newValue, options: .withApps) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterRuntimes : Bool {
|
||||||
|
get { filter.options.contains(.runtimeInstalled) }
|
||||||
|
set { filter.options.booleanSet(newValue, options: .runtimeInstalled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func filtering<T: SourceItem>(_ items: [T]) -> [T] {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
init(model: SimModel) {
|
||||||
|
self.model = model
|
||||||
|
style = Style.byDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootFor(style: Style) -> Root {
|
||||||
|
switch style {
|
||||||
|
case .placeholder: return .placeholder()
|
||||||
|
case .byDevice: return .device(SourceRoot(items: deviceStyleItems()))
|
||||||
|
case .byRuntime: return .runtime(SourceRoot(items: runtimeStyleItems()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deviceStyleItems() -> [ProductFamily] {
|
||||||
|
SimProductFamily.presentation.map { family in
|
||||||
|
ProductFamily(data: family, children: model.deviceTypes.supporting(productFamily: family).map { devType in
|
||||||
|
DeviceType_DS(data: devType, children: model.runtimes.supporting(deviceType: devType).map { runtime in
|
||||||
|
Runtime_DS(data: runtime, children: runtime.devices.of(deviceType: devType).map { device in
|
||||||
|
let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red)
|
||||||
|
|
||||||
|
return Device(data: device, children: device.apps.map { app in App(data: app) }, customImgDesc: imageDesc)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runtimeStyleItems() -> [Platform] {
|
||||||
|
SimPlatform.presentation.map { platform in
|
||||||
|
Platform(data: platform, children: model.runtimes.supporting(platform: platform).map { runtime in
|
||||||
|
Runtime_RT(data: runtime, children: model.deviceTypes.supporting(runtime: runtime).map { devType in
|
||||||
|
DeviceType_RT(data: devType, children: runtime.devices.of(deviceType: devType).map { device in
|
||||||
|
let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red)
|
||||||
|
|
||||||
|
return Device(data: device, children: device.apps.map { app in App(data: app) }, customImgDesc: imageDesc)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OLDWAY
|
||||||
|
static func testItemsOf<T>(type: T.Type) -> [T] {
|
||||||
|
return PresentationState(model: SimModel()).allUnderlyingOf(type: type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allItemsOf<T>(type: T.Type) -> [PresentationItem] {
|
||||||
|
return presentationItems().flatItems.itemsOf(type: type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allUnderlyingOf<T>(type: T.Type) -> [T] {
|
||||||
|
return presentationItems().flatItems.underlyingOf(type: type)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -15,6 +15,7 @@ struct SimDirsApp: App {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView(model: simModel)
|
ContentView(model: simModel)
|
||||||
}
|
}
|
||||||
|
.windowStyle(.hiddenTitleBar)
|
||||||
.commands {
|
.commands {
|
||||||
SimCommands()
|
SimCommands()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
//
|
|
||||||
// AppView.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 6/4/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
extension SimApp {
|
|
||||||
var contentView : AnyView? { return AnyView(AppView(app: self)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppView: View {
|
|
||||||
var app : SimApp
|
|
||||||
var icon : Image { app.nsIcon.map({ Image(nsImage: $0) }) ??
|
|
||||||
Image(systemName: "questionmark.app.dashed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 2.0) {
|
|
||||||
Group {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Display Name: \(app.displayName)")
|
|
||||||
Text("Bundle Name: \(app.bundleName)")
|
|
||||||
Text("Bundle ID: \(app.bundleID)")
|
|
||||||
Text("Version: \(app.version)")
|
|
||||||
Text("Minimum OS Version: \(app.minOSVersion)")
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
icon
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(maxWidth: 72.0, maxHeight: 72.0)
|
|
||||||
.cornerRadius(4.0)
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
.padding([.top, .bottom], 4.0)
|
|
||||||
PathRow(title: "Bundle Path", path: app.bundlePath)
|
|
||||||
if let sandboxPath = app.sandboxPath {
|
|
||||||
PathRow(title: "Sandbox Path", path: sandboxPath)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Text("Sandbox Path: <unknown>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.subheadline)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let apps = PresentationState.testItemsOf(type: SimApp.self)
|
|
||||||
|
|
||||||
if apps.isEmpty {
|
|
||||||
Text("No SimApp present in model data")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ForEach(apps[0...2]) {
|
|
||||||
AppView(app: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
//
|
|
||||||
// DeviceTypeView.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 5/25/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
extension SimDeviceType {
|
|
||||||
var contentView : AnyView? { return AnyView(DeviceTypeView(deviceType: self)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceTypeView: View {
|
|
||||||
var deviceType : SimDeviceType
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 2.0) {
|
|
||||||
Group {
|
|
||||||
Text("Product Family: \(deviceType.productFamily.title)")
|
|
||||||
Text("Model ID: \(deviceType.modelIdentifier)")
|
|
||||||
Text("Min Runtime: \(deviceType.minRuntimeVersionString)")
|
|
||||||
Text("Max Runtime: \(UInt32.max == deviceType.maxRuntimeVersion ? "-" : deviceType.maxRuntimeVersionString)")
|
|
||||||
Text("Identifier: \(deviceType.identifier)")
|
|
||||||
PathRow(title: "Bundle Path", path: deviceType.bundlePath)
|
|
||||||
}
|
|
||||||
.font(.subheadline)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceTypeView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let deviceTypes = PresentationState.testItemsOf(type: SimDeviceType.self)
|
|
||||||
|
|
||||||
if deviceTypes.isEmpty {
|
|
||||||
Text("No SimDeviceType present in model data")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ForEach(deviceTypes[0...2]) {
|
|
||||||
DeviceTypeView(deviceType: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
//
|
|
||||||
// DeviceView.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 6/1/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
extension SimDevice {
|
|
||||||
var contentView : AnyView? { return AnyView(DeviceView(device: self)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceView: View {
|
|
||||||
var device : SimDevice
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 2.0) {
|
|
||||||
Group {
|
|
||||||
Text(device.isAvailable ? "Available" : "Unavailable")
|
|
||||||
.foregroundColor(device.isAvailable ? .green : .red)
|
|
||||||
if !device.isAvailable {
|
|
||||||
let errText = device.availabilityError ?? "Unknown Error"
|
|
||||||
|
|
||||||
Text(errText)
|
|
||||||
.foregroundColor(.red)
|
|
||||||
.padding(.leading)
|
|
||||||
}
|
|
||||||
Text("State: \(device.state.rawValue)")
|
|
||||||
Text("UDID: \(device.udid)")
|
|
||||||
PathRow(title: "Data Path", path: device.dataPath)
|
|
||||||
PathRow(title: "Log Path", path: device.logPath)
|
|
||||||
}
|
|
||||||
.font(.subheadline)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let devices = PresentationState.testItemsOf(type: SimDevice.self)
|
|
||||||
|
|
||||||
if devices.isEmpty {
|
|
||||||
Text("No SimDevice present in model data")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if let available = devices.first(where: { $0.isAvailable }) {
|
|
||||||
DeviceView(device: available)
|
|
||||||
}
|
|
||||||
if let unavailable = devices.first(where: { !$0.isAvailable }) {
|
|
||||||
DeviceView(device: unavailable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
SimDirs/Views/ErrorView.swift
Normal file
35
SimDirs/Views/ErrorView.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// ErrorView.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ErrorView: View {
|
||||||
|
let title : String
|
||||||
|
let description : String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
Image(systemName: "xmark.octagon.fill")
|
||||||
|
.symbolRenderingMode(.multicolor)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(title)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
Text(description)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 8.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ErrorView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ErrorView(
|
||||||
|
title: "Something bad",
|
||||||
|
description: "Did you try turning it off and back on again?")
|
||||||
|
}
|
||||||
|
}
|
||||||
43
SimDirs/Views/Model Views/AppContent.swift
Normal file
43
SimDirs/Views/Model Views/AppContent.swift
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
//
|
||||||
|
// AppContent.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimApp {
|
||||||
|
public var content : some View { AppContent(app: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppContent: View {
|
||||||
|
var app : SimApp
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0.0) {
|
||||||
|
Text("PATHS")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
PathRow(title: "Bundle Path", path: app.bundlePath)
|
||||||
|
if let sandboxPath = app.sandboxPath {
|
||||||
|
PathRow(title: "Sandbox Path", path: sandboxPath)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Text("Sandbox Path: <unknown>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppContent_Previews: PreviewProvider {
|
||||||
|
static var apps = SimModel().apps
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
AppContent(app: apps[0])
|
||||||
|
AppContent(app: apps.randomElement() ?? apps[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
36
SimDirs/Views/Model Views/AppHeader.swift
Normal file
36
SimDirs/Views/Model Views/AppHeader.swift
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// AppHeader.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimApp {
|
||||||
|
public var header : some View { AppHeader(app: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppHeader: View {
|
||||||
|
var app : SimApp
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 3.0) {
|
||||||
|
Text("Display Name: \(app.displayName)")
|
||||||
|
Text("Bundle Name: \(app.bundleName)")
|
||||||
|
Text("Bundle ID: \(app.bundleID)")
|
||||||
|
Text("Version: \(app.version)")
|
||||||
|
Text("Minimum OS Version: \(app.minOSVersion)")
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppHeader_Previews: PreviewProvider {
|
||||||
|
static var apps = SimModel().apps
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
AppHeader(app: apps[0])
|
||||||
|
AppHeader(app: apps.randomElement() ?? apps[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
46
SimDirs/Views/Model Views/DeviceContent.swift
Normal file
46
SimDirs/Views/Model Views/DeviceContent.swift
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// DeviceContent.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimDevice {
|
||||||
|
public var content : some View { DeviceContent(device: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceContent: View {
|
||||||
|
var device : SimDevice
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 3.0) {
|
||||||
|
Group {
|
||||||
|
if !device.isAvailable {
|
||||||
|
ErrorView(
|
||||||
|
title: "\(device.name) is unavailable",
|
||||||
|
description: device.availabilityError ?? "Unknown Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("PATHS")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
PathRow(title: "Data Path", path: device.dataPath)
|
||||||
|
PathRow(title: "Log Path", path: device.logPath)
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceContent_Previews: PreviewProvider {
|
||||||
|
static var devices = SimModel().devices
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
DeviceContent(device: devices[0])
|
||||||
|
DeviceContent(device: devices.randomElement() ?? devices[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
34
SimDirs/Views/Model Views/DeviceHeader.swift
Normal file
34
SimDirs/Views/Model Views/DeviceHeader.swift
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// DeviceHeader.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimDevice {
|
||||||
|
public var header : some View { DeviceHeader(device: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceHeader: View {
|
||||||
|
var device : SimDevice
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 3.0) {
|
||||||
|
Text("State: \(device.state.rawValue)")
|
||||||
|
Text("UDID: \(device.udid)")
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceHeader_Previews: PreviewProvider {
|
||||||
|
static var devices = SimModel().devices
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
DeviceHeader(device: devices[0])
|
||||||
|
DeviceHeader(device: devices.randomElement() ?? devices[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
32
SimDirs/Views/Model Views/DeviceTypeContent.swift
Normal file
32
SimDirs/Views/Model Views/DeviceTypeContent.swift
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// DeviceTypeContent.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimDeviceType {
|
||||||
|
public var content : some View { DeviceTypeContent(deviceType: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceTypeContent: View {
|
||||||
|
var deviceType : SimDeviceType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0.0) {
|
||||||
|
PathRow(title: "Bundle Path", path: deviceType.bundlePath)
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceTypeContent_Previews: PreviewProvider {
|
||||||
|
static var deviceTypes = SimModel().deviceTypes
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
DeviceTypeContent(deviceType: deviceTypes[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
36
SimDirs/Views/Model Views/DeviceTypeHeader.swift
Normal file
36
SimDirs/Views/Model Views/DeviceTypeHeader.swift
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// DeviceTypeHeader.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimDeviceType {
|
||||||
|
public var header : some View { DeviceTypeHeader(deviceType: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceTypeHeader: View {
|
||||||
|
var deviceType : SimDeviceType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 3.0) {
|
||||||
|
Text("Product Family: \(deviceType.productFamily.title)")
|
||||||
|
Text("Model ID: \(deviceType.modelIdentifier)")
|
||||||
|
Text("Min Runtime: \(deviceType.minRuntimeVersionString)")
|
||||||
|
Text("Max Runtime: \(UInt32.max == deviceType.maxRuntimeVersion ? "-" : deviceType.maxRuntimeVersionString)")
|
||||||
|
Text("Identifier: \(deviceType.identifier)")
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceTypeHeader_Previews: PreviewProvider {
|
||||||
|
static var deviceTypes = SimModel().deviceTypes
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
DeviceTypeHeader(deviceType: deviceTypes[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
61
SimDirs/Views/Model Views/RuntimeContent.swift
Normal file
61
SimDirs/Views/Model Views/RuntimeContent.swift
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// RuntimeContent.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimRuntime {
|
||||||
|
public var content : some View { RuntimeContent(runtime: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuntimeContent: View {
|
||||||
|
struct SupportedItem: Identifiable {
|
||||||
|
let name : String
|
||||||
|
var id : String { return name }
|
||||||
|
}
|
||||||
|
|
||||||
|
var runtime : SimRuntime
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 3.0) {
|
||||||
|
let items = runtime.supportedDeviceTypes.map { SupportedItem(name: $0.name) }
|
||||||
|
|
||||||
|
Group {
|
||||||
|
if !runtime.isAvailable {
|
||||||
|
ErrorView(
|
||||||
|
title: "\(runtime.name) is unavailable",
|
||||||
|
description: runtime.availabilityError ?? "Unknown Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("PATHS")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
if !runtime.bundlePath.isEmpty {
|
||||||
|
PathRow(title: "Bundle Path", path: runtime.bundlePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("SUPPORTED DEVICES \(runtime.isPlaceholder ? "(partial list)" : "")")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.top, 8.0)
|
||||||
|
ForEach(items) { item in
|
||||||
|
Text("• \(item.name)")
|
||||||
|
}
|
||||||
|
.padding(.leading)
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuntimeContent_Previews: PreviewProvider {
|
||||||
|
static var runtimes = SimModel().runtimes
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
RuntimeContent(runtime: runtimes[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
34
SimDirs/Views/Model Views/RuntimeHeader.swift
Normal file
34
SimDirs/Views/Model Views/RuntimeHeader.swift
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// RuntimeHeader.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/21/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SimRuntime {
|
||||||
|
public var header : some View { RuntimeHeader(runtime: self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuntimeHeader: View {
|
||||||
|
var runtime : SimRuntime
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 3.0) {
|
||||||
|
if !runtime.buildversion.isEmpty {
|
||||||
|
Text("Build Version: \(runtime.buildversion)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuntimeHeader_Previews: PreviewProvider {
|
||||||
|
static var runtimes = SimModel().runtimes
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
RuntimeContent(runtime: runtimes[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,8 +27,6 @@ struct PathActions: View {
|
||||||
.stroke(.white.opacity(0.4), lineWidth: 1.0))
|
.stroke(.white.opacity(0.4), lineWidth: 1.0))
|
||||||
.background(.black.opacity(0.4))
|
.background(.black.opacity(0.4))
|
||||||
.cornerRadius(6.0)
|
.cornerRadius(6.0)
|
||||||
// based on scenePhase?
|
|
||||||
// .shadow(color: .black.opacity(0.4), radius: 8.0, x: 4.0, y: 4.0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ struct PathRow: View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("\(title): \(path)")
|
Text("\(title): \(path)")
|
||||||
.truncationMode(/*@START_MENU_TOKEN@*/.middle/*@END_MENU_TOKEN@*/)
|
.truncationMode(/*@START_MENU_TOKEN@*/.middle/*@END_MENU_TOKEN@*/)
|
||||||
|
Spacer()
|
||||||
PathActions(path: path)
|
PathActions(path: path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
//
|
|
||||||
// RuntimeView.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 5/30/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
extension SimRuntime {
|
|
||||||
var contentView : AnyView? { return AnyView(RuntimeView(runtime: self)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RuntimeView: View {
|
|
||||||
struct SupportedItem: Identifiable {
|
|
||||||
let name : String
|
|
||||||
var id : String { return name }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
|
||||||
|
|
||||||
var runtime : SimRuntime
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 2.0) {
|
|
||||||
let items = runtime.supportedDeviceTypes.map({ SupportedItem(name: $0.name) })
|
|
||||||
|
|
||||||
Group {
|
|
||||||
Text(runtime.isAvailable ? "Available" : "Unavailable")
|
|
||||||
.foregroundColor(runtime.isAvailable ? .green : .red)
|
|
||||||
if !runtime.isAvailable {
|
|
||||||
let errText = runtime.availabilityError ?? "Unknown Error"
|
|
||||||
|
|
||||||
Text(errText)
|
|
||||||
.foregroundColor(.red)
|
|
||||||
.padding(.leading)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !runtime.buildversion.isEmpty {
|
|
||||||
Text("Build Version: \(runtime.buildversion)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !runtime.bundlePath.isEmpty {
|
|
||||||
PathRow(title: "Bundle Path", path: runtime.bundlePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding(.vertical, 4.0)
|
|
||||||
Text("Supported Devices\(runtime.isPlaceholder ? " (partial list)" : "")")
|
|
||||||
ForEach(items) { item in
|
|
||||||
Text("• \(item.name)")
|
|
||||||
}
|
|
||||||
.padding(.leading)
|
|
||||||
}
|
|
||||||
.font(.subheadline)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RuntimeView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let runtimes = PresentationState.testItemsOf(type: SimRuntime.self)
|
|
||||||
|
|
||||||
if runtimes.isEmpty {
|
|
||||||
Text("No SimRuntime present in model data")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if let available = runtimes.first(where: { $0.isAvailable }) {
|
|
||||||
RuntimeView(runtime: available)
|
|
||||||
.previewLayout(.sizeThatFits)
|
|
||||||
}
|
|
||||||
if let unavailable = runtimes.first(where: { !$0.isAvailable }) {
|
|
||||||
RuntimeView(runtime: unavailable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
//
|
|
||||||
// SimItemContent.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 5/30/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SimItemContent: View {
|
|
||||||
var item : PresentationItem
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView {
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
item.contentView
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.all)
|
|
||||||
.navigationTitle(item.navTitle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimItemContent_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let testItem = PresentationState().presentationItems(from: SimModel())[0]
|
|
||||||
|
|
||||||
SimItemContent(item: testItem.children![0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
//
|
|
||||||
// SimItemGroup.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 6/7/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SimItemGroup: View {
|
|
||||||
let item : PresentationItem
|
|
||||||
@Binding var state : PresentationState
|
|
||||||
@State private var isExpanded = false
|
|
||||||
|
|
||||||
var children : [PresentationItem]? {
|
|
||||||
guard var items = item.children else { return nil }
|
|
||||||
|
|
||||||
if state.filter.contains(.withApps) {
|
|
||||||
items = items.filter { $0.containsType(SimApp.self) }
|
|
||||||
}
|
|
||||||
if state.filter.contains(.runtimeInstalled) {
|
|
||||||
items = items.filter {
|
|
||||||
guard let runtime = $0.underlying as? SimRuntime else { return true }
|
|
||||||
|
|
||||||
return runtime.isAvailable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !state.searchTerm.isEmpty {
|
|
||||||
items = items.filter { $0.titlesContain(state.searchTerm) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return items.isEmpty ? nil : items
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
if let childItems = children {
|
|
||||||
DisclosureGroup(
|
|
||||||
isExpanded: $isExpanded,
|
|
||||||
content: {
|
|
||||||
ForEach(childItems) { childItem in
|
|
||||||
SimItemGroup(item: childItem, state: $state)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label: { SimItemNavLink(item: item) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SimItemNavLink(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimItemGroup_Previews: PreviewProvider {
|
|
||||||
static var simModel = SimModel()
|
|
||||||
@State static var state = PresentationState()
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
let testItem = state.presentationItems(from: simModel)[0]
|
|
||||||
|
|
||||||
SimItemGroup(item: testItem, state: $state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// SimItemNavLink.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 6/7/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SimItemNavLink: View {
|
|
||||||
let item : PresentationItem
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationLink {
|
|
||||||
SimItemContent(item: item) } label: {
|
|
||||||
SimItemRow(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimItemNavLink_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let testItem = PresentationState().presentationItems(from: SimModel())[0]
|
|
||||||
|
|
||||||
SimItemNavLink(item: testItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// SimItemRow.swift
|
|
||||||
// SimDirs
|
|
||||||
//
|
|
||||||
// Created by Casey Fleser on 5/30/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SimItemRow: View {
|
|
||||||
var item : PresentationItem
|
|
||||||
var body: some View {
|
|
||||||
Label {
|
|
||||||
Text(item.title)
|
|
||||||
} icon: {
|
|
||||||
if let icon = item.icon {
|
|
||||||
Image(nsImage: icon)
|
|
||||||
.resizable()
|
|
||||||
.frame(maxWidth: 20.0, maxHeight: 20.0)
|
|
||||||
.cornerRadius(4.0)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Image(systemName: item.imageName)
|
|
||||||
.foregroundColor(item.imageColor)
|
|
||||||
.symbolRenderingMode(.hierarchical)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimItemRow_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let testItem = PresentationState().presentationItems(from: SimModel())[0]
|
|
||||||
|
|
||||||
SimItemRow(item: testItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
67
SimDirs/Views/SourceItem Views/SourceItemContent.swift
Normal file
67
SimDirs/Views/SourceItem Views/SourceItemContent.swift
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
//
|
||||||
|
// SourceItemContent.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SourceItemContent<Item: SourceItem>: View {
|
||||||
|
var item : Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
VStack(alignment: .leading, spacing: 0.0) {
|
||||||
|
// --- Header section ---
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(item.headerTitle)
|
||||||
|
.font(.system(size: 20))
|
||||||
|
.padding(.top, 12.0)
|
||||||
|
.padding(.bottom, 8.0)
|
||||||
|
item.header
|
||||||
|
.padding(.trailing, 136.0)
|
||||||
|
}
|
||||||
|
.padding([.leading, .trailing])
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: 144.0, alignment: .topLeading)
|
||||||
|
Rectangle().frame(height: 1.0).foregroundColor(Color("HeaderEdge"))
|
||||||
|
|
||||||
|
// --- Content section ---
|
||||||
|
ScrollView {
|
||||||
|
HStack {
|
||||||
|
item.content
|
||||||
|
.padding(.top, 4.0)
|
||||||
|
.padding(.trailing)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding([.leading, .top])
|
||||||
|
.background(.background)
|
||||||
|
}
|
||||||
|
.overlay(
|
||||||
|
SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false)
|
||||||
|
.padding([.top, .trailing], 24.0),
|
||||||
|
alignment: .topTrailing
|
||||||
|
)
|
||||||
|
.padding(.top, -geometry.frame(in: .global).origin.y)
|
||||||
|
}
|
||||||
|
.navigationTitle(item.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceItemContent_Previews: PreviewProvider {
|
||||||
|
static var state = SourceState(model: SimModel())
|
||||||
|
static var sampleItems = state.deviceStyleItems()[0...1]
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
ForEach(sampleItems) { item in
|
||||||
|
SourceItemContent(item: item)
|
||||||
|
.preferredColorScheme(.dark)
|
||||||
|
SourceItemContent(item: item)
|
||||||
|
.preferredColorScheme(.light)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
SimDirs/Views/SourceItem Views/SourceItemGroup.swift
Normal file
42
SimDirs/Views/SourceItem Views/SourceItemGroup.swift
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// SourceItemGroup.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SourceItemGroup<Item: SourceItem>: View {
|
||||||
|
@State private var isExpanded = false
|
||||||
|
@Binding var selection: UUID?
|
||||||
|
|
||||||
|
var item : Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let childItems = item.children, childItems.count > 0 {
|
||||||
|
DisclosureGroup(
|
||||||
|
isExpanded: $isExpanded) {
|
||||||
|
ForEach(childItems) { childItem in
|
||||||
|
SourceItemGroup<Item.Child>(selection: $selection, item: childItem)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
SourceItemLink(selection: $selection, item: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SourceItemLink(selection: $selection, item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceItemGroup_Previews: PreviewProvider {
|
||||||
|
@State static var selection : UUID?
|
||||||
|
static var state = SourceState(model: SimModel())
|
||||||
|
static var sampleItem = state.deviceStyleItems()[0]
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
SourceItemGroup(selection: $selection, item: sampleItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
54
SimDirs/Views/SourceItem Views/SourceItemImage.swift
Normal file
54
SimDirs/Views/SourceItem Views/SourceItemImage.swift
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// SourceItemImage.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SourceItemImage: View {
|
||||||
|
var imageDesc : SourceImageDesc
|
||||||
|
var isLabelImage = true
|
||||||
|
var imageSize : CGFloat?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let size = imageSize ?? (isLabelImage ? 20.0 : 128.0)
|
||||||
|
|
||||||
|
switch imageDesc {
|
||||||
|
case let .icon(nsImage):
|
||||||
|
Image(nsImage: nsImage)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(maxWidth: size, maxHeight: size)
|
||||||
|
.cornerRadius(size / 5.0)
|
||||||
|
.shadow(radius: 4.0, x: 2.0, y: 2.0)
|
||||||
|
|
||||||
|
case let .symbol(systemName, color):
|
||||||
|
if isLabelImage {
|
||||||
|
Image(systemName: systemName)
|
||||||
|
.foregroundColor(color)
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Image(systemName: systemName)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(maxWidth: size, maxHeight: size)
|
||||||
|
.shadow(radius: 4.0, x: 2.0, y: 2.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceItemImage_Previews: PreviewProvider {
|
||||||
|
static var state = SourceState(model: SimModel())
|
||||||
|
static var sampleItems = state.deviceStyleItems()[0...1]
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
ForEach(sampleItems) { item in
|
||||||
|
SourceItemImage(imageDesc: item.imageDesc)
|
||||||
|
SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
SimDirs/Views/SourceItem Views/SourceItemLabel.swift
Normal file
31
SimDirs/Views/SourceItem Views/SourceItemLabel.swift
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// SourceItemLabel.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SourceItemLabel<Item: SourceItem>: View {
|
||||||
|
var item : Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Label(
|
||||||
|
title: { Text(item.title) },
|
||||||
|
icon: { SourceItemImage(imageDesc: item.imageDesc) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceItemLabel_Previews: PreviewProvider {
|
||||||
|
static var state = SourceState(model: SimModel())
|
||||||
|
static var sampleItems = state.deviceStyleItems()[0...1]
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
ForEach(sampleItems) { item in
|
||||||
|
SourceItemLabel(item: item)
|
||||||
|
SourceItemLabel(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
SimDirs/Views/SourceItem Views/SourceItemLink.swift
Normal file
37
SimDirs/Views/SourceItem Views/SourceItemLink.swift
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// SourceItemLink.swift
|
||||||
|
// SimDirs
|
||||||
|
//
|
||||||
|
// Created by Casey Fleser on 6/20/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SourceItemLink<Item: SourceItem>: View {
|
||||||
|
@Binding var selection: UUID?
|
||||||
|
|
||||||
|
var item : Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationLink(tag: item.id, selection: $selection,
|
||||||
|
destination: { SourceItemContent(item: item) },
|
||||||
|
label: {
|
||||||
|
SourceItemLabel(item: item)
|
||||||
|
.padding(.leading, 2.0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SourceItemLink_Previews: PreviewProvider {
|
||||||
|
@State static var selection : UUID?
|
||||||
|
static var state = SourceState(model: SimModel())
|
||||||
|
static var sampleItem = state.deviceStyleItems()[0]
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
SourceItemLink(selection: $selection, item: sampleItem)
|
||||||
|
.preferredColorScheme(.dark)
|
||||||
|
SourceItemLink(selection: $selection, item: sampleItem)
|
||||||
|
.preferredColorScheme(.light)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,27 +8,20 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ToolbarMenu: View {
|
struct ToolbarMenu: View {
|
||||||
@Binding var state : PresentationState
|
@ObservedObject var state : SourceState
|
||||||
|
|
||||||
var withApps : Binding<Bool> {
|
|
||||||
Binding(get: { state.filter.contains(.withApps) },
|
|
||||||
set: { state.filter.booleanSet($0, options: .withApps) })
|
|
||||||
}
|
|
||||||
var withRuntimes : Binding<Bool> {
|
|
||||||
Binding(get: { state.filter.contains(.runtimeInstalled) },
|
|
||||||
set: { state.filter.booleanSet($0, options: .runtimeInstalled) })
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
Picker("Organization", selection: $state.organization) {
|
Picker("Style", selection: $state.style) {
|
||||||
ForEach(PresentationState.Organization.allCases) { style in
|
ForEach(SourceState.Style.allCases) { style in
|
||||||
Text(style.rawValue).tag(style)
|
if style.visible {
|
||||||
|
Text(style.title).tag(style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.inline)
|
.pickerStyle(.inline)
|
||||||
Toggle(isOn: withApps) { Label("With Apps", systemImage: "app.fill") }
|
Toggle(isOn: $state.filterApps) { Label("With Apps", systemImage: "app.fill") }
|
||||||
Toggle(isOn: withRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") }
|
Toggle(isOn: $state.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") }
|
||||||
} label: {
|
} label: {
|
||||||
Label("Filter", systemImage: "slider.horizontal.3")
|
Label("Filter", systemImage: "slider.horizontal.3")
|
||||||
}
|
}
|
||||||
|
|
@ -36,10 +29,10 @@ struct ToolbarMenu: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ToolbarMenu_Previews: PreviewProvider {
|
struct ToolbarMenu_Previews: PreviewProvider {
|
||||||
@State static var state = PresentationState(filter: [])
|
static var state = SourceState(model: SimModel())
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ToolbarMenu(state: $state)
|
ToolbarMenu(state: state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 459 KiB After Width: | Height: | Size: 485 KiB |
Loading…
Reference in a new issue