From 06b663052e3b1f262c426d6511298eb4ce4ad8a2 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Mon, 9 Feb 2026 16:36:24 -0500 Subject: [PATCH] feat: Renames quick calc routes / views to ductulator. Adds button to home page for using ductulator, needs added to navbar still. --- Public/css/output.css | 71 +++++++------ .../App/Middleware/ViewRoute+middleware.swift | 2 +- Sources/ManualDCore/Routes/ViewRoute.swift | 8 +- Sources/Styleguide/Buttons.swift | 16 +++ Sources/ViewController/Live.swift | 8 +- .../DuctulatorView.swift} | 8 +- Sources/ViewController/Views/Home.swift | 99 ++++++++++--------- .../ViewControllerTests.swift | 4 +- .../ViewControllerTests/ductulator.1.html | 77 +++++++++++++++ .../ViewControllerTests/home.1.html | 79 +++++++-------- 10 files changed, 235 insertions(+), 137 deletions(-) rename Sources/ViewController/Views/{QuickCalc/Index.swift => Ductulator/DuctulatorView.swift} (95%) create mode 100644 Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/ductulator.1.html diff --git a/Public/css/output.css b/Public/css/output.css index 251bf04..f0a018d 100644 --- a/Public/css/output.css +++ b/Public/css/output.css @@ -8,8 +8,6 @@ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; --color-green-400: oklch(79.2% 0.209 151.711); - --color-sky-600: oklch(58.8% 0.158 241.966); - --color-violet-600: oklch(54.1% 0.281 293.009); --color-gray-200: oklch(92.8% 0.006 264.531); --color-gray-400: oklch(70.7% 0.022 261.325); --color-black: #000; @@ -5304,6 +5302,12 @@ } } } + .-mx-2 { + margin-inline: calc(var(--spacing) * -2); + } + .-mx-4 { + margin-inline: calc(var(--spacing) * -4); + } .mx-10 { margin-inline: calc(var(--spacing) * 10); } @@ -5399,12 +5403,18 @@ } } } + .-my-4 { + margin-block: calc(var(--spacing) * -4); + } .my-1\.5 { margin-block: calc(var(--spacing) * 1.5); } .my-6 { margin-block: calc(var(--spacing) * 6); } + .my-8 { + margin-block: calc(var(--spacing) * 8); + } .my-auto { margin-block: auto; } @@ -5620,9 +5630,6 @@ .me-4 { margin-inline-end: calc(var(--spacing) * 4); } - .me-6 { - margin-inline-end: calc(var(--spacing) * 6); - } .me-10 { margin-inline-end: calc(var(--spacing) * 10); } @@ -5686,6 +5693,12 @@ .mt-10 { margin-top: calc(var(--spacing) * 10); } + .mt-20 { + margin-top: calc(var(--spacing) * 20); + } + .mt-30 { + margin-top: calc(var(--spacing) * 30); + } .breadcrumbs { @layer daisyui.l1.l2.l3 { max-width: 100%; @@ -6707,12 +6720,6 @@ .w-px { width: 1px; } - .max-w-lg { - max-width: var(--container-lg); - } - .max-w-xl { - max-width: var(--container-xl); - } .min-w-0 { min-width: calc(var(--spacing) * 0); } @@ -6990,13 +6997,6 @@ .gap-4 { gap: calc(var(--spacing) * 4); } - .space-y-1 { - :where(& > :not(:last-child)) { - --tw-space-y-reverse: 0; - margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); - margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); - } - } .space-y-2 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -7642,9 +7642,6 @@ } } } - .bg-base-100 { - background-color: var(--color-base-100); - } .bg-base-200 { background-color: var(--color-base-200); } @@ -8779,9 +8776,6 @@ color: var(--color-warning); } } - .text-accent { - color: var(--color-accent); - } .text-base-content { color: var(--color-base-content); } @@ -9751,16 +9745,21 @@ } } } + .md\:mt-6 { + @media (width >= 48rem) { + margin-top: calc(var(--spacing) * 6); + } + } + .md\:mt-15 { + @media (width >= 48rem) { + margin-top: calc(var(--spacing) * 15); + } + } .md\:grid-cols-2 { @media (width >= 48rem) { grid-template-columns: repeat(2, minmax(0, 1fr)); } } - .md\:grid-cols-3 { - @media (width >= 48rem) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - } .lg\:drawer-open { @media (width >= 64rem) { @layer daisyui.l1.l2.l3 { @@ -9814,9 +9813,14 @@ } } } - .lg\:grid-cols-4 { + .lg\:mx-20 { @media (width >= 64rem) { - grid-template-columns: repeat(4, minmax(0, 1fr)); + margin-inline: calc(var(--spacing) * 20); + } + } + .lg\:mt-6 { + @media (width >= 64rem) { + margin-top: calc(var(--spacing) * 6); } } .is-drawer-close\:mx-auto { @@ -9858,11 +9862,6 @@ overflow: visible; } } - .is-drawer-close\:text-green-400 { - &:where(.drawer-toggle:not(:checked) ~ .drawer-side, .drawer-toggle:not(:checked) ~ .drawer-side *) { - color: var(--color-green-400); - } - } .is-drawer-open\:flex { &:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) { display: flex; diff --git a/Sources/App/Middleware/ViewRoute+middleware.swift b/Sources/App/Middleware/ViewRoute+middleware.swift index a38fe45..971c1f2 100644 --- a/Sources/App/Middleware/ViewRoute+middleware.swift +++ b/Sources/App/Middleware/ViewRoute+middleware.swift @@ -14,7 +14,7 @@ private let viewRouteMiddleware: [any Middleware] = [ extension SiteRoute.View { var middleware: [any Middleware]? { switch self { - case .home, .login, .signup, .test, .quickCalc: + case .home, .login, .signup, .test, .ductulator: return nil case .project, .user: return viewRouteMiddleware diff --git a/Sources/ManualDCore/Routes/ViewRoute.swift b/Sources/ManualDCore/Routes/ViewRoute.swift index 6f824ca..3f3de2b 100644 --- a/Sources/ManualDCore/Routes/ViewRoute.swift +++ b/Sources/ManualDCore/Routes/ViewRoute.swift @@ -12,7 +12,7 @@ extension SiteRoute { case login(LoginRoute) case signup(SignupRoute) case project(ProjectRoute) - case quickCalc(QuickCalcRoute) + case ductulator(DuctulatorRoute) case user(UserRoute) //FIX: Remove. case test @@ -34,8 +34,8 @@ extension SiteRoute { Route(.case(Self.project)) { SiteRoute.View.ProjectRoute.router } - Route(.case(Self.quickCalc)) { - SiteRoute.View.QuickCalcRoute.router + Route(.case(Self.ductulator)) { + SiteRoute.View.DuctulatorRoute.router } Route(.case(Self.user)) { SiteRoute.View.UserRoute.router @@ -991,7 +991,7 @@ extension SiteRoute.View.UserRoute { } extension SiteRoute.View { - public enum QuickCalcRoute: Equatable, Sendable { + public enum DuctulatorRoute: Equatable, Sendable { case index case submit(Form) diff --git a/Sources/Styleguide/Buttons.swift b/Sources/Styleguide/Buttons.swift index f421bf7..0ec9596 100644 --- a/Sources/Styleguide/Buttons.swift +++ b/Sources/Styleguide/Buttons.swift @@ -1,4 +1,6 @@ import Elementary +import ElementaryHTMX +import ManualDCore public struct SubmitButton: HTML, Sendable { let title: String @@ -74,3 +76,17 @@ public struct TrashButton: HTML, Sendable { } } } + +public struct DuctulatorButton: HTML, Sendable { + public init() {} + + public var body: some HTML { + a( + .class("btn"), + .href(route: .ductulator(.index)), + .target(.blank) + ) { + "Ductulator" + } + } +} diff --git a/Sources/ViewController/Live.swift b/Sources/ViewController/Live.swift index f43e8e5..b87bd46 100644 --- a/Sources/ViewController/Live.swift +++ b/Sources/ViewController/Live.swift @@ -96,7 +96,7 @@ extension ViewController.Request { case .project(let route): return await route.renderView(on: self) - case .quickCalc(let route): + case .ductulator(let route): return await route.renderView(on: self) case .user(let route): @@ -712,7 +712,7 @@ extension SiteRoute.View.UserRoute.Profile { } } -extension SiteRoute.View.QuickCalcRoute { +extension SiteRoute.View.DuctulatorRoute { func renderView( on request: ViewController.Request @@ -722,7 +722,7 @@ extension SiteRoute.View.QuickCalcRoute { switch self { case .index: return await request.view { - QuickCalcView( + DuctulatorView( isLoggedIn: request.isLoggedIn ) } @@ -736,7 +736,7 @@ extension SiteRoute.View.QuickCalcRoute { } return (ductSize, rectangularSize) } onSuccess: { (ductSize, rectangularSize) in - QuickCalcView.Result(ductSize: ductSize, rectangularSize: rectangularSize) + DuctulatorView.Result(ductSize: ductSize, rectangularSize: rectangularSize) } } } diff --git a/Sources/ViewController/Views/QuickCalc/Index.swift b/Sources/ViewController/Views/Ductulator/DuctulatorView.swift similarity index 95% rename from Sources/ViewController/Views/QuickCalc/Index.swift rename to Sources/ViewController/Views/Ductulator/DuctulatorView.swift index eb79c32..e5f1b95 100644 --- a/Sources/ViewController/Views/QuickCalc/Index.swift +++ b/Sources/ViewController/Views/Ductulator/DuctulatorView.swift @@ -6,7 +6,7 @@ import ManualDClient import ManualDCore import Styleguide -struct QuickCalcView: HTML, Sendable { +struct DuctulatorView: HTML, Sendable { let isLoggedIn: Bool @@ -32,7 +32,7 @@ struct QuickCalcView: HTML, Sendable { div(.class("flex space-x-6 items-center text-4xl")) { SVG(.calculator) h1(.class("text-4xl font-bold me-10")) { - "Duct Size" + "Ductulator" } } @@ -42,7 +42,7 @@ struct QuickCalcView: HTML, Sendable { form( .class("space-y-4 mt-6"), - .hx.post(route: .quickCalc(.index)), + .hx.post(route: .ductulator(.index)), .hx.target("#\(Result.id)"), .hx.swap(.outerHTML) ) { @@ -103,7 +103,7 @@ struct QuickCalcView: HTML, Sendable { h2(.class("text-3xl font-bold")) { "Result" } button( .class("btn btn-primary"), - .hx.get(route: .quickCalc(.index)), + .hx.get(route: .ductulator(.index)), .hx.target("body"), .hx.swap(.outerHTML) ) { diff --git a/Sources/ViewController/Views/Home.swift b/Sources/ViewController/Views/Home.swift index bf3bd36..42302e1 100644 --- a/Sources/ViewController/Views/Home.swift +++ b/Sources/ViewController/Views/Home.swift @@ -1,15 +1,19 @@ import Elementary import ElementaryHTMX +import Styleguide struct HomeView: HTML, Sendable { var body: some HTML { 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. + // NOTE: Footer background color will follow system theme. ) { - div(.class("flex justify-end m-4")) { + div(.class("flex justify-end space-x-4 m-4")) { + DuctulatorButton() + .attributes(.class("btn-ghost btn-accent text-lg")) + .tooltip("Duct size calculator", position: .left) + button( .class("btn btn-ghost btn-secondary text-lg"), .hx.get(route: .login(.index())), @@ -20,12 +24,13 @@ struct HomeView: HTML, Sendable { "Login" } } - div(.class("hero")) { + + div(.class("mx-10 lg:mx-20")) { div( .class( """ - relative hero-content text-center bg-base-300 - w-full min-h-[400px] rounded-3xl shadow-3xl overflow-hidden + relative text-center bg-base-300 + rounded-3xl shadow-3xl overflow-hidden """ ) ) { @@ -62,7 +67,7 @@ struct HomeView: HTML, Sendable { ) { "Get Started" } - p(.class("text-xs italic mt-8")) { + p(.class("text-xs italic my-6")) { """ Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). @@ -72,46 +77,45 @@ struct HomeView: HTML, Sendable { } } - } - - 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" - } + div(.class("grid grid-cols-1 md:grid-cols-2 gap-4 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)." } } - 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." } + 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." } + } } } } @@ -119,23 +123,24 @@ struct HomeView: HTML, Sendable { } } + // TODO: When beta flag is gone, then remove the responsive margin of the header. var header: some HTML { - div(.class("flex justify-center items-center")) { + div(.class("flex justify-center mt-30 md:mt-15 lg:mt-6")) { div( .class( """ - flex border-b-6 border-accent + flex items-end border-b-6 border-accent text-8xl font-bold my-auto space-2 """ ) ) { h1(.class("me-2")) { "Duct Calc" } - div(.class("")) { + div { span( .class( """ bg-secondary rounded-md - text-5xl rotate-180 p-2 + text-5xl rotate-180 p-2 -mx-2 """ ), .style("writing-mode: vertical-rl") diff --git a/Tests/ViewControllerTests/ViewControllerTests.swift b/Tests/ViewControllerTests/ViewControllerTests.swift index d7829a4..31a45ec 100644 --- a/Tests/ViewControllerTests/ViewControllerTests.swift +++ b/Tests/ViewControllerTests/ViewControllerTests.swift @@ -28,13 +28,13 @@ struct ViewControllerTests { } @Test - func quickCalc() async throws { + func ductulator() async throws { try await withDependencies { $0.viewController = .liveValue $0.auth = .failing } operation: { @Dependency(\.viewController) var viewController - let view = try await viewController.view(.test(.quickCalc(.index))) + let view = try await viewController.view(.test(.ductulator(.index))) assertSnapshot(of: view, as: .html) } } diff --git a/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/ductulator.1.html b/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/ductulator.1.html new file mode 100644 index 0000000..3183b1a --- /dev/null +++ b/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/ductulator.1.html @@ -0,0 +1,77 @@ + + + + Duct Calc + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ +

