109 lines
3.6 KiB
Swift
109 lines
3.6 KiB
Swift
import Elementary
|
|||
|
|
import Foundation
|
||
|
|
import Infrastructure
|
||
|
|
import Persistence
|
||
|
|
|
||
|
|
/// Every series in the catalogue, with how many copies of each are listed.
|
||
|
|
struct SeriesIndexPage {
|
||
|
|
|
||
|
|
// MARK: Properties
|
||
|
|
|
||
|
|
let analytics: Analytics?
|
||
|
|
let assetVersion: String?
|
||
|
|
let locale: Locale
|
||
|
|
|
||
|
|
/// The series, by name, each with the listings of it still for sale.
|
||
|
|
let entries: [(series: Series, listings: [Listing])]
|
||
|
|
|
||
|
|
/// The signed-in viewer, or `nil` when signed out.
|
||
|
|
let viewer: Viewer?
|
||
|
|
|
||
|
|
// MARK: Initializers
|
||
|
|
|
||
|
|
/// Creates the series index.
|
||
|
|
/// - Parameters:
|
||
|
|
/// - entries: the series, by name, each with the listings of it still for sale.
|
||
|
|
/// - viewer: the signed-in viewer, or `nil` (the default) when signed out.
|
||
|
|
/// - assetVersion: the version token appended to the page's asset URLs, or `nil` (the default) to leave them unversioned.
|
||
|
|
/// - analytics: the analytics tracker embedded in the document head, or `nil` (the default) to omit it.
|
||
|
|
init(
|
||
|
|
entries: [(series: Series, listings: [Listing])],
|
||
|
|
viewer: Viewer? = nil,
|
||
|
|
assetVersion: String? = nil,
|
||
|
|
analytics: Analytics? = nil
|
||
|
|
) {
|
||
|
|
self.analytics = analytics
|
||
|
|
self.assetVersion = assetVersion
|
||
|
|
self.entries = entries
|
||
|
|
self.locale = .init(identifier: "en")
|
||
|
|
self.viewer = viewer
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
// MARK: - Page
|
||
|
|
|
||
|
|
extension SeriesIndexPage: Page {
|
||
|
|
|
||
|
|
// MARK: Computed
|
||
|
|
|
||
|
|
var content: some HTML {
|
||
|
|
SiteLayout(viewer: viewer) {
|
||
|
|
div(.class("page series")) {
|
||
|
|
div(.class("series__intro")) {
|
||
|
|
p(.class("type-eyebrow")) {
|
||
|
|
"Browse by series"
|
||
|
|
}
|
||
|
|
h1(.class("type-h1")) {
|
||
|
|
"Series"
|
||
|
|
}
|
||
|
|
p(.class("type-lead")) {
|
||
|
|
"Every issue of a run, what is listed of each, and the gaps — one tap from an alert."
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ul(.class("series__index")) {
|
||
|
|
ForEach(entries) { entry in
|
||
|
|
li {
|
||
|
|
a(
|
||
|
|
.class("card series__entry"),
|
||
|
|
.href(.Route.series(entry.series.slug))
|
||
|
|
) {
|
||
|
|
if let cover = entry.listings.first {
|
||
|
|
ListingImage(cover, modifier: "cover--agreed")
|
||
|
|
}
|
||
|
|
span(.class("series__entry-text")) {
|
||
|
|
span(.class("series__entry-name")) {
|
||
|
|
entry.series.name
|
||
|
|
}
|
||
|
|
span(.class("type-caption")) {
|
||
|
|
"\(entry.series.publisher) · \(entry.series.years) · \(entry.series.issueCount) issues"
|
||
|
|
}
|
||
|
|
span(.class("type-small")) {
|
||
|
|
"\(entry.listings.count) \(entry.listings.count == 1 ? "copy" : "copies") listed"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var scripts: [any Asset] {
|
||
|
|
[StaticFile.shared]
|
||
|
|
}
|
||
|
|
|
||
|
|
var stylesheets: [any Asset] {
|
||
|
|
[StaticFile.shared, StaticFile.collecting]
|
||
|
|
}
|
||
|
|
|
||
|
|
var summary: String? {
|
||
|
|
"Browse comics by series on CCN: every issue of a run, what is listed of each, and the gaps."
|
||
|
|
}
|
||
|
|
|
||
|
|
var title: String {
|
||
|
|
"Series — CCN"
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|