Generate pages, image descriptions

This commit is contained in:
Christoph Hagen
2024-12-06 21:59:36 +01:00
parent 18eb64f289
commit 5fb689ac7c
42 changed files with 1653 additions and 273 deletions

View File

@ -0,0 +1,19 @@
struct ContentPageVideo: HtmlProducer {
let filePath: String
let videoType: String
let options: [VideoOption]
func populate(_ result: inout String) {
result += "<video\(optionString)>Video not supported."
result += "<source src='\(filePath)' type='\(videoType)'>"
result += "</video>"
}
private var optionString: String {
options.map { " " + $0.rawValue }.joined()
}
}

View File

@ -0,0 +1,34 @@
struct DownloadButtons {
struct Item {
let filePath: String
let text: String
let downloadFileName: String?
}
let items: [Item]
init(items: [Item]) {
self.items = items
}
var content: String {
var result = "<p style='display: flex'>"
for item in items {
addButton(of: item, to: &result)
}
result += "</p>"
return result
}
private func addButton(of item: Item, to result: inout String) {
let downloadText = item.downloadFileName.map { " download='\($0)'" } ?? ""
result += "<a class='download-button' href='\(item.filePath)'\(downloadText)>"
result += "\(item.text)<span class='icon icon-download'></span>"
result += "</a>"
}
}

View File

@ -0,0 +1,42 @@
struct HikingStatistics {
private let time: String?
private let elevationUp: String?
private let elevationDown: String?
private let distance: String?
private let calories: String?
init(time: String?, elevationUp: String?, elevationDown: String?, distance: String?, calories: String?) {
self.time = time
self.elevationUp = elevationUp
self.elevationDown = elevationDown
self.distance = distance
self.calories = calories
}
var content: String {
var result = "<div class='stats-container'>"
if let time {
result += "<div><svg><use href='#icon-clock'></use></svg>\(time)</div>"
}
if let elevationUp {
result += "<div><svg><use href='#icon-arrow-up'></use></svg>\(elevationUp)</div>"
}
if let elevationDown {
result += "<div><svg><use href='#icon-arrow-down'></use></svg>\(elevationDown)</div>"
}
if let distance {
result += "<div><svg><use href='#icon-sign'></use></svg>\(distance)</div>"
}
if let calories {
result += "<div><svg><use href='#icon-flame'></use></svg>\(calories)</div>"
}
result += "</div>"
return result
}
}

View File

@ -0,0 +1,29 @@
protocol HtmlProducer {
func populate(_ result: inout String)
}
extension HtmlProducer {
var content: String {
var result = ""
populate(&result)
return result
}
}
struct TagList: HtmlProducer {
let tags: [FeedEntryData.Tag]
func populate(_ result: inout String) {
guard !tags.isEmpty else {
return
}
result += "<div class='tags'>"
for tag in tags {
result += "<span class='tag' onclick=\"location.href='\(tag.url)'; event.stopPropagation();\">\(tag.name)</span>"
}
result += "</div>"
}
}

View File

