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 { 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 } } }