feat: Adds level field to rooms, updates urls to point to public mirror of the project.
Some checks failed
CI / Linux Tests (push) Failing after 15s
Some checks failed
CI / Linux Tests (push) Failing after 15s
This commit is contained in:
@@ -38,6 +38,11 @@ struct RoomCreateParser: ParserPrinter {
|
||||
ParsePrint {
|
||||
Prefix { $0 != UInt8(ascii: ",") }.map(.string)
|
||||
",".utf8
|
||||
Optionally {
|
||||
Int.parser()
|
||||
.map(.memberwise(Room.Level.init(rawValue:)))
|
||||
}
|
||||
",".utf8
|
||||
Double.parser()
|
||||
",".utf8
|
||||
Optionally {
|
||||
|
||||
@@ -28,6 +28,7 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
$0.delegatedToName != nil && $0.delegatedToName != ""
|
||||
})
|
||||
|
||||
// Filter out the rest of the rooms that don't delegate their airflow / loads.
|
||||
let initialRooms = rows.filter({
|
||||
$0.delegatedToName == nil || $0.delegatedToName == ""
|
||||
})
|
||||
@@ -54,6 +55,7 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
||||
array.append(
|
||||
Room.Create.init(
|
||||
name: row.name,
|
||||
level: row.level,
|
||||
heatingLoad: row.heatingLoad,
|
||||
coolingTotal: row.coolingTotal,
|
||||
coolingSensible: row.coolingSensible,
|
||||
@@ -134,6 +136,7 @@ extension Room.CSV.Row {
|
||||
assert(delegatedToName == nil || delegatedToName == "")
|
||||
return .init(
|
||||
name: name,
|
||||
level: level,
|
||||
heatingLoad: heatingLoad,
|
||||
coolingTotal: coolingTotal,
|
||||
coolingSensible: coolingSensible,
|
||||
@@ -170,6 +173,7 @@ extension Room.Create {
|
||||
|
||||
return .init(
|
||||
name: name,
|
||||
level: level?.rawValue,
|
||||
heatingLoad: heatingLoad,
|
||||
coolingLoad: coolingLoad,
|
||||
registerCount: registerCount,
|
||||
@@ -187,6 +191,7 @@ extension Room {
|
||||
try await database.schema(RoomModel.schema)
|
||||
.id()
|
||||
.field("name", .string, .required)
|
||||
.field("level", .int8)
|
||||
.field("heatingLoad", .double, .required)
|
||||
.field("coolingLoad", .dictionary, .required)
|
||||
.field("registerCount", .int8, .required)
|
||||
@@ -217,6 +222,9 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
|
||||
@Field(key: "level")
|
||||
var level: Int?
|
||||
|
||||
@Field(key: "heatingLoad")
|
||||
var heatingLoad: Double
|
||||
|
||||
@@ -246,6 +254,7 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
||||
init(
|
||||
id: UUID? = nil,
|
||||
name: String,
|
||||
level: Int? = nil,
|
||||
heatingLoad: Double,
|
||||
coolingLoad: Room.CoolingLoad,
|
||||
registerCount: Int,
|
||||
@@ -257,6 +266,7 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.level = level
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingLoad = coolingLoad
|
||||
self.registerCount = registerCount
|
||||
@@ -272,6 +282,7 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
||||
id: requireID(),
|
||||
projectID: $project.id,
|
||||
name: name,
|
||||
level: level.map(Room.Level.init(rawValue:)),
|
||||
heatingLoad: heatingLoad,
|
||||
coolingLoad: coolingLoad,
|
||||
registerCount: registerCount,
|
||||
@@ -287,6 +298,9 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
|
||||
if let name = updates.name, name != self.name {
|
||||
self.name = name
|
||||
}
|
||||
if let level = updates.level?.rawValue, level != self.level {
|
||||
self.level = level
|
||||
}
|
||||
if let heatingLoad = updates.heatingLoad, heatingLoad != self.heatingLoad {
|
||||
self.heatingLoad = heatingLoad
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import Tagged
|
||||
|
||||
/// Represents a room in a project.
|
||||
///
|
||||
@@ -17,6 +18,9 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
/// A unique name for the room in the project.
|
||||
public let name: String
|
||||
|
||||
/// The level of the home the room is on.
|
||||
public let level: Level?
|
||||
|
||||
/// The heating load required for the room (from Manual-J).
|
||||
public let heatingLoad: Double
|
||||
|
||||
@@ -45,6 +49,7 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
id: UUID,
|
||||
projectID: Project.ID,
|
||||
name: String,
|
||||
level: Level? = nil,
|
||||
heatingLoad: Double,
|
||||
coolingLoad: CoolingLoad,
|
||||
registerCount: Int = 1,
|
||||
@@ -56,6 +61,7 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
self.id = id
|
||||
self.projectID = projectID
|
||||
self.name = name
|
||||
self.level = level
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingLoad = coolingLoad
|
||||
self.registerCount = registerCount
|
||||
@@ -98,6 +104,11 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public enum LevelTag {}
|
||||
|
||||
public typealias Level = Tagged<LevelTag, Int>
|
||||
|
||||
}
|
||||
|
||||
extension Room {
|
||||
@@ -106,6 +117,9 @@ extension Room {
|
||||
/// A unique name for the room in the project.
|
||||
public let name: String
|
||||
|
||||
/// An optional level of the home the room is on.
|
||||
public let level: Room.Level?
|
||||
|
||||
/// The heating load required for the room (from Manual-J).
|
||||
public let heatingLoad: Double
|
||||
|
||||
@@ -127,6 +141,7 @@ extension Room {
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
level: Room.Level? = nil,
|
||||
heatingLoad: Double,
|
||||
coolingTotal: Double? = nil,
|
||||
coolingSensible: Double? = nil,
|
||||
@@ -134,6 +149,7 @@ extension Room {
|
||||
delegatedTo: Room.ID? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.level = level
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingTotal = coolingTotal
|
||||
self.coolingSensible = coolingSensible
|
||||
@@ -160,6 +176,9 @@ extension Room {
|
||||
/// A unique name for the room in the project.
|
||||
public let name: String
|
||||
|
||||
/// An optional level of the home the room is on.
|
||||
public let level: Room.Level?
|
||||
|
||||
/// The heating load required for the room (from Manual-J).
|
||||
public let heatingLoad: Double
|
||||
|
||||
@@ -177,6 +196,7 @@ extension Room {
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
level: Room.Level? = nil,
|
||||
heatingLoad: Double,
|
||||
coolingTotal: Double? = nil,
|
||||
coolingSensible: Double? = nil,
|
||||
@@ -184,6 +204,7 @@ extension Room {
|
||||
delegatedToName: String? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.level = level
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingTotal = coolingTotal
|
||||
self.coolingSensible = coolingSensible
|
||||
@@ -226,6 +247,10 @@ extension Room {
|
||||
public struct Update: Codable, Equatable, Sendable {
|
||||
/// A unique name for the room in the project.
|
||||
public let name: String?
|
||||
|
||||
/// An optional level of the home the room is on.
|
||||
public let level: Room.Level?
|
||||
|
||||
/// The heating load required for the room (from Manual-J).
|
||||
public let heatingLoad: Double?
|
||||
/// The total cooling load required for the room (from Manual-J).
|
||||
@@ -246,12 +271,14 @@ extension Room {
|
||||
|
||||
public init(
|
||||
name: String? = nil,
|
||||
level: Room.Level? = nil,
|
||||
heatingLoad: Double? = nil,
|
||||
coolingTotal: Double? = nil,
|
||||
coolingSensible: Double? = nil,
|
||||
registerCount: Int? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.level = level
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingTotal = coolingTotal
|
||||
self.coolingSensible = coolingSensible
|
||||
@@ -263,6 +290,7 @@ extension Room {
|
||||
rectangularSizes: [RectangularSize]
|
||||
) {
|
||||
self.name = nil
|
||||
self.level = nil
|
||||
self.heatingLoad = nil
|
||||
self.coolingTotal = nil
|
||||
self.coolingSensible = nil
|
||||
@@ -304,6 +332,16 @@ public struct CoolingLoadError: Error, Equatable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Room.Level {
|
||||
/// The label for the level, i.e. 'Basement' or 'Level-1', etc.
|
||||
public var label: String {
|
||||
if rawValue <= 0 {
|
||||
return "Basement"
|
||||
}
|
||||
return "Level-\(rawValue)"
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
extension Room {
|
||||
|
||||
@@ -216,12 +216,6 @@ extension SiteRoute.View.ProjectRoute {
|
||||
}
|
||||
Method.post
|
||||
Body().map(.memberwise(Room.CSV.init))
|
||||
// Body {
|
||||
// FormData {
|
||||
//
|
||||
// }
|
||||
// .map(.memberwise(Room.CSV.init))
|
||||
// }
|
||||
}
|
||||
Route(.case(Self.delete)) {
|
||||
Path {
|
||||
@@ -242,6 +236,12 @@ extension SiteRoute.View.ProjectRoute {
|
||||
Body {
|
||||
FormData {
|
||||
Field("name", .string)
|
||||
Optionally {
|
||||
Field("level") {
|
||||
Int.parser()
|
||||
}
|
||||
.map(.memberwise(Room.Level.init(rawValue:)))
|
||||
}
|
||||
Field("heatingLoad") { Double.parser() }
|
||||
Optionally {
|
||||
Field("coolingTotal") { Double.parser() }
|
||||
@@ -268,6 +268,12 @@ extension SiteRoute.View.ProjectRoute {
|
||||
Optionally {
|
||||
Field("name", .string)
|
||||
}
|
||||
Optionally {
|
||||
Field("level") {
|
||||
Int.parser()
|
||||
}
|
||||
.map(.memberwise(Room.Level.init(rawValue:)))
|
||||
}
|
||||
Optionally {
|
||||
Field("heatingLoad") { Double.parser() }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Elementary
|
||||
import Foundation
|
||||
import Validations
|
||||
|
||||
public struct ResultView<ValueView, ErrorView>: HTML where ValueView: HTML, ErrorView: HTML {
|
||||
|
||||
@@ -69,7 +70,11 @@ public struct ErrorView: HTML, Sendable {
|
||||
div {
|
||||
h1(.class("text-xl font-bold text-error")) { "Oops: Error" }
|
||||
p {
|
||||
"\(error.localizedDescription)"
|
||||
if let validationError = (error as? ValidationError) {
|
||||
"\(validationError.debugDescription)"
|
||||
} else {
|
||||
"\(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ struct HomeView: HTML, Sendable {
|
||||
header
|
||||
a(
|
||||
.class("btn btn-ghost text-md text-primary font-bold italic"),
|
||||
.href("https://git.housh.dev/michael/swift-duct-calc"),
|
||||
.href("https://github.com/m-housh/swift-duct-calc"),
|
||||
.target(.blank)
|
||||
) {
|
||||
"Open source residential duct design program"
|
||||
|
||||
@@ -94,22 +94,36 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
|
||||
footer(
|
||||
.class(
|
||||
"""
|
||||
footer sm:footer-horizontal footer-center
|
||||
footer footer-horizontal footer-center
|
||||
bg-base-300 text-base-content p-4
|
||||
"""
|
||||
)
|
||||
) {
|
||||
aside {
|
||||
p {
|
||||
"Copyright © \(Date().description.prefix(4)) - All rights reserved by Michael Housh"
|
||||
aside(
|
||||
.class("grid-flow-row items-center")
|
||||
) {
|
||||
|
||||
div(.class("flex mx-auto")) {
|
||||
a(
|
||||
.class("btn btn-ghost"),
|
||||
.href("mailto:support@ductcalc.pro")
|
||||
) {
|
||||
SVG(.email)
|
||||
span { "support@ductcalc.pro" }
|
||||
}
|
||||
}
|
||||
|
||||
a(
|
||||
.class("btn btn-ghost"),
|
||||
.href("https://git.housh.dev/michael/swift-duct-calc/src/branch/main/LICENSE"),
|
||||
.class("btn btn-ghost mx-auto"),
|
||||
.href("https://github.com/m-housh/swift-duct-calc/src/branch/main/LICENSE"),
|
||||
.target(.blank)
|
||||
) {
|
||||
"Openly licensed via CC-BY-NC-SA 4.0"
|
||||
}
|
||||
|
||||
p(.class("")) {
|
||||
"Copyright © \(Date().description.prefix(4)) - All rights reserved by Michael Housh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ struct Navbar: HTML, Sendable {
|
||||
label(
|
||||
.for("my-drawer-1"),
|
||||
.class("size-7"),
|
||||
.init(name: "aria-label", value: "open sidebar")
|
||||
.init(name: "aria-label", value: "open / close sidebar")
|
||||
) {
|
||||
SVG(.sidebarToggle)
|
||||
}
|
||||
.navButton()
|
||||
.tooltip("Open sidebar", position: .right)
|
||||
.tooltip("Open / close sidebar", position: .right)
|
||||
}
|
||||
|
||||
a(
|
||||
|
||||
@@ -68,6 +68,21 @@ struct RoomForm: HTML, Sendable {
|
||||
.value(room?.name)
|
||||
)
|
||||
|
||||
LabeledInput(
|
||||
"Level",
|
||||
.name("level"),
|
||||
.type(.number),
|
||||
.placeholder("1 (Optional)"),
|
||||
.value(room?.level?.rawValue),
|
||||
.min("-1"),
|
||||
.step("1")
|
||||
)
|
||||
div(.class("text-sm italic -mt-2")) {
|
||||
span(.class("text-primary")) {
|
||||
"Use -1 or 0 for a basement"
|
||||
}
|
||||
}
|
||||
|
||||
LabeledInput(
|
||||
"Heating Load",
|
||||
.name("heatingLoad"),
|
||||
@@ -78,8 +93,6 @@ struct RoomForm: HTML, Sendable {
|
||||
.value(room?.heatingLoad)
|
||||
)
|
||||
|
||||
// TODO: Add description that only one is required (cooling total or sensible)
|
||||
|
||||
LabeledInput(
|
||||
"Cooling Total",
|
||||
.name("coolingTotal"),
|
||||
@@ -97,6 +110,14 @@ struct RoomForm: HTML, Sendable {
|
||||
.min("0"),
|
||||
.value(room?.coolingLoad.sensible)
|
||||
)
|
||||
div(.class("text-primary text-sm italic -mt-2")) {
|
||||
p {
|
||||
"Should enter at least one of the cooling loads."
|
||||
}
|
||||
p {
|
||||
"Both are also acceptable."
|
||||
}
|
||||
}
|
||||
|
||||
LabeledInput(
|
||||
"Registers",
|
||||
|
||||
@@ -15,6 +15,14 @@ struct RoomsView: HTML, Sendable {
|
||||
.appendingPath("csv")
|
||||
}
|
||||
|
||||
// Sort the rooms based on level, they should already be sorted by name,
|
||||
// so this puts lower level rooms towards the top in alphabetical order.
|
||||
//
|
||||
// If rooms do not have a level we shove those all the way to the bottom.
|
||||
private var sortedRooms: [Room] {
|
||||
rooms.sorted { ($0.level?.rawValue ?? 20) < ($1.level?.rawValue ?? 20) }
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
div(.class("flex w-full flex-col")) {
|
||||
PageTitleRow {
|
||||
@@ -133,7 +141,7 @@ struct RoomsView: HTML, Sendable {
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for room in rooms {
|
||||
for room in sortedRooms {
|
||||
RoomRow(room: room, shr: sensibleHeatRatio, rooms: rooms)
|
||||
}
|
||||
}
|
||||
@@ -166,7 +174,13 @@ struct RoomsView: HTML, Sendable {
|
||||
|
||||
public var body: some HTML {
|
||||
tr(.id("roomRow_\(room.id.idString)")) {
|
||||
td { room.name }
|
||||
td {
|
||||
if let level = room.level {
|
||||
"\(level.label) - \(room.name)"
|
||||
} else {
|
||||
room.name
|
||||
}
|
||||
}
|
||||
td {
|
||||
div(.class("flex justify-center")) {
|
||||
Number(room.heatingLoad, digits: 0)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Elementary
|
||||
import ElementaryHTMX
|
||||
import ManualDCore
|
||||
import Styleguide
|
||||
|
||||
struct LoginForm: HTML, Sendable {
|
||||
@@ -12,6 +13,13 @@ struct LoginForm: HTML, Sendable {
|
||||
self.next = next
|
||||
}
|
||||
|
||||
private var route: SiteRoute.View {
|
||||
if style == .login {
|
||||
return .login(.index(next: next))
|
||||
}
|
||||
return .signup(.index)
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
ModalForm(id: "loginForm", closeButton: false, dismiss: false) {
|
||||
h1(.class("text-2xl font-bold mb-6")) { style.title }
|
||||
|
||||
@@ -150,6 +150,10 @@ struct UserProfileForm: HTML, Sendable {
|
||||
.attributes(.class("btn-block"))
|
||||
|
||||
}
|
||||
.attributes(
|
||||
.hx.pushURL("/projects"),
|
||||
when: signup == true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user