import Elementary import ElementaryHTMX import PsychrometricClient import Routes import Styleguide struct PsychrometricsForm: HTML, Sendable { // let mode: Psychrometrics.Mode let response: Psychrometrics.Response? init(response: Psychrometrics.Response? = nil) { self.response = response } var content: some HTML { FormHeader(label: "Psychrometric Properties", svg: .droplets) form( .hx.post(route: .psychrometrics(.index)), .hx.target("#result") ) { div(.class("space-y-6")) { div(.class("grid grid-cols-1 lg:grid-cols-2 gap-4")) { LabeledContent(label: "Temperature (°F)") { Input(id: Psychrometrics.Request.FieldKey.temperature.rawValue, placeholder: "Dry bulb temperature") .attributes(.type(.number), .min("0.1"), .step("0.1"), .autofocus, .required) } LabeledContent(label: "Relative Humidity (%)") { Input(id: Psychrometrics.Request.FieldKey.humidity.rawValue, placeholder: "Relative humidity") .attributes(.type(.number), .min("0.1"), .step("0.1"), .max("100"), .required) } } LabeledContent(label: "Altitude (ft.)") { Input(id: Psychrometrics.Request.FieldKey.altitude.rawValue, placeholder: "Altitude (optional)") .attributes(.type(.number), .min("0.1"), .step("0.1")) } div { SubmitButton(label: "Calculate Psychrometrics") } } } div(.id("result")) { if let response { PsychrometricsResponse(response: response) } } } } struct PsychrometricsResponse: HTML, Sendable { let response: Psychrometrics.Response var content: some HTML { ResultContainer(reset: .psychrometrics(.index)) { div(.class("w-full rounded-lg shadow-lg bg-blue-100 border border-blue-600 text-blue-600 p-6")) { div(.class("flex mb-8")) { SVG(.droplets, color: "text-blue-600") h3(.class("text-xl px-2 font-semibold")) { "Psychrometrics" } } div(.class("grid grid-cols-3 gap-4")) { div(.class("rounded-lg bg-purple-200 border border-purple-600 text-purple-600")) { VerticalGroup( label: "Dew Point", value: "\(double: response.properties.dewPoint.value, fractionDigits: 1)", valueLabel: response.properties.dewPoint.units.symbol ) .attributes(.class("my-8")) } div(.class("rounded-lg bg-orange-200 border border-orange-600 text-orange-600")) { VerticalGroup( label: "Enthalpy", value: "\(double: response.properties.enthalpy.value, fractionDigits: 1)", valueLabel: response.properties.enthalpy.units.rawValue ) .attributes(.class("my-8")) } div(.class("rounded-lg bg-green-200 border border-green-600 text-green-600")) { VerticalGroup( label: "Wet Bulb", value: "\(double: response.properties.wetBulb.value, fractionDigits: 1)", valueLabel: response.properties.wetBulb.units.symbol ) .attributes(.class("my-8")) } } div(.class("mt-8")) { h4(.class("text-lg font-semibold")) { "Other Properties" } div(.class("rounded-lg border border-blue-300")) { displayProperty("Density", \.density.rawValue) displayProperty("Vapor Pressure", \.vaporPressure.rawValue) displayProperty("Specific Volume", response.properties.specificVolume.rawValue) displayProperty("Absolute Humidity", \.absoluteHumidity) displayProperty("Humidity Ratio", response.properties.humidityRatio.value) displayProperty("Degree of Saturation", response.properties.degreeOfSaturation.value) } } } WarningBox(warnings: response.warnings) } } private func displayProperty(_ label: String, _ value: Double, _ symbol: String? = nil) -> some HTML { let symbol = "\(symbol == nil ? "" : " \(symbol!)")" return div(.class("flex items-center justify-between border-b border-blue-300 p-2")) { span(.class("font-semibold")) { "\(label): " } span(.class("font-light")) { "\(double: value, fractionDigits: 2)\(symbol)" } } } private func displayProperty( _ label: String, _ keyPath: KeyPath ) -> some HTML where N.Number == Double, N.Units: RawRepresentable, N.Units.RawValue == String { let property = response.properties[keyPath: keyPath] return displayProperty(label, property.rawValue, property.units.rawValue) } }