diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index d35565d..7975c4c 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 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 */; }; + E20BCCAB2D53B86900B8DBEB /* GenerationResultsIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20BCCAA2D53B85300B8DBEB /* GenerationResultsIssueView.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 */; }; @@ -272,6 +273,7 @@ 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 = ""; }; + E20BCCAA2D53B85300B8DBEB /* GenerationResultsIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationResultsIssueView.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 = ""; }; @@ -540,6 +542,7 @@ E20BCCA02D53985500B8DBEB /* Generation */ = { isa = PBXGroup; children = ( + E20BCCAA2D53B85300B8DBEB /* GenerationResultsIssueView.swift */, E25DA5702D01015400AEF16D /* GenerationContentView.swift */, ); path = Generation; @@ -1292,6 +1295,7 @@ E29D31432D0488960051B7F4 /* MainContentView.swift in Sources */, E29D31282D0371930051B7F4 /* ContentPageVideo.swift in Sources */, E20BCC9F2D53851400B8DBEB /* SelectableListItem.swift in Sources */, + E20BCCAB2D53B86900B8DBEB /* GenerationResultsIssueView.swift in Sources */, E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */, E2FD1D462D46428100B48627 /* PageIconView.swift in Sources */, E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */, diff --git a/CHDataManagement/Views/Generation/GenerationContentView.swift b/CHDataManagement/Views/Generation/GenerationContentView.swift index 8646963..a038d76 100644 --- a/CHDataManagement/Views/Generation/GenerationContentView.swift +++ b/CHDataManagement/Views/Generation/GenerationContentView.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols struct GenerationContentView: View { @@ -34,71 +35,61 @@ struct GenerationContentView: View { if content.isGeneratingWebsite { ProgressView() .progressViewStyle(.circular) - .frame(height: 25) + .scaleEffect(0.6) + .frame(height: 16) } 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) + GenerationResultsIssueView( + text: "\(content.results.imagesToGenerate.count) images", + status: .nominal, + items: { [] }) + GenerationResultsIssueView( + text: "\(content.results.resultCount) items processed", + status: .nominal, + items: { [] }) + GenerationStringIssuesView( + text: "external links", + statusWhenNonEmpty: .nominal, + items: $content.results.externalLinks) + GenerationStringIssuesView( + text: "required files", + statusWhenNonEmpty: .nominal, + items: $content.results.requiredFiles) { $0.id } + GenerationStringIssuesView( + text: "empty pages", + statusWhenNonEmpty: .warning, + items: $content.results.emptyPages) { "\($0.pageId) (\($0.language))" } + GenerationStringIssuesView( + text: "inaccessible files", + items: $content.results.inaccessibleFiles) { $0.id } + GenerationStringIssuesView( + text: "unparsable files", + items: $content.results.unparsableFiles) { $0.id } + GenerationStringIssuesView( + text: "unsaved output files", + items: $content.results.unsavedOutputFiles) + GenerationStringIssuesView( + text: "missing files", + items: $content.results.missingFiles) + GenerationStringIssuesView( + text: "missing tags", + items: $content.results.missingTags) + GenerationStringIssuesView( + text: "missing pages", + items: $content.results.missingPages) + GenerationStringIssuesView( + text: "invalid commands", + items: $content.results.invalidCommands) + GenerationStringIssuesView( + text: "invalid blocks", + items: $content.results.invalidBlocks) + GenerationStringIssuesView( + text: "warnings", + statusWhenNonEmpty: .warning, + items: $content.results.warnings) HorizontalCenter { Button(action: { dismiss() }) { Text("Close") diff --git a/CHDataManagement/Views/Generation/GenerationResultsIssueView.swift b/CHDataManagement/Views/Generation/GenerationResultsIssueView.swift new file mode 100644 index 0000000..6c04bdc --- /dev/null +++ b/CHDataManagement/Views/Generation/GenerationResultsIssueView.swift @@ -0,0 +1,125 @@ +import SwiftUI +import SFSafeSymbols + +enum IssueStatus { + case nominal + case warning + case error + + var symbol: SFSymbol { + switch self { + case .nominal: .checkmarkCircleFill + case .warning, .error: .exclamationmarkTriangle + } + } + + var color: Color { + switch self { + case .nominal: .green + case .warning: .yellow + case .error: .red + } + } +} + +struct GenerationStringIssuesView: View where T: Hashable { + + let text: String + + let statusWhenNonEmpty: IssueStatus + + @Binding + var items: Set + + let map: (T) -> String + + @State + private var showList = false + + var status: IssueStatus { + items.isEmpty ? .nominal : statusWhenNonEmpty + } + + init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding>, map: @escaping (T) -> String) { + self.text = text + self.statusWhenNonEmpty = statusWhenNonEmpty + self._items = items + self.map = map + } + + var body: some View { + HStack { + Button(action: showListIfNonEmpty) { + Image(systemSymbol: status.symbol) + .foregroundStyle(status.color) + }.buttonStyle(.plain) + Text("\(items.count) \(text)") + } + .sheet(isPresented: $showList) { + VStack { + Text("\(items.count) \(text)") + .font(.title) + List(items.map(map).sorted(), id: \.self) { item in + Text(item) + } + .frame(minHeight: 400) + Button("Close") { showList = false } + }.padding() + } + } + + private func showListIfNonEmpty() { + guard !items.isEmpty else { + return + } + showList = true + } +} + +extension GenerationStringIssuesView where T == String { + + init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding>) { + self.text = text + self.statusWhenNonEmpty = statusWhenNonEmpty + self._items = items + self.map = { $0 } + } +} + +struct GenerationResultsIssueView: View { + + @State + private var showList = false + + let text: String + + let status: IssueStatus + + let items: () -> [String] + + var body: some View { + HStack { + Button(action: showListIfNonEmpty) { + Image(systemSymbol: status.symbol) + .foregroundStyle(status.color) + }.buttonStyle(.plain) + Text(text) + } + .sheet(isPresented: $showList) { + VStack { + List(items(), id: \.self) { item in + Text(item) + } + .frame(minHeight: 400) + Button("Close") { showList = false } + } + } + } + + private func showListIfNonEmpty() { +// guard !items.isEmpty else { +// return +// } + showList = true + } +}