@ -2,15 +2,18 @@ import Foundation
struct FeedEntry {
let data: FeedEntryData
private let data: FeedEntryData
init(data: FeedEntryData) {
self.data = data
}
private var cardLinkClassText: String {
data.link != nil ? " linked-card" : ""
}
var content: String {
#warning("TODO: Select CSS classes based on existence of link (hover effects, mouse pointer")
var result = "<div class='card'>"
var result = "<div class='card\(cardLinkClassText)'>"
ImageGallery(id: data.entryId, images: data.images)
.addContent(to: &result)
@ -23,14 +26,8 @@ struct FeedEntry {
if let title = data.title {
result += "<h2>\(title.htmlEscaped())</h2>"
}
if !data.tags.isEmpty {
result += "<div class='tags'>"
for tag in data.tags {
result += "<span class='tag' onclick=\"location.href='\(tag.url)'; event.stopPropagation();\">\(tag.name)</span>"
//result += "<a class='tag' href='\(tag.url)'>\(tag.name)</a>"
}
result += "</div>"
}
result += TagList(tags: data.tags).content
for paragraph in data.text {
result += "<p>\(paragraph)</p>"
}

View File

@ -43,19 +43,13 @@ struct FeedEntryData {
struct Image {
let rawImagePath: String
let width: Int
let height: Int
let altText: String
let avif1x: String
let avif2x: String
let webp1x: String
let webp2x: String
let jpg1x: String
let jpg2x: String
}
}

View File

@ -15,16 +15,6 @@ struct ImageGallery {
self.images = images
}
private func imageCode(_ image: FeedEntryData.Image) -> String {
//return "<img src='\(image.mainImageUrl)' loading='lazy' alt='\(image.altText.htmlEscaped())'>"
var result = "<picture>"
result += "<source type='image/avif' srcset='\(image.avif1x) 1x, \(image.avif2x) 2x'/>"
result += "<source type='image/webp' srcset='\(image.webp1x) 1x, \(image.webp2x) 2x'/>"
result += "<img srcset='\(image.jpg2x) 2x' src='\(image.jpg1x)' loading='lazy' alt='\(image.altText.htmlEscaped())'/>"
result += "</picture>"
return result
}
func addContent(to result: inout String) {
guard !images.isEmpty else {
return
@ -33,7 +23,7 @@ struct ImageGallery {
result += "<div id='\(htmlSafeId)' class='swiper'><div class='swiper-wrapper'>"
guard images.count > 1 else {
result += imageCode(images[0])
result += WebsiteImage(image: images[0]).content
result += "</div></div>" // Close swiper, swiper-wrapper
return
}
@ -42,7 +32,7 @@ struct ImageGallery {
// TODO: Use different images based on device
result += "<div class='swiper-slide'>"
result += imageCode(image)
result += WebsiteImage(image: image).content
result += "<div class='swiper-lazy-preloader swiper-lazy-preloader-white'></div>"

View File

@ -0,0 +1,27 @@
import Foundation
struct PageImage {
let imageId: String
let thumbnail: FeedEntryData.Image
let largeImage: FeedEntryData.Image
let caption: String?
var content: String {
var result = ""
result += "<div class='content-image' onclick=\"document.getElementById('\(imageId)').classList.add('active')\">"
result += WebsiteImage(image: thumbnail).content
result += "</div>"
result += "<div id='\(imageId)' class='fullscreen-image' onclick=\"document.getElementById('\(imageId)').classList.remove('active')\">"
result += WebsiteImage(image: largeImage).content
if let caption {
result += "<div class='caption'>\(caption)</div>"
}
result += "<div class='close'></div>"
result += "</div>"
return result
}
}

View File

@ -0,0 +1,35 @@
struct WebsiteImage {
private let prefix1x: String
private let prefix2x: String
private let altText: String
private let ext: String
init(image: FeedEntryData.Image) {
self.init(rawImagePath: image.rawImagePath,
width: image.width,
height: image.height,
altText: image.altText)
}
init(rawImagePath: String, width: Int, height: Int, altText: String) {
let (prefix, ext) = rawImagePath.fileNameAndExtension
self.prefix1x = "\(prefix)@\(width)x\(height)"
self.prefix2x = "\(prefix)@\(width*2)x\(height*2)"
self.altText = altText.htmlEscaped()
self.ext = ext ?? "jpg"
}
var content: String {
var result = "<picture>"
result += "<source type='image/avif' srcset='\(prefix1x).avif 1x, \(prefix2x).avif 2x'/>"
result += "<source type='image/webp' srcset='\(prefix1x).webm 1x, \(prefix1x).webm 2x'/>"
result += "<img srcset='\(prefix2x).\(ext) 2x' src='\(prefix1x).\(ext)' loading='lazy' alt='\(altText)'/>"
result += "</picture>"
return result
}
}