Files
swift-hvac-toolbox/Sources/ViewController/Views/Capacitor.swift

240 lines
7.8 KiB
Swift

import Elementary
import ElementaryHTMX
import Routes
import Styleguide
struct CapacitorForm: HTML, Sendable {
let mode: Capacitor.Mode
let response: Capacitor.Response?
init(mode: Capacitor.Mode?, response: Capacitor.Response? = nil) {
self.mode = mode ?? .test
self.response = response
}
var content: some HTML {
div(.class("relative")) {
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
)
}
}
}
Form(mode: mode).attributes(.class("mt-6"))
div(.id("result")) {
if let response {
CapacitorResponse(response: response)
}
}
}
}
private struct Form: HTML, Sendable {
let mode: Capacitor.Mode
var content: some HTML<HTMLTag.form> {
form(
.hx.post(route: .capacitor(.index)),
.hx.target("#result")
) {
div(.class("space-y-6")) {
switch mode {
case .size:
LabeledContent(label: "Running Current: (amps)") {
Input(id: "runningAmps", placeholder: "Current amps")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus, .required)
}
LabeledContent(label: "Line Voltage") {
Input(id: "lineVoltage", placeholder: "Voltage")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
LabeledContent(label: "Power Factor") {
Input(id: "powerFactor", placeholder: "Power factor (0-1)")
.attributes(.type(.number), .step("0.01"), .min("0.1"), .max("1.00"), .required)
}
case .test:
LabeledContent(label: "Start Winding: (amps)") {
Input(id: "startWindingAmps", placeholder: "Current amps")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus, .required)
}
LabeledContent(label: "Run to Common: (volts)") {
Input(id: "runToCommonVoltage", placeholder: "Voltage")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .required)
}
LabeledContent(label: "Capacitor Rated Size: (µF)") {
Input(id: "ratedCapacitorSize", placeholder: "Size (optional)")
.attributes(.type(.number), .step("0.1"), .min("0.1"))
}
}
div {
SubmitButton(label: "\(mode == .size ? "Calculate Size" : "Test Capacitor")")
}
}
}
}
}
}
struct CapacitorResponse: HTML, Sendable {
let response: Capacitor.Response
var content: some HTML {
ResultContainer(reset: .capacitor(.index(mode: response.mode))) {
switch response {
case let .size(result: result):
sizeResponse(result)
case let .test(result: result):
testResponse(result)
}
}
}
private func sizeResponse(_ response: Capacitor.Response.SizeResponse) -> some HTML {
div {
div(
.class("""
w-full rounded-lg shadow-lg p-4
border-2 border-blue-600 bg-blue-100 text-blue-600
""")
) {
div(.class("flex justify-center")) {
span(.class("font-extrabold")) { "Required Capacitor Size" }
}
row("Recommended Standard Size", "\(response.standardSize) µF")
row("Calculated Size:", "\(double: response.capacitance, fractionDigits: 1) µF")
toleranceRow(response.tolerance)
}
WarningBox(
"Always verify voltage rating matches the application.",
"Use the next larger size if exact match is unavailable.",
"Ensure capacitor is rated for continuous duty.",
"Consider ambient temperature in final selection."
) { _ in
span(.class("font-extrabold")) { "Important Notes:" }
}
}
}
private func testResponse(_ response: Capacitor.Response.TestResponse) -> some HTML {
div {
if let comparison = response.ratedComparison {
div(
.class("""
w-full rounded-lg shadow-lg p-4
border-2 \(comparison.borderColor) \(comparison.bgColor) \(comparison.textColor)
""")
) {
div(.class("flex font-bold gap-x-4")) {
SVG(
comparison.isInRange ? .checkCircle : .exclamation,
color: comparison.textColor
)
span(.class("font-normal")) {
"Capacitor is \(comparison.isInRange ? "within" : "outside of") acceptable range: (± 6% of rated value)"
}
}
}
}
div(.class("grid grid-cols-1 \(response.ratedComparison != nil ? "lg:grid-cols-2" : "") gap-4 mt-8")) {
div(.class("bg-blue-100 rounded-lg border-2 border-blue-600 text-blue-500 px-4 pb-4")) {
div(.class("flex justify-center mb-6 mt-2")) {
span(.class("text-2xl font-extrabold")) { "Measured" }
}
row("Capacitance", "\(double: response.capacitance) µF")
toleranceRow(response.tolerance)
}
if let comparison = response.ratedComparison {
div(.class("bg-blue-100 rounded-lg border-2 border-blue-600 text-blue-500 px-4 pb-4")) {
div(.class("flex justify-center mb-6 mt-2")) {
span(.class("text-2xl font-extrabold")) { "Rated Comparison" }
}
row("Rated Value", "\(comparison.value) µF")
row("Deviation", "\(double: comparison.percentDeviation, fractionDigits: 1)%")
}
}
}
}
}
private func row(_ label: String, _ value: String) -> some HTML {
div(.class("flex justify-between mb-2")) {
span(.class("font-semibold")) { label }
span { value }
}
}
private func toleranceRow(_ tolerance: Capacitor.Tolerance) -> some HTML {
row(
"Acceptable Range (±6%):",
"\(double: tolerance.minimum, fractionDigits: 1) - \(double: tolerance.maximum, fractionDigits: 1) µF"
)
}
}
private extension Capacitor.RatedComparison {
var bgColor: String {
guard isInRange else { return "bg-red-100" }
return "bg-green-100"
}
var borderColor: String {
guard isInRange else { return "border-red-600" }
return "border-green-600"
}
var textColor: String {
guard isInRange else { return "text-red-600" }
return "text-green-600"
}
}
private extension Capacitor.Response {
var mode: Capacitor.Mode {
switch self {
case .size: return .size
case .test: return .test
}
}
}