feat: Adds capacitor calculations.
This commit is contained in:
@@ -76,6 +76,17 @@ extension ViewController: DependencyKey {
|
||||
}
|
||||
}
|
||||
|
||||
case let .capacitor(route):
|
||||
switch route {
|
||||
case let .index(mode: mode):
|
||||
logger.debug("Capacitor view: \(String(describing: mode))")
|
||||
return request.respond(CapacitorForm(mode: mode, response: nil))
|
||||
case let .submit(request):
|
||||
logger.debug("Capaitor view submit: \(request)")
|
||||
let response = try await request.respond(logger: logger)
|
||||
return CapacitorResponse(response: response)
|
||||
}
|
||||
|
||||
case let .dehumidifierSize(route):
|
||||
switch route {
|
||||
case .index:
|
||||
|
||||
@@ -66,6 +66,7 @@ private struct Header: HTML {
|
||||
navLink(label: "Dehumidifier-Sizing", route: .dehumidifierSize(.index))
|
||||
navLink(label: "HVAC-System-Performance", route: .hvacSystemPerformance(.index))
|
||||
navLink(label: "Room-Pressure", route: .roomPressure(.index))
|
||||
navLink(label: "Capcitor-Calculator", route: .capacitor(.index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
239
Sources/ViewController/Views/Capacitor.swift
Normal file
239
Sources/ViewController/Views/Capacitor.swift
Normal file
@@ -0,0 +1,239 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,47 +14,50 @@ struct RoomPressureForm: HTML, Sendable {
|
||||
|
||||
var content: some HTML {
|
||||
div(.class("relative")) {
|
||||
FormHeader(label: "Room Pressure Calculator", svg: .leftRightArrow)
|
||||
div(.class("flex flex-wrap justify-between")) {
|
||||
FormHeader(label: "Room Pressure Calculator - \(mode.label)", svg: .leftRightArrow)
|
||||
|
||||
// Mode toggle / buttons.
|
||||
div(.class("absolute top-0 right-0 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
|
||||
)
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Form(mode: mode)
|
||||
.attributes(.class("mt-6"))
|
||||
|
||||
div(.id("result")) {
|
||||
if let response {
|
||||
@@ -241,3 +244,13 @@ struct RoomPressureResult: HTML, Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomPressure.Mode {
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case let .knownAirflow: return "Known Airflow"
|
||||
case let .measuredPressure: return "Measured Pressure"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user