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 = {
|
||||
|
||||
/* 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 */; };
|
||||
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 */; };
|
||||
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.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 */; };
|
||||
C982F879283D042E00D491F4 /* SimModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F878283D042E00D491F4 /* SimModel.swift */; };
|
||||
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87A283E40C800D491F4 /* SimCtl.swift */; };
|
||||
C982F880283E57E600D491F4 /* PresentationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87F283E57E600D491F4 /* PresentationItem.swift */; };
|
||||
C982F883283E813F00D491F4 /* DeviceTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F882283E813F00D491F4 /* DeviceTypeView.swift */; };
|
||||
C9D729F128478AB00064152D /* DeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D729F028478AB00064152D /* DeviceView.swift */; };
|
||||
C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C24285C8C0C0044A279 /* SourceItemData.swift */; };
|
||||
C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C28285C8C4B0044A279 /* SourceItem.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 */; };
|
||||
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD32847B79E00E9B97A /* SimApp.swift */; };
|
||||
/* End PBXBuildFile 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -59,9 +68,16 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
C982F882283E813F00D491F4 /* DeviceTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeView.swift; sourceTree = "<group>"; };
|
||||
C9D729F028478AB00064152D /* DeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceView.swift; sourceTree = "<group>"; };
|
||||
C9D73C24285C8C0C0044A279 /* SourceItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemData.swift; sourceTree = "<group>"; };
|
||||
C9D73C28285C8C4B0044A279 /* SourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItem.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>"; };
|
||||
C9EE0CD32847B79E00E9B97A /* SimApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimApp.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
|
@ -100,6 +116,7 @@
|
|||
C982F85A283B9F9000D491F4 /* ContentView.swift */,
|
||||
C927A0D82846414900533D66 /* Helpers.swift */,
|
||||
C982F867283BA09B00D491F4 /* Model */,
|
||||
C9D73C23285C8B3B0044A279 /* Presentation */,
|
||||
C982F881283E7F0400D491F4 /* Views */,
|
||||
C982F85C283B9F9200D491F4 /* Assets.xcassets */,
|
||||
C982F861283B9F9200D491F4 /* SimDirs.entitlements */,
|
||||
|
|
@ -119,7 +136,6 @@
|
|||
C982F867283BA09B00D491F4 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C982F87C283E579900D491F4 /* Presentation */,
|
||||
C982F87A283E40C800D491F4 /* SimCtl.swift */,
|
||||
C982F878283D042E00D491F4 /* SimModel.swift */,
|
||||
C9EE0CD32847B79E00E9B97A /* SimApp.swift */,
|
||||
|
|
@ -132,31 +148,55 @@
|
|||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C982F87C283E579900D491F4 /* Presentation */ = {
|
||||
C982F881283E7F0400D491F4 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C982F87F283E57E600D491F4 /* PresentationItem.swift */,
|
||||
C977973B284F58A900706DFB /* PresentationState.swift */,
|
||||
C9DD54C12860935300D46AB3 /* SourceItem Views */,
|
||||
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;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C982F881283E7F0400D491F4 /* Views */ = {
|
||||
C9DD54C12860935300D46AB3 /* SourceItem Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C95E5AB5284B6DDE00A2124E /* AppView.swift */,
|
||||
C982F882283E813F00D491F4 /* DeviceTypeView.swift */,
|
||||
C9D729F028478AB00064152D /* DeviceView.swift */,
|
||||
C927A0DA2846502300533D66 /* PathActions.swift */,
|
||||
C9EE0CD128478FDB00E9B97A /* PathRow.swift */,
|
||||
C94C52CA2844EAAC00E2129E /* RuntimeView.swift */,
|
||||
C94C52C82844E99B00E2129E /* SimItemContent.swift */,
|
||||
C977973F284F5CBB00706DFB /* SimItemGroup.swift */,
|
||||
C977973D284F5AE100706DFB /* SimItemNavLink.swift */,
|
||||
C94C52C62844E80A00E2129E /* SimItemRow.swift */,
|
||||
C9779741284F6DE000706DFB /* ToolbarMenu.swift */,
|
||||
C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */,
|
||||
C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */,
|
||||
C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */,
|
||||
C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */,
|
||||
C9DD54C42860938C00D46AB3 /* SourceItemLink.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>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
|
@ -229,29 +269,37 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C982F883283E813F00D491F4 /* DeviceTypeView.swift in Sources */,
|
||||
C94C52C92844E99B00E2129E /* SimItemContent.swift in Sources */,
|
||||
C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */,
|
||||
C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */,
|
||||
C90BCE4E2861E4E400C2EF35 /* AppContent.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 */,
|
||||
C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */,
|
||||
C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */,
|
||||
C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */,
|
||||
C9779740284F5CBB00706DFB /* SimItemGroup.swift in Sources */,
|
||||
C95E5AB6284B6DDE00A2124E /* AppView.swift in Sources */,
|
||||
C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */,
|
||||
C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */,
|
||||
C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */,
|
||||
C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */,
|
||||
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */,
|
||||
C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */,
|
||||
C977973C284F58A900706DFB /* PresentationState.swift in Sources */,
|
||||
C9DD54C52860938C00D46AB3 /* SourceItemLink.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 */,
|
||||
C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */,
|
||||
C982F871283CE7B800D491F4 /* SimRuntime.swift in Sources */,
|
||||
C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */,
|
||||
C94C52C72844E80A00E2129E /* SimItemRow.swift in Sources */,
|
||||
C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */,
|
||||
C927A0D92846414900533D66 /* Helpers.swift in Sources */,
|
||||
C94C52CB2844EAAC00E2129E /* RuntimeView.swift in Sources */,
|
||||
C982F880283E57E600D491F4 /* PresentationItem.swift in Sources */,
|
||||
C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */,
|
||||
C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */,
|
||||
C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */,
|
||||
C982F879283D042E00D491F4 /* SimModel.swift in Sources */,
|
||||
C9D729F128478AB00064152D /* DeviceView.swift in Sources */,
|
||||
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */,
|
||||
);
|
||||
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
|
||||
|
||||
struct ContentView: View {
|
||||
@ObservedObject var model : SimModel
|
||||
@State private var state = PresentationState(filter: [])
|
||||
@ObservedObject var state : SourceState
|
||||
|
||||
init(model: SimModel) {
|
||||
state = SourceState(model: model)
|
||||
}
|
||||
|
||||
var rootItems : [PresentationItem] { state.presentationItems(from: model) }
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(rootItems) { item in
|
||||
SimItemGroup(item: item, state: $state)
|
||||
VStack {
|
||||
NavigationView {
|
||||
List {
|
||||
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)
|
||||
.toolbar {
|
||||
ToolbarItem { ToolbarMenu(state: $state) }
|
||||
}
|
||||
Image("Icon-256")
|
||||
.searchable(text: $state.filter.searchTerm, placement: .sidebar)
|
||||
}
|
||||
.searchable(text: $state.searchTerm, placement: .sidebar)
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var simModel = SimModel()
|
||||
static var model = SimModel()
|
||||
|
||||
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.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
extension NSPasteboard {
|
||||
static func copy(text: String) {
|
||||
|
|
@ -23,12 +23,21 @@ extension NSWorkspace {
|
|||
}
|
||||
|
||||
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) {
|
||||
if value { update(with: options) }
|
||||
else { subtract(options) }
|
||||
}
|
||||
}
|
||||
|
||||
extension ProcessInfo {
|
||||
var isPreviewing : Bool { environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" }
|
||||
}
|
||||
|
||||
extension PropertyListSerialization {
|
||||
class func propertyList(from url: URL) -> [String : AnyObject]? {
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
var title : String { return displayName }
|
||||
var id : String { return identifier }
|
||||
extension SimApp: SourceItemData {
|
||||
var title : String { return displayName }
|
||||
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 icon : NSImage? { return nsIcon }
|
||||
var optionTrait : SourceFilter.Options { .withApps }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,11 +105,14 @@ struct SimDevice: Decodable, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
extension SimDevice: PresentableItem, Identifiable {
|
||||
extension SimDevice: SourceItemData {
|
||||
var title : String { return name }
|
||||
var id : String { return udid }
|
||||
|
||||
var imageName : String { return "shippingbox" }
|
||||
var imageColor : Color? { return isAvailable ? .green : .red }
|
||||
var headerTitle : String { "Device: \(title)" }
|
||||
var imageDesc : SourceImageDesc { .symbol(systemName: "questionmark.circle", color: 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 id : String { return identifier }
|
||||
var imageName : String { return productFamily.imageName }
|
||||
var headerTitle : String { "Device Type: \(title)" }
|
||||
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 {
|
||||
var deviceTypes : [SimDeviceType]
|
||||
@Published var runtimes : [SimRuntime]
|
||||
let timer = DispatchSource.makeTimerSource()
|
||||
var monitorSource : DispatchSourceTimer?
|
||||
let updateInterval = 1.0
|
||||
|
||||
|
||||
var devices : [SimDevice] { runtimes.flatMap { $0.devices } }
|
||||
var apps : [SimApp] { devices.flatMap { $0.apps } }
|
||||
|
||||
init() {
|
||||
let simctl = SimCtl()
|
||||
|
||||
|
|
@ -38,7 +41,9 @@ class SimModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
runtimes.sort()
|
||||
beginMonitor()
|
||||
if !ProcessInfo.processInfo.isPreviewing {
|
||||
beginMonitor()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
fatalError("Failed to initialize data model:\n\(error)")
|
||||
|
|
@ -46,6 +51,8 @@ class SimModel: ObservableObject {
|
|||
}
|
||||
|
||||
func beginMonitor() {
|
||||
let timer = DispatchSource.makeTimerSource()
|
||||
|
||||
timer.setEventHandler {
|
||||
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.resume()
|
||||
monitorSource = timer
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,8 @@ enum SimPlatform: String, Decodable {
|
|||
case watchOS
|
||||
|
||||
static let presentation : [SimPlatform] = [.iOS, .watchOS, .tvOS]
|
||||
}
|
||||
|
||||
extension SimPlatform: PresentableItem {
|
||||
var title : String { return self.rawValue }
|
||||
var id : String { return self.rawValue }
|
||||
var imageName : String {
|
||||
|
||||
var symbolName : String {
|
||||
switch self {
|
||||
case .iOS: return "iphone"
|
||||
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
|
||||
|
||||
static let presentation : [SimProductFamily] = [.iPhone, .iPad, .appleWatch, .appleTV]
|
||||
}
|
||||
|
||||
extension SimProductFamily: PresentableItem {
|
||||
var title : String { return self.rawValue }
|
||||
var id : String { return self.rawValue }
|
||||
|
||||
var imageName : String {
|
||||
var symbolName : String {
|
||||
switch self {
|
||||
case .iPad: return "ipad"
|
||||
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 {
|
||||
var title : String { return name }
|
||||
var id : String { return identifier }
|
||||
var imageName : String { return "v.circle" }
|
||||
var imageColor : Color? { return isAvailable ? .green : .red }
|
||||
extension SimRuntime: SourceItemData {
|
||||
var title : String { return name }
|
||||
var headerTitle : String { "Runtime: \(title)" }
|
||||
var imageDesc : SourceImageDesc { .symbol(systemName: "shippingbox", color: isAvailable ? .green : .red) }
|
||||
|
||||
var optionTrait : SourceFilter.Options { isAvailable ? .runtimeInstalled : [] }
|
||||
}
|
||||
|
||||
extension Array where Element == SimRuntime {
|
||||
|
|
@ -142,4 +143,12 @@ extension Array where Element == SimRuntime {
|
|||
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 {
|
||||
ContentView(model: simModel)
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.commands {
|
||||
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))
|
||||
.background(.black.opacity(0.4))
|
||||
.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 {
|
||||
Text("\(title): \(path)")
|
||||
.truncationMode(/*@START_MENU_TOKEN@*/.middle/*@END_MENU_TOKEN@*/)
|
||||
Spacer()
|
||||
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
|
||||
|
||||
struct ToolbarMenu: View {
|
||||
@Binding var state : PresentationState
|
||||
|
||||
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) })
|
||||
}
|
||||
@ObservedObject var state : SourceState
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Picker("Organization", selection: $state.organization) {
|
||||
ForEach(PresentationState.Organization.allCases) { style in
|
||||
Text(style.rawValue).tag(style)
|
||||
Picker("Style", selection: $state.style) {
|
||||
ForEach(SourceState.Style.allCases) { style in
|
||||
if style.visible {
|
||||
Text(style.title).tag(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(.inline)
|
||||
Toggle(isOn: withApps) { Label("With Apps", systemImage: "app.fill") }
|
||||
Toggle(isOn: withRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") }
|
||||
Toggle(isOn: $state.filterApps) { Label("With Apps", systemImage: "app.fill") }
|
||||
Toggle(isOn: $state.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") }
|
||||
} label: {
|
||||
Label("Filter", systemImage: "slider.horizontal.3")
|
||||
}
|
||||
|
|
@ -36,10 +29,10 @@ struct ToolbarMenu: View {
|
|||
}
|
||||
|
||||
struct ToolbarMenu_Previews: PreviewProvider {
|
||||
@State static var state = PresentationState(filter: [])
|
||||
static var state = SourceState(model: SimModel())
|
||||
|
||||
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