From 156bbf77d15d965cbbc4a68a22b544d8158e7a78 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Wed, 5 Feb 2025 15:40:09 +0100 Subject: [PATCH] Move settings + generation to sheets --- CHDataManagement.xcodeproj/project.pbxproj | 140 +++++--- CHDataManagement/Main/MainView.swift | 75 ++-- CHDataManagement/Main/SelectedContent.swift | 3 - CHDataManagement/Main/TabSelection.swift | 8 - .../Views/Files/FileListView.swift | 8 +- .../Generation/GenerationContentView.swift | 115 +++++++ .../Views/Generic/SelectableListItem.swift | 26 ++ .../Pages/LocalizedPageContentView.swift | 1 + .../Views/Pages/PageDetailView.swift | 24 -- .../Views/Pages/PageListView.swift | 15 +- .../Views/Posts/PostListView.swift | 15 +- .../{ => Audio}/AudioSettingsDetailView.swift | 17 - .../LocalizedAudioSettingsDetailView.swift | 14 + .../Content/PageSettingsContentView.swift | 78 ----- .../Settings/Content/Pages/PageIssue.swift | 49 --- .../Content/Pages/PageIssueChecker.swift | 82 ----- .../Content/Pages/PageIssueView.swift | 319 ------------------ .../GeneralSettingsDetailView.swift | 3 - .../Settings/GenerationContentView.swift | 123 ------- .../LocalizedNavigationBarSettingsView.swift | 0 .../NavigationBarSettingsView.swift | 4 - .../LocalizedPageSettingsView.swift | 0 .../{ => Pages}/PageSettingsDetailView.swift | 4 - .../{ => Paths}/PathSettingsView.swift | 4 - .../LocalizedPostFeedSettingsView.swift | 0 .../{ => Posts}/PostFeedSettingsView.swift | 3 - ...ilView.swift => SettingsContentView.swift} | 6 +- .../Views/Settings/SettingsListView.swift | 20 +- .../Views/Settings/SettingsSection.swift | 4 +- .../Views/Settings/SettingsSheet.swift | 70 ++++ .../{ => Tags}/TagOverviewDetailView.swift | 4 - CHDataManagement/Views/Tags/TagListView.swift | 20 +- 32 files changed, 402 insertions(+), 852 deletions(-) create mode 100644 CHDataManagement/Views/Generation/GenerationContentView.swift create mode 100644 CHDataManagement/Views/Generic/SelectableListItem.swift rename CHDataManagement/Views/Settings/{ => Audio}/AudioSettingsDetailView.swift (75%) create mode 100644 CHDataManagement/Views/Settings/Audio/LocalizedAudioSettingsDetailView.swift delete mode 100644 CHDataManagement/Views/Settings/Content/PageSettingsContentView.swift delete mode 100644 CHDataManagement/Views/Settings/Content/Pages/PageIssue.swift delete mode 100644 CHDataManagement/Views/Settings/Content/Pages/PageIssueChecker.swift delete mode 100644 CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift rename CHDataManagement/Views/Settings/{ => General}/GeneralSettingsDetailView.swift (88%) delete mode 100644 CHDataManagement/Views/Settings/GenerationContentView.swift rename CHDataManagement/Views/Settings/{ => Navigation Bar}/LocalizedNavigationBarSettingsView.swift (100%) rename CHDataManagement/Views/Settings/{ => Navigation Bar}/NavigationBarSettingsView.swift (91%) rename CHDataManagement/Views/Settings/{ => Pages}/LocalizedPageSettingsView.swift (100%) rename CHDataManagement/Views/Settings/{ => Pages}/PageSettingsDetailView.swift (95%) rename CHDataManagement/Views/Settings/{ => Paths}/PathSettingsView.swift (95%) rename CHDataManagement/Views/Settings/{ => Posts}/LocalizedPostFeedSettingsView.swift (100%) rename CHDataManagement/Views/Settings/{ => Posts}/PostFeedSettingsView.swift (93%) rename CHDataManagement/Views/Settings/{GenerationDetailView.swift => SettingsContentView.swift} (87%) create mode 100644 CHDataManagement/Views/Settings/SettingsSheet.swift rename CHDataManagement/Views/Settings/{ => Tags}/TagOverviewDetailView.swift (89%) diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 60fe72e..d35565d 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ E20BCC972D53454C00B8DBEB /* StorageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCC962D53454500B8DBEB /* StorageItem.swift */; }; E20BCC992D53597D00B8DBEB /* SaveState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCC982D53597D00B8DBEB /* SaveState.swift */; }; E20BCC9B2D535C3500B8DBEB /* ChangeObservableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCC9A2D535C3100B8DBEB /* ChangeObservableItem.swift */; }; + E20BCC9D2D5382F000B8DBEB /* SettingsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCC9C2D5382ED00B8DBEB /* SettingsSheet.swift */; }; + E20BCC9F2D53851400B8DBEB /* SelectableListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCC9E2D53850A00B8DBEB /* SelectableListItem.swift */; }; + E20BCCA32D5398AA00B8DBEB /* LocalizedAudioSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCCA22D5398AA00B8DBEB /* LocalizedAudioSettingsDetailView.swift */; }; E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850082CEE01BF0090B18B /* PagePickerView.swift */; }; E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218500A2CEE02FA0090B18B /* Content+Mock.swift */; }; E21850172CEE55FC0090B18B /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850162CEE55FB0090B18B /* FileType.swift */; }; @@ -114,7 +117,7 @@ E29D316B2D07488B0051B7F4 /* PostListPageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D316A2D07488B0051B7F4 /* PostListPageGenerator.swift */; }; E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D316C2D07A4FF0051B7F4 /* PageGenerationResults.swift */; }; E29D316F2D0822770051B7F4 /* SettingsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D316E2D0822720051B7F4 /* SettingsListView.swift */; }; - E29D31712D08234D0051B7F4 /* GenerationDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */; }; + E29D31712D08234D0051B7F4 /* SettingsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31702D08234D0051B7F4 /* SettingsContentView.swift */; }; E29D31792D083DE50051B7F4 /* PageContentResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31782D083DDA0051B7F4 /* PageContentResultsView.swift */; }; E29D317D2D086AB00051B7F4 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D317C2D086AAE0051B7F4 /* Int+Random.swift */; }; E29D317F2D086F4C0051B7F4 /* StatisticsIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D317E2D086F490051B7F4 /* StatisticsIcons.swift */; }; @@ -122,13 +125,9 @@ E29D31852D0AE8EE0051B7F4 /* KnownHeaderElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31842D0AE8EE0051B7F4 /* KnownHeaderElement.swift */; }; E29D31892D0AED1F0051B7F4 /* ModelViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31882D0AED1B0051B7F4 /* ModelViewer.swift */; }; E29D318B2D0B07EE0051B7F4 /* ContentBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D318A2D0B07E60051B7F4 /* ContentBox.swift */; }; - E29D318E2D0B2E680051B7F4 /* PageSettingsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D318D2D0B2E640051B7F4 /* PageSettingsContentView.swift */; }; E29D31902D0B34870051B7F4 /* GenerationAnomaly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D318F2D0B34870051B7F4 /* GenerationAnomaly.swift */; }; E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31932D0B7D250051B7F4 /* SimpleImage.swift */; }; E29D31962D0C186E0051B7F4 /* PathSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31952D0C18690051B7F4 /* PathSettings.swift */; }; - E29D319B2D0C452B0051B7F4 /* PageIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D319A2D0C452B0051B7F4 /* PageIssue.swift */; }; - E29D319D2D0C45B90051B7F4 /* PageIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D319C2D0C45B60051B7F4 /* PageIssueView.swift */; }; - E29D319F2D0C46310051B7F4 /* PageIssueChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D319E2D0C46290051B7F4 /* PageIssueChecker.swift */; }; E29D31A12D0C75CA0051B7F4 /* Content+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31A02D0C75C50051B7F4 /* Content+Validation.swift */; }; E29D31A32D0CC98C0051B7F4 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31A22D0CC98B0051B7F4 /* Item.swift */; }; E29D31A52D0CD03F0051B7F4 /* FileSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31A42D0CD03A0051B7F4 /* FileSelectionView.swift */; }; @@ -270,6 +269,9 @@ E20BCC962D53454500B8DBEB /* StorageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageItem.swift; sourceTree = ""; }; E20BCC982D53597D00B8DBEB /* SaveState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveState.swift; sourceTree = ""; }; E20BCC9A2D535C3100B8DBEB /* ChangeObservableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeObservableItem.swift; sourceTree = ""; }; + E20BCC9C2D5382ED00B8DBEB /* SettingsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSheet.swift; sourceTree = ""; }; + E20BCC9E2D53850A00B8DBEB /* SelectableListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableListItem.swift; sourceTree = ""; }; + E20BCCA22D5398AA00B8DBEB /* LocalizedAudioSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedAudioSettingsDetailView.swift; sourceTree = ""; }; E21850082CEE01BF0090B18B /* PagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePickerView.swift; sourceTree = ""; }; E218500A2CEE02FA0090B18B /* Content+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Mock.swift"; sourceTree = ""; }; E21850162CEE55FB0090B18B /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = ""; }; @@ -370,7 +372,7 @@ E29D316A2D07488B0051B7F4 /* PostListPageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListPageGenerator.swift; sourceTree = ""; }; E29D316C2D07A4FF0051B7F4 /* PageGenerationResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageGenerationResults.swift; sourceTree = ""; }; E29D316E2D0822720051B7F4 /* SettingsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsListView.swift; sourceTree = ""; }; - E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationDetailView.swift; sourceTree = ""; }; + E29D31702D08234D0051B7F4 /* SettingsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsContentView.swift; sourceTree = ""; }; E29D31782D083DDA0051B7F4 /* PageContentResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageContentResultsView.swift; sourceTree = ""; }; E29D317C2D086AAE0051B7F4 /* Int+Random.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; E29D317E2D086F490051B7F4 /* StatisticsIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsIcons.swift; sourceTree = ""; }; @@ -378,13 +380,9 @@ E29D31842D0AE8EE0051B7F4 /* KnownHeaderElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnownHeaderElement.swift; sourceTree = ""; }; E29D31882D0AED1B0051B7F4 /* ModelViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelViewer.swift; sourceTree = ""; }; E29D318A2D0B07E60051B7F4 /* ContentBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBox.swift; sourceTree = ""; }; - E29D318D2D0B2E640051B7F4 /* PageSettingsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageSettingsContentView.swift; sourceTree = ""; }; E29D318F2D0B34870051B7F4 /* GenerationAnomaly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationAnomaly.swift; sourceTree = ""; }; E29D31932D0B7D250051B7F4 /* SimpleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleImage.swift; sourceTree = ""; }; E29D31952D0C18690051B7F4 /* PathSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathSettings.swift; sourceTree = ""; }; - E29D319A2D0C452B0051B7F4 /* PageIssue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIssue.swift; sourceTree = ""; }; - E29D319C2D0C45B60051B7F4 /* PageIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIssueView.swift; sourceTree = ""; }; - E29D319E2D0C46290051B7F4 /* PageIssueChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIssueChecker.swift; sourceTree = ""; }; E29D31A02D0C75C50051B7F4 /* Content+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Validation.swift"; sourceTree = ""; }; E29D31A22D0CC98B0051B7F4 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; E29D31A42D0CD03A0051B7F4 /* FileSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSelectionView.swift; sourceTree = ""; }; @@ -539,6 +537,74 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + E20BCCA02D53985500B8DBEB /* Generation */ = { + isa = PBXGroup; + children = ( + E25DA5702D01015400AEF16D /* GenerationContentView.swift */, + ); + path = Generation; + sourceTree = ""; + }; + E20BCCA12D53989900B8DBEB /* Audio */ = { + isa = PBXGroup; + children = ( + E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */, + E20BCCA22D5398AA00B8DBEB /* LocalizedAudioSettingsDetailView.swift */, + ); + path = Audio; + sourceTree = ""; + }; + E20BCCA42D5398BF00B8DBEB /* Navigation Bar */ = { + isa = PBXGroup; + children = ( + E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */, + E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */, + ); + path = "Navigation Bar"; + sourceTree = ""; + }; + E20BCCA52D5398E200B8DBEB /* Pages */ = { + isa = PBXGroup; + children = ( + E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */, + E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */, + ); + path = Pages; + sourceTree = ""; + }; + E20BCCA62D53995400B8DBEB /* General */ = { + isa = PBXGroup; + children = ( + E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */, + ); + path = General; + sourceTree = ""; + }; + E20BCCA72D53996600B8DBEB /* Paths */ = { + isa = PBXGroup; + children = ( + E2A21C352CB9A3D70060935B /* PathSettingsView.swift */, + ); + path = Paths; + sourceTree = ""; + }; + E20BCCA82D53997500B8DBEB /* Posts */ = { + isa = PBXGroup; + children = ( + E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */, + E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */, + ); + path = Posts; + sourceTree = ""; + }; + E20BCCA92D53998500B8DBEB /* Tags */ = { + isa = PBXGroup; + children = ( + E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */, + ); + path = Tags; + sourceTree = ""; + }; E229901A2D0E3F09009F8D77 /* Item */ = { isa = PBXGroup; children = ( @@ -625,25 +691,6 @@ path = Main; sourceTree = ""; }; - E29D318C2D0B2E5E0051B7F4 /* Content */ = { - isa = PBXGroup; - children = ( - E29D31992D0C451B0051B7F4 /* Pages */, - E29D318D2D0B2E640051B7F4 /* PageSettingsContentView.swift */, - ); - path = Content; - sourceTree = ""; - }; - E29D31992D0C451B0051B7F4 /* Pages */ = { - isa = PBXGroup; - children = ( - E29D319E2D0C46290051B7F4 /* PageIssueChecker.swift */, - E29D319C2D0C45B60051B7F4 /* PageIssueView.swift */, - E29D319A2D0C452B0051B7F4 /* PageIssue.swift */, - ); - path = Pages; - sourceTree = ""; - }; E29D31AB2D0DA52C0051B7F4 /* Icons */ = { isa = PBXGroup; children = ( @@ -687,21 +734,17 @@ E2A21C342CB9A3CA0060935B /* Settings */ = { isa = PBXGroup; children = ( - E25DA5442D00952D00AEF16D /* SettingsSection.swift */, + E29D31702D08234D0051B7F4 /* SettingsContentView.swift */, E29D316E2D0822720051B7F4 /* SettingsListView.swift */, - E25DA5702D01015400AEF16D /* GenerationContentView.swift */, - E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */, - E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */, - E2FD1D2F2D37196500B48627 /* GeneralSettingsDetailView.swift */, - E29D318C2D0B2E5E0051B7F4 /* Content */, - E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */, - E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */, - E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */, - E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */, - E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */, - E2A21C352CB9A3D70060935B /* PathSettingsView.swift */, - E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */, - E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */, + E25DA5442D00952D00AEF16D /* SettingsSection.swift */, + E20BCC9C2D5382ED00B8DBEB /* SettingsSheet.swift */, + E20BCCA12D53989900B8DBEB /* Audio */, + E20BCCA62D53995400B8DBEB /* General */, + E20BCCA42D5398BF00B8DBEB /* Navigation Bar */, + E20BCCA52D5398E200B8DBEB /* Pages */, + E20BCCA72D53996600B8DBEB /* Paths */, + E20BCCA82D53997500B8DBEB /* Posts */, + E20BCCA92D53998500B8DBEB /* Tags */, ); path = Settings; sourceTree = ""; @@ -709,6 +752,7 @@ E2A21C372CB9A4F10060935B /* Generic */ = { isa = PBXGroup; children = ( + E20BCC9E2D53850A00B8DBEB /* SelectableListItem.swift */, E2FD1D2B2D35B76D00B48627 /* ListPopup.swift */, E2FD1D292D35B74C00B48627 /* TextWithPopup.swift */, E2FE0F6F2D2D5231002963B7 /* TextIndicator.swift */, @@ -832,6 +876,7 @@ E2B85F462C42C7CA0047CD0C /* Views */ = { isa = PBXGroup; children = ( + E20BCCA02D53985500B8DBEB /* Generation */, E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */, E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */, E2A21C372CB9A4F10060935B /* Generic */, @@ -1159,6 +1204,7 @@ E2FD1D252D2EBA8000B48627 /* TagOverview.swift in Sources */, E2FE0F152D26918F002963B7 /* HtmlCommand.swift in Sources */, E2FE0F202D29A70E002963B7 /* Array+Remove.swift in Sources */, + E20BCCA32D5398AA00B8DBEB /* LocalizedAudioSettingsDetailView.swift in Sources */, E25DA5772D018B9900AEF16D /* File+Mock.swift in Sources */, E25DA5892D01CBD300AEF16D /* Content+Generation.swift in Sources */, E229904C2D10BE5D009F8D77 /* InitialSetupView.swift in Sources */, @@ -1239,13 +1285,13 @@ E29D31262D0370A80051B7F4 /* VideoCommand+Option.swift in Sources */, E2FE0EF82D1D8110002963B7 /* IconCommand.swift in Sources */, E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */, - E29D318E2D0B2E680051B7F4 /* PageSettingsContentView.swift in Sources */, E22990242D0EDBD0009F8D77 /* HeaderElement.swift in Sources */, E29D31BC2D0DB5120051B7F4 /* CommandProcessor.swift in Sources */, E2FE0F662D2C3B3A002963B7 /* LabelsBlock.swift in Sources */, E29D312C2D039DB80051B7F4 /* PageDetailView.swift in Sources */, E29D31432D0488960051B7F4 /* MainContentView.swift in Sources */, E29D31282D0371930051B7F4 /* ContentPageVideo.swift in Sources */, + E20BCC9F2D53851400B8DBEB /* SelectableListItem.swift in Sources */, E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */, E2FD1D462D46428100B48627 /* PageIconView.swift in Sources */, E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */, @@ -1286,6 +1332,7 @@ E2FD1D2C2D35B76D00B48627 /* ListPopup.swift in Sources */, E2B85F412C4294790047CD0C /* PageHead.swift in Sources */, E2FE0F2A2D2AFBE6002963B7 /* ImageCompareIcons.swift in Sources */, + E20BCC9D2D5382F000B8DBEB /* SettingsSheet.swift in Sources */, E29D316B2D07488B0051B7F4 /* PostListPageGenerator.swift in Sources */, E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */, E2FD1D5C2D47EEB800B48627 /* LinkedPageTagView.swift in Sources */, @@ -1295,7 +1342,6 @@ E2FE0EEC2D1C1253002963B7 /* MultiFileSelectionView.swift in Sources */, E29D31B32D0DA6E80051B7F4 /* ButtonIcons.swift in Sources */, E2FD1D212D2EB22900B48627 /* ModelLoader.swift in Sources */, - E29D319D2D0C45B90051B7F4 /* PageIssueView.swift in Sources */, E25DA5732D018AA100AEF16D /* FileContentView.swift in Sources */, E2FD1D3F2D46405000B48627 /* PostLabelsView.swift in Sources */, E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */, @@ -1309,7 +1355,6 @@ E29D31B52D0DA8490051B7F4 /* PageIcon.swift in Sources */, E2FE0F332D2B2665002963B7 /* AudioBlock.swift in Sources */, E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */, - E29D319B2D0C452B0051B7F4 /* PageIssue.swift in Sources */, E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */, E2FD1D322D3AEB6300B48627 /* PostVideo.swift in Sources */, E29D31472D04892E0051B7F4 /* FileListView.swift in Sources */, @@ -1328,11 +1373,10 @@ E2DD04742C276F31003BFF1F /* MainView.swift in Sources */, E29D31452D0488CB0051B7F4 /* SelectedContentView.swift in Sources */, E2A37D1B2CEA45560000979F /* Tag+Mock.swift in Sources */, - E29D319F2D0C46310051B7F4 /* PageIssueChecker.swift in Sources */, E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */, E29D31322D03B5680051B7F4 /* LocalizedPostDetailView.swift in Sources */, E2FE0EE82D16D4A3002963B7 /* ConvertThrowing.swift in Sources */, - E29D31712D08234D0051B7F4 /* GenerationDetailView.swift in Sources */, + E29D31712D08234D0051B7F4 /* SettingsContentView.swift in Sources */, E2A37D1F2CEA94370000979F /* Optional+Extensions.swift in Sources */, E29D31C32D0DBEF20051B7F4 /* Song.swift in Sources */, E2521E022D51776300C56662 /* StorageError.swift in Sources */, diff --git a/CHDataManagement/Main/MainView.swift b/CHDataManagement/Main/MainView.swift index 8aba1ba..badffb1 100644 --- a/CHDataManagement/Main/MainView.swift +++ b/CHDataManagement/Main/MainView.swift @@ -52,6 +52,12 @@ struct MainView: App { @State private var showStorageErrorSheet = false + @State + private var showSettingsSheet = false + + @State + private var showGenerationSheet = false + @ViewBuilder var sidebar: some View { switch selection.tab { @@ -59,7 +65,6 @@ struct MainView: App { case .pages: PageListView() case .tags: TagListView() case .files: FileListView(selectedFile: $selection.file) - case .generation: SettingsListView() } } @@ -74,8 +79,6 @@ struct MainView: App { SelectedContentView(selected: $selection.tag) case .files: SelectedContentView(selected: $selection.file) - case .generation: - GenerationContentView(selected: $selection.section) } } @@ -90,8 +93,6 @@ struct MainView: App { SelectedDetailView(selected: $selection.tag) case .files: SelectedDetailView(selected: $selection.file) - case .generation: - GenerationDetailView(section: selection.section) } } @@ -106,8 +107,6 @@ struct MainView: App { AddTagView(selected: $selection.tag) case .files: AddFileView(selectedFile: $selection.file) - case .generation: - Text("Not implemented") } } @@ -115,42 +114,61 @@ struct MainView: App { WindowGroup { NavigationSplitView { sidebar - .toolbar { - ToolbarItem(placement: .navigation) { - Picker("", selection: $selection.tab) { - Text("Posts").tag(MainViewTab.posts) - Text("Pages").tag(MainViewTab.pages) - Text("Tags").tag(MainViewTab.tags) - Text("Files").tag(MainViewTab.files) - Text("Generation").tag(MainViewTab.generation) - }.pickerStyle(.segmented) - } - } .navigationSplitViewColumnWidth(min: sidebarWidth, ideal: sidebarWidth, max: sidebarWidth) .toolbar { - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: .automatic) { Button(action: { showAddSheet = true }) { Label("Add", systemSymbol: .plus) } - .disabled(!selection.tab.canAddItems) } } } content: { viewContent + .toolbar { + ToolbarItem(placement: .navigation) { + HStack { + Picker("", selection: $selection.tab) { + Text("Posts").tag(MainViewTab.posts) + Text("Pages").tag(MainViewTab.pages) + Text("Tags").tag(MainViewTab.tags) + Text("Files").tag(MainViewTab.files) + }.pickerStyle(.segmented) + }.frame(minWidth: 400) + } + } } detail: { detail .navigationSplitViewColumnWidth(min: detailWidth, ideal: detailWidth, max: detailWidth) } .toolbar { - ToolbarItem(placement: .primaryAction) { + ToolbarItem { Picker("", selection: $language) { Text("English") .tag(ContentLanguage.english) Text("German") .tag(ContentLanguage.german) - }.pickerStyle(.segmented) + } + .pickerStyle(.segmented) + .frame(minWidth: 200) } - ToolbarItem(placement: .primaryAction) { + ToolbarItem { + Button(action: { showSettingsSheet = true }) { + Image(systemSymbol: .gearshape) + } + } + ToolbarItem { + Button(action: { showGenerationSheet = true }) { + if content.isGeneratingWebsite { + ProgressView() + .scaleEffect(0.6) + .frame(width: 20, height: 20, alignment: .center) + } else { + Image(systemSymbol: .globe) + .frame(width: 20, height: 20, alignment: .center) + } + } + } + ToolbarItem { Button(action: saveButtonPressed) { Image(systemSymbol: content.saveState.symbol) .foregroundStyle(content.saveState.color) @@ -178,6 +196,15 @@ struct MainView: App { StorageErrorView(isPresented: $showStorageErrorSheet) .environmentObject(content) } + .sheet(isPresented: $showSettingsSheet) { + SettingsSheet(language: $language) + .environmentObject(content) + .presentedWindowStyle(.titleBar) + } + .sheet(isPresented: $showGenerationSheet) { + GenerationContentView() + .environmentObject(content) + } } } @@ -224,8 +251,6 @@ struct MainView: App { private func showInitialSheet() { DispatchQueue.main.async { - selection.section = .folders - selection.tab = .generation showInitialSetupSheet = true } } diff --git a/CHDataManagement/Main/SelectedContent.swift b/CHDataManagement/Main/SelectedContent.swift index bd2105f..c5d3add 100644 --- a/CHDataManagement/Main/SelectedContent.swift +++ b/CHDataManagement/Main/SelectedContent.swift @@ -16,7 +16,4 @@ final class SelectedContent: ObservableObject { @Published var file: FileResource? - - @Published - var section: SettingsSection = .folders } diff --git a/CHDataManagement/Main/TabSelection.swift b/CHDataManagement/Main/TabSelection.swift index a7a7db5..7070584 100644 --- a/CHDataManagement/Main/TabSelection.swift +++ b/CHDataManagement/Main/TabSelection.swift @@ -6,13 +6,5 @@ enum MainViewTab { case pages case tags case files - case generation - - var canAddItems: Bool { - if case .generation = self { - return false - } - return true - } } diff --git a/CHDataManagement/Views/Files/FileListView.swift b/CHDataManagement/Views/Files/FileListView.swift index e5ea28a..aa5542f 100644 --- a/CHDataManagement/Views/Files/FileListView.swift +++ b/CHDataManagement/Views/Files/FileListView.swift @@ -52,15 +52,9 @@ struct FileListView: View { .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) List(filteredFiles) { file in - HStack { + SelectableListItem(selected: selectedFile == file) { Text(file.id) - Spacer() } - .listRowBackground(RoundedRectangle(cornerRadius: 5) - .fill(selectedFile == file ? Color.blue : Color.clear) - .padding(.horizontal, 10) - ) - .contentShape(Rectangle()) .onTapGesture { selectedFile = file } diff --git a/CHDataManagement/Views/Generation/GenerationContentView.swift b/CHDataManagement/Views/Generation/GenerationContentView.swift new file mode 100644 index 0000000..8646963 --- /dev/null +++ b/CHDataManagement/Views/Generation/GenerationContentView.swift @@ -0,0 +1,115 @@ +import SwiftUI + +struct GenerationContentView: View { + + @Environment(\.language) + private var language + + @EnvironmentObject + private var content: Content + + @Environment(\.dismiss) + private var dismiss + + var body: some View { + VStack(alignment: .leading) { + Text("Website Generation") + .font(.largeTitle) + .bold() + Text("Regenerate the website and monitor the output") + .foregroundStyle(.secondary) + .padding(.bottom, 30) + + HStack { + Button { + if content.isGeneratingWebsite { + content.endCurrentGeneration() + } else { + content.generateWebsiteInAllLanguages() + } + } label: { + Text(content.isGeneratingWebsite ? "Cancel" : "Generate") + } + .disabled(content.isGeneratingWebsite != content.shouldGenerateWebsite) + if content.isGeneratingWebsite { + ProgressView() + .progressViewStyle(.circular) + .frame(height: 25) + } + Spacer() + } + Text(content.generationStatus) + .padding(.vertical, 5) + HStack(spacing: 8) { + Text("\(content.results.imagesToGenerate.count) images") + Text("\(content.results.externalLinks.count) external links") + Text("\(content.results.resultCount) items processed") + Text("\(content.results.requiredFiles.count) files") + } + List { + Section("Inaccessible files (\(content.results.inaccessibleFiles.count))") { + ForEach(content.results.inaccessibleFiles.sorted()) { file in + Text(file.id) + } + } + Section("Unparsable files (\(content.results.unparsableFiles.count))") { + ForEach(content.results.unparsableFiles.sorted()) { file in + Text(file.id) + } + } + Section("Missing files (\(content.results.missingFiles.count))") { + ForEach(content.results.missingFiles.sorted(), id: \.self) { file in + Text(file) + } + } + Section("Missing tags (\(content.results.missingTags.count))") { + ForEach(content.results.missingTags.sorted(), id: \.self) { tag in + Text(tag) + } + } + Section("Missing pages (\(content.results.missingPages.count))") { + ForEach(content.results.missingPages.sorted(), id: \.self) { page in + Text(page) + } + } + Section("Invalid commands (\(content.results.invalidCommands.count))") { + ForEach(content.results.invalidCommands.sorted(), id: \.self) { markdown in + Text(markdown) + } + } + Section("Invalid blocks (\(content.results.invalidBlocks.count))") { + ForEach(content.results.invalidBlocks.sorted(), id: \.self) { markdown in + Text(markdown) + } + } + Section("Warnings (\(content.results.warnings.count))") { + ForEach(content.results.warnings.sorted(), id: \.self) { warning in + Text(warning) + } + } + Section("Unsaved output files (\(content.results.unsavedOutputFiles.count))") { + ForEach(content.results.unsavedOutputFiles.sorted(), id: \.self) { file in + Text(file) + } + } + Section("Empty pages (\(content.results.emptyPages.count))") { + ForEach(content.results.emptyPages.sorted()) { id in + Text("\(id.pageId) (\(id.language))") + } + } + } + .frame(minHeight: 400) + HorizontalCenter { + Button(action: { dismiss() }) { + Text("Close") + } + } + }.padding() + } +} + +#Preview { + GenerationContentView() + .environmentObject(Content.mock) + .padding() +} diff --git a/CHDataManagement/Views/Generic/SelectableListItem.swift b/CHDataManagement/Views/Generic/SelectableListItem.swift new file mode 100644 index 0000000..53c7aa0 --- /dev/null +++ b/CHDataManagement/Views/Generic/SelectableListItem.swift @@ -0,0 +1,26 @@ +import SwiftUI + +struct SelectableListItem: View where Content: View { + + let content: Content + + let selected: Bool + + public init(selected: Bool, @ViewBuilder content: () -> Content) { + self.selected = selected + self.content = content() + } + + var body: some View { + HStack { + content + Spacer() + } + .foregroundStyle(selected ? Color.white : Color.primary) + .listRowBackground(RoundedRectangle(cornerRadius: 5) + .fill(selected ? Color.blue : Color.clear) + .padding(.horizontal, 10) + ) + .contentShape(Rectangle()) + } +} diff --git a/CHDataManagement/Views/Pages/LocalizedPageContentView.swift b/CHDataManagement/Views/Pages/LocalizedPageContentView.swift index 3073be4..9e30b1a 100644 --- a/CHDataManagement/Views/Pages/LocalizedPageContentView.swift +++ b/CHDataManagement/Views/Pages/LocalizedPageContentView.swift @@ -38,6 +38,7 @@ struct LocalizedPageContentView: View { }.disabled(content.isGeneratingWebsite) if content.isGeneratingWebsite { ProgressView() + .scaleEffect(0.6) .frame(height: 15) } Spacer() diff --git a/CHDataManagement/Views/Pages/PageDetailView.swift b/CHDataManagement/Views/Pages/PageDetailView.swift index 17b9092..aec73b4 100644 --- a/CHDataManagement/Views/Pages/PageDetailView.swift +++ b/CHDataManagement/Views/Pages/PageDetailView.swift @@ -31,30 +31,12 @@ struct PageDetailView: View { } } - #warning("Show info on page generation") var body: some View { ScrollView { VStack(alignment: .leading) { DetailTitle( title: "Page", text: "A page contains longer content") - HStack(alignment: .firstTextBaseline) { - Button(action: generate) { - Text("Generate") - } - .disabled(content.isGeneratingWebsite) -// switch didGenerateWebsite { -// case .none: -// Image(systemSymbol: .questionmarkCircleFill) -// .foregroundStyle(.gray) -// case .some(true): -// Image(systemSymbol: .checkmarkCircleFill) -// .foregroundStyle(.green) -// case .some(false): -// Image(systemSymbol: .xmarkCircleFill) -// .foregroundStyle(.red) -// } - } IdPropertyView( id: $page.id, footer: "The page id is used to link to it internally.", @@ -127,12 +109,6 @@ struct PageDetailView: View { MultiFileSelectionView(selectedFiles: $page.requiredFiles, insertSorted: true) } } - - private func generate() { - DispatchQueue.global(qos: .userInitiated).async { - content.generatePage(page) - } - } } extension PageDetailView: MainContentView { diff --git a/CHDataManagement/Views/Pages/PageListView.swift b/CHDataManagement/Views/Pages/PageListView.swift index 0014604..f1bd4d2 100644 --- a/CHDataManagement/Views/Pages/PageListView.swift +++ b/CHDataManagement/Views/Pages/PageListView.swift @@ -64,15 +64,12 @@ struct PageListView: View { .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) List(filteredPages) { page in - PageListItem(page: page) - .listRowBackground(RoundedRectangle(cornerRadius: 5) - .fill(selection.page == page ? Color.blue : Color.clear) - .padding(.horizontal, 10) - ) - .contentShape(Rectangle()) - .onTapGesture { - selection.page = page - } + SelectableListItem(selected: selection.page == page) { + PageListItem(page: page) + } + .onTapGesture { + selection.page = page + } } } .onAppear { diff --git a/CHDataManagement/Views/Posts/PostListView.swift b/CHDataManagement/Views/Posts/PostListView.swift index dc2bc90..c534975 100644 --- a/CHDataManagement/Views/Posts/PostListView.swift +++ b/CHDataManagement/Views/Posts/PostListView.swift @@ -73,15 +73,12 @@ struct PostListView: View { .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) List(filteredAndSortedPosts) { post in - PostListItem(post: post) - .listRowBackground(RoundedRectangle(cornerRadius: 5) - .fill(selection.post == post ? Color.blue : Color.clear) - .padding(.horizontal, 10) - ) - .contentShape(Rectangle()) - .onTapGesture { - selection.post = post - } + SelectableListItem(selected: selection.post == post) { + PostListItem(post: post) + } + .onTapGesture { + selection.post = post + } } }.onAppear { if selection.post == nil, let first = content.posts.first { diff --git a/CHDataManagement/Views/Settings/AudioSettingsDetailView.swift b/CHDataManagement/Views/Settings/Audio/AudioSettingsDetailView.swift similarity index 75% rename from CHDataManagement/Views/Settings/AudioSettingsDetailView.swift rename to CHDataManagement/Views/Settings/Audio/AudioSettingsDetailView.swift index c75401a..69e8d8f 100644 --- a/CHDataManagement/Views/Settings/AudioSettingsDetailView.swift +++ b/CHDataManagement/Views/Settings/Audio/AudioSettingsDetailView.swift @@ -11,10 +11,6 @@ struct AudioSettingsDetailView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle( - title: "Audio Player Settings", - text: "Configure the files and settings for the audio player components") - IntegerPropertyView( title: "Playlist Cover Image Size", value: $audioPlayer.playlistCoverImageSize, @@ -44,16 +40,3 @@ struct AudioSettingsDetailView: View { } } } - -struct LocalizedAudioSettingsDetailView: View { - - @ObservedObject - var settings: LocalizedAudioPlayerSettings - - var body: some View { - StringPropertyView( - title: "Playlist Text", - text: $settings.playlistText, - footer: "The text on the audio player indicating the playlist") - } -} diff --git a/CHDataManagement/Views/Settings/Audio/LocalizedAudioSettingsDetailView.swift b/CHDataManagement/Views/Settings/Audio/LocalizedAudioSettingsDetailView.swift new file mode 100644 index 0000000..7ec9796 --- /dev/null +++ b/CHDataManagement/Views/Settings/Audio/LocalizedAudioSettingsDetailView.swift @@ -0,0 +1,14 @@ +import SwiftUI + +struct LocalizedAudioSettingsDetailView: View { + + @ObservedObject + var settings: LocalizedAudioPlayerSettings + + var body: some View { + StringPropertyView( + title: "Playlist Text", + text: $settings.playlistText, + footer: "The text on the audio player indicating the playlist") + } +} diff --git a/CHDataManagement/Views/Settings/Content/PageSettingsContentView.swift b/CHDataManagement/Views/Settings/Content/PageSettingsContentView.swift deleted file mode 100644 index c3dfde4..0000000 --- a/CHDataManagement/Views/Settings/Content/PageSettingsContentView.swift +++ /dev/null @@ -1,78 +0,0 @@ -import SwiftUI - -private struct FixSheet: View { - - @Binding - var isPresented: Bool - - @Binding - var message: String - - @Binding - var infoItems: [String] - - let action: () -> Void - - init(isPresented: Binding, message: Binding, infoItems: Binding<[String]>, action: @escaping () -> Void) { - self._isPresented = isPresented - self._message = message - self._infoItems = infoItems - self.action = action - } - - var body: some View { - VStack { - Text("Fix issue") - .font(.headline) - Text(message) - .font(.body) - List { - ForEach(infoItems, id: \.self) { item in - Text(item) - } - } - HStack { - Button("Fix", action: { - isPresented = false - action() - }) - Button("Cancel", action: { isPresented = false }) - } - } - .frame(minHeight: 200) - .padding() - } -} - -struct PageSettingsContentView: View { - - @EnvironmentObject - private var content: Content - - @StateObject - var checker: PageIssueChecker = .init() - - var body: some View { - VStack(alignment: .leading) { - HStack { - Button("Check pages", action: { checker.check(pages: content.pages) }) - .disabled(checker.isCheckingPages) - if checker.isCheckingPages { - ProgressView() - .progressViewStyle(.circular) - .frame(height: 20) - } - } - Text("\(checker.issues.count) Issues") - .font(.headline) - List(checker.issues.sorted()) { issue in - HStack { - PageIssueView(issue: issue) - .id(issue.id) - } - .environmentObject(checker) - } - } - .padding() - } -} diff --git a/CHDataManagement/Views/Settings/Content/Pages/PageIssue.swift b/CHDataManagement/Views/Settings/Content/Pages/PageIssue.swift deleted file mode 100644 index fb33b1c..0000000 --- a/CHDataManagement/Views/Settings/Content/Pages/PageIssue.swift +++ /dev/null @@ -1,49 +0,0 @@ - -struct PageIssue { - - let page: Page - - let language: ContentLanguage - - let message: GenerationAnomaly - - init(page: Page, language: ContentLanguage, message: GenerationAnomaly) { - self.page = page - self.language = language - self.message = message - - print("\(title) (\(language)): \(message)") - } - - var title: String { - page.localized(in: language).title - } -} - -extension PageIssue: Identifiable { - - var id: String { - page.id + "-" + language.rawValue + "-" + message.id - } -} - -extension PageIssue: Equatable { - - static func == (lhs: PageIssue, rhs: PageIssue) -> Bool { - lhs.id == rhs.id - } -} - -extension PageIssue: Hashable { - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - -extension PageIssue: Comparable { - - static func < (lhs: PageIssue, rhs: PageIssue) -> Bool { - lhs.id < rhs.id - } -} diff --git a/CHDataManagement/Views/Settings/Content/Pages/PageIssueChecker.swift b/CHDataManagement/Views/Settings/Content/Pages/PageIssueChecker.swift deleted file mode 100644 index 70a7d3e..0000000 --- a/CHDataManagement/Views/Settings/Content/Pages/PageIssueChecker.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation - -final class PageIssueChecker: ObservableObject { - - @Published - var isCheckingPages: Bool = false - - @Published - var issues: Set = [] - - init() { - - } - - func check(pages: [Page], clearListBeforeStart: Bool = true) { - guard !isCheckingPages else { - return - } - isCheckingPages = true - if clearListBeforeStart { - issues = [] - } - DispatchQueue.global(qos: .userInitiated).async { - for language in ContentLanguage.allCases { - self.check(pages: pages, in: language) - } - DispatchQueue.main.async { - self.isCheckingPages = false - } - } - } - - private func check(pages: [Page], in language: ContentLanguage) { - for page in pages { - analyze(page: page, in: language) - } - } - - func check(page: Page, in language: ContentLanguage) { - guard !isCheckingPages else { - return - } - isCheckingPages = true - DispatchQueue.global(qos: .userInitiated).async { - self.analyze(page: page, in: language) - DispatchQueue.main.async { - self.isCheckingPages = false - } - } - } - - private func analyze(page: Page, in language: ContentLanguage) { - let results = page.content.results.makeResults(for: page, in: language) - let parser = PageContentParser(content: page.content, language: language, results: results) - - let hasPreviousIssues = issues.contains { $0.page == page && $0.language == language } - let pageIssues: [PageIssue] - if let rawPageContent = page.content.storage.pageContent(for: page.id, language: language) { - _ = parser.generatePage(from: rawPageContent) - pageIssues = [] - } else { - let message = GenerationAnomaly.failedToLoadContent - let error = PageIssue(page: page, language: language, message: message) - pageIssues = [error] - } - guard hasPreviousIssues || !pageIssues.isEmpty else { - return - } - update(issues: pageIssues, for: page, in: language) - } - - private func update(issues: [PageIssue], for page: Page, in language: ContentLanguage) { - let newIssues = self.issues - .filter { $0.page != page || $0.language != language } - .union(issues) - - DispatchQueue.main.async { - self.issues = newIssues - } - } - -} diff --git a/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift b/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift deleted file mode 100644 index be1e445..0000000 --- a/CHDataManagement/Views/Settings/Content/Pages/PageIssueView.swift +++ /dev/null @@ -1,319 +0,0 @@ -import SwiftUI - -private struct ButtonAction { - - let name: String - - let action: () -> Void -} - -private struct PopupSheet: View { - - @Binding - var isPresented: Bool - - @Binding - var title: String - - @Binding - var message: String - - var body: some View { - VStack { - Text(title) - .font(.headline) - Text(message) - Button("Dismiss") { - message = "" - isPresented = false - } - }.padding() - } -} - -private struct PageIssueGenericView: View { - - let issue: PageIssue - - let buttons: [ButtonAction] - - var body: some View { - HStack { - VStack(alignment: .leading) { - Text(issue.message.description) - Text("\(issue.title) (\(issue.language.rawValue.uppercased()))") - .font(.caption) - } - Spacer() - ForEach(buttons, id: \.name) { button in - Button(button.name, action: button.action) - } - } - } -} - -struct PageIssueView: View { - - let issue: PageIssue - - @EnvironmentObject - private var checker: PageIssueChecker - - @EnvironmentObject - private var content: Content - - @State - private var showPopupMessage = false - - - @State - private var popupTitle = "Error" - - @State - private var popupMessage = "" - - @State - private var showPagePicker = false - - @State - private var selectedPage: Page? - - @State - private var showFilePicker = false - - @State - private var selectedFile: FileResource? - - private var buttons: [ButtonAction] { - switch issue.message { - case .warning: - return [.init(name: "Retry", action: retryPageCheck)] - case .failedToLoadContent: - return [.init(name: "Retry", action: retryPageCheck)] - case .failedToParseContent: - return [.init(name: "Retry", action: retryPageCheck)] - case .missingFile(let missing, _): - return [ - .init(name: "Select file", action: { selectFile(missingFile: missing) }), - .init(name: "Create external file", action: { createExternalFile(fileId: missing) }) - ] - case .missingPage(let missing, _): - return [ - .init(name: "Select page", action: selectPage), - .init(name: "Create page", action: { createPage(pageId: missing) }) - ] - case .missingTag(let missing, _): - return [ - .init(name: "Select tag", action: { selectTag(missingPage: missing) }), - .init(name: "Create tag", action: { createTag(tagId: missing) }) - ] - case .invalidCommand(_, let markdown): - return [.init(name: "Replace text", action: { replaceCommand(originalText: markdown) })] - } - } - - var body: some View { - PageIssueGenericView(issue: issue, buttons: buttons) - .sheet(isPresented: $showPopupMessage) { - PopupSheet(isPresented: $showPopupMessage, title: $popupTitle, message: $popupMessage) - } - .sheet(isPresented: $showPagePicker) { - if let page = selectedPage { - didSelect(page: page) - } - } content: { - PagePickerView(selectedPage: $selectedPage) - } - .sheet(isPresented: $showFilePicker) { - if let file = selectedFile { - didSelect(file: file) - } - } content: { - FileSelectionView(selectedFile: $selectedFile) - } - - } - - private func show(error: String) { - DispatchQueue.main.async { - self.popupTitle = "Error" - self.popupMessage = error - self.showPopupMessage = true - } - } - - private func show(info: String) { - DispatchQueue.main.async { - self.popupTitle = "Info" - self.popupMessage = info - self.showPopupMessage = true - } - } - - - private func retryPageCheck() { - DispatchQueue.main.async { - checker.check(pages: content.pages, clearListBeforeStart: false) - } - } - - private func selectFile(missingFile: String) { - selectedFile = nil - showFilePicker = true - } - - private func didSelect(file newFile: FileResource) { - guard case .missingFile(let missingFile, let markdown) = issue.message else { - show(error: "Inconsistency: Selected file, but issue is not a missing file") - return - } - replace(missing: missingFile, with: newFile.id, in: markdown) - retryPageCheck() - DispatchQueue.main.async { - selectedFile = nil - } - } - - private func createExternalFile(fileId: String) { - guard content.isValidIdForFile(fileId) else { - show(error: "Invalid file id, can't create external file") - return - } - - let file = FileResource( - content: content, - id: fileId, - isExternallyStored: true, - english: "", - german: "") - content.add(file) - - retryPageCheck() - } - - private func selectPage() { - selectedPage = nil - showPagePicker = true - } - - private func didSelect(page newPage: Page) { - guard case .missingPage(let missingPage, let markdown) = issue.message else { - show(error: "Inconsistency: Selected page, but issue is not a missing page") - return - } - - replace(missing: missingPage, with: newPage.id, in: markdown) - retryPageCheck() - DispatchQueue.main.async { - selectedPage = nil - } - } - - private func createPage(pageId: String) { - guard content.isValidIdForTagOrPageOrPost(pageId) else { - show(error: "Invalid page id, can't create page") - return - } - - let deString = pageId + "-" + ContentLanguage.german.rawValue - - let page = Page( - content: content, - id: pageId, - externalLink: nil, - isDraft: true, - createdDate: .now, - hideDate: false, - startDate: .now, - endDate: nil, - german: .init(content: content, - urlString: deString, - title: pageId), - english: .init(content: content, - urlString: pageId, - title: pageId), - tags: [], - requiredFiles: []) - content.pages.insert(page, at: 0) - - retryPageCheck() - } - - private func selectTag(missingPage: String) { - // TODO: Show sheet to select a tag - // TODO: Replace tag id in page content with new tag id - - retryPageCheck() - } - - private func createTag(tagId: String) { - guard content.isValidIdForTagOrPageOrPost(tagId) else { - show(error: "Invalid tag id, can't create tag") - return - } - - let tag = Tag(content: content, id: tagId) - content.tags.append(tag) - - retryPageCheck() - } - - private func replaceCommand(originalText: String) { - // TODO: Show sheet with text input - // TODO: Replace original text in page content with new text - - retryPageCheck() - } - - // MARK: Page Content manipulation - - private func replace(missing: String, with newText: String, in markdown: String) { - - let newString = markdown.replacingOccurrences(of: missing, with: newText) - guard newString != markdown else { - show(error: "No change in content detected trying to perform replacement") - return - } - - let occurrences = findOccurrences(of: markdown, in: issue.page, language: issue.language) - guard !occurrences.isEmpty else { - show(error: "No occurrences of '\(markdown)' found in the page") - return - } - replace(markdown, with: newString, in: issue.page, language: issue.language) - - show(info: "Replaced \(occurrences.count) occurrences of '\(missing)' with '\(newText)'") - - retryPageCheck() - } - - private func replace(_ oldString: String, with newString: String, in page: Page, language: ContentLanguage) { - guard let pageContent = content.storage.pageContent(for: page.id, language: language) else { - print("Failed to replace in page \(page.id) (\(language)), no content") - return - } - let modified = pageContent.replacingOccurrences(of: oldString, with: newString) - - guard content.storage.save(pageContent: modified, for: page.id, in: language) else { - print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))") - return - } - } - - private func findOccurrences(of searchString: String, in page: Page, language: ContentLanguage) -> [String] { - guard let parts = content.storage.pageContent(for: page.id, language: language)? - .components(separatedBy: searchString) else { - print("Failed to get page content to find occurrences, no content") - return [] - } - - var occurrences: [String] = [] - for index in parts.indices.dropLast() { - let start = parts[index].suffix(10) - let end = parts[index+1].prefix(10) - let full = "...\(start)\(searchString)\(end)...".replacingOccurrences(of: "\n", with: "\\n") - occurrences.append(full) - } - return occurrences - } -} diff --git a/CHDataManagement/Views/Settings/GeneralSettingsDetailView.swift b/CHDataManagement/Views/Settings/General/GeneralSettingsDetailView.swift similarity index 88% rename from CHDataManagement/Views/Settings/GeneralSettingsDetailView.swift rename to CHDataManagement/Views/Settings/General/GeneralSettingsDetailView.swift index c53bd60..42d7da4 100644 --- a/CHDataManagement/Views/Settings/GeneralSettingsDetailView.swift +++ b/CHDataManagement/Views/Settings/General/GeneralSettingsDetailView.swift @@ -8,9 +8,6 @@ struct GeneralSettingsDetailView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle(title: "General", - text: "General settings for the webpage") - StringPropertyView( title: "Website URL", text: $generalSettings.url, diff --git a/CHDataManagement/Views/Settings/GenerationContentView.swift b/CHDataManagement/Views/Settings/GenerationContentView.swift deleted file mode 100644 index 2fc466c..0000000 --- a/CHDataManagement/Views/Settings/GenerationContentView.swift +++ /dev/null @@ -1,123 +0,0 @@ -import SwiftUI - -struct GenerationContentView: View { - - @Environment(\.language) - private var language - - @EnvironmentObject - private var content: Content - - @Binding - private var selectedSection: SettingsSection - - init(selected: Binding) { - self._selectedSection = selected - } - - var body: some View { - switch selectedSection { - case .pages: - PageSettingsContentView() - default: - generationView - } - } - - @ViewBuilder - private var generationView: some View { - VStack(alignment: .leading) { - Text("Website Generation") - .font(.largeTitle) - .bold() - Text("Regenerate the website and monitor the output") - .foregroundStyle(.secondary) - .padding(.bottom, 30) - - HStack { - Button { - if content.isGeneratingWebsite { - content.endCurrentGeneration() - } else { - content.generateWebsiteInAllLanguages() - } - } label: { - Text(content.isGeneratingWebsite ? "Cancel" : "Generate") - } - .disabled(content.isGeneratingWebsite != content.shouldGenerateWebsite) - if content.isGeneratingWebsite { - ProgressView() - .progressViewStyle(.circular) - .frame(height: 25) - } - Spacer() - } - Text(content.generationStatus) - .padding(.vertical, 5) - HStack(spacing: 8) { - Text("\(content.results.imagesToGenerate.count) images") - Text("\(content.results.externalLinks.count) external links") - Text("\(content.results.resultCount) items processed") - Text("\(content.results.requiredFiles.count) files") - } - List { - Section("Inaccessible files (\(content.results.inaccessibleFiles.count))") { - ForEach(content.results.inaccessibleFiles.sorted()) { file in - Text(file.id) - } - } - Section("Unparsable files (\(content.results.unparsableFiles.count))") { - ForEach(content.results.unparsableFiles.sorted()) { file in - Text(file.id) - } - } - Section("Missing files (\(content.results.missingFiles.count))") { - ForEach(content.results.missingFiles.sorted(), id: \.self) { file in - Text(file) - } - } - Section("Missing tags (\(content.results.missingTags.count))") { - ForEach(content.results.missingTags.sorted(), id: \.self) { tag in - Text(tag) - } - } - Section("Missing pages (\(content.results.missingPages.count))") { - ForEach(content.results.missingPages.sorted(), id: \.self) { page in - Text(page) - } - } - Section("Invalid commands (\(content.results.invalidCommands.count))") { - ForEach(content.results.invalidCommands.sorted(), id: \.self) { markdown in - Text(markdown) - } - } - Section("Invalid blocks (\(content.results.invalidBlocks.count))") { - ForEach(content.results.invalidBlocks.sorted(), id: \.self) { markdown in - Text(markdown) - } - } - Section("Warnings (\(content.results.warnings.count))") { - ForEach(content.results.warnings.sorted(), id: \.self) { warning in - Text(warning) - } - } - Section("Unsaved output files (\(content.results.unsavedOutputFiles.count))") { - ForEach(content.results.unsavedOutputFiles.sorted(), id: \.self) { file in - Text(file) - } - } - Section("Empty pages (\(content.results.emptyPages.count))") { - ForEach(content.results.emptyPages.sorted()) { id in - Text("\(id.pageId) (\(id.language))") - } - } - } - }.padding() - } -} - -#Preview { - GenerationContentView(selected: .constant(.folders)) - .environmentObject(Content.mock) - .padding() -} diff --git a/CHDataManagement/Views/Settings/LocalizedNavigationBarSettingsView.swift b/CHDataManagement/Views/Settings/Navigation Bar/LocalizedNavigationBarSettingsView.swift similarity index 100% rename from CHDataManagement/Views/Settings/LocalizedNavigationBarSettingsView.swift rename to CHDataManagement/Views/Settings/Navigation Bar/LocalizedNavigationBarSettingsView.swift diff --git a/CHDataManagement/Views/Settings/NavigationBarSettingsView.swift b/CHDataManagement/Views/Settings/Navigation Bar/NavigationBarSettingsView.swift similarity index 91% rename from CHDataManagement/Views/Settings/NavigationBarSettingsView.swift rename to CHDataManagement/Views/Settings/Navigation Bar/NavigationBarSettingsView.swift index d55d5f5..6e18f8f 100644 --- a/CHDataManagement/Views/Settings/NavigationBarSettingsView.swift +++ b/CHDataManagement/Views/Settings/Navigation Bar/NavigationBarSettingsView.swift @@ -15,10 +15,6 @@ struct NavigationBarSettingsView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle( - title: "Navigation Bar", - text: "Customize the navigation bar for all pages at the top of the website") - HStack { Text("Links") .font(.headline) diff --git a/CHDataManagement/Views/Settings/LocalizedPageSettingsView.swift b/CHDataManagement/Views/Settings/Pages/LocalizedPageSettingsView.swift similarity index 100% rename from CHDataManagement/Views/Settings/LocalizedPageSettingsView.swift rename to CHDataManagement/Views/Settings/Pages/LocalizedPageSettingsView.swift diff --git a/CHDataManagement/Views/Settings/PageSettingsDetailView.swift b/CHDataManagement/Views/Settings/Pages/PageSettingsDetailView.swift similarity index 95% rename from CHDataManagement/Views/Settings/PageSettingsDetailView.swift rename to CHDataManagement/Views/Settings/Pages/PageSettingsDetailView.swift index b4f3dce..b518099 100644 --- a/CHDataManagement/Views/Settings/PageSettingsDetailView.swift +++ b/CHDataManagement/Views/Settings/Pages/PageSettingsDetailView.swift @@ -11,10 +11,6 @@ struct PageSettingsDetailView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle( - title: "Page Settings", - text: "Change the way pages are displayed") - IntegerPropertyView( title: "Content Width", value: $pageSettings.contentWidth, diff --git a/CHDataManagement/Views/Settings/PathSettingsView.swift b/CHDataManagement/Views/Settings/Paths/PathSettingsView.swift similarity index 95% rename from CHDataManagement/Views/Settings/PathSettingsView.swift rename to CHDataManagement/Views/Settings/Paths/PathSettingsView.swift index 28498ac..139aab3 100644 --- a/CHDataManagement/Views/Settings/PathSettingsView.swift +++ b/CHDataManagement/Views/Settings/Paths/PathSettingsView.swift @@ -17,10 +17,6 @@ struct PathSettingsView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle( - title: "Folder Settings", - text: "Select the folders for the app to work.") - FolderOnDiskPropertyView( title: "Content Folder", folder: $content.storage.contentScope, diff --git a/CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift b/CHDataManagement/Views/Settings/Posts/LocalizedPostFeedSettingsView.swift similarity index 100% rename from CHDataManagement/Views/Settings/LocalizedPostFeedSettingsView.swift rename to CHDataManagement/Views/Settings/Posts/LocalizedPostFeedSettingsView.swift diff --git a/CHDataManagement/Views/Settings/PostFeedSettingsView.swift b/CHDataManagement/Views/Settings/Posts/PostFeedSettingsView.swift similarity index 93% rename from CHDataManagement/Views/Settings/PostFeedSettingsView.swift rename to CHDataManagement/Views/Settings/Posts/PostFeedSettingsView.swift index ed9c7e4..d8b9727 100644 --- a/CHDataManagement/Views/Settings/PostFeedSettingsView.swift +++ b/CHDataManagement/Views/Settings/Posts/PostFeedSettingsView.swift @@ -17,9 +17,6 @@ struct PostFeedSettingsView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle(title: "Post Feed Settings", - text: "Change the way the posts are displayed") - IntegerPropertyView( title: "Content Width", value: $postSettings.contentWidth, diff --git a/CHDataManagement/Views/Settings/GenerationDetailView.swift b/CHDataManagement/Views/Settings/SettingsContentView.swift similarity index 87% rename from CHDataManagement/Views/Settings/GenerationDetailView.swift rename to CHDataManagement/Views/Settings/SettingsContentView.swift index 8fa8a3d..2d2b2b8 100644 --- a/CHDataManagement/Views/Settings/GenerationDetailView.swift +++ b/CHDataManagement/Views/Settings/SettingsContentView.swift @@ -1,6 +1,6 @@ import SwiftUI -struct GenerationDetailView: View { +struct SettingsContentView: View { let section: SettingsSection @@ -11,7 +11,7 @@ struct GenerationDetailView: View { switch section { case .general: GeneralSettingsDetailView(generalSettings: content.settings.general) - case .folders: + case .paths: PathSettingsView() case .navigationBar: NavigationBarSettingsView() @@ -28,5 +28,5 @@ struct GenerationDetailView: View { } #Preview { - GenerationDetailView(section: .folders) + SettingsContentView(section: .paths) } diff --git a/CHDataManagement/Views/Settings/SettingsListView.swift b/CHDataManagement/Views/Settings/SettingsListView.swift index 8956b31..41c3bcc 100644 --- a/CHDataManagement/Views/Settings/SettingsListView.swift +++ b/CHDataManagement/Views/Settings/SettingsListView.swift @@ -2,20 +2,18 @@ import SwiftUI struct SettingsListView: View { - @EnvironmentObject - private var selection: SelectedContent + @Binding + var section: SettingsSection + var body: some View { List(SettingsSection.allCases) { section in - Label(section.rawValue, systemSymbol: section.icon) - .listRowBackground(RoundedRectangle(cornerRadius: 5) - .fill(selection.section == section ? Color.blue : Color.clear) - .padding(.horizontal, 10) - ) - .contentShape(Rectangle()) - .onTapGesture { - selection.section = section - } + SelectableListItem(selected: self.section == section) { + Label(section.rawValue, systemSymbol: section.icon) + } + .onTapGesture { + self.section = section + } } } } diff --git a/CHDataManagement/Views/Settings/SettingsSection.swift b/CHDataManagement/Views/Settings/SettingsSection.swift index f77f583..1191839 100644 --- a/CHDataManagement/Views/Settings/SettingsSection.swift +++ b/CHDataManagement/Views/Settings/SettingsSection.swift @@ -4,7 +4,7 @@ enum SettingsSection: String { case general = "General" - case folders = "Folders" + case paths = "Paths" case navigationBar = "Navigation Bar" @@ -23,7 +23,7 @@ extension SettingsSection { var icon: SFSymbol { switch self { case .general: return .noteText - case .folders: return .folder + case .paths: return .folder case .navigationBar: return .menubarArrowUpRectangle case .postFeed: return .rectangleGrid1x2 case .pages: return .docRichtext diff --git a/CHDataManagement/Views/Settings/SettingsSheet.swift b/CHDataManagement/Views/Settings/SettingsSheet.swift new file mode 100644 index 0000000..a72abfe --- /dev/null +++ b/CHDataManagement/Views/Settings/SettingsSheet.swift @@ -0,0 +1,70 @@ +import SwiftUI + +struct SettingsSheet: View { + + private let sidebarWidth: CGFloat = 250 + + private let contentWidth: CGFloat = 300 + + @Environment(\.dismiss) + private var dismiss + + @Binding + var language: ContentLanguage + + @State + var section: SettingsSection = .general + + private var title: String { + switch section { + case .general: "General Settings" + case .paths: "Folder Settings" + case .navigationBar: "Navigation Bar Settings" + case .postFeed: "Post Feed Settings" + case .pages: "Pages Settings" + case .tagOverview: "Tag Overview Settings" + case .audioPlayer: "Audio Player Settings" + } + } + + private var text: String { + switch section { + case .general: "General settings for the webpage" + case .paths: "Select the folders for the app to work." + case .navigationBar: "Customize the navigation bar for all pages at the top of the website" + case .postFeed: "Change the way the posts are displayed" + case .pages: "Change the way pages are displayed" + case .tagOverview: "Configure the page showing all tags" + case .audioPlayer: "Configure the files and settings for the audio player components" + } + } + + var body: some View { + VStack(spacing: 0) { + HStack(alignment: .top) { + DetailTitle( + title: title, + text: text) + Spacer() + Picker("", selection: $language) { + Text("English") + .tag(ContentLanguage.english) + Text("German") + .tag(ContentLanguage.german) + } + .pickerStyle(.segmented) + Button(action: { dismiss() }) { + Text("Close") + } + } + .padding() + .background(Color(NSColor.windowBackgroundColor)) + NavigationSplitView { + SettingsListView(section: $section) + .navigationSplitViewColumnWidth(min: sidebarWidth, ideal: sidebarWidth, max: sidebarWidth) + } detail: { + SettingsContentView(section: section) + } + }.frame(width: 550, height: 600) + } +} diff --git a/CHDataManagement/Views/Settings/TagOverviewDetailView.swift b/CHDataManagement/Views/Settings/Tags/TagOverviewDetailView.swift similarity index 89% rename from CHDataManagement/Views/Settings/TagOverviewDetailView.swift rename to CHDataManagement/Views/Settings/Tags/TagOverviewDetailView.swift index 333f238..f5c2e2b 100644 --- a/CHDataManagement/Views/Settings/TagOverviewDetailView.swift +++ b/CHDataManagement/Views/Settings/Tags/TagOverviewDetailView.swift @@ -15,10 +15,6 @@ struct TagOverviewDetailView: View { var body: some View { ScrollView { VStack(alignment: .leading) { - DetailTitle( - title: "Tag Overview", - text: "Configure the page showing all tags") - if let tag = content.tagOverview?.localized(in: language) { LocalizedTagDetailView( tag: tag, diff --git a/CHDataManagement/Views/Tags/TagListView.swift b/CHDataManagement/Views/Tags/TagListView.swift index bc9209f..672b0fd 100644 --- a/CHDataManagement/Views/Tags/TagListView.swift +++ b/CHDataManagement/Views/Tags/TagListView.swift @@ -33,15 +33,12 @@ struct TagListView: View { .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) List(filteredAndSortedTags) { tag in - TagListItem(tag: tag.localized(in: language)) - .listRowBackground(RoundedRectangle(cornerRadius: 5) - .fill(selection.tag == tag ? Color.blue : Color.clear) - .padding(.horizontal, 10) - ) - .contentShape(Rectangle()) - .onTapGesture { - selection.tag = tag - } + SelectableListItem(selected: selection.tag == tag) { + TagListItem(tag: tag.localized(in: language)) + } + .onTapGesture { + selection.tag = tag + } } }.onAppear { if selection.tag == nil, let first = content.tags.first { @@ -57,9 +54,6 @@ private struct TagListItem: View { var tag: LocalizedTag var body: some View { - HStack { - Text(tag.title) - Spacer() - } + Text(tag.title) } }