feat: Initial filter pressure drop views, calculations need implemented.
This commit is contained in:
@@ -143,6 +143,7 @@ struct AtticVentilationResponse: HTML, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use Styleguid.VerticalGroup
|
||||
private func group(_ label: String, _ value: String) -> some HTML<HTMLTag.div> {
|
||||
div(.class("justify-self-center")) {
|
||||
div(.class("grid grid-cols-1 justify-items-center")) {
|
||||
|
||||
@@ -17,42 +17,16 @@ struct CapacitorForm: HTML, Sendable {
|
||||
div(.class("flex flex-wrap justify-between")) {
|
||||
FormHeader(label: "Capacitor Calculator - \(mode.rawValue.capitalized) Capacitor", svg: .zap)
|
||||
// Mode toggle / buttons.
|
||||
div(.class("flex items-center gap-x-0")) {
|
||||
switch mode {
|
||||
case .test:
|
||||
SecondaryButton(label: "Test Capacitor")
|
||||
.attributes(.class("rounded-s-lg"))
|
||||
.attributes(.disabled, when: mode == .test)
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .test))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .size
|
||||
)
|
||||
PrimaryButton(label: "Size Capacitor")
|
||||
.attributes(.class("rounded-e-lg"))
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .size))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .test
|
||||
)
|
||||
case .size:
|
||||
PrimaryButton(label: "Test Capacitor")
|
||||
.attributes(.class("rounded-s-lg"))
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .test))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .size
|
||||
)
|
||||
SecondaryButton(label: "Size Capacitor")
|
||||
.attributes(.class("rounded-e-lg"))
|
||||
.attributes(.disabled, when: mode == .size)
|
||||
.attributes(
|
||||
.hx.get(route: .capacitor(.index(mode: .size))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .test
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Mode toggle / buttons.
|
||||
Toggle(
|
||||
isOn: mode == .test,
|
||||
onLabel: "Test Capacitor",
|
||||
onAttributes: .hxDefaults(get: .capacitor(.index(mode: .test))),
|
||||
offLabel: "Size Capacitor",
|
||||
offAttributes: .hxDefaults(get: .capacitor(.index(mode: .size)))
|
||||
)
|
||||
.attributes(.class("mb-6"))
|
||||
}
|
||||
|
||||
Form(mode: mode).attributes(.class("mt-6"))
|
||||
|
||||
249
Sources/ViewController/Views/FilterPressureDrop.swift
Normal file
249
Sources/ViewController/Views/FilterPressureDrop.swift
Normal file
@@ -0,0 +1,249 @@
|
||||
import Elementary
|
||||
import ElementaryHTMX
|
||||
import Routes
|
||||
import Styleguide
|
||||
|
||||
struct FilterPressureDropForm: HTML, Sendable {
|
||||
let mode: FilterPressureDrop.Mode
|
||||
let response: FilterPressureDrop.Response?
|
||||
|
||||
init(mode: FilterPressureDrop.Mode? = nil, response: FilterPressureDrop.Response? = nil) {
|
||||
self.mode = mode ?? .basic
|
||||
self.response = response
|
||||
}
|
||||
|
||||
var content: some HTML {
|
||||
div(.class("flex flex-wrap justify-between")) {
|
||||
FormHeader(label: "Filter Pressure Drop - \(mode.label)", svg: .funnel)
|
||||
Toggle(
|
||||
isOn: mode == .basic,
|
||||
onLabel: FilterPressureDrop.Mode.basic.label,
|
||||
onAttributes: .hxDefaults(get: .filterPressureDrop(.index(mode: .basic))),
|
||||
offLabel: FilterPressureDrop.Mode.fanLaw.label,
|
||||
offAttributes: .hxDefaults(get: .filterPressureDrop(.index(mode: .fanLaw)))
|
||||
)
|
||||
.attributes(.class("mb-6"))
|
||||
}
|
||||
|
||||
// FIX: Remove when done.
|
||||
WarningBox("This is under construction and does not work currently.")
|
||||
|
||||
form(
|
||||
.hx.post(route: .filterPressureDrop(.index)),
|
||||
.hx.target("#result")
|
||||
) {
|
||||
div(.class("space-y-6")) {
|
||||
switch mode {
|
||||
case .basic:
|
||||
BasicFormFields()
|
||||
case .fanLaw:
|
||||
FanLawFormFields()
|
||||
}
|
||||
div {
|
||||
SubmitButton(label: "Calculate Pressure Drop")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div(.id("result")) {
|
||||
if let response {
|
||||
FilterPressureDropResponse(response: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct BasicFormFields: HTML, Sendable {
|
||||
|
||||
var content: some HTML {
|
||||
div {
|
||||
div {
|
||||
h4(.class("text-lg font-bold mb-4")) { "System Parameters" }
|
||||
div(.class("grid grid-cols-1 lg:grid-cols-3 gap-x-4 space-y-6")) {
|
||||
LabeledContent(label: "System Size: (Tons)") {
|
||||
Input(id: "systemSize", placeholder: "Tons")
|
||||
.attributes(.type(.number), .step("0.5"), .min("1.0"), .autofocus, .required)
|
||||
}
|
||||
div {
|
||||
InputLabel(for: "climateZone") { "Climate Zone" }
|
||||
Select(for: ClimateZone.self, id: "climateZone") {
|
||||
$0.label
|
||||
}
|
||||
}
|
||||
div {
|
||||
InputLabel(for: "filterType") { "Filter Type" }
|
||||
Select(for: FilterPressureDrop.FilterType.self, id: "filterType") {
|
||||
$0.label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div(.class("mt-6 lg:mt-0")) {
|
||||
h4(.class("text-lg font-bold mb-4")) { "Filter Parameters" }
|
||||
|
||||
div(.class("grid grid-cols-1 lg:grid-cols-2 gap-x-4 space-y-6")) {
|
||||
LabeledContent(label: "Width: (in)") {
|
||||
Input(id: "filterWidth", placeholder: "Width")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
LabeledContent(label: "Height: (in)") {
|
||||
Input(id: "filterHeight", placeholder: "Height")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct FanLawFormFields: HTML, Sendable {
|
||||
|
||||
var content: some HTML {
|
||||
div {
|
||||
div(.class("mt-6 lg:mt-0")) {
|
||||
h4(.class("text-lg font-bold mb-4")) { "Filter Parameters" }
|
||||
div(.class("grid grid-cols-1 lg:grid-cols-3 gap-x-4 space-y-6")) {
|
||||
LabeledContent(label: "Width: (in)") {
|
||||
Input(id: "filterWidth", placeholder: "Width")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required, .autofocus)
|
||||
}
|
||||
LabeledContent(label: "Height: (in)") {
|
||||
Input(id: "filterHeight", placeholder: "Height")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
LabeledContent(label: "Depth: (in)") {
|
||||
Input(id: "filterDepth", placeholder: "Depth")
|
||||
.attributes(.type(.number), .step("1.0"), .min("1.0"), .required)
|
||||
}
|
||||
}
|
||||
}
|
||||
div(.class("mt-6 lg:mt-0")) {
|
||||
h4(.class("text-lg font-bold mb-4")) { "Airflow Parameters" }
|
||||
div(.class("grid grid-cols-1 lg:grid-cols-3 gap-x-4 space-y-6")) {
|
||||
LabeledContent(label: "Rated Airflow: (CFM)") {
|
||||
Input(id: "ratedAirflow", placeholder: "Rated or measured Airflow")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
LabeledContent(label: "Filter Pressure Drop: (in. w.c.)") {
|
||||
Input(id: "ratedPressure", placeholder: "Rated or measured pressure drop")
|
||||
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
|
||||
}
|
||||
LabeledContent(label: "Design Airflow: (CFM)") {
|
||||
Input(id: "designAirflow", placeholder: "Design or target airflow")
|
||||
.attributes(.type(.number), .step("1.0"), .min("1.0"), .required)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FilterPressureDropResponse: HTML, Sendable {
|
||||
let response: FilterPressureDrop.Response
|
||||
|
||||
var content: some HTML {
|
||||
ResultContainer(reset: .filterPressureDrop(.index(mode: response.resetMode))) {
|
||||
switch response {
|
||||
case let .basic(response):
|
||||
BasicView(response: response)
|
||||
case let .fanLaw(response):
|
||||
FanLawView(response: response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct BasicView: HTML {
|
||||
let response: FilterPressureDrop.Response.Basic
|
||||
|
||||
var content: some HTML {
|
||||
div(
|
||||
.class("""
|
||||
grid grid-cols-1 lg:grid-cols-2 gap-6 rounded-lg shadow-lg
|
||||
border border-blue-600 text-blue-600 bg-blue-100 p-6
|
||||
""")
|
||||
) {
|
||||
VerticalGroup(
|
||||
label: "Filter Face Area",
|
||||
value: "\(double: response.filterArea, fractionDigits: 2)",
|
||||
valueLabel: "ft²"
|
||||
)
|
||||
VerticalGroup(
|
||||
label: "Filter Face Velocity",
|
||||
value: "\(double: response.feetPerMinute, fractionDigits: 1)",
|
||||
valueLabel: "FPM"
|
||||
)
|
||||
VerticalGroup(
|
||||
label: "Initial Pressure Drop",
|
||||
value: "\(double: response.initialPressureDrop, fractionDigits: 2)\"",
|
||||
valueLabel: "w.c."
|
||||
)
|
||||
VerticalGroup(
|
||||
label: "Maximum Allowable Pressure Drop",
|
||||
value: "\(double: response.maxPressureDrop, fractionDigits: 2)\"",
|
||||
valueLabel: "w.c."
|
||||
)
|
||||
}
|
||||
|
||||
WarningBox(warnings: response.warnings)
|
||||
}
|
||||
}
|
||||
|
||||
private struct FanLawView: HTML {
|
||||
let response: FilterPressureDrop.Response.FanLaw
|
||||
|
||||
var content: some HTML {
|
||||
div(
|
||||
.class("""
|
||||
grid grid-cols-1 lg:grid-cols-2 gap-6 rounded-lg shadow-lg
|
||||
border border-blue-600 text-blue-600 bg-blue-100 p-6
|
||||
""")
|
||||
) {
|
||||
VerticalGroup(
|
||||
label: "Filter Face Velocity",
|
||||
value: "\(double: response.faceVelocity, fractionDigits: 1)",
|
||||
valueLabel: "FPM"
|
||||
)
|
||||
VerticalGroup(
|
||||
label: "Predicted Pressure Drop",
|
||||
value: "\(double: response.predictedPressureDrop, fractionDigits: 2)\"",
|
||||
valueLabel: "w.c."
|
||||
)
|
||||
VerticalGroup(
|
||||
label: "Velocity Ratio",
|
||||
value: "\(double: response.velocityRatio, fractionDigits: 2)"
|
||||
)
|
||||
VerticalGroup(
|
||||
label: "Pressure Ratio",
|
||||
value: "\(double: response.pressureRatio, fractionDigits: 2)"
|
||||
)
|
||||
}
|
||||
Note {
|
||||
"""
|
||||
Predictions are based on fan laws where pressure drop varies with the square of the airflow ratio.
|
||||
Results assume similar air properties and filter loading conditions.
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension FilterPressureDrop.Mode {
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .basic: return rawValue.capitalized
|
||||
case .fanLaw: return "Fan Law"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension FilterPressureDrop.Response {
|
||||
|
||||
var resetMode: FilterPressureDrop.Mode {
|
||||
switch self {
|
||||
case .basic: return .basic
|
||||
case .fanLaw: return .fanLaw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,44 +16,14 @@ struct RoomPressureForm: HTML, Sendable {
|
||||
div(.class("relative")) {
|
||||
div(.class("flex flex-wrap justify-between")) {
|
||||
FormHeader(label: "Room Pressure Calculator - \(mode.label)", svg: .leftRightArrow)
|
||||
|
||||
// Mode toggle / buttons.
|
||||
div(.class("flex items-center gap-x-0")) {
|
||||
switch mode {
|
||||
case .knownAirflow:
|
||||
SecondaryButton(label: "Known Airflow")
|
||||
.attributes(.class("rounded-s-lg"))
|
||||
.attributes(.disabled, when: mode == .knownAirflow)
|
||||
.attributes(
|
||||
.hx.get(route: .roomPressure(.index(mode: .knownAirflow))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .measuredPressure
|
||||
)
|
||||
PrimaryButton(label: "Measured Pressure")
|
||||
.attributes(.class("rounded-e-lg"))
|
||||
.attributes(
|
||||
.hx.get(route: .roomPressure(.index(mode: .measuredPressure))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .knownAirflow
|
||||
)
|
||||
case .measuredPressure:
|
||||
PrimaryButton(label: "Known Airflow")
|
||||
.attributes(.class("rounded-s-lg"))
|
||||
.attributes(
|
||||
.hx.get(route: .roomPressure(.index(mode: .knownAirflow))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .measuredPressure
|
||||
)
|
||||
SecondaryButton(label: "Measured Pressure")
|
||||
.attributes(.class("rounded-e-lg"))
|
||||
.attributes(.disabled, when: mode == .measuredPressure)
|
||||
.attributes(
|
||||
.hx.get(route: .roomPressure(.index(mode: .measuredPressure))),
|
||||
.hx.target("#content"),
|
||||
when: mode == .knownAirflow
|
||||
)
|
||||
}
|
||||
}
|
||||
Toggle(
|
||||
isOn: mode == .knownAirflow,
|
||||
onLabel: "Known Airflow",
|
||||
onAttributes: .hxDefaults(get: .roomPressure(.index(mode: .knownAirflow))),
|
||||
offLabel: "Measured Pressure",
|
||||
offAttributes: .hxDefaults(get: .roomPressure(.index(mode: .measuredPressure)))
|
||||
)
|
||||
.attributes(.class("mb-6"))
|
||||
}
|
||||
|
||||
Form(mode: mode)
|
||||
@@ -156,6 +126,7 @@ struct RoomPressureForm: HTML, Sendable {
|
||||
|
||||
var content: some HTML {
|
||||
InputLabel(for: "preferredGrilleHeight") { "Preferred Grille Height" }
|
||||
// TODO: Use Styleguide.Select
|
||||
select(
|
||||
.id("preferredGrilleHeight"),
|
||||
.name("preferredGrilleHeight"),
|
||||
|
||||
Reference in New Issue
Block a user