feat: Adds styleguide, working on result view container.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "7efd57cbec8a157adddec3ffcc6ff2b58e602f8762df879e933220d7070945f7",
|
"originHash" : "af48ddc16f0ca460cff188345cf870056ca53edee23c016080fc1ad55f366bf9",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity" : "async-http-client",
|
||||||
@@ -276,8 +276,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/swift-psychrometrics/swift-psychrometrics.git",
|
"location" : "https://github.com/swift-psychrometrics/swift-psychrometrics.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "df8b764b06b52386948b2ddcd99cdf000db666ec",
|
"revision" : "04f3324b2e3498852461b43102b8835136f2f0e7",
|
||||||
"version" : "0.2.4"
|
"version" : "0.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ let package = Package(
|
|||||||
.executable(name: "App", targets: ["App"]),
|
.executable(name: "App", targets: ["App"]),
|
||||||
.library(name: "ApiController", targets: ["ApiController"]),
|
.library(name: "ApiController", targets: ["ApiController"]),
|
||||||
.library(name: "Routes", targets: ["Routes"]),
|
.library(name: "Routes", targets: ["Routes"]),
|
||||||
|
.library(name: "Styleguide", targets: ["Styleguide"]),
|
||||||
.library(name: "ViewController", targets: ["ViewController"])
|
.library(name: "ViewController", targets: ["ViewController"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -25,7 +26,7 @@ let package = Package(
|
|||||||
.package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"),
|
.package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.7"),
|
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.7"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
|
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
|
||||||
.package(url: "https://github.com/swift-psychrometrics/swift-psychrometrics.git", from: "0.2.4")
|
.package(url: "https://github.com/swift-psychrometrics/swift-psychrometrics.git", from: "0.3.0")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
@@ -68,10 +69,18 @@ let package = Package(
|
|||||||
],
|
],
|
||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "Styleguide",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Elementary", package: "elementary")
|
||||||
|
],
|
||||||
|
swiftSettings: swiftSettings
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "ViewController",
|
name: "ViewController",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"Routes",
|
"Routes",
|
||||||
|
"Styleguide",
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
.product(name: "Elementary", package: "elementary")
|
.product(name: "Elementary", package: "elementary")
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
<svg width="800px" height="800px" viewBox="-102.4 -102.4 1228.80 1228.80" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#1E2938" stroke="#1E2938" stroke-width="15.36">
|
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000">
|
||||||
|
|
||||||
<g id="SVGRepo_bgCarrier" stroke-width="0" transform="translate(97.27999999999997,97.27999999999997), scale(0.81)">
|
|
||||||
|
|
||||||
<rect x="-102.4" y="-102.4" width="1228.80" height="1228.80" rx="0" fill="#FFDE5A" strokewidth="0"/>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="38.912">
|
|
||||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||||
|
|
||||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.6 KiB |
File diff suppressed because one or more lines are too long
@@ -40,3 +40,32 @@ public enum MoldRisk {
|
|||||||
case severe
|
case severe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
import Dependencies
|
||||||
|
|
||||||
|
public extension MoldRisk.Response {
|
||||||
|
static var mock: Self {
|
||||||
|
return .init(
|
||||||
|
psychrometricProperties: .init(
|
||||||
|
absoluteHumidity: .zero,
|
||||||
|
atmosphericPressure: .zero,
|
||||||
|
degreeOfSaturation: .zero,
|
||||||
|
density: .zero, dewPoint: 59.4,
|
||||||
|
dryBulb: 75,
|
||||||
|
enthalpy: .zero,
|
||||||
|
grainsOfMoisture: .zero,
|
||||||
|
humidityRatio: .zero,
|
||||||
|
relativeHumidity: 50%,
|
||||||
|
specificVolume: .zero,
|
||||||
|
vaporPressure: .zero,
|
||||||
|
wetBulb: .zero,
|
||||||
|
units: .imperial
|
||||||
|
),
|
||||||
|
riskLevel: .low,
|
||||||
|
recommendations: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
19
Sources/Styleguide/Buttons.swift
Normal file
19
Sources/Styleguide/Buttons.swift
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct SubmitButton: HTML, Sendable {
|
||||||
|
let label: String
|
||||||
|
|
||||||
|
public init(label: String) {
|
||||||
|
self.label = label
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML<HTMLTag.button> {
|
||||||
|
button(
|
||||||
|
.type(.submit),
|
||||||
|
.class("""
|
||||||
|
w-full \(bg: .blue) \(text: .yellow) font-bold py-3 rounded-md
|
||||||
|
hover:\(bg: .darkBlue) transition-colors
|
||||||
|
""")
|
||||||
|
) { label }
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Sources/Styleguide/Colors.swift
Normal file
85
Sources/Styleguide/Colors.swift
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/// Common tailwind colors used.
|
||||||
|
///
|
||||||
|
/// Use these with string interpolation in the class.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// div(.class("\(text: .yellow) \(border: .gray) \(bg: .blue)")) {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
public enum Color {
|
||||||
|
|
||||||
|
public enum BackgroundColor: Sendable {
|
||||||
|
case blue
|
||||||
|
case darkBlue
|
||||||
|
case darkGray
|
||||||
|
case darkSlate
|
||||||
|
case slate
|
||||||
|
case yellow
|
||||||
|
|
||||||
|
var color: String {
|
||||||
|
switch self {
|
||||||
|
case .blue: return "bg-blue-500"
|
||||||
|
case .darkBlue: return "bg-blue-600"
|
||||||
|
case .darkGray: return "bg-gray-700"
|
||||||
|
case .darkSlate: return "bg-slate-700"
|
||||||
|
case .slate: return "bg-slate-300"
|
||||||
|
case .yellow: return "bg-yellow-300"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BorderColor: Sendable {
|
||||||
|
case darkYellow
|
||||||
|
case gray
|
||||||
|
case yellow
|
||||||
|
|
||||||
|
var color: String {
|
||||||
|
switch self {
|
||||||
|
case .darkYellow: return "border-yellow-800"
|
||||||
|
case .gray: return "border-gray-300"
|
||||||
|
case .yellow: return "border-yellow-300"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextColor: Sendable {
|
||||||
|
case blue
|
||||||
|
case darkBlue
|
||||||
|
case darkGray
|
||||||
|
case gray
|
||||||
|
case green
|
||||||
|
case yellow
|
||||||
|
case white
|
||||||
|
|
||||||
|
var color: String {
|
||||||
|
switch self {
|
||||||
|
case .blue: return "text-blue-500"
|
||||||
|
case .darkBlue: return "text-blue-600"
|
||||||
|
case .darkGray: return "text-gray-700"
|
||||||
|
case .gray: return "text-gray-300"
|
||||||
|
case .green: return "text-green-600"
|
||||||
|
case .yellow: return "text-yellow-300"
|
||||||
|
case .white: return "text-white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension String.StringInterpolation {
|
||||||
|
|
||||||
|
mutating func appendInterpolation(bg background: Color.BackgroundColor) {
|
||||||
|
appendInterpolation(background.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func appendInterpolation(border: Color.BorderColor) {
|
||||||
|
appendInterpolation(border.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func appendInterpolation(text: Color.TextColor) {
|
||||||
|
appendInterpolation(text.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Sources/Styleguide/FormElements.swift
Normal file
84
Sources/Styleguide/FormElements.swift
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
/// A styled header for a form element, which consists of an
|
||||||
|
/// svg image and label / name for the form.
|
||||||
|
///
|
||||||
|
public struct FormHeader: HTML, Sendable {
|
||||||
|
|
||||||
|
let label: String
|
||||||
|
let svg: SVGType
|
||||||
|
|
||||||
|
public init(
|
||||||
|
label: String,
|
||||||
|
svg: SVGType
|
||||||
|
) {
|
||||||
|
self.label = label
|
||||||
|
self.svg = svg
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML {
|
||||||
|
LabeledContent {
|
||||||
|
h2(.class("text-2xl font-extrabold dark:\(text: .white)")) { label }
|
||||||
|
} label: {
|
||||||
|
SVG(svg, color: .blue)
|
||||||
|
}
|
||||||
|
.attributes(.class("flex items-center gap-3 mb-6"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A styled form input, does not contain the input type which is generally
|
||||||
|
/// added at the call site.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
/// ```swift
|
||||||
|
/// Input(id: "email", placeholder: "Email")
|
||||||
|
/// .attributes(.type(.email))
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
public struct Input: HTML, Sendable {
|
||||||
|
let id: String
|
||||||
|
let name: String?
|
||||||
|
let placeholder: String
|
||||||
|
|
||||||
|
public init(id: String, name: String? = nil, placeholder: String) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.placeholder = placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML<HTMLTag.input> {
|
||||||
|
input(
|
||||||
|
.id(id), .placeholder(placeholder), .name(name ?? id),
|
||||||
|
.class("""
|
||||||
|
w-full px-4 py-2 border \(border: .gray) rounded-md focus:ring-2
|
||||||
|
focus:ring-yellow-800 focus:border-yellow-800 \(text: .darkGray) dark:\(text: .white)
|
||||||
|
""")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A style form input label.
|
||||||
|
public struct InputLabel<InputLabel: HTML>: HTML {
|
||||||
|
|
||||||
|
let forInputId: String
|
||||||
|
let inputLabel: InputLabel
|
||||||
|
|
||||||
|
public init(
|
||||||
|
for forInputId: String,
|
||||||
|
@HTMLBuilder label: () -> InputLabel
|
||||||
|
) {
|
||||||
|
self.forInputId = forInputId
|
||||||
|
self.inputLabel = label()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML<HTMLTag.label> {
|
||||||
|
label(
|
||||||
|
.for(forInputId),
|
||||||
|
.class("block text-sm font-medium \(text: .darkGray) dark:\(text: .gray) mb-2")
|
||||||
|
) {
|
||||||
|
self.inputLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension InputLabel: Sendable where InputLabel: Sendable {}
|
||||||
54
Sources/Styleguide/LabeledContent.swift
Normal file
54
Sources/Styleguide/LabeledContent.swift
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct LabeledContent<Label: HTML, Body: HTML>: HTML {
|
||||||
|
|
||||||
|
let body: Body
|
||||||
|
let label: Label
|
||||||
|
|
||||||
|
public init(
|
||||||
|
@HTMLBuilder body: () -> Body,
|
||||||
|
@HTMLBuilder label: () -> Label
|
||||||
|
) {
|
||||||
|
self.body = body()
|
||||||
|
self.label = label()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML<HTMLTag.div> {
|
||||||
|
div {
|
||||||
|
label
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LabeledContent: Sendable where Label: Sendable, Body: Sendable {}
|
||||||
|
|
||||||
|
// MARK: - Forms
|
||||||
|
|
||||||
|
public extension LabeledContent where Label == InputLabel<HTMLText>, Body == Input {
|
||||||
|
|
||||||
|
init(
|
||||||
|
label: String,
|
||||||
|
input: () -> Body
|
||||||
|
) {
|
||||||
|
self.init {
|
||||||
|
input()
|
||||||
|
} label: {
|
||||||
|
InputLabel(for: input().id) { HTMLText(label) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension LabeledContent where Label == InputLabel<HTMLText>, Body == _AttributedElement<Input> {
|
||||||
|
|
||||||
|
init(
|
||||||
|
label: String,
|
||||||
|
input: () -> Body
|
||||||
|
) {
|
||||||
|
self.init {
|
||||||
|
input()
|
||||||
|
} label: {
|
||||||
|
InputLabel(for: input().content.id) { HTMLText(label) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Sources/Styleguide/ResultContainer.swift
Normal file
20
Sources/Styleguide/ResultContainer.swift
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct ResultContainer<Body: HTML>: HTML {
|
||||||
|
let body: Body
|
||||||
|
|
||||||
|
public init(
|
||||||
|
@HTMLBuilder body: () -> Body
|
||||||
|
) {
|
||||||
|
self.body = body()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML {
|
||||||
|
div(.class("mt-6 p-6 bg-blue-50 dark:bg-slate-400 rounded-lg")) {
|
||||||
|
h3(.class("text-xl font-semibold \(text: .darkGray) mb-4")) { "Results" }
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ResultContainer: Sendable where Body: Sendable {}
|
||||||
158
Sources/Styleguide/SVG.swift
Normal file
158
Sources/Styleguide/SVG.swift
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct SVG: HTML, Sendable {
|
||||||
|
let color: String
|
||||||
|
let size: SVGSize
|
||||||
|
let svg: SVGType
|
||||||
|
|
||||||
|
public init(
|
||||||
|
_ svg: SVGType,
|
||||||
|
color: String,
|
||||||
|
size: SVGSize = .init()
|
||||||
|
) {
|
||||||
|
self.svg = svg
|
||||||
|
self.size = size
|
||||||
|
self.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
_ svg: SVGType,
|
||||||
|
color: Color.TextColor,
|
||||||
|
size: SVGSize = .init()
|
||||||
|
) {
|
||||||
|
self.svg = svg
|
||||||
|
self.size = size
|
||||||
|
self.color = color.color
|
||||||
|
}
|
||||||
|
|
||||||
|
public var content: some HTML<HTMLTag.div> {
|
||||||
|
div(.class("block \(color)")) {
|
||||||
|
svg.html(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SVGSize: Sendable {
|
||||||
|
let width: Int
|
||||||
|
let height: Int
|
||||||
|
|
||||||
|
public init(width: Int = 24, height: Int? = nil) {
|
||||||
|
self.width = width
|
||||||
|
self.height = height ?? width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SVGType: Sendable {
|
||||||
|
case calculator
|
||||||
|
case exclamation
|
||||||
|
case menu
|
||||||
|
case thermometer
|
||||||
|
case toolbox
|
||||||
|
case wind
|
||||||
|
|
||||||
|
public func html(_ size: SVGSize) -> some HTML {
|
||||||
|
switch self {
|
||||||
|
case .calculator: return calculatorSvg(size: size)
|
||||||
|
case .exclamation: return exclamationSvg(size: size)
|
||||||
|
case .menu: return menuSvg(size: size)
|
||||||
|
case .thermometer: return thermometerSvg(size: size)
|
||||||
|
case .toolbox: return toolboxSvg(size: size)
|
||||||
|
case .wind: return windSvg(size: size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - SVGs
|
||||||
|
|
||||||
|
// swiftlint:disable line_length
|
||||||
|
|
||||||
|
// TODO: Requires attribution:
|
||||||
|
// Vectors and icons by <a href="https://dribbble.com/Laridae?ref=svgrepo.com" target="_blank">Laridae</a> in CC Attribution License via <a href="https://www.svgrepo.com/" target="_blank">SVG Repo</a>
|
||||||
|
// FIX: This doesn't work, but does as a file
|
||||||
|
private func toolboxSvg(size: SVGSize) -> HTMLRaw {
|
||||||
|
HTMLRaw("""
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<svg width="\(size.width)" height="\(size.height)" stroke="currentColor" viewBox="0 0 \(size.width) \(size.height)" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000">
|
||||||
|
|
||||||
|
<g id="SVGRepo_bgCarrier" stroke="currentColor" stroke-width="2"/>
|
||||||
|
|
||||||
|
<g id="SVGRepo_tracerCarrier" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
|
||||||
|
<path d="M619.52 194.56h35.84V128c0-5.632-4.608-10.24-10.24-10.24H378.88c-5.632 0-10.24 4.608-10.24 10.24v66.56h35.84v-30.72c0-5.632 4.608-10.24 10.24-10.24h194.56c5.632 0 10.24 4.608 10.24 10.24v30.72z" fill="#2C7FFF"/>
|
||||||
|
|
||||||
|
<path d="M184.32 209.92v20.48h655.36v-20.48H184.32z m-5.12-15.36h665.6c5.632 0 10.24 4.608 10.24 10.24v30.72c0 5.632-4.608 10.24-10.24 10.24H179.2c-5.632 0-10.24-4.608-10.24-10.24v-30.72c0-5.632 4.608-10.24 10.24-10.24zM240.64 890.88h51.2v-10.24H240.64v10.24z m-5.12-25.6h61.44c5.632 0 10.24 4.608 10.24 10.24v20.48c0 5.632-4.608 10.24-10.24 10.24H235.52c-5.632 0-10.24-4.608-10.24-10.24v-20.48c0-5.632 4.608-10.24 10.24-10.24zM737.28 890.88h51.2v-10.24h-51.2v10.24z m-5.12-25.6h61.44c5.632 0 10.24 4.608 10.24 10.24v20.48c0 5.632-4.608 10.24-10.24 10.24h-61.44c-5.632 0-10.24-4.608-10.24-10.24v-20.48c0-5.632 4.608-10.24 10.24-10.24z" fill=""/>
|
||||||
|
|
||||||
|
<path d="M199.68 343.04h122.88c5.632 0 10.24 4.608 10.24 10.24v81.92c0 5.632-4.608 10.24-10.24 10.24H199.68c-5.632 0-10.24-4.608-10.24-10.24V353.28c0-5.632 4.608-10.24 10.24-10.24zM701.44 343.04h122.88c5.632 0 10.24 4.608 10.24 10.24v81.92c0 5.632-4.608 10.24-10.24 10.24h-122.88c-5.632 0-10.24-4.608-10.24-10.24V353.28c0-5.632 4.608-10.24 10.24-10.24z" fill="#2C7FFF"/>
|
||||||
|
|
||||||
|
<path d="M873.472 276.48H148.48c-11.264 1.024-19.456 10.752-18.432 22.016l48.128 522.24c1.024 10.752 9.728 18.432 20.48 18.432h626.688c10.752 0 19.456-8.192 20.48-18.432l48.64-522.24v-2.048c-0.512-10.752-9.728-19.968-20.992-19.968zM150.528 291.84h722.944c3.072 0 5.12 2.048 5.12 5.632L870.4 384h-35.84v-30.72c0-5.632-4.608-10.24-10.24-10.24h-122.88c-5.632 0-10.24 4.608-10.24 10.24v30.72H332.8v-30.72c0-5.632-4.608-10.24-10.24-10.24H199.68c-5.632 0-10.24 4.608-10.24 10.24v30.72h-36.352l-8.192-86.528c0-3.072 2.048-5.12 5.632-5.632zM819.2 358.4v71.68h-112.64V358.4h112.64z m-501.76 0v71.68H204.8V358.4h112.64z m513.024 461.312c0 2.56-2.56 4.608-5.12 4.608H198.144c-2.56 0-4.608-2.048-5.12-4.608L154.624 399.36H189.44v35.84c0 5.632 4.608 10.24 10.24 10.24h122.88c5.632 0 10.24-4.608 10.24-10.24v-35.84h358.4v35.84c0 5.632 4.608 10.24 10.24 10.24h122.88c5.632 0 10.24-4.608 10.24-10.24v-35.84h34.816l-38.912 420.352z" fill=""/>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func exclamationSvg(size: SVGSize) -> HTMLRaw {
|
||||||
|
HTMLRaw("""
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
|
||||||
|
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
|
||||||
|
<path d="M12 9v4"></path>
|
||||||
|
<path d="M12 17h.01"></path>
|
||||||
|
</svg>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func windSvg(size: SVGSize) -> HTMLRaw {
|
||||||
|
return HTMLRaw("""
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 \(size.width) \(size.height)" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
class="w-8 h-8">
|
||||||
|
<path d="M17.7 7.7a2.5 2.5 0 1 1 1.8 4.3H2"></path>
|
||||||
|
<path d="M9.6 4.6A2 2 0 1 1 11 8H2"></path>
|
||||||
|
<path d="M12.6 19.4A2 2 0 1 0 14 16H2"></path>
|
||||||
|
</svg>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func calculatorSvg(size: SVGSize) -> HTMLRaw {
|
||||||
|
return HTMLRaw("""
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 \(size.width) \(size.height)" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
class="w-5 h-5">
|
||||||
|
<rect width="16" height="20" x="4" y="2" rx="2"></rect>
|
||||||
|
<line x1="8" x2="16" y1="6" y2="6"></line>
|
||||||
|
<line x1="16" x2="16" y1="14" y2="18"></line>
|
||||||
|
<path d="M16 10h.01"></path>
|
||||||
|
<path d="M12 10h.01"></path>
|
||||||
|
<path d="M8 10h.01"></path>
|
||||||
|
<path d="M12 14h.01"></path>
|
||||||
|
<path d="M8 14h.01"></path>
|
||||||
|
<path d="M12 18h.01"></path><path d="M8 18h.01"></path>
|
||||||
|
</svg>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func thermometerSvg(size: SVGSize) -> HTMLRaw {
|
||||||
|
HTMLRaw("""
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 \(size.width) \(size.height)" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
class="w-8 h-8">
|
||||||
|
<path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"></path>
|
||||||
|
</svg>
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func menuSvg(size: SVGSize) -> HTMLRaw {
|
||||||
|
HTMLRaw("""
|
||||||
|
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24" x="0" y="0" id="menu-icon">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:enable line_length
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
||||||
let title = "HVAC-Toolbox"
|
let title = "HVAC-Toolbox"
|
||||||
@@ -33,36 +34,36 @@ struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
|||||||
main(.class("bg-white dark:bg-gray-800")) {
|
main(.class("bg-white dark:bg-gray-800")) {
|
||||||
div(.class("min-h-screen")) {
|
div(.class("min-h-screen")) {
|
||||||
Header()
|
Header()
|
||||||
inner()
|
PageContent(body: inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Header: HTML {
|
private struct Header: HTML {
|
||||||
|
|
||||||
var content: some HTML {
|
var content: some HTML {
|
||||||
header(.class("bg-blue-500 mb-8 flex flex-row gap-2 border-b border-yellow-300")) {
|
header(.class("\(bg: .blue) mb-8 flex flex-row gap-2 border \(border: .yellow)")) {
|
||||||
a(
|
a(
|
||||||
.href(route: .index),
|
.href(route: .index),
|
||||||
.class("flex flex-row gap-2 bg-yellow-300 pe-2 rounded-e-lg text-blue-500 [&:hover]:text-blue-600")
|
.class("flex flex-row gap-2 \(bg: .yellow) pe-2 rounded-e-lg \(text: .blue) hover:\(text: .darkBlue)")
|
||||||
) {
|
) {
|
||||||
img(.src("/images/toolbox.svg"), .width(40), .height(40), .class("py-1"))
|
img(.src("/images/toolbox.svg"), .width(40), .height(40), .class("py-1"))
|
||||||
div(.class("flex flex-row mt-2")) {
|
div(.class("flex flex-row mt-2")) {
|
||||||
h2(.class("text-2xl font-extrabold")) { "HVAC-Toolbox" }
|
h2(.class("text-2xl font-extrabold pe-3")) { "HVAC-Toolbox" }
|
||||||
SVG.wind
|
SVG(.wind, color: .blue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nav(.class("flex flex-row gap-2 p-2 mt-2")) {
|
nav(.class("flex flex-row gap-2 p-2 mt-2")) {
|
||||||
// TODO: Add class active, to button that is the active route.
|
// TODO: Add class active, to button that is the active route.
|
||||||
ul(.class("flex flex-wrap gap-x-2 lg:gap-x-5 text-yellow-300 font-bold")) {
|
ul(.class("flex flex-wrap gap-x-2 lg:gap-x-5 \(text: .yellow) font-bold")) {
|
||||||
li {
|
li {
|
||||||
a(.href(route: .moldRisk(.index)), .class("[&:hover]:border-b border-yellow-300")) {
|
a(.href(route: .moldRisk(.index)), .class("hover:border-b \(border: .yellow)")) {
|
||||||
"Mold-Risk"
|
"Mold-Risk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
a(.href("#"), .class("[&:hover]:border-b border-yellow-300")) {
|
a(.href("#"), .class("[&:hover]:border-b \(border: .yellow)")) {
|
||||||
"Dehumidifier-Sizing"
|
"Dehumidifier-Sizing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,4 +73,16 @@ struct Header: HTML {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct PageContent<Body: HTML>: HTML where Body: Sendable {
|
||||||
|
let body: () -> Body
|
||||||
|
|
||||||
|
var content: some HTML {
|
||||||
|
div(.class("mx-5 lg:mx-20")) {
|
||||||
|
div(.class("rounded-xl shadow-lg \(bg: .slate) dark:\(bg: .darkSlate) p-8")) {
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protocol SendableHTMLDocument: HTMLDocument, Sendable {}
|
protocol SendableHTMLDocument: HTMLDocument, Sendable {}
|
||||||
|
|||||||
@@ -1,56 +1,91 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
|
import PsychrometricClient
|
||||||
import Routes
|
import Routes
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
struct MoldRiskForm: HTML {
|
struct MoldRiskForm: HTML {
|
||||||
|
|
||||||
var content: some HTML {
|
var content: some HTML {
|
||||||
div(.class("grid grid-cols-1 lg:grid-cols-12")) {
|
FormHeader(label: "Mold Risk Calculator", svg: .thermometer)
|
||||||
div(.class("col-span-1")) {}
|
form {
|
||||||
div(.class("col-span-10 rounded-xl shadow-lg bg-slate-300 dark:bg-slate-700 p-8")) {
|
div(.class("space-y-6")) {
|
||||||
div(.class("flex items-center gap-3 mb-6")) {
|
LabeledContent(label: "Indoor Temperature (°F)") {
|
||||||
SVG.thermometer
|
Input(id: "temperature", placeholder: "Dry bulb temperature")
|
||||||
h2(.class("text-2xl font-extrabold dark:text-white")) { "Mold Risk Calculator" }
|
.attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus)
|
||||||
}
|
}
|
||||||
form {
|
|
||||||
div(.class("space-y-6")) {
|
LabeledContent(label: "Indor Humdity (%)") {
|
||||||
div {
|
Input(id: "humidity", placeholder: "Relative humidity")
|
||||||
label(.for("temperature"), .class("block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2")) {
|
.attributes(.type(.number), .step("0.1"), .min("0.1"))
|
||||||
"Indoor Temperature"
|
}
|
||||||
}
|
|
||||||
input(
|
div {
|
||||||
.type(.number), .id("temperature"), .placeholder("Dry bulb temperature"), .required,
|
SubmitButton(label: "Calculate Mold Risk")
|
||||||
.step("0.1"), .min("0.1"),
|
}
|
||||||
.class("""
|
|
||||||
w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-yellow-800
|
div(.id("result")) {
|
||||||
focus:border-yellow-800 text-gray-700 dark:text-white
|
MoldRiskResponse(response: .mock)
|
||||||
""")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
label(.for("humidity"), .class("block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2")) {
|
|
||||||
"Indoor Humidity (%)"
|
|
||||||
}
|
|
||||||
input(
|
|
||||||
.type(.number), .id("humidity"), .name("humidity"), .placeholder("Relative humidity"), .required,
|
|
||||||
.step("0.1"), .min("0.1"),
|
|
||||||
.class("""
|
|
||||||
w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-yellow-800
|
|
||||||
focus:border-yellow-800 text-gray-700 dark:text-white
|
|
||||||
""")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
button(.type(.submit), .class("""
|
|
||||||
w-full bg-\(Colors.blue) text-\(Colors.yellow) font-bold py-3 rounded-md hover:bg-blue-600
|
|
||||||
transition-colors
|
|
||||||
""")) {
|
|
||||||
"Calculate Mold Risk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
div(.class("col-span-1")) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MoldRiskResponse: HTML {
|
||||||
|
let response: MoldRisk.Response
|
||||||
|
|
||||||
|
var content: some HTML {
|
||||||
|
ResultContainer {
|
||||||
|
// TODO: Color needs to be derived from risk level.
|
||||||
|
div(.class("p-2 rounded-lg shadow-lg bg-lime-100 border-2 border border-lime-600")) {
|
||||||
|
LabeledContent {
|
||||||
|
p(.class("text-lg font-semibold \(text: .green) dark:text-lime-600 mt-2")) {
|
||||||
|
"Risk Level: \(response.riskLevel.rawValue.capitalized)"
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
SVG(.exclamation, color: "\(text: .green) dark:text-lime-600")
|
||||||
|
}
|
||||||
|
.attributes(.class("flex items-center gap-2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychrometricPropertiesGrid(properties: response.psychrometricProperties)
|
||||||
|
.attributes(.class("mx-6"))
|
||||||
|
|
||||||
|
div(.class("mt-8 p-4 bg-gray-700 rounded-md shadow-md border border-blue-500")) {
|
||||||
|
p(.class("text-sm \(text: .blue)")) {
|
||||||
|
span(.class("font-extrabold pe-2")) { "Note:" }
|
||||||
|
"""
|
||||||
|
These calculations are based on typical indoor conditions and common mold species. Actual mold growth can
|
||||||
|
vary based on surface materials, air movement, and other environmental factors. Always address moisture
|
||||||
|
issues promptly and consult professionals for severe cases.
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PsychrometricPropertiesGrid: HTML {
|
||||||
|
let properties: PsychrometricProperties
|
||||||
|
|
||||||
|
var content: some HTML<HTMLTag.div> {
|
||||||
|
div(.class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-6 md:mx-20")) {
|
||||||
|
displayProperty("Dew Point", "\(properties.dewPoint.value) \(properties.dewPoint.units.symbol)")
|
||||||
|
displayProperty("Wet Bulb", "\(properties.wetBulb.value) \(properties.wetBulb.units.symbol)")
|
||||||
|
displayProperty("Enthalpy", "\(properties.enthalpy.value) \(properties.wetBulb.units.symbol)")
|
||||||
|
displayProperty("Density", "\(properties.density.value) \(properties.density.units.rawValue)")
|
||||||
|
displayProperty("Vapor Pressure", "\(properties.vaporPressure.value) \(properties.vaporPressure.units.symbol)")
|
||||||
|
displayProperty("Specific Volume", "\(properties.specificVolume.rawValue)")
|
||||||
|
displayProperty("Absolute Humidity", "\(properties.absoluteHumidity.value) \(properties.absoluteHumidity.units.symbol)")
|
||||||
|
displayProperty("Humidity Ratio", "\(properties.humidityRatio.value)")
|
||||||
|
displayProperty("Degree of Saturation", "\(properties.degreeOfSaturation.value)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayProperty(_ label: String, _ value: String) -> some HTML {
|
||||||
|
p(.class("\(text: .darkGray) dark:\(text: .white)")) {
|
||||||
|
span(.class("font-semibold")) { "\(label): " }
|
||||||
|
span(.class("font-light")) { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import Elementary
|
|
||||||
|
|
||||||
enum SVG {
|
|
||||||
static let wind = HTMLRaw("""
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
class="w-8 h-8">
|
|
||||||
<path d="M17.7 7.7a2.5 2.5 0 1 1 1.8 4.3H2"></path>
|
|
||||||
<path d="M9.6 4.6A2 2 0 1 1 11 8H2"></path>
|
|
||||||
<path d="M12.6 19.4A2 2 0 1 0 14 16H2"></path>
|
|
||||||
</svg>
|
|
||||||
""")
|
|
||||||
|
|
||||||
static let calculator = HTMLRaw("""
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
class="w-5 h-5">
|
|
||||||
<rect width="16" height="20" x="4" y="2" rx="2"></rect>
|
|
||||||
<line x1="8" x2="16" y1="6" y2="6"></line>
|
|
||||||
<line x1="16" x2="16" y1="14" y2="18"></line>
|
|
||||||
<path d="M16 10h.01"></path>
|
|
||||||
<path d="M12 10h.01"></path>
|
|
||||||
<path d="M8 10h.01"></path>
|
|
||||||
<path d="M12 14h.01"></path>
|
|
||||||
<path d="M8 14h.01"></path>
|
|
||||||
<path d="M12 18h.01"></path><path d="M8 18h.01"></path>
|
|
||||||
</svg>
|
|
||||||
""")
|
|
||||||
|
|
||||||
static let thermometer = HTMLRaw("""
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
class="w-8 h-8 text-blue-500">
|
|
||||||
<path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"></path>
|
|
||||||
</svg>
|
|
||||||
""")
|
|
||||||
|
|
||||||
static let menu = HTMLRaw("""
|
|
||||||
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24" x="0" y="0" id="menu-icon">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// NOTE: These don't always work as expected with tailwind they generally
|
|
||||||
// need to be in the class itself, but here for reference of the primary
|
|
||||||
// colors.
|
|
||||||
enum Colors {
|
|
||||||
static let yellow = "yellow-300"
|
|
||||||
static let blue = "blue-500"
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ module.exports = {
|
|||||||
content: [
|
content: [
|
||||||
"./Sources/ViewControllerLive/Views/*.swift",
|
"./Sources/ViewControllerLive/Views/*.swift",
|
||||||
"./Sources/ViewControllerLive/*.swift",
|
"./Sources/ViewControllerLive/*.swift",
|
||||||
"./Public/images/*.svg"
|
"./Public/images/*.svg",
|
||||||
|
"./Sources/Styleguide/*.swift"
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user