Implement image comparison command
This commit is contained in:
@ -12,6 +12,8 @@ extension HeaderElement {
|
||||
static let defaultCssFileOrder = 42
|
||||
|
||||
static let audioPlayerCssOrder = 43
|
||||
|
||||
static let imageCompareCssOrder = 44
|
||||
}
|
||||
|
||||
enum HeaderElement {
|
||||
|
@ -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 = "<picture>"
|
||||
result += "<picture>"
|
||||
result += "<source type='image/avif' srcset='\(prefix1x).avif 1x, \(prefix2x).avif 2x'/>"
|
||||
result += "<source type='image/webp' srcset='\(prefix1x).webp 1x, \(prefix1x).webp 2x'/>"
|
||||
result += "<img srcset='\(prefix2x)\(fileExtension) 2x' src='\(prefix1x)\(fileExtension)' loading='lazy' alt='\(description.htmlEscaped())'/>"
|
||||
result += "<img srcset='\(prefix2x)\(fileExtension) 2x' src='\(prefix1x)\(fileExtension)' loading='lazy' alt='\(description.htmlEscaped())'\(extraAttributes)/>"
|
||||
result += "</picture>"
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -53,4 +53,10 @@ enum ShorthandMarkdownKey: String {
|
||||
/// Format: ``
|
||||
case icons
|
||||
|
||||
/**
|
||||
Create an image comparison with a slider.
|
||||
Format: ``
|
||||
*/
|
||||
case imageCompare = "compare"
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user