feat: Updates to home / landing page.
All checks were successful
CI / Linux Tests (push) Successful in 6m32s

This commit is contained in:
2026-02-08 19:12:52 -05:00
parent bb88d48eb3
commit e4ddec0d53
10 changed files with 354 additions and 208 deletions

View File

@@ -1,50 +1,6 @@
import Foundation
import ManualDCore
extension Room {
public var heatingLoadPerRegister: Double {
heatingLoad / Double(registerCount)
}
public func coolingSensiblePerRegister(projectSHR: Double) throws -> Double {
let sensible = try coolingLoad.ensured(shr: projectSHR).sensible
return sensible / Double(registerCount)
}
}
extension TrunkSize.RoomProxy {
// We need to make sure if registers got removed after a trunk
// was already made / saved that we do not include registers that
// no longer exist.
private var actualRegisterCount: Int {
guard registers.count <= room.registerCount else {
return room.registerCount
}
return registers.count
}
var totalHeatingLoad: Double {
room.heatingLoadPerRegister * Double(actualRegisterCount)
}
func totalCoolingSensible(projectSHR: Double) throws -> Double {
try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
}
}
extension TrunkSize {
var totalHeatingLoad: Double {
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
}
func totalCoolingSensible(projectSHR: Double) throws -> Double {
try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) }
}
}
extension Array where Element == EffectiveLengthGroup {
var totalEffectiveLength: Int {
reduce(0) { $0 + $1.effectiveLength }

View File

@@ -33,6 +33,7 @@ extension ManualDClient {
)
}
// FIX: Need to add the loads for rooms that get delegated to other rooms here.
func calculateRoomSizes(
rooms: [Room],
sharedRequest: DuctSizeSharedRequest,
@@ -42,10 +43,15 @@ extension ManualDClient {
var retval: [DuctSizes.RoomContainer] = []
let totalHeatingLoad = rooms.totalHeatingLoad
let totalCoolingSensible = try rooms.totalCoolingSensible(shr: sharedRequest.projectSHR)
let nonDelegatedRooms = rooms.filter { $0.delegatedTo == nil }
for room in rooms {
let heatingLoad = room.heatingLoadPerRegister
for room in nonDelegatedRooms {
// Get all the rooms that delegate their loads to this room.
let delegatedRooms = rooms.filter { $0.delegatedTo == room.id }
let heatingLoad = room.heatingLoadPerRegister(delegatedRooms: delegatedRooms)
let coolingLoad = try room.coolingSensiblePerRegister(projectSHR: sharedRequest.projectSHR)
let heatingPercent = heatingLoad / totalHeatingLoad
let coolingPercent = coolingLoad / totalCoolingSensible
let heatingCFM = heatingPercent * Double(sharedRequest.equipmentInfo.heatingCFM)
@@ -181,47 +187,34 @@ extension DuctSizes.SizeContainer {
}
}
// extension Room {
// extension TrunkSize.RoomProxy {
//
// var heatingLoadPerRegister: Double {
//
// heatingLoad / Double(registerCount)
// // We need to make sure if registers got removed after a trunk
// // was already made / saved that we do not include registers that
// // no longer exist.
// private var actualRegisterCount: Int {
// guard registers.count <= room.registerCount else {
// return room.registerCount
// }
// return registers.count
// }
//
// func coolingSensiblePerRegister(projectSHR: Double) -> Double {
// let sensible = coolingSensible ?? (coolingTotal * projectSHR)
// return sensible / Double(registerCount)
// var totalHeatingLoad: Double {
// room.heatingLoadPerRegister() * Double(actualRegisterCount)
// }
//
// func totalCoolingSensible(projectSHR: Double) throws -> Double {
// try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
// }
// }
extension TrunkSize.RoomProxy {
// We need to make sure if registers got removed after a trunk
// was already made / saved that we do not include registers that
// no longer exist.
private var actualRegisterCount: Int {
guard registers.count <= room.registerCount else {
return room.registerCount
}
return registers.count
}
var totalHeatingLoad: Double {
room.heatingLoadPerRegister * Double(actualRegisterCount)
}
func totalCoolingSensible(projectSHR: Double) throws -> Double {
try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
}
}
extension TrunkSize {
var totalHeatingLoad: Double {
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
}
func totalCoolingSensible(projectSHR: Double) throws -> Double {
try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) }
}
}
// extension TrunkSize {
//
// var totalHeatingLoad: Double {
// rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
// }
//
// func totalCoolingSensible(projectSHR: Double) throws -> Double {
// try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) }
// }
// }

View File

@@ -0,0 +1,20 @@
import Foundation
import ManualDCore
extension Room {
public func heatingLoadPerRegister(delegatedRooms: [Room]? = nil) -> Double {
(heatingLoad + (delegatedRooms?.totalHeatingLoad ?? 0)) / Double(registerCount)
}
public func coolingSensiblePerRegister(
projectSHR: Double,
delegatedRooms: [Room]? = nil
) throws -> Double {
let sensible =
try coolingLoad.ensured(shr: projectSHR).sensible
+ (delegatedRooms?.totalCoolingSensible(shr: projectSHR) ?? 0)
return sensible / Double(registerCount)
}
}

View File

@@ -0,0 +1,34 @@
import Foundation
import ManualDCore
extension TrunkSize.RoomProxy {
// We need to make sure if registers got removed after a trunk
// was already made / saved that we do not include registers that
// no longer exist.
private var actualRegisterCount: Int {
guard registers.count <= room.registerCount else {
return room.registerCount
}
return registers.count
}
public var totalHeatingLoad: Double {
room.heatingLoadPerRegister() * Double(actualRegisterCount)
}
public func totalCoolingSensible(projectSHR: Double) throws -> Double {
try room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(actualRegisterCount)
}
}
extension TrunkSize {
public var totalHeatingLoad: Double {
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
}
public func totalCoolingSensible(projectSHR: Double) throws -> Double {
try rooms.reduce(into: 0) { $0 += try $1.totalCoolingSensible(projectSHR: projectSHR) }
}
}

View File

@@ -4,53 +4,113 @@ import ElementaryHTMX
struct HomeView: HTML, Sendable {
var body: some HTML {
div(.class("flex justify-end me-4")) {
button(
.class("btn btn-ghost btn-secondary text-lg"),
.hx.get(route: .login(.index())),
.hx.target("body"),
.hx.swap(.outerHTML)
) {
"Login"
div( // Uncomment to test different theme's.
// .data("theme", value: "cyberpunk")
// NOTE: Footer background color will follow system theme, it will actually be the
// same as the `hero` background in reality.
) {
div(.class("flex justify-end m-4")) {
button(
.class("btn btn-ghost btn-secondary text-lg"),
.hx.get(route: .login(.index())),
.hx.target("body"),
.hx.swap(.outerHTML)
) {
"Login"
}
}
}
div(.class("hero min-h-screen")) {
div(
.class(
"""
hero-content text-center bg-base-200 dark:bg-base-300
min-w-[80%] min-h-[400px] rounded-3xl shadow-3xl
"""
)
) {
div {
header
a(
.class("btn btn-ghost text-md italic"),
.href("https://git.housh.dev/michael/swift-manual-d"),
.target(.blank)
div(.class("hero")) {
div(
.class(
"""
relative hero-content text-center bg-base-300
w-full min-h-[400px] rounded-3xl shadow-3xl overflow-hidden
"""
)
) {
div(
.class(
"""
bg-secondary text-xl font-bold
absolute top-10 -left-15
px-6 py-2 w-[250px] -rotate-45
"""
)
) {
"Open source residential duct design program"
"BETA"
}
p(.class("text-xl py-6")) {
"""
Manual-D™ speed sheet, but on the web!
"""
}
button(
.class("btn btn-xl bg-violet-600 mt-6"),
.hx.get(route: .signup(.index)),
.hx.target("body"),
.hx.swap(.outerHTML)
) {
"Get Started"
}
p(.class("text-xs italic mt-8")) {
"""
Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA).
div {
header
a(
.class("btn btn-ghost text-md text-primary font-bold italic"),
.href("https://git.housh.dev/michael/swift-manual-d"),
.target(.blank)
) {
"Open source residential duct design program"
}
p(.class("text-3xl py-6")) {
"""
Manual-D™ speed sheet, but on the web!
"""
}
button(
.class("btn btn-xl btn-primary mt-6"),
.hx.get(route: .signup(.index)),
.hx.target("body"),
.hx.swap(.outerHTML)
) {
"Get Started"
}
p(.class("text-xs italic mt-8")) {
"""
Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA).
This site is not designed by or affiliated with ACCA.
"""
This site is not designed by or affiliated with ACCA.
"""
}
}
}
}
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 mx-20 my-6")) {
div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) {
div(.class("flex items-center space-x-4")) {
div(.class("text-5xl text-primary font-bold")) {
"Features"
}
}
div(.class("text-xl ms-10 mt-10")) {
ul(.class("list-disc")) {
li {
div(
.class("font-bold italic bg-secondary rounded-lg shadow-lg px-4 w-fit")
) {
"Built by humans"
}
}
li { "Fully open source." }
li { "Great replacement for speed sheet users." }
li { "Great for classrooms." }
li { "Store your projects in one place." }
li { "Export final project to pdf." }
li { "Import room loads via CSV file." }
li { "Web based." }
li { "Self host (run on your own infrastructure)." }
}
}
}
div(.class("border-3 border-accent rounded-lg shadow-lg p-4")) {
div(.class("text-5xl text-primary font-bold")) {
"Coming Soon"
}
div(.class("text-xl ms-10 mt-10")) {
ul(.class("list-disc")) {
li { "API integration." }
li { "Command line interface." }
li { "Fitting selection tool." }
li { "Room load import from PDF." }
}
}
}
}
@@ -62,7 +122,7 @@ struct HomeView: HTML, Sendable {
div(
.class(
"""
flex border-b-8 border-sky-600
flex border-b-6 border-accent
text-8xl font-bold my-auto space-2
"""
)
@@ -72,7 +132,7 @@ struct HomeView: HTML, Sendable {
span(
.class(
"""
bg-violet-600 rounded-md
bg-secondary rounded-md
text-5xl rotate-180 p-2
"""
),