diff --git a/Public/css/main.css b/Public/css/main.css index e69de29..3a961f4 100644 --- a/Public/css/main.css +++ b/Public/css/main.css @@ -0,0 +1,13 @@ +:root { + --primary: #ff66ff; + --secondary: #00ffcc; + --dark-bg: #14141f; + --bg: #1e1e2e; + --hover-bg: #444; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} diff --git a/Sources/ViewController/Interface.swift b/Sources/ViewController/Interface.swift index 6fb573a..7a48242 100644 --- a/Sources/ViewController/Interface.swift +++ b/Sources/ViewController/Interface.swift @@ -43,13 +43,8 @@ extension ViewController: DependencyKey { // FIX: Fix. public static let liveValue = Self( - view: { _ in - return MainPage { - div { - h1 { "It works!" } - h2 { "Browser sync works!" } - } - } + view: { request in + try await request.render() } ) } diff --git a/Sources/ViewController/Live.swift b/Sources/ViewController/Live.swift new file mode 100644 index 0000000..2ebc6fe --- /dev/null +++ b/Sources/ViewController/Live.swift @@ -0,0 +1,59 @@ +import Elementary +import ManualDCore + +extension ViewController.Request { + + func render() async throws -> AnySendableHTML { + switch route { + case .project(let route): + return try await route.renderView(isHtmxRequest: isHtmxRequest) + default: + // FIX: FIX + return mainPage + } + } +} + +extension SiteRoute.View.ProjectRoute { + func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { + switch self { + case .index: + return mainPage + case .form: + return MainPage { + ProjectForm() + } + case .create: + return mainPage + } + } +} + +private let mainPage: AnySendableHTML = { + MainPage { + div { + h1 { "It works!" } + } + } +}() + +@Sendable +private func render( + _ mainPage: (C) async throws -> AnySendableHTML, + _ isHtmxRequest: Bool, + @HTMLBuilder html: () -> C +) async rethrows -> AnySendableHTML where C: Sendable { + guard isHtmxRequest else { + return try await mainPage(html()) + } + return html() +} + +@Sendable +private func render( + _ mainPage: (C) async throws -> AnySendableHTML, + _ isHtmxRequest: Bool, + _ html: @autoclosure @escaping () -> C +) async rethrows -> AnySendableHTML where C: Sendable { + try await render(mainPage, isHtmxRequest) { html() } +} diff --git a/Sources/ViewController/Views/MainPage.swift b/Sources/ViewController/Views/MainPage.swift index 41b7867..89387e9 100644 --- a/Sources/ViewController/Views/MainPage.swift +++ b/Sources/ViewController/Views/MainPage.swift @@ -11,14 +11,18 @@ public struct MainPage: SendableHTMLDocument where Inner: Sendable public var head: some HTML { meta(.charset(.utf8)) + meta(.name(.viewport), .content("width=device-width, initial-scale=1.0")) script(.src("https://unpkg.com/htmx.org@2.0.8")) {} + script(.src("https://cdn.tailwindcss.com")) {} script(.src("/js/main.js")) {} link(.rel(.stylesheet), .href("/css/main.css")) link(.rel(.icon), .href("/images/favicon.ico"), .custom(name: "type", value: "image/x-icon")) } public var body: some HTML { - inner + div(.class("bg-white dark:bg-gray-800 dark:text-white")) { + inner + } } } diff --git a/Sources/ViewController/Views/Project/ProjectForm.swift b/Sources/ViewController/Views/Project/ProjectForm.swift new file mode 100644 index 0000000..a3fbc56 --- /dev/null +++ b/Sources/ViewController/Views/Project/ProjectForm.swift @@ -0,0 +1,86 @@ +import Elementary +import ElementaryHTMX +import ManualDCore + +struct ProjectForm: HTML, Sendable { + + let project: Project? + + init( + project: Project? = nil + ) { + self.project = project + } + + var body: some HTML { + // TODO: Add htmx attributes. + div(.class("mx-20 my-20")) { + form(.class("w-full max-w-sm")) { + div(.class("flex items-center mb-6")) { + label( + .for("name"), .class("block text-gray-500 font-bold mr-4") + ) { "Name:" } + input( + .type(.text), .name("name"), .placeholder("Customer Name"), + .value(project?.name ?? ""), .required, .autofocus + ) + .defaultInput() + } + div(.class("flex items-center mb-6")) { + label(.for("streetAddress"), .class("block text-gray-500 font-bold mr-4")) { "Address:" } + input( + .type(.text), .name("streetAddress"), + .placeholder("Street Address"), + .value(project?.streetAddress ?? ""), + .required + ) + .defaultInput() + } + // div(.class("w-full space-x-2")) { + // label(.for("city")) { "City:" } + // + // input( + // .type(.text), .name("city"), + // .placeholder("City"), + // .value(project?.city ?? ""), + // .required + // ) + // .defaultInput() + // } + // div(.class("w-full space-y-2")) { + // label(.for("state")) { "State:" } + // input( + // .type(.text), .name("state"), + // .placeholder("State"), + // .value(project?.state ?? ""), + // .required + // ) + // .defaultInput() + // } + // div(.class("w-full space-y-2")) { + // label(.for("zipCode")) { + // "Zip:" + // } + // input( + // .type(.text), .name("zipCode"), + // .placeholder("Zip Code"), + // .value(project?.zipCode ?? ""), + // .required + // ) + // .defaultInput() + // } + } + } + } +} + +// TODO: Move +extension input { + func defaultInput() -> some HTML { + attributes( + .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" + ) + ) + } +} diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 1cc28f4..6708c80 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -42,5 +42,5 @@ RUN npm install -g browser-sync ENV SWIFT_BACKTRACE=enable=no ENV LOG_LEVEL=debug -CMD ["swift", "test"] +CMD ["swift", "run", "App", "serve", "--hostname", "0.0.0.0", "--port", "8080"] diff --git a/justfile b/justfile index e3cf100..8dcad40 100644 --- a/justfile +++ b/justfile @@ -4,4 +4,4 @@ build-docker: @podman build -f docker/Dockerfile.dev -t {{docker_image}}:dev . run-dev: - @podman run -it --rm -v $PWD:/app -p 3000:3000 -p 3002:3002 -p 8080:8080 {{docker_image}}:dev ./swift-dev + @podman run -it --rm -v $PWD:/app -p 8080:8080 {{docker_image}}:dev