feat: Adds psychrometrics calculator.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import Elementary
|
||||
import PsychrometricClient
|
||||
|
||||
// FIX: Remove text colors.
|
||||
struct PsychrometricPropertiesView: HTML {
|
||||
let properties: PsychrometricProperties
|
||||
|
||||
|
||||
126
Sources/ViewController/Views/Psychrometrics.swift
Normal file
126
Sources/ViewController/Views/Psychrometrics.swift
Normal file
@@ -0,0 +1,126 @@
|
||||
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<N: NumberWithUnitOfMeasure>(
|
||||
_ label: String,
|
||||
_ keyPath: KeyPath<PsychrometricProperties, N>
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user