diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index c96e5c4..e6b230e 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -209,6 +209,7 @@ E2F3B39E2DC55B1C00CFA712 /* LabelCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */; }; E2F3B3A22DC769C300CFA712 /* ColoredButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3A12DC769BF00CFA712 /* ColoredButton.swift */; }; E2F3B3A42DC7DC2400CFA712 /* GenerationIssuesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3A32DC7DC1F00CFA712 /* GenerationIssuesView.swift */; }; + E2F3B3A62DC7F61600CFA712 /* GenerationIssuesActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F3B3A52DC7F60E00CFA712 /* GenerationIssuesActionView.swift */; }; E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; }; E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; }; E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; }; @@ -494,6 +495,7 @@ E2F3B39D2DC55B1C00CFA712 /* LabelCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelCreationView.swift; sourceTree = ""; }; E2F3B3A12DC769BF00CFA712 /* ColoredButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredButton.swift; sourceTree = ""; }; E2F3B3A32DC7DC1F00CFA712 /* GenerationIssuesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationIssuesView.swift; sourceTree = ""; }; + E2F3B3A52DC7F60E00CFA712 /* GenerationIssuesActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationIssuesActionView.swift; sourceTree = ""; }; E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = ""; }; E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = ""; }; E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = ""; }; @@ -603,6 +605,7 @@ E20BCCA02D53985500B8DBEB /* Generation */ = { isa = PBXGroup; children = ( + E2F3B3A52DC7F60E00CFA712 /* GenerationIssuesActionView.swift */, E2F3B3A32DC7DC1F00CFA712 /* GenerationIssuesView.swift */, E20BCCAE2D53F4A500B8DBEB /* GenerationStringIssuesView.swift */, E20BCCAC2D53F48100B8DBEB /* IssueStatus.swift */, @@ -1589,6 +1592,7 @@ E229903C2D0F8A7B009F8D77 /* OptionalTextFieldPropertyView.swift in Sources */, E2FD1D582D477A9400B48627 /* InsertableCommand.swift in Sources */, E2EC1FB42DC0FA8700C41784 /* Insert+Route.swift in Sources */, + E2F3B3A62DC7F61600CFA712 /* GenerationIssuesActionView.swift in Sources */, E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */, E2FE0F222D2A84A0002963B7 /* VideoCommand.swift in Sources */, E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */, diff --git a/CHDataManagement/Main/MainView.swift b/CHDataManagement/Main/MainView.swift index 4f8a5e0..731ebdd 100644 --- a/CHDataManagement/Main/MainView.swift +++ b/CHDataManagement/Main/MainView.swift @@ -20,9 +20,6 @@ struct MainView: App { @StateObject private var notifications: NotificationSender = .init() - @State - private var language: ContentLanguage = .english - @StateObject private var selection: SelectedContent = .init() @@ -131,7 +128,7 @@ struct MainView: App { } .toolbar { ToolbarItem { - Picker("", selection: $language) { + Picker("", selection: $selection.language) { Text("English") .tag(ContentLanguage.english) Text("German") @@ -180,13 +177,13 @@ struct MainView: App { } } .navigationTitle("") - .environment(\.language, language) + .environment(\.language, selection.language) .environmentObject(content) .environmentObject(selection) .onAppear(perform: loadContent) .sheet(isPresented: $showAddSheet) { addItemSheet - .environment(\.language, language) + .environment(\.language, selection.language) .environmentObject(content) .environmentObject(selection) } @@ -195,13 +192,14 @@ struct MainView: App { .environmentObject(content) } .sheet(isPresented: $showSettingsSheet) { - SettingsSheet(language: $language) + SettingsSheet(language: $selection.language) .environmentObject(content) .presentedWindowStyle(.titleBar) } .sheet(isPresented: $showGenerationSheet) { GenerationContentView() .environmentObject(content) + .environmentObject(selection) } .sheet(isPresented: $showPreviewSheet) { WebsitePreviewSheet() diff --git a/CHDataManagement/Main/SelectedContent.swift b/CHDataManagement/Main/SelectedContent.swift index 40eaee0..743cecb 100644 --- a/CHDataManagement/Main/SelectedContent.swift +++ b/CHDataManagement/Main/SelectedContent.swift @@ -2,6 +2,9 @@ import Foundation final class SelectedContent: ObservableObject { + @Published + var language: ContentLanguage = .english + @Published var tab: MainViewTab = .posts diff --git a/CHDataManagement/Views/Generation/GenerationContentView.swift b/CHDataManagement/Views/Generation/GenerationContentView.swift index 5bfd5d5..8551438 100644 --- a/CHDataManagement/Views/Generation/GenerationContentView.swift +++ b/CHDataManagement/Views/Generation/GenerationContentView.swift @@ -9,6 +9,9 @@ struct GenerationContentView: View { @EnvironmentObject private var content: Content + @EnvironmentObject + private var selection: SelectedContent + @Environment(\.dismiss) private var dismiss @@ -74,18 +77,34 @@ struct GenerationContentView: View { text: "external files", statusWhenNonEmpty: .nominal, items: content.results.externalFiles) { $0.id } - GenerationStringIssuesView( + GenerationIssuesView( text: "empty pages", statusWhenNonEmpty: .warning, - items: content.results.emptyPages) { "\($0.pageId) (\($0.language))" } - GenerationStringIssuesView( - text: "draft pages", + items: $content.results.emptyPages) { pageId in + HStack { + Text("\(pageId.pageId) (\(pageId.language))") + Spacer() + Button("Show") { + show(page: pageId.pageId, + language: pageId.language) + } + } + } + + GenerationIssuesActionView( + title: "draft pages", statusWhenNonEmpty: .warning, - items: draftPages) { $0.id } - GenerationStringIssuesView( - text: "draft posts", + items: draftPages, + buttonText: "Show", + itemText: { $0.id }, + action: { show($0) }) + GenerationIssuesActionView( + title: "draft posts", statusWhenNonEmpty: .warning, - items: draftPosts) { $0.id } + items: draftPosts, + buttonText: "Show", + itemText: { $0.id }, + action: { show($0) }) GenerationIssuesView( text: "additional output files", statusWhenNonEmpty: .warning, @@ -147,6 +166,31 @@ struct GenerationContentView: View { } content.results.unusedFilesInOutput.remove(unusedFile) } + + private func show(page pageId: String, language: ContentLanguage? = nil) { + guard let page = content.page(pageId) else { + return + } + show(page, language: language) + } + + private func show(_ page: Page, language: ContentLanguage? = nil) { + selection.page = page + if let language { + selection.language = language + } + selection.tab = .pages + dismiss() + } + + private func show(_ post: Post, language: ContentLanguage? = nil) { + selection.post = post + if let language { + selection.language = language + } + selection.tab = .posts + dismiss() + } } #Preview { diff --git a/CHDataManagement/Views/Generation/GenerationIssuesActionView.swift b/CHDataManagement/Views/Generation/GenerationIssuesActionView.swift new file mode 100644 index 0000000..050a91d --- /dev/null +++ b/CHDataManagement/Views/Generation/GenerationIssuesActionView.swift @@ -0,0 +1,76 @@ +import SwiftUI + +struct GenerationIssuesActionView: View where S: Collection, T: Hashable & Comparable, S.Element == T { + + let title: String + + let statusWhenNonEmpty: IssueStatus + + let items: S + + let buttonText: String + + let itemText: (T) -> String + + let action: (T) -> Void + + @State + private var showList = false + + var status: IssueStatus { + items.isEmpty ? .nominal : statusWhenNonEmpty + } + + init(title: String, statusWhenNonEmpty: IssueStatus = .error, items: S, buttonText: String, itemText: @escaping (T) -> String, action: @escaping (T) -> Void) { + self.title = title + self.statusWhenNonEmpty = statusWhenNonEmpty + self.items = items + self.buttonText = buttonText + self.itemText = itemText + self.action = action + } + + var body: some View { + HStack { + Button(action: showListIfNonEmpty) { + Image(systemSymbol: status.symbol) + .foregroundStyle(status.color) + }.buttonStyle(.plain) + Text("\(items.count) \(title)") + } + .sheet(isPresented: $showList) { + VStack { + Text("\(items.count) \(title)") + .font(.title) + List(items.sorted(), id: \.self) { item in + HStack { + Text(itemText(item)) + Spacer() + Button(buttonText) { action(item) } + } + } + .frame(minHeight: 400) + Button("Close") { showList = false } + }.padding() + } + } + + private func showListIfNonEmpty() { + guard !items.isEmpty else { + return + } + showList = true + } +} + +extension GenerationIssuesActionView where S == Set { + + init(title: String, statusWhenNonEmpty: IssueStatus = .error, items: Set, buttonText: String, action: @escaping (T) -> Void) { + self.title = title + self.statusWhenNonEmpty = statusWhenNonEmpty + self.items = items + self.buttonText = buttonText + self.itemText = { $0 } + self.action = action + } +}