diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index d244062..d3a6ad1 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -180,6 +180,8 @@ E2B4821A2D63AFF6005C309D /* NotificationSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B482192D63AFEE005C309D /* NotificationSender.swift */; }; E2B4821C2D63B062005C309D /* NotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B4821B2D63B05B005C309D /* NotificationRequest.swift */; }; E2B4821E2D63B096005C309D /* WebNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B4821D2D63B096005C309D /* WebNotification.swift */; }; + E2B482202D670753005C309D /* WallpaperSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B4821F2D67074C005C309D /* WallpaperSlider.swift */; }; + E2B482222D676BF7005C309D /* PhoneScreensBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B482212D676BEB005C309D /* PhoneScreensBlock.swift */; }; E2B85F362C426BEE0047CD0C /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2B85F352C426BEE0047CD0C /* SFSafeSymbols */; }; E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F3A2C428F0D0047CD0C /* Post.swift */; }; E2B85F3D2C4293F80047CD0C /* FeedPageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F3C2C4293F80047CD0C /* FeedPageGenerator.swift */; }; @@ -447,6 +449,8 @@ E2B482192D63AFEE005C309D /* NotificationSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSender.swift; sourceTree = ""; }; E2B4821B2D63B05B005C309D /* NotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequest.swift; sourceTree = ""; }; E2B4821D2D63B096005C309D /* WebNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebNotification.swift; sourceTree = ""; }; + E2B4821F2D67074C005C309D /* WallpaperSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperSlider.swift; sourceTree = ""; }; + E2B482212D676BEB005C309D /* PhoneScreensBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneScreensBlock.swift; sourceTree = ""; }; E2B85F3A2C428F0D0047CD0C /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; E2B85F3C2C4293F80047CD0C /* FeedPageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPageGenerator.swift; sourceTree = ""; }; E2B85F402C4294790047CD0C /* PageHead.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHead.swift; sourceTree = ""; }; @@ -690,6 +694,7 @@ E29D311E2D0320D90051B7F4 /* ContentElements */ = { isa = PBXGroup; children = ( + E2B4821F2D67074C005C309D /* WallpaperSlider.swift */, E29D31C12D0DBED70051B7F4 /* AudioPlayer */, E29D318A2D0B07E60051B7F4 /* ContentBox.swift */, E29D31212D0363FA0051B7F4 /* ContentButtons.swift */, @@ -1103,6 +1108,7 @@ E2FE0F342D2B27E6002963B7 /* Blocks */ = { isa = PBXGroup; children = ( + E2B482212D676BEB005C309D /* PhoneScreensBlock.swift */, E2FE0F652D2C3B33002963B7 /* LabelsBlock.swift */, E2FE0F5C2D2BD006002963B7 /* Types */, E2FE0F322D2B265F002963B7 /* AudioBlock.swift */, @@ -1328,6 +1334,7 @@ E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */, E29D31632D06E95D0051B7F4 /* NavigationIcon.swift in Sources */, E2FE0F362D2B27F9002963B7 /* BlockProcessor.swift in Sources */, + E2B482222D676BF7005C309D /* PhoneScreensBlock.swift in Sources */, E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */, E22990462D10B7A7009F8D77 /* SecurityScopeStatus.swift in Sources */, E29D31512D06168E0051B7F4 /* PostListView.swift in Sources */, @@ -1343,6 +1350,7 @@ E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */, E29D31492D0489BB0051B7F4 /* AddFileView.swift in Sources */, E2A21C2A2CB2AA4F0060935B /* Post+Mock.swift in Sources */, + E2B482202D670753005C309D /* WallpaperSlider.swift in Sources */, E29D312E2D03A0D70051B7F4 /* LocalizedPageDetailView.swift in Sources */, E2581DED2C75202400F1F079 /* Tag.swift in Sources */, E29D31302D03A2C50051B7F4 /* DescriptionField.swift in Sources */, diff --git a/CHDataManagement/Generator/Blocks/ContentBlock.swift b/CHDataManagement/Generator/Blocks/ContentBlock.swift index ea11f91..e473893 100644 --- a/CHDataManagement/Generator/Blocks/ContentBlock.swift +++ b/CHDataManagement/Generator/Blocks/ContentBlock.swift @@ -13,6 +13,8 @@ enum ContentBlock: String, CaseIterable { case labels + case screens + var processor: BlockProcessor.Type { switch self { case .audio: return AudioBlock.self @@ -21,6 +23,7 @@ enum ContentBlock: String, CaseIterable { case .button: return ButtonBlock.self case .buttons: return ButtonsBlock.self case .labels: return LabelsBlock.self + case .screens: return PhoneScreensBlock.self } } } diff --git a/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift b/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift new file mode 100644 index 0000000..4ee68d5 --- /dev/null +++ b/CHDataManagement/Generator/Blocks/PhoneScreensBlock.swift @@ -0,0 +1,104 @@ + +struct PhoneScreensBlock: OrderedKeyBlockProcessor { + + enum Key: String { + case id + case frame + case tall + case wide + } + + static let blockId: ContentBlock = .screens + + 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: Key, value: String)], markdown: Substring) -> String { + print("Processing Phone Screens Block") + guard let frameId = arguments.first(where: {$0.key == .frame })?.value else { + invalid(markdown) + return "" + } + guard let frameFile = content.file(frameId) else { + results.missing(file: frameId, source: "Wallpaper Block") + return "" + } + + guard let id = arguments.first(where: {$0.key == .id })?.value else { + invalid(markdown) + return "" + } + + let remaining = arguments.filter { $0.key == .tall || $0.key == .wide } + + var images = [WallpaperSlider.Image]() + var tall: FileResource? + var wide: FileResource? + for (key, fileId) in remaining { + guard let file = content.file(fileId) else { + print("Missing file: \(fileId)") + results.missing(file: fileId, source: "Wallpaper Block") + return "" + } + if key == .tall { + if tall != nil { + print("Another tall image: \(file.id)") + invalid(markdown) + return "" + } + if let other = wide { + let image = WallpaperSlider.Image( + tall: file, + wide: other, + language: language, + results: results) + images.append(image) + wide = nil + continue + } + tall = file + continue + } + // key == .wide + if wide != nil { + print("Another wide image: \(file.id)") + invalid(markdown) + return "" + } + guard let other = tall else { + wide = file + continue + } + let image = WallpaperSlider.Image( + tall: other, + wide: file, + language: language, + results: results) + images.append(image) + tall = nil + } + if tall != nil || wide != nil { + invalid(markdown) + print("Wide/tall does not have the same number of items") + return "" + } + + let frame = WallpaperSlider.Frame(frame: frameFile, language: language, results: results) + let slider = WallpaperSlider(frame: frame, images: images, id: id) + + results.require(footer: slider.script) + results.require(headers: .swiperJs, .swiperCss) + results.require(header: .style(slider.style)) + + return slider.content + } +} diff --git a/CHDataManagement/Generator/HeaderElement.swift b/CHDataManagement/Generator/HeaderElement.swift index 393cc39..52bc4ee 100644 --- a/CHDataManagement/Generator/HeaderElement.swift +++ b/CHDataManagement/Generator/HeaderElement.swift @@ -52,6 +52,8 @@ enum HeaderElement { case manifest(FileResource) + case style(String) + var order: Int { switch self { case .charset: 1 @@ -69,6 +71,7 @@ enum HeaderElement { case .ogDescription: 104 case .ogImage: 105 case .ogUrl: 106 + case .style: 200 } } @@ -136,6 +139,8 @@ extension HeaderElement { return "" case .manifest(let file): return "" + case .style(let style): + return "" } } } @@ -174,6 +179,8 @@ extension HeaderElement: CustomStringConvertible { return "robots" case .manifest: return "manifest" + case .style: + return "style" } } } diff --git a/CHDataManagement/Generator/KnownHeaderElement.swift b/CHDataManagement/Generator/KnownHeaderElement.swift index 8833a39..84fb2d9 100644 --- a/CHDataManagement/Generator/KnownHeaderElement.swift +++ b/CHDataManagement/Generator/KnownHeaderElement.swift @@ -1,19 +1,25 @@ -enum KnownHeaderElement: Int { +enum KnownHeaderElement { - case codeHightlighting = 4 + case codeHightlighting - case modelViewer = 3 + case modelViewer /// CSS File to style the audio player - case audioPlayerCss = 1 + case audioPlayerCss /// JavaScript file for the audio player - case audioPlayerJs = 2 + case audioPlayerJs - case imageCompareJs = 5 + case imageCompareJs - case imageCompareCss = 6 + case imageCompareCss + + case swiperCss + + case swiperJs + + case style(String) func header(content: Content) -> HeaderElement? { switch self { @@ -41,15 +47,32 @@ enum KnownHeaderElement: Int { if let file = content.settings.pages.imageCompareCssFile { return .css(file: file, order: HeaderElement.imageCompareCssOrder) } + case .swiperCss: + if let swiperCss = content.settings.posts.swiperCssFile { + return .css(file: swiperCss, order: HeaderElement.swiperCssFileOrder) + } + case .swiperJs: + if let swiperJs = content.settings.posts.swiperJsFile { + return .js(file: swiperJs, defer: true) + } + case .style(let code): + return .style(code) } return nil } } -extension KnownHeaderElement: Comparable { +extension KnownHeaderElement: Equatable { - static func < (lhs: KnownHeaderElement, rhs: KnownHeaderElement) -> Bool { - lhs.rawValue < rhs.rawValue + static func == (lhs: KnownHeaderElement, rhs: KnownHeaderElement) -> Bool { + lhs.description == rhs.description + } +} + +extension KnownHeaderElement: Hashable { + + func hash(into hasher: inout Hasher) { + hasher.combine(description) } } @@ -69,6 +92,12 @@ extension KnownHeaderElement: CustomStringConvertible { return "image-compare-js" case .imageCompareCss: return "image-compare-css" + case .swiperCss: + return "swiper-css" + case .swiperJs: + return "swiper-js" + case .style(let style): + return "style: " + style } } } diff --git a/CHDataManagement/Page Elements/ContentElements/WallpaperSlider.swift b/CHDataManagement/Page Elements/ContentElements/WallpaperSlider.swift new file mode 100644 index 0000000..abb7056 --- /dev/null +++ b/CHDataManagement/Page Elements/ContentElements/WallpaperSlider.swift @@ -0,0 +1,167 @@ + +struct WallpaperSlider: HtmlProducer { + + static let frameHeight = 585 + + static let frameWidth = 270 + + static let imageWidth = 250 + + static let imageHeight = 500 + + let frame: Frame + + let images: [Image] + + let id: String + + struct Frame { + + let alt: String + + let x1: ImageVersion + + let x2: ImageVersion + + init(frame: FileResource, language: ContentLanguage, results: PageGenerationResults) { + self.x1 = frame.imageVersion( + width: WallpaperSlider.frameWidth * 2, + height: WallpaperSlider.frameHeight * 2, + type: frame.type) + + self.x2 = frame.imageVersion( + width: WallpaperSlider.frameWidth * 2, + height: WallpaperSlider.frameHeight * 2, + type: frame.type) + + self.alt = frame.localized(in: language) ?? "A frame" + + results.require(image: x1) + results.require(image: x2) + } + } + + struct Image { + + let display: ImageSet + + let wide: DownloadImage + + let tall: DownloadImage + + struct DownloadImage { + + let url: String + + let button: String + } + + init(tall: FileResource, wide: FileResource, language: ContentLanguage, results: PageGenerationResults) { + self.display = tall.imageSet( + width: WallpaperSlider.imageWidth, + height: WallpaperSlider.imageHeight, + language: language) + let wideButton = language == .english ? "Wide" : "Breit" + self.wide = .init(url: wide.absoluteUrl, button: wideButton) + + let tallButton = language == .english ? "Tall" : "Hoch" + self.tall = .init(url: tall.absoluteUrl, button: tallButton) + + results.require(imageSet: display) + results.require(file: wide) + results.require(file: tall) + results.require(icon: .buttonDownload) + } + } + + func populate(_ result: inout String) { + let icon: PageIcon = .buttonDownload + + result += "
" + result += "" + result += "
" + for image in images { + result += "
" + image.display.populate(&result) + result += "
" // Close button-container, swiper-slide + } + result += "
" // Close swiper-wrapper + result += "
" + result += "
" + result += "
" // Close swiper + result += "
" // Close swiper-container + } + + var style: String { + """ + .swiper { + overflow: hidden; + border-radius: 10px; + } + .swiper-container { + position: relative; + max-width: 250px; + max-height: 500px; + margin: auto; + margin-top: 40px; + aspect-ratio: 1 / 2; + } + + .overlay-image { + position: absolute; + top: -8.5%; + left: -4%; + width: 108%; + height: 117%; + z-index: 10; + object-fit: contain; + pointer-events: none; + } + .button-container { + position: absolute; + bottom: 15%; + width: 100%; + margin: auto; + display: flex; + justify-content: space-evenly; + } + .custom-button { + padding: 10px 10px; + width: 60px; + border: none; + border-radius: 10px; + background-color: rgba(29, 29, 29, 0.74); + color: white; + cursor: pointer; + text-decoration: none; + font-size: 12px; + justify-content: space-evenly; + } + .swiper-button-next { + margin-right: 5px; + } + .swiper-button-prev { + margin-left: 5px; + } + """ + } + + var script: String { + """ + + """ + } +}