From ac7fbdd63892881e9d91327fd5e094dcbfe52c72 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sun, 5 Jan 2025 20:16:16 +0100 Subject: [PATCH] Implement image comparison command --- CHDataManagement.xcodeproj/project.pbxproj | 20 +++++-- .../Generator/HeaderElement.swift | 2 + CHDataManagement/Generator/ImageSet.swift | 14 ++--- .../Generator/KnownHeaderElement.swift | 16 ++++++ .../Page Content/AudioPlayerCommand.swift | 2 +- .../Page Content/BoxCommandProcessor.swift | 2 +- .../Page Content/ButtonCommand.swift | 2 +- .../Page Content/CommandProcessor.swift | 2 +- .../Page Content/IconCommandProcessor.swift | 2 +- .../Page Content/ImageCompareCommand.swift | 53 +++++++++++++++++++ .../Page Content/LabelsCommand.swift | 2 +- .../Page Content/PageHtmlProcessor.swift | 2 +- .../Page Content/VideoCommandProcessor.swift | 2 +- .../Generator/PageContentGenerator.swift | 26 +++++---- .../Generator/ShorthandMarkdownKey.swift | 6 +++ CHDataManagement/Main/MainView.swift | 2 - CHDataManagement/Model/FileResource.swift | 5 +- .../Model/Settings/PageSettings.swift | 11 +++- .../Icons/ImageCompareIcons.swift | 12 +++++ .../ContentElements/Icons/PageIcon.swift | 5 ++ .../ContentElements/ImageCompare.swift | 20 +++++++ .../Model/Settings/PageSettingsFile.swift | 8 ++- .../Settings/PageSettingsDetailView.swift | 24 +++++++-- 23 files changed, 200 insertions(+), 40 deletions(-) create mode 100644 CHDataManagement/Generator/Page Content/ImageCompareCommand.swift create mode 100644 CHDataManagement/Page Elements/ContentElements/Icons/ImageCompareIcons.swift create mode 100644 CHDataManagement/Page Elements/ContentElements/ImageCompare.swift diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index f3fa458..4a63198 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -214,6 +214,9 @@ E2FE0F202D29A70E002963B7 /* Array+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */; }; E2FE0F222D2A84A0002963B7 /* VideoCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */; }; E2FE0F242D2A8C21002963B7 /* TagDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */; }; + E2FE0F262D2AF9B0002963B7 /* ImageCompareCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F252D2AF9AA002963B7 /* ImageCompareCommand.swift */; }; + E2FE0F282D2AFB11002963B7 /* ImageCompare.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F272D2AFB0A002963B7 /* ImageCompare.swift */; }; + E2FE0F2A2D2AFBE6002963B7 /* ImageCompareIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F292D2AFBE3002963B7 /* ImageCompareIcons.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -419,6 +422,9 @@ E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Remove.swift"; sourceTree = ""; }; E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCommandProcessor.swift; sourceTree = ""; }; E2FE0F232D2A8C1A002963B7 /* TagDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagDisplayView.swift; sourceTree = ""; }; + E2FE0F252D2AF9AA002963B7 /* ImageCompareCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompareCommand.swift; sourceTree = ""; }; + E2FE0F272D2AFB0A002963B7 /* ImageCompare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompare.swift; sourceTree = ""; }; + E2FE0F292D2AFBE3002963B7 /* ImageCompareIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompareIcons.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -528,6 +534,7 @@ E29D31232D0366820051B7F4 /* TagList.swift */, E29D31212D0363FA0051B7F4 /* ContentButtons.swift */, E29D311F2D0320E20051B7F4 /* ContentLabels.swift */, + E2FE0F272D2AFB0A002963B7 /* ImageCompare.swift */, ); path = ContentElements; sourceTree = ""; @@ -568,12 +575,13 @@ E29D31AB2D0DA52C0051B7F4 /* Icons */ = { isa = PBXGroup; children = ( - E2FE0EF52D1D6DEE002963B7 /* Icon.swift */, - E2FE0EF32D1D6D22002963B7 /* GeneralIcons.swift */, - E29D31B42D0DA8490051B7F4 /* PageIcon.swift */, + E29D31AC2D0DA5310051B7F4 /* AudioPlayerIcons.swift */, E29D31B22D0DA6E50051B7F4 /* ButtonIcons.swift */, E29D31B02D0DA5510051B7F4 /* ContentIcon.swift */, - E29D31AC2D0DA5310051B7F4 /* AudioPlayerIcons.swift */, + E2FE0EF32D1D6D22002963B7 /* GeneralIcons.swift */, + E2FE0EF52D1D6DEE002963B7 /* Icon.swift */, + E2FE0F292D2AFBE3002963B7 /* ImageCompareIcons.swift */, + E29D31B42D0DA8490051B7F4 /* PageIcon.swift */, E29D317E2D086F490051B7F4 /* StatisticsIcons.swift */, ); path = Icons; @@ -582,6 +590,7 @@ E29D31B62D0DAC030051B7F4 /* Page Content */ = { isa = PBXGroup; children = ( + E2FE0F252D2AF9AA002963B7 /* ImageCompareCommand.swift */, E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */, E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */, E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */, @@ -998,6 +1007,7 @@ E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */, E22990422D107A95009F8D77 /* ImageVersion.swift in Sources */, E29D317F2D086F4C0051B7F4 /* StatisticsIcons.swift in Sources */, + E2FE0F282D2AFB11002963B7 /* ImageCompare.swift in Sources */, E229904E2D13535C009F8D77 /* SecurityBookmark.swift in Sources */, E2A21C082CB17B870060935B /* TagView.swift in Sources */, E29D313D2D047C1B0051B7F4 /* LocalizedPageContentView.swift in Sources */, @@ -1072,6 +1082,7 @@ E29D31B82D0DAC250051B7F4 /* ButtonCommand.swift in Sources */, E29D31962D0C186E0051B7F4 /* PathSettings.swift in Sources */, E2B85F412C4294790047CD0C /* PageHead.swift in Sources */, + E2FE0F2A2D2AFBE6002963B7 /* ImageCompareIcons.swift in Sources */, E29D316B2D07488B0051B7F4 /* PostListPageGenerator.swift in Sources */, E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */, E22990382D0F7B32009F8D77 /* OptionalImagePropertyView.swift in Sources */, @@ -1151,6 +1162,7 @@ E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */, E2A21C362CB9A3D70060935B /* PathSettingsView.swift in Sources */, E29D31362D0435430051B7F4 /* TabSelection.swift in Sources */, + E2FE0F262D2AF9B0002963B7 /* ImageCompareCommand.swift in Sources */, E2FE0F1E2D281AE1002963B7 /* TagOverviewGenerator.swift in Sources */, E29D31572D06D38B0051B7F4 /* AddTagView.swift in Sources */, E25DA5362D0041EB00AEF16D /* PostSettingsFile.swift in Sources */, diff --git a/CHDataManagement/Generator/HeaderElement.swift b/CHDataManagement/Generator/HeaderElement.swift index 2ab8f5d..1216de0 100644 --- a/CHDataManagement/Generator/HeaderElement.swift +++ b/CHDataManagement/Generator/HeaderElement.swift @@ -12,6 +12,8 @@ extension HeaderElement { static let defaultCssFileOrder = 42 static let audioPlayerCssOrder = 43 + + static let imageCompareCssOrder = 44 } enum HeaderElement { diff --git a/CHDataManagement/Generator/ImageSet.swift b/CHDataManagement/Generator/ImageSet.swift index 2df62e7..bf02f56 100644 --- a/CHDataManagement/Generator/ImageSet.swift +++ b/CHDataManagement/Generator/ImageSet.swift @@ -1,6 +1,6 @@ import Foundation -struct ImageSet { +struct ImageSet: HtmlProducer { let image: FileResource @@ -12,12 +12,15 @@ struct ImageSet { let description: String - init(image: FileResource, maxWidth: Int, maxHeight: Int, description: String, quality: CGFloat = 0.7) { + let extraAttributes: String + + init(image: FileResource, maxWidth: Int, maxHeight: Int, description: String, quality: CGFloat = 0.7, extraAttributes: String? = nil) { self.image = image self.maxWidth = maxWidth self.maxHeight = maxHeight self.description = description self.quality = quality + self.extraAttributes = extraAttributes ?? "" } var jobs: [ImageVersion] { @@ -36,17 +39,16 @@ struct ImageSet { ] } - var content: String { + func populate(_ result: inout String) { let fileExtension = image.type.fileExtension.map { "." + $0 } ?? "" let prefix1x = "/\(image.outputImageFolder)/\(maxWidth)x\(maxHeight)" let prefix2x = "/\(image.outputImageFolder)/\(maxWidth*2)x\(maxHeight*2)" - var result = "" + result += "" result += "" result += "" - result += "\(description.htmlEscaped())" + result += "\(description.htmlEscaped())" result += "" - return result } } diff --git a/CHDataManagement/Generator/KnownHeaderElement.swift b/CHDataManagement/Generator/KnownHeaderElement.swift index 1d4d327..45ede26 100644 --- a/CHDataManagement/Generator/KnownHeaderElement.swift +++ b/CHDataManagement/Generator/KnownHeaderElement.swift @@ -11,6 +11,10 @@ enum KnownHeaderElement: Int { /// JavaScript file for the audio player case audioPlayerJs = 2 + case imageCompareJs = 5 + + case imageCompareCss = 6 + func header(content: Content) -> HeaderElement? { switch self { case .codeHightlighting: @@ -29,6 +33,14 @@ enum KnownHeaderElement: Int { if let file = content.settings.pages.audioPlayerJsFile { return .js(file: file, defer: true) } + case .imageCompareJs: + if let file = content.settings.pages.imageCompareJsFile { + return .js(file: file, defer: true) + } + case .imageCompareCss: + if let file = content.settings.pages.imageCompareCssFile { + return .css(file: file, order: HeaderElement.imageCompareCssOrder) + } } return nil } @@ -53,6 +65,10 @@ extension KnownHeaderElement: CustomStringConvertible { return "audio-player-css" case .audioPlayerJs: return "audio-player-js" + case .imageCompareJs: + return "image-compare-js" + case .imageCompareCss: + return "image-compare-css" } } } diff --git a/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift b/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift index 73b4826..eef3b96 100644 --- a/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift +++ b/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift @@ -8,7 +8,7 @@ struct AudioPlayerCommandProcessor: CommandProcessor { let results: PageGenerationResults - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results } diff --git a/CHDataManagement/Generator/Page Content/BoxCommandProcessor.swift b/CHDataManagement/Generator/Page Content/BoxCommandProcessor.swift index f99ac73..747622c 100644 --- a/CHDataManagement/Generator/Page Content/BoxCommandProcessor.swift +++ b/CHDataManagement/Generator/Page Content/BoxCommandProcessor.swift @@ -5,7 +5,7 @@ struct BoxCommandProcessor: CommandProcessor { let results: PageGenerationResults - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.results = results } diff --git a/CHDataManagement/Generator/Page Content/ButtonCommand.swift b/CHDataManagement/Generator/Page Content/ButtonCommand.swift index 8ebbe4c..c2ca57d 100644 --- a/CHDataManagement/Generator/Page Content/ButtonCommand.swift +++ b/CHDataManagement/Generator/Page Content/ButtonCommand.swift @@ -7,7 +7,7 @@ struct ButtonCommandProcessor: CommandProcessor { let results: PageGenerationResults - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results } diff --git a/CHDataManagement/Generator/Page Content/CommandProcessor.swift b/CHDataManagement/Generator/Page Content/CommandProcessor.swift index d82fecf..953ae9a 100644 --- a/CHDataManagement/Generator/Page Content/CommandProcessor.swift +++ b/CHDataManagement/Generator/Page Content/CommandProcessor.swift @@ -3,7 +3,7 @@ protocol CommandProcessor { var commandType: ShorthandMarkdownKey { get } - init(content: Content, results: PageGenerationResults) + init(content: Content, results: PageGenerationResults, language: ContentLanguage) func process(_ arguments: [String], markdown: Substring) -> String } diff --git a/CHDataManagement/Generator/Page Content/IconCommandProcessor.swift b/CHDataManagement/Generator/Page Content/IconCommandProcessor.swift index 9b8fdef..8a2e96f 100644 --- a/CHDataManagement/Generator/Page Content/IconCommandProcessor.swift +++ b/CHDataManagement/Generator/Page Content/IconCommandProcessor.swift @@ -5,7 +5,7 @@ struct IconCommandProcessor: CommandProcessor { let results: PageGenerationResults - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.results = results } diff --git a/CHDataManagement/Generator/Page Content/ImageCompareCommand.swift b/CHDataManagement/Generator/Page Content/ImageCompareCommand.swift new file mode 100644 index 0000000..f1073e0 --- /dev/null +++ b/CHDataManagement/Generator/Page Content/ImageCompareCommand.swift @@ -0,0 +1,53 @@ + +struct ImageCompareCommandProcessor: CommandProcessor { + + let commandType: ShorthandMarkdownKey = .imageCompare + + 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: [String], markdown: Substring) -> String { + guard arguments.count == 2 else { + results.invalid(command: .imageCompare, markdown) + return "" + } + let leftImageId = arguments[0] + let rightImageId = arguments[1] + guard let leftImage = content.image(leftImageId) else { + results.missing(file: leftImageId, source: "Image compare") + return "" + } + guard let rightImage = content.image(rightImageId) else { + results.missing(file: rightImageId, source: "Image compare") + return "" + } + + let size = content.settings.pages.contentWidth + + let leftImageSet = leftImage.imageSet( + width: size, height: size, + language: language, + extraAttributes: ImageCompare.extraAttributes) + + let rightImageSet = rightImage.imageSet( + width: size, height: size, + language: language, + extraAttributes: ImageCompare.extraAttributes) + + results.require(imageSet: leftImageSet) + results.require(imageSet: rightImageSet) + results.require(icon: ImageCompare.requiredIcon) + results.require(headers: .imageCompareJs, .imageCompareCss) + + return ImageCompare(left: leftImageSet, right: rightImageSet).content + } +} diff --git a/CHDataManagement/Generator/Page Content/LabelsCommand.swift b/CHDataManagement/Generator/Page Content/LabelsCommand.swift index ec0ca40..38949f3 100644 --- a/CHDataManagement/Generator/Page Content/LabelsCommand.swift +++ b/CHDataManagement/Generator/Page Content/LabelsCommand.swift @@ -7,7 +7,7 @@ struct LabelsCommandProcessor: CommandProcessor { let results: PageGenerationResults - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results } diff --git a/CHDataManagement/Generator/Page Content/PageHtmlProcessor.swift b/CHDataManagement/Generator/Page Content/PageHtmlProcessor.swift index 0c0b101..5a8fe57 100644 --- a/CHDataManagement/Generator/Page Content/PageHtmlProcessor.swift +++ b/CHDataManagement/Generator/Page Content/PageHtmlProcessor.swift @@ -11,7 +11,7 @@ struct PageHtmlProcessor: CommandProcessor { let content: Content - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results } diff --git a/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift b/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift index bedaaea..b88f254 100644 --- a/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift +++ b/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift @@ -7,7 +7,7 @@ struct VideoCommandProcessor: CommandProcessor { let results: PageGenerationResults - init(content: Content, results: PageGenerationResults) { + init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results } diff --git a/CHDataManagement/Generator/PageContentGenerator.swift b/CHDataManagement/Generator/PageContentGenerator.swift index 6c2d647..89b380d 100644 --- a/CHDataManagement/Generator/PageContentGenerator.swift +++ b/CHDataManagement/Generator/PageContentGenerator.swift @@ -25,6 +25,8 @@ final class PageContentParser { private let video: VideoCommandProcessor + private let imageCompare: ImageCompareCommandProcessor + // MARK: Other handlers private let inlineLink: InlineLinkProcessor @@ -43,13 +45,14 @@ final class PageContentParser { self.content = content self.results = results self.language = language - self.buttonHandler = .init(content: content, results: results) - self.labelHandler = .init(content: content, results: results) - self.audioPlayer = .init(content: content, results: results) - self.icons = .init(content: content, results: results) - self.box = .init(content: content, results: results) - self.html = .init(content: content, results: results) - self.video = .init(content: content, results: results) + self.buttonHandler = .init(content: content, results: results, language: language) + self.labelHandler = .init(content: content, results: results, language: language) + self.audioPlayer = .init(content: content, results: results, language: language) + self.icons = .init(content: content, results: results, language: language) + self.box = .init(content: content, results: results, language: language) + self.html = .init(content: content, results: results, language: language) + self.video = .init(content: content, results: results, language: language) + self.imageCompare = .init(content: content, results: results, language: language) self.inlineLink = .init(content: content, results: results, language: language) self.code = .init(results: results) @@ -136,6 +139,8 @@ final class PageContentParser { return handleTagLink(arguments, markdown: markdown) case .icons: return icons.process(arguments, markdown: markdown) + case .imageCompare: + return imageCompare.process(arguments, markdown: markdown) } } @@ -155,12 +160,10 @@ final class PageContentParser { } results.used(file: image) - let caption = arguments.count == 2 ? arguments[1] : nil - let altText = image.localized(in: language) - - let path = image.absoluteUrl guard !image.type.isSvg else { + let path = image.absoluteUrl + let altText = image.localized(in: language) return SvgImage(imagePath: path, altText: altText).content } let thumbnail = image.imageSet(width: thumbnailWidth, height: thumbnailWidth, language: language) @@ -169,6 +172,7 @@ final class PageContentParser { let largeImage = image.imageSet(width: largeImageWidth, height: largeImageWidth, language: language) results.require(imageSet: largeImage) + let caption = arguments.count == 2 ? arguments[1] : nil return PageImage( imageId: imageId.replacingOccurrences(of: ".", with: "-"), thumbnail: thumbnail, diff --git a/CHDataManagement/Generator/ShorthandMarkdownKey.swift b/CHDataManagement/Generator/ShorthandMarkdownKey.swift index ab8831c..29e120d 100644 --- a/CHDataManagement/Generator/ShorthandMarkdownKey.swift +++ b/CHDataManagement/Generator/ShorthandMarkdownKey.swift @@ -53,4 +53,10 @@ enum ShorthandMarkdownKey: String { /// Format: `![icons](icon-id;...)` case icons + /** + Create an image comparison with a slider. + Format: `![compare](image1;image2)` + */ + case imageCompare = "compare" + } diff --git a/CHDataManagement/Main/MainView.swift b/CHDataManagement/Main/MainView.swift index 7371b2b..3440872 100644 --- a/CHDataManagement/Main/MainView.swift +++ b/CHDataManagement/Main/MainView.swift @@ -4,7 +4,6 @@ import SFSafeSymbols /** **Content** - Podcast: Fix audio player, preview image - - Endeavor Basics: -> image compare command - Article Cap Mosaic: -> GIF feature - iPhone Backgrounds: Add page, html @@ -17,7 +16,6 @@ import SFSafeSymbols **Features** - GIF Support (No image set, don't rescale) - - Image compare command `![compare](image1;image2)` - Files: Optional Property `customFilePath` for external files to place them in another location - Files: Property `version` and `sourceUrl` to track asset files - Posts: Generate separate pages for posts to link to diff --git a/CHDataManagement/Model/FileResource.swift b/CHDataManagement/Model/FileResource.swift index 9899f45..249b957 100644 --- a/CHDataManagement/Model/FileResource.swift +++ b/CHDataManagement/Model/FileResource.swift @@ -164,14 +164,15 @@ final class FileResource: Item { return prefix + "." + ext } - func imageSet(width: Int, height: Int, language: ContentLanguage, quality: CGFloat = 0.7) -> ImageSet { + func imageSet(width: Int, height: Int, language: ContentLanguage, quality: CGFloat = 0.7, extraAttributes: String? = nil) -> ImageSet { let description = self.localized(in: language) return .init( image: self, maxWidth: width, maxHeight: height, description: description, - quality: quality) + quality: quality, + extraAttributes: extraAttributes) } func imageVersion(width: Int, height: Int, type: FileType) -> ImageVersion { diff --git a/CHDataManagement/Model/Settings/PageSettings.swift b/CHDataManagement/Model/Settings/PageSettings.swift index f7da147..bd17e2e 100644 --- a/CHDataManagement/Model/Settings/PageSettings.swift +++ b/CHDataManagement/Model/Settings/PageSettings.swift @@ -26,6 +26,12 @@ final class PageSettings: ObservableObject { @Published var modelViewerJsFile: FileResource? + @Published + var imageCompareJsFile: FileResource? + + @Published + var imageCompareCssFile: FileResource? + init(file: PageSettingsFile, files: [String : FileResource]) { self.contentWidth = file.contentWidth self.largeImageWidth = file.largeImageWidth @@ -35,6 +41,7 @@ final class PageSettings: ObservableObject { self.audioPlayerJsFile = file.audioPlayerJsFile.map { files[$0] } self.audioPlayerCssFile = file.audioPlayerCssFile.map { files[$0] } self.modelViewerJsFile = file.modelViewerJsFile.map { files[$0] } + self.imageCompareCssFile = file.imageCompareCssFile.map { files[$0] } } var file: PageSettingsFile { @@ -45,6 +52,8 @@ final class PageSettings: ObservableObject { codeHighlightingJsFile: codeHighlightingJsFile?.id, audioPlayerJsFile: audioPlayerJsFile?.id, audioPlayerCssFile: audioPlayerCssFile?.id, - modelViewerJsFile: modelViewerJsFile?.id) + modelViewerJsFile: modelViewerJsFile?.id, + imageCompareJsFile: imageCompareJsFile?.id, + imageCompareCssFile: imageCompareCssFile?.id) } } diff --git a/CHDataManagement/Page Elements/ContentElements/Icons/ImageCompareIcons.swift b/CHDataManagement/Page Elements/ContentElements/Icons/ImageCompareIcons.swift new file mode 100644 index 0000000..9371fa2 --- /dev/null +++ b/CHDataManagement/Page Elements/ContentElements/Icons/ImageCompareIcons.swift @@ -0,0 +1,12 @@ + +extension Icon { + + struct LeftRightArrow: ContentIcon { + + static let name = "left-right-arrow" + + static let content = """ + + """ + } +} diff --git a/CHDataManagement/Page Elements/ContentElements/Icons/PageIcon.swift b/CHDataManagement/Page Elements/ContentElements/Icons/PageIcon.swift index e235534..da9f7e9 100644 --- a/CHDataManagement/Page Elements/ContentElements/Icons/PageIcon.swift +++ b/CHDataManagement/Page Elements/ContentElements/Icons/PageIcon.swift @@ -53,6 +53,10 @@ enum PageIcon: String, CaseIterable { case audioPlayerNext = "next" + // MARK: Image compare + + case leftRightArrow = "left-right-arrow" + var icon: ContentIcon.Type { switch self { case .statisticsTime: return StatisticsTimeIcon.self @@ -77,6 +81,7 @@ enum PageIcon: String, CaseIterable { case .location: return Icon.Location.self case .poster: return Icon.Poster.self case .video: return Icon.Video.self + case .leftRightArrow:return Icon.LeftRightArrow.self } } } diff --git a/CHDataManagement/Page Elements/ContentElements/ImageCompare.swift b/CHDataManagement/Page Elements/ContentElements/ImageCompare.swift new file mode 100644 index 0000000..c61737c --- /dev/null +++ b/CHDataManagement/Page Elements/ContentElements/ImageCompare.swift @@ -0,0 +1,20 @@ + +struct ImageCompare: HtmlProducer { + + let left: ImageSet + + let right: ImageSet + + static let extraAttributes = " draggable='false''" + + static var requiredIcon: PageIcon { .leftRightArrow } + + func populate(_ result: inout String) { + result += "
" + result += "
\(left.content)
" + result += "
\(right.content)
" + result += "
" + result += "" + result += "
" // Close drag, image-compare + } +} diff --git a/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift b/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift index 88ea923..bc61a86 100644 --- a/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift +++ b/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift @@ -16,6 +16,10 @@ struct PageSettingsFile { let audioPlayerCssFile: String? let modelViewerJsFile: String? + + let imageCompareJsFile: String? + + let imageCompareCssFile: String? } extension PageSettingsFile: Codable { @@ -32,6 +36,8 @@ extension PageSettingsFile { codeHighlightingJsFile: nil, audioPlayerJsFile: nil, audioPlayerCssFile: nil, - modelViewerJsFile: nil) + modelViewerJsFile: nil, + imageCompareJsFile: nil, + imageCompareCssFile: nil) } } diff --git a/CHDataManagement/Views/Settings/PageSettingsDetailView.swift b/CHDataManagement/Views/Settings/PageSettingsDetailView.swift index 106baf5..fef6ce6 100644 --- a/CHDataManagement/Views/Settings/PageSettingsDetailView.swift +++ b/CHDataManagement/Views/Settings/PageSettingsDetailView.swift @@ -34,31 +34,45 @@ struct PageSettingsDetailView: View { title: "Default CSS File", footer: "The CSS file containing the styling of all pages", selectedFile: $content.settings.pages.defaultCssFile, - allowedType: .text) + allowedType: .asset) FilePropertyView( title: "Code Highlighting File", footer: "The JavaScript file to provide syntax highlighting of code blocks", selectedFile: $content.settings.pages.codeHighlightingJsFile, - allowedType: .text) + allowedType: .asset) FilePropertyView( title: "Audio Player CSS File", footer: "The CSS file to provide the style for the audio player", selectedFile: $content.settings.pages.audioPlayerCssFile, - allowedType: .text) + allowedType: .asset) FilePropertyView( title: "Audio Player JavaScript File", footer: "The CSS file to provide the functionality for the audio player", selectedFile: $content.settings.pages.audioPlayerJsFile, - allowedType: .text) + allowedType: .asset) FilePropertyView( title: "3D Model Viewer File", footer: "The JavaScript file to provide the functionality for the 3D model viewer", selectedFile: $content.settings.pages.modelViewerJsFile, - allowedType: .text) + allowedType: .asset) + + + FilePropertyView( + title: "Image Comparison CSS File", + footer: "The CSS file to provide image comparisons", + selectedFile: $content.settings.pages.imageCompareCssFile, + allowedType: .asset) + + FilePropertyView( + title: "Image Comparison JaveScript File", + footer: "The JavaScript file to provide image comparisons", + selectedFile: $content.settings.pages.imageCompareJsFile, + allowedType: .asset) + } .padding() }