feat: Begins htmx integration
This commit is contained in:
@@ -28,6 +28,7 @@ struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
||||
.init(name: "sizes", value: "180x180")
|
||||
)
|
||||
link(.rel(.init(rawValue: "mainifest")), .href("/site.webmanifest"))
|
||||
script(.src("https://unpkg.com/htmx.org@2.0.4")) {}
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
@@ -59,7 +60,10 @@ private struct Header: HTML {
|
||||
// TODO: Add class active, to button that is the active route.
|
||||
ul(.class("flex flex-wrap gap-x-2 lg:gap-x-5 \(text: .yellow) font-bold")) {
|
||||
li {
|
||||
a(.href(route: .moldRisk(.index)), .class("hover:border-b \(border: .yellow)")) {
|
||||
a(
|
||||
.class("hover:border-b \(border: .yellow)"),
|
||||
.hx.get(route: .moldRisk(.index)), .hx.target("#content"), .hx.pushURL(true)
|
||||
) {
|
||||
"Mold-Risk"
|
||||
}
|
||||
}
|
||||
@@ -80,7 +84,9 @@ private struct PageContent<Body: HTML>: HTML where Body: Sendable {
|
||||
var content: some HTML {
|
||||
div(.class("mx-5 lg:mx-20")) {
|
||||
div(.class("rounded-xl shadow-lg bg-white dark:bg-slate-700 p-8")) {
|
||||
body()
|
||||
div(.id("content")) {
|
||||
body()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,15 @@ import Styleguide
|
||||
|
||||
struct MoldRiskForm: HTML {
|
||||
|
||||
let response: MoldRisk.Response?
|
||||
|
||||
var content: some HTML {
|
||||
FormHeader(label: "Mold Risk Calculator", svg: .thermometer)
|
||||
form(.action("#")) {
|
||||
form(
|
||||
// Using index to get the correct path, but really uses the `submit` end-point.
|
||||
.hx.post(route: .moldRisk(.index)),
|
||||
.hx.target("#result")
|
||||
) {
|
||||
div(.class("space-y-6")) {
|
||||
LabeledContent(label: "Indoor Temperature (°F)") {
|
||||
Input(id: "temperature", placeholder: "Dry bulb temperature")
|
||||
@@ -22,10 +28,11 @@ struct MoldRiskForm: HTML {
|
||||
div {
|
||||
SubmitButton(label: "Calculate Mold Risk")
|
||||
}
|
||||
|
||||
div(.id("result")) {
|
||||
MoldRiskResponse(response: .mock)
|
||||
}
|
||||
}
|
||||
}
|
||||
div(.id("result")) {
|
||||
if let response {
|
||||
MoldRiskResponse(response: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,21 +43,51 @@ struct MoldRiskResponse: HTML {
|
||||
|
||||
var content: some HTML {
|
||||
ResultContainer {
|
||||
// TODO: Color needs to be derived from risk level.
|
||||
div(.class("p-2 rounded-lg shadow-lg bg-lime-100 border-2 border border-lime-600")) {
|
||||
LabeledContent {
|
||||
p(.class("text-lg font-semibold \(text: .green) dark:text-lime-600 mt-2")) {
|
||||
"Risk Level: \(response.riskLevel.rawValue.capitalized)"
|
||||
div(
|
||||
.class("""
|
||||
p-2 rounded-lg shadow-lg \(response.riskLevel.backgroundColor) border-2 border \(response.riskLevel.borderColor)
|
||||
""")
|
||||
) {
|
||||
// Risk level and days to mold header.
|
||||
div(.class("\(response.riskLevel.textColor)")) {
|
||||
div(.class("flex flex-wrap mt-2")) {
|
||||
div(.class("w-full sm:w-1/2 flex gap-2")) {
|
||||
SVG(.exclamation, color: "\(response.riskLevel.textColor)")
|
||||
span(.class("text-lg font-extrabold")) {
|
||||
"Risk Level: \(response.riskLevel.rawValue.capitalized)"
|
||||
}
|
||||
}
|
||||
if let daysToMold = response.daysToMold {
|
||||
div(.class("w-full sm:w-1/2 gap-2")) {
|
||||
span(.class("font-semibold")) { "Estimated Days to Mold Growth: " }
|
||||
span { "\(daysToMold) days" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recommendations
|
||||
if response.recommendations.count > 0 {
|
||||
div(.class("mt-6 pb-4")) {
|
||||
p(.class("font-semibold mb-4")) {
|
||||
u {
|
||||
"Recommendation\(response.recommendations.count == 1 ? "" : "s"):"
|
||||
}
|
||||
}
|
||||
ul(.class("list-disc mx-10")) {
|
||||
for recommendation in response.recommendations {
|
||||
li { recommendation }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
SVG(.exclamation, color: "text-green-600 dark:text-lime-600")
|
||||
}
|
||||
.attributes(.class("flex items-center gap-2"))
|
||||
}
|
||||
|
||||
// Display psychrometric properties.
|
||||
PsychrometricPropertiesGrid(properties: response.psychrometricProperties)
|
||||
.attributes(.class("mx-6"))
|
||||
|
||||
// Disclaimer.
|
||||
div(.class("mt-8 p-4 bg-gray-100 dark:bg-gray-700 rounded-md shadow-md border border-blue-500")) {
|
||||
p(.class("text-sm text-blue-500")) {
|
||||
span(.class("font-extrabold pe-2")) { "Note:" }
|
||||
@@ -65,20 +102,55 @@ struct MoldRiskResponse: HTML {
|
||||
}
|
||||
}
|
||||
|
||||
private extension MoldRisk.RiskLevel {
|
||||
var backgroundColor: String {
|
||||
switch self {
|
||||
case .low: return "bg-green-200"
|
||||
case .moderate: return "bg-amber-200"
|
||||
case .high: return "bg-orange-200"
|
||||
case .severe: return "bg-red-200"
|
||||
}
|
||||
}
|
||||
|
||||
var borderColor: String {
|
||||
switch self {
|
||||
case .low: return "border-green-500"
|
||||
case .moderate: return "border-amber-500"
|
||||
case .high: return "border-orange-500"
|
||||
case .severe: return "border-red-500"
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: String {
|
||||
switch self {
|
||||
case .low: return "text-green-500"
|
||||
case .moderate: return "text-amber-500"
|
||||
case .high: return "text-orange-500"
|
||||
case .severe: return "text-red-500"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct PsychrometricPropertiesGrid: HTML {
|
||||
let properties: PsychrometricProperties
|
||||
|
||||
var content: some HTML<HTMLTag.div> {
|
||||
div(.class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-6 md:mx-20")) {
|
||||
displayProperty("Dew Point", \.dewPoint.rawValue)
|
||||
displayProperty("Wet Bulb", \.wetBulb.rawValue)
|
||||
displayProperty("Enthalpy", \.enthalpy.rawValue)
|
||||
displayProperty("Density", \.density.rawValue)
|
||||
displayProperty("Vapor Pressure", \.vaporPressure.rawValue)
|
||||
displayProperty("Specific Volume", properties.specificVolume.rawValue)
|
||||
displayProperty("Absolute Humidity", \.absoluteHumidity)
|
||||
displayProperty("Humidity Ratio", properties.humidityRatio.value)
|
||||
displayProperty("Degree of Saturation", properties.degreeOfSaturation.value)
|
||||
div(.class("pt-8")) {
|
||||
p(.class("text-xl font-semibold border-b")) {
|
||||
"Psychrometric Properties:"
|
||||
}
|
||||
div(.class("w-full mt-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4")) {
|
||||
displayProperty("Dew Point", \.dewPoint.rawValue)
|
||||
displayProperty("Wet Bulb", \.wetBulb.rawValue)
|
||||
displayProperty("Enthalpy", \.enthalpy.rawValue)
|
||||
displayProperty("Density", \.density.rawValue)
|
||||
displayProperty("Vapor Pressure", \.vaporPressure.rawValue)
|
||||
displayProperty("Specific Volume", properties.specificVolume.rawValue)
|
||||
displayProperty("Absolute Humidity", \.absoluteHumidity)
|
||||
displayProperty("Humidity Ratio", properties.humidityRatio.value)
|
||||
displayProperty("Degree of Saturation", properties.degreeOfSaturation.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user