WIP: Moves some common views to a Styleguide module, working on room table and form.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ DerivedData/
|
|||||||
.swiftpm/configuration/registries.json
|
.swiftpm/configuration/registries.json
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
.netrc
|
.netrc
|
||||||
|
.swift-version
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ let package = Package(
|
|||||||
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
||||||
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
||||||
.library(name: "ManualDClient", targets: ["ManualDClient"]),
|
.library(name: "ManualDClient", targets: ["ManualDClient"]),
|
||||||
|
.library(name: "Styleguide", targets: ["Styleguide"]),
|
||||||
.library(name: "ViewController", targets: ["ViewController"]),
|
.library(name: "ViewController", targets: ["ViewController"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -84,6 +85,14 @@ let package = Package(
|
|||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(
|
||||||
|
name: "Styleguide",
|
||||||
|
dependencies: [
|
||||||
|
"ManualDCore",
|
||||||
|
.product(name: "Elementary", package: "elementary"),
|
||||||
|
.product(name: "ElementaryHTMX", package: "elementary-htmx"),
|
||||||
|
]
|
||||||
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "ManualDClientTests",
|
name: "ManualDClientTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -95,6 +104,7 @@ let package = Package(
|
|||||||
name: "ViewController",
|
name: "ViewController",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "ManualDCore"),
|
.target(name: "ManualDCore"),
|
||||||
|
.target(name: "Styleguide"),
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
.product(name: "Elementary", package: "elementary"),
|
.product(name: "Elementary", package: "elementary"),
|
||||||
|
|||||||
@@ -62,11 +62,19 @@ extension SiteRoute.View {
|
|||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
public enum RoomRoute: Equatable, Sendable {
|
public enum RoomRoute: Equatable, Sendable {
|
||||||
case form
|
case form
|
||||||
|
case index
|
||||||
|
|
||||||
static let rootPath = "rooms"
|
static let rootPath = "rooms"
|
||||||
|
|
||||||
public static let router = OneOf {
|
public static let router = OneOf {
|
||||||
Route(.case(Self.form)) {
|
Route(.case(Self.form)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
"create"
|
||||||
|
}
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
|
Route(.case(Self.index)) {
|
||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
|
|||||||
53
Sources/Styleguide/Buttons.swift
Normal file
53
Sources/Styleguide/Buttons.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct SubmitButton: HTML, Sendable {
|
||||||
|
let title: String
|
||||||
|
let type: HTMLAttribute<HTMLTag.button>.ButtonType
|
||||||
|
|
||||||
|
public init(
|
||||||
|
title: String = "Submit",
|
||||||
|
type: HTMLAttribute<HTMLTag.button>.ButtonType = .submit
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some HTML<HTMLTag.button> {
|
||||||
|
button(
|
||||||
|
.class(
|
||||||
|
"""
|
||||||
|
text-white font-bold text-xl bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded-lg shadow-lg
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
.type(type)
|
||||||
|
) {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CancelButton: HTML, Sendable {
|
||||||
|
let title: String
|
||||||
|
let type: HTMLAttribute<HTMLTag.button>.ButtonType
|
||||||
|
|
||||||
|
public init(
|
||||||
|
title: String = "Cancel",
|
||||||
|
type: HTMLAttribute<HTMLTag.button>.ButtonType = .button
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some HTML<HTMLTag.button> {
|
||||||
|
button(
|
||||||
|
.class(
|
||||||
|
"""
|
||||||
|
text-white font-bold text-xl bg-red-500 hover:bg-red-600 px-4 py-2 rounded-lg shadow-lg
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
.type(type)
|
||||||
|
) {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Sources/Styleguide/HTMXExtensions.swift
Normal file
30
Sources/Styleguide/HTMXExtensions.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension HTMLAttribute.hx {
|
||||||
|
@Sendable
|
||||||
|
public static func get(route: SiteRoute.View) -> HTMLAttribute {
|
||||||
|
get(SiteRoute.View.router.path(for: route))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Sendable
|
||||||
|
public static func patch(route: SiteRoute.View) -> HTMLAttribute {
|
||||||
|
patch(SiteRoute.View.router.path(for: route))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Sendable
|
||||||
|
public static func post(route: SiteRoute.View) -> HTMLAttribute {
|
||||||
|
post(SiteRoute.View.router.path(for: route))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Sendable
|
||||||
|
public static func put(route: SiteRoute.View) -> HTMLAttribute {
|
||||||
|
put(SiteRoute.View.router.path(for: route))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Sendable
|
||||||
|
// static func delete(route: SiteRoute.Api) -> HTMLAttribute {
|
||||||
|
// delete(SiteRoute.Api.router.path(for: route))
|
||||||
|
// }
|
||||||
|
}
|
||||||
44
Sources/Styleguide/Icon.swift
Normal file
44
Sources/Styleguide/Icon.swift
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct Icon: HTML, Sendable {
|
||||||
|
|
||||||
|
let icon: String
|
||||||
|
|
||||||
|
public init(icon: String) {
|
||||||
|
self.icon = icon
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some HTML {
|
||||||
|
i(.data("lucide", value: icon)) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Icon {
|
||||||
|
|
||||||
|
public init(_ icon: Key) {
|
||||||
|
self.init(icon: icon.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Key: String {
|
||||||
|
|
||||||
|
case circlePlus
|
||||||
|
case close
|
||||||
|
case doorClosed
|
||||||
|
case mapPin
|
||||||
|
case rulerDimensionLine
|
||||||
|
case squareFunction
|
||||||
|
case wind
|
||||||
|
|
||||||
|
var icon: String {
|
||||||
|
switch self {
|
||||||
|
case .circlePlus: return "circle-plus"
|
||||||
|
case .close: return "x"
|
||||||
|
case .doorClosed: return "door-closed"
|
||||||
|
case .mapPin: return "map-pin"
|
||||||
|
case .rulerDimensionLine: return "ruler-dimension-line"
|
||||||
|
case .squareFunction: return "square-function"
|
||||||
|
case .wind: return rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Sources/Styleguide/Input.swift
Normal file
45
Sources/Styleguide/Input.swift
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
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 body: some HTML<HTMLTag.input> {
|
||||||
|
input(
|
||||||
|
.id(id), .name(name ?? id), .placeholder(placeholder),
|
||||||
|
.class(
|
||||||
|
"""
|
||||||
|
w-full rounded-md bg-white px-3 py-1.5 text-slate-900 outline-1
|
||||||
|
-outline-offset-1 outline-slate-300 focus:outline focus:-outline-offset-2
|
||||||
|
focus:outline-indigo-600 invalid:border-red-500 out-of-range:border-red-500
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HTMLAttribute where Tag == HTMLTag.input {
|
||||||
|
|
||||||
|
public static func max(_ value: String) -> Self {
|
||||||
|
.init(name: "max", value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func min(_ value: String) -> Self {
|
||||||
|
.init(name: "min", value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func step(_ value: String) -> Self {
|
||||||
|
.init(name: "step", value: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Sources/Styleguide/Row.swift
Normal file
18
Sources/Styleguide/Row.swift
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct Row<T: HTML>: HTML, Sendable where T: Sendable {
|
||||||
|
|
||||||
|
let inner: T
|
||||||
|
|
||||||
|
public init(
|
||||||
|
@HTMLBuilder _ body: () -> T
|
||||||
|
) {
|
||||||
|
self.inner = body()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some HTML<HTMLTag.div> {
|
||||||
|
div(.class("flex justify-between")) {
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Sources/Styleguide/SVG.swift
Normal file
29
Sources/Styleguide/SVG.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import Elementary
|
||||||
|
|
||||||
|
public struct SVG: HTML, Sendable {
|
||||||
|
|
||||||
|
let key: Key
|
||||||
|
|
||||||
|
public init(_ key: Key) {
|
||||||
|
self.key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some HTML {
|
||||||
|
HTMLRaw(key.svg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SVG {
|
||||||
|
public enum Key: Sendable {
|
||||||
|
case close
|
||||||
|
|
||||||
|
var svg: String {
|
||||||
|
switch self {
|
||||||
|
case .close:
|
||||||
|
return """
|
||||||
|
<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="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,8 +37,13 @@ extension SiteRoute.View.RoomRoute {
|
|||||||
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML {
|
||||||
switch self {
|
switch self {
|
||||||
case .form:
|
case .form:
|
||||||
|
// TODO: Check that it's an htmx request.
|
||||||
|
return RoomForm()
|
||||||
|
case .index:
|
||||||
return MainPage {
|
return MainPage {
|
||||||
RoomTable(rooms: Room.mocks)
|
div {
|
||||||
|
RoomTable(rooms: Room.mocks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +1,63 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Elementary
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
// TODO: Need to hold the project ID in hidden input field.
|
// TODO: Need to hold the project ID in hidden input field.
|
||||||
struct RoomForm: HTML, Sendable {
|
struct RoomForm: HTML, Sendable {
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
div(.class("mx-10 my-10")) {
|
div(
|
||||||
h1(.class("text-3xl font-bold pb-6")) { "Rooms " }
|
.class(
|
||||||
|
"fixed top-20 z-50 w-1/2 mx-[20vw] my-10 bg-gray-700 rounded-lg shadow-lg p-4"
|
||||||
|
),
|
||||||
|
.id("roomForm")
|
||||||
|
) {
|
||||||
|
h1(.class("text-3xl font-bold pb-6")) { "New Room" }
|
||||||
form(
|
form(
|
||||||
.class(
|
.class(
|
||||||
"""
|
"""
|
||||||
grid md:grid-cols-3 gap-4
|
space-y-4
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
div(.class("col-span-1")) {
|
div {
|
||||||
div {
|
label(.for("name")) { "Name:" }
|
||||||
label(.for("name")) { "Name:" }
|
Input(id: "name", placeholder: "Room Name")
|
||||||
}
|
.attributes(.type(.text), .required, .autofocus)
|
||||||
input(
|
|
||||||
.type(.text), .name("name"), .id("name"), .placeholder("Room Name"), .required,
|
|
||||||
.autofocus
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
div(.class("col-span-1")) {
|
div {
|
||||||
div {
|
label(.for("heatingLoad")) { "Heating Load:" }
|
||||||
label(.for("heatingLoad")) { "Heating Load:" }
|
Input(id: "heatingLoad", placeholder: "Heating Load")
|
||||||
}
|
.attributes(.type(.number), .required, .min("0"))
|
||||||
input(
|
|
||||||
.type(.number), .name("heatingLoad"), .id("heatingLoad"), .placeholder("Heating Load"),
|
|
||||||
.required
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
div(.class("col-span-1")) {
|
div {
|
||||||
div {
|
label(.for("coolingLoad")) { "Cooling Load:" }
|
||||||
label(.for("coolingLoad")) { "Cooling Load:" }
|
Input(id: "coolingLoad", placeholder: "Cooling Load")
|
||||||
}
|
.attributes(.type(.number), .required, .min("0"))
|
||||||
input(
|
|
||||||
.type(.number), .name("coolingLoad"), .id("coolingLoad"), .placeholder("Cooling Load"),
|
|
||||||
.required
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
div {
|
||||||
|
label(.for("registerCount")) { "Registers:" }
|
||||||
|
Input(id: "registerCount", placeholder: "Register Count")
|
||||||
|
.attributes(.type(.number), .required, .value("1"), .min("1"))
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
// Force button to the right, probably a better way.
|
||||||
|
div {}
|
||||||
|
div(.class("space-x-4")) {
|
||||||
|
CancelButton()
|
||||||
|
.attributes(
|
||||||
|
.hx.get(route: .room(.index)),
|
||||||
|
.hx.target("body"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
)
|
||||||
|
SubmitButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.attributes(.class("py-4"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomTable: HTML, Sendable {
|
|
||||||
let rooms: [Room]
|
|
||||||
|
|
||||||
var body: some HTML {
|
|
||||||
div(.class("m-10")) {
|
|
||||||
h1(.class("text-3xl font-bold")) { "Rooms" }
|
|
||||||
table(
|
|
||||||
.id("rooms"),
|
|
||||||
.class(
|
|
||||||
"w-full border-collapse border border-gray-200 table-fixed"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
thead { tableHeader }
|
|
||||||
tbody {
|
|
||||||
Rows(rooms: rooms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var tableHeader: some HTML<HTMLTag.tr> {
|
|
||||||
tr {
|
|
||||||
th(.class("border border-gray-200 text-xl font-bold")) { "Name" }
|
|
||||||
th(.class("border border-gray-200 text-xl font-bold")) { "Heating Load" }
|
|
||||||
th(.class("border border-gray-200 text-xl font-bold")) { "Cooling Total" }
|
|
||||||
th(.class("border border-gray-200 text-xl font-bold")) { "Cooling Sensible" }
|
|
||||||
th(.class("border border-gray-200 text-xl font-bold")) { "Register Count" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct Rows: HTML, Sendable {
|
|
||||||
let rooms: [Room]
|
|
||||||
|
|
||||||
var body: some HTML {
|
|
||||||
for room in rooms {
|
|
||||||
tr {
|
|
||||||
td(.class("border border-gray-200 p-2")) { room.name }
|
|
||||||
td(.class("border border-gray-200 p-2")) { "\(room.heatingLoad)" }
|
|
||||||
td(.class("border border-gray-200 p-2")) { "\(room.coolingLoad.total)" }
|
|
||||||
td(.class("border border-gray-200 p-2")) { "\(room.coolingLoad.sensible)" }
|
|
||||||
td(.class("border border-gray-200 p-2")) { "\(room.registerCount)" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
71
Sources/ViewController/Views/Rooms/RoomTable.swift
Normal file
71
Sources/ViewController/Views/Rooms/RoomTable.swift
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import Foundation
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
|
struct RoomTable: HTML, Sendable {
|
||||||
|
let rooms: [Room]
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div(.class("m-10")) {
|
||||||
|
h1(.class("text-3xl font-bold")) { "Rooms" }
|
||||||
|
table(
|
||||||
|
.id("rooms"),
|
||||||
|
.class(
|
||||||
|
"w-full border-collapse border border-gray-200 table-fixed"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
thead { tableHeader }
|
||||||
|
tbody {
|
||||||
|
Rows(rooms: rooms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div(.id("roomForm")) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tableHeader: some HTML<HTMLTag.tr> {
|
||||||
|
tr {
|
||||||
|
th(.class("border border-gray-200 text-xl font-bold")) { "Name" }
|
||||||
|
th(.class("border border-gray-200 text-xl font-bold")) { "Heating Load" }
|
||||||
|
th(.class("border border-gray-200 text-xl font-bold")) { "Cooling Total" }
|
||||||
|
th(.class("border border-gray-200 text-xl font-bold")) { "Cooling Sensible" }
|
||||||
|
th(.class("border border-gray-200 text-xl font-bold")) { "Register Count" }
|
||||||
|
th(.class("border border-gray-200 text-xl font-bold")) {
|
||||||
|
div(.class("flex justify-between")) {
|
||||||
|
div {}
|
||||||
|
button(
|
||||||
|
.class("px-2"),
|
||||||
|
.hx.get(route: .room(.form)),
|
||||||
|
.hx.target("#roomForm"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
) {
|
||||||
|
Icon(.circlePlus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Rows: HTML, Sendable {
|
||||||
|
let rooms: [Room]
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
for room in rooms {
|
||||||
|
tr {
|
||||||
|
td(.class("border border-gray-200 p-2")) { room.name }
|
||||||
|
td(.class("border border-gray-200 p-2")) { "\(room.heatingLoad)" }
|
||||||
|
td(.class("border border-gray-200 p-2")) { "\(room.coolingLoad.total)" }
|
||||||
|
td(.class("border border-gray-200 p-2")) { "\(room.coolingLoad.sensible)" }
|
||||||
|
td(.class("border border-gray-200 p-2")) { "\(room.registerCount)" }
|
||||||
|
td(.class("border border-gray-200 p-2")) {
|
||||||
|
// TODO: Add edit button.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
|
// TODO: Need to add active to sidebar links.
|
||||||
struct Sidebar: HTML {
|
struct Sidebar: HTML {
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
aside(
|
aside(
|
||||||
.class(
|
.class(
|
||||||
"""
|
"""
|
||||||
h-screen sticky top-0 border-r-3 border-gray-800 bg-gray-100 shadow
|
h-screen sticky top-0 min-w-[280px] flex-none border border-r-3 border-gray-800 bg-gray-100 shadow
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
row(title: "Project", icon: "map-pin", href: "/projects")
|
row(title: "Project", icon: .mapPin, href: "/projects")
|
||||||
row(title: "Rooms", icon: "door-closed", href: "/rooms")
|
row(title: "Rooms", icon: .doorClosed, href: "/rooms")
|
||||||
row(title: "Equivalent Lengths", icon: "ruler-dimension-line", href: "#")
|
row(title: "Equivalent Lengths", icon: .rulerDimensionLine, href: "#")
|
||||||
row(title: "Friction Rate", icon: "square-function", href: "#")
|
row(title: "Friction Rate", icon: .squareFunction, href: "#")
|
||||||
row(title: "Duct Sizes", icon: "wind", href: "#")
|
row(title: "Duct Sizes", icon: .wind, href: "#")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func row(
|
private func row(
|
||||||
title: String,
|
title: String,
|
||||||
icon: String,
|
icon: Icon.Key,
|
||||||
href: String
|
href: String
|
||||||
) -> some HTML {
|
) -> some HTML {
|
||||||
a(
|
a(
|
||||||
@@ -31,10 +33,8 @@ struct Sidebar: HTML {
|
|||||||
),
|
),
|
||||||
.href(href)
|
.href(href)
|
||||||
) {
|
) {
|
||||||
i(.data("lucide", value: icon)) {}
|
Icon(icon)
|
||||||
p(
|
span(.class("text-xl font-bold")) {
|
||||||
.class("text-xl font-bold")
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM swift:6.2-noble
|
FROM docker.io/swift:6.2-noble
|
||||||
|
|
||||||
# Make sure all system packages are up to date, and install only essential packages.
|
# Make sure all system packages are up to date, and install only essential packages.
|
||||||
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
touch .build/browser-dev-sync
|
touch .build/browser-dev-sync
|
||||||
browser-sync start -p 0.0.0.0:8080 --ws --no-open &
|
# browser-sync start --proxy localhost:8080 --ws -w --no-notify &
|
||||||
watchexec -w Sources -e .swift -r 'swift build --product App && touch .build/browser-dev-sync' &
|
watchexec -w Sources -e .swift -r 'swift build --product App && touch .build/browser-dev-sync' &
|
||||||
watchexec -w .build/browser-dev-sync --ignore-nothing -r '.build/debug/App'
|
watchexec -w .build/browser-dev-sync -r 'swift run App'
|
||||||
|
|||||||
Reference in New Issue
Block a user