From cc19ff4a6fae42296fb0329ef35abe06d4008354 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Mon, 6 Jan 2025 17:12:38 +0100 Subject: [PATCH] Add video block --- CHDataManagement.xcodeproj/project.pbxproj | 4 + .../Generator/Blocks/ButtonBlock.swift | 128 ++++++++++++++++++ .../Generator/Blocks/ContentBlock.swift | 6 + .../Generator/Blocks/VideoBlock.swift | 3 +- 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 CHDataManagement/Generator/Blocks/ButtonBlock.swift diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 6714f09..95166dd 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -241,6 +241,7 @@ E2FE0F5E2D2BE190002963B7 /* FileResourceFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F5D2D2BE18B002963B7 /* FileResourceFile.swift */; }; E2FE0F602D2C0422002963B7 /* VideoBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F5F2D2C041E002963B7 /* VideoBlock.swift */; }; E2FE0F622D2C0D8D002963B7 /* VersionedVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F612D2C0D8D002963B7 /* VersionedVideo.swift */; }; + E2FE0F642D2C2F4D002963B7 /* ButtonBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F632D2C2F46002963B7 /* ButtonBlock.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -473,6 +474,7 @@ E2FE0F5D2D2BE18B002963B7 /* FileResourceFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileResourceFile.swift; sourceTree = ""; }; E2FE0F5F2D2C041E002963B7 /* VideoBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoBlock.swift; sourceTree = ""; }; E2FE0F612D2C0D8D002963B7 /* VersionedVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionedVideo.swift; sourceTree = ""; }; + E2FE0F632D2C2F46002963B7 /* ButtonBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonBlock.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -932,6 +934,7 @@ E2FE0F412D2B480B002963B7 /* OtherCodeBlock.swift */, E2FE0F3F2D2B45CD002963B7 /* SwiftBlock.swift */, E2FE0F5F2D2C041E002963B7 /* VideoBlock.swift */, + E2FE0F632D2C2F46002963B7 /* ButtonBlock.swift */, ); path = Blocks; sourceTree = ""; @@ -1273,6 +1276,7 @@ E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */, E25DA56D2D00EBCF00AEF16D /* NavigationBarSettingsView.swift in Sources */, E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */, + E2FE0F642D2C2F4D002963B7 /* ButtonBlock.swift in Sources */, E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */, E29D31342D03B5D50051B7F4 /* IconButton.swift in Sources */, E25DA5712D01015400AEF16D /* GenerationContentView.swift in Sources */, diff --git a/CHDataManagement/Generator/Blocks/ButtonBlock.swift b/CHDataManagement/Generator/Blocks/ButtonBlock.swift new file mode 100644 index 0000000..7e12148 --- /dev/null +++ b/CHDataManagement/Generator/Blocks/ButtonBlock.swift @@ -0,0 +1,128 @@ + +struct ButtonsBlock: BlockLineProcessor { + + static let blockId: ContentBlock = .buttons + + let content: Content + + let results: PageGenerationResults + + let language: ContentLanguage + + private let buttons: ButtonBlock + + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { + self.content = content + self.results = results + self.language = language + self.buttons = .init(content: content, results: results, language: language) + } + + func process(_ lines: [String], markdown: Substring) -> String { + let buttons = lines.split(separator: "").compactMap { buttonLines in + makeButton(buttonLines, markdown: markdown) + } + return ContentButtons(items: buttons).content + } + + func makeButton(_ lines: ArraySlice, markdown: Substring) -> ContentButtons.Item? { + let arguments: [ButtonBlock.Key : String] = lines.reduce(into: [:]) { dict, line in + guard line.trimmed != "" else { + return + } + let (rawKey, rawValue) = line.splitAtFirst(":") + guard let key = ButtonBlock.Key(rawValue: rawKey.trimmed) else { + print("Invalid key \(rawKey)") + invalid(markdown) + return + } + dict[key] = rawValue.trimmed + } + return self.buttons.process(arguments, markdown: markdown) + } +} + +struct ButtonBlock: KeyedBlockProcessor { + enum Key: String, Equatable { + case icon + case file + case text + case name + case url + case event + } + + static let blockId: ContentBlock = .button + + let content: Content + + let results: PageGenerationResults + + let language: ContentLanguage + + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { + self.content = content + self.results = results + self.language = language + } + + func process(_ arguments: [Key : String], markdown: Substring) -> String { + guard let button = process(arguments, markdown: markdown) else { + return "" + } + return ContentButtons(items: [button]).content + } + + func process(_ arguments: [Key : String], markdown: Substring) -> ContentButtons.Item? { + guard let rawIcon = arguments[.icon], + let text = arguments[.text], + let icon = PageIcon(rawValue: rawIcon) else { + invalid(markdown) + return nil + } + + if let file = arguments[.file] { + let name = arguments[.name] + return download(fileId: file, icon: icon, text: text, filename: name) + } + if let url = arguments[.url] { + return link(url: url, icon: icon, text: text, markdown: markdown) + } + if let event = arguments[.event] { + return action(event: event, icon: icon, text: text) + } + invalid(markdown) + return nil + } + + private func download(fileId: String, icon: PageIcon, text: String, filename: String?) -> ContentButtons.Item? { + guard let file = content.file(fileId) else { + results.missing(file: fileId, source: "Download button") + return nil + } + results.require(file: file) + results.require(icon: icon) + return ContentButtons.Item( + icon: icon, + filePath: file.absoluteUrl, + text: text, + downloadFileName: filename) + } + + private func link(url: String, icon: PageIcon, text: String, markdown: Substring) -> ContentButtons.Item? { + guard let encodedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { + invalid(markdown) + return nil + } + + results.externalLink(to: url) + results.require(icon: icon) + return .init(icon: icon, filePath: url, text: text) + } + + private func action(event: String, icon: PageIcon, text: String) -> ContentButtons.Item? { + results.require(icon: icon) + + return .init(icon: icon, filePath: nil, text: text, onClickText: event) + } +} diff --git a/CHDataManagement/Generator/Blocks/ContentBlock.swift b/CHDataManagement/Generator/Blocks/ContentBlock.swift index bbcfb34..a363e66 100644 --- a/CHDataManagement/Generator/Blocks/ContentBlock.swift +++ b/CHDataManagement/Generator/Blocks/ContentBlock.swift @@ -7,11 +7,17 @@ enum ContentBlock: String, CaseIterable { case video + case button + + case buttons + var processor: BlockProcessor.Type { switch self { case .audio: return AudioBlock.self case .swift: return SwiftBlock.self case .video: return VideoBlock.self + case .button: return ButtonBlock.self + case .buttons: return ButtonsBlock.self } } } diff --git a/CHDataManagement/Generator/Blocks/VideoBlock.swift b/CHDataManagement/Generator/Blocks/VideoBlock.swift index 6009866..12a806d 100644 --- a/CHDataManagement/Generator/Blocks/VideoBlock.swift +++ b/CHDataManagement/Generator/Blocks/VideoBlock.swift @@ -1,7 +1,7 @@ struct VideoBlock: OrderedKeyBlockProcessor { - static let blockId: ContentBlock = .audio + static let blockId: ContentBlock = .video let content: Content @@ -33,6 +33,7 @@ struct VideoBlock: OrderedKeyBlockProcessor { results.missing(file: fileId, source: "Video Block: \(key)") continue } + results.require(file: file) let source = Source(file: file, type: sourceType) sources.append(source) }