Ductulator

+
+

Calculate duct size for the given parameters

+
+ + +
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html b/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html index dc8e5fe..fdba3c8 100644 --- a/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html +++ b/Tests/ViewControllerTests/__Snapshots__/ViewControllerTests/home.1.html @@ -32,67 +32,68 @@
-
+
+
-
-
+
+
BETA
-
-
+

Duct Calc

-
+
Pro + text-5xl rotate-180 p-2 -mx-2" style="writing-mode: vertical-rl">Pro
Open source residential duct design program

Manual-D™ speed sheet, but on the web!

-

+

Manual-D™ is a trademark of Air Conditioning Contractors of America (ACCA). This site is not designed by or affiliated with ACCA.

-
-
-
-
-
Features
+
+
+
+
Features
+
+
+
    +
  • +
    Built by humans
    +
  • +
  • Fully open source.
  • +
  • Great replacement for speed sheet users.
  • +
  • Great for classrooms.
  • +
  • Store your projects in one place.
  • +
  • Export final project to pdf.
  • +
  • Import room loads via CSV file.
  • +
  • Web based.
  • +
  • Self host (run on your own infrastructure).
  • +
+
-
-
    -
  • -
    Built by humans
    -
  • -
  • Fully open source.
  • -
  • Great replacement for speed sheet users.
  • -
  • Great for classrooms.
  • -
  • Store your projects in one place.
  • -
  • Export final project to pdf.
  • -
  • Import room loads via CSV file.
  • -
  • Web based.
  • -
  • Self host (run on your own infrastructure).
  • -
-
-
-
-
Coming Soon
-
-
    -
  • API integration.
  • -
  • Command line interface.
  • -
  • Fitting selection tool.
  • -
  • Room load import from PDF.
  • -
+
+
Coming Soon
+
+
    +
  • API integration.
  • +
  • Command line interface.
  • +
  • Fitting selection tool.
  • +
  • Room load import from PDF.
  • +
+