Compare commits

...

15 Commits

21 changed files with 235 additions and 110 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
output.css /tmp
config.json config.json

View File

@ -3,16 +3,15 @@ package main
import ( import (
"GoStart/src/api" "GoStart/src/api"
"GoStart/src/routes" "GoStart/src/routes"
"GoStart/src/util" "GoStart/src/utils"
"fmt" "fmt"
"net/http" "net/http"
) )
func main() { func main() {
// Load Configurations // Load Configurations
cfg, err := util.LoadConfig() cfg, err := utils.LoadConfig()
if err != nil { if err != nil {
fmt.Printf("Failed to load config: %v\n", err) fmt.Printf("Failed to load config: %v\n", err)
return return

View File

@ -1,6 +1,7 @@
# GoStart # GoStart
This is a basic starter project for building web applications using Go, Tailwind CSS, and htmx. It provides a foundation for creating interactive and responsive web applications without relying on any third-party dependencies, utilizing only the packages included with the Go language by default. This is a basic starter project for building web applications using Go, Tailwind CSS, and htmx. It provides a foundation for creating interactive and responsive web applications without relying on any third-party dependencies, utilizing only the packages included with the Go language by default. This module includes a minified version of HTMX and a Custom Tailwind CSS for that add a Dark/Light themeing capability out of the box. If you would like to change the default styling of the app beyond the default tailwind components, you will need the standalone Tailwind Executable to rebuild the CSS to your liking.
-_More information on custom styling can be found in the [Style readme](web/style/readme.md)_
## Features ## Features
@ -14,7 +15,7 @@ This is a basic starter project for building web applications using Go, Tailwind
1. Clone the repository: 1. Clone the repository:
```bash ```bash
git clone https://git.happytavern.co/oceanslim/gostart.git git clone https://forge.happytavern.co/oceanslim/gostart.git
``` ```
2. Navigate to the project directory: 2. Navigate to the project directory:
@ -32,7 +33,7 @@ This is a basic starter project for building web applications using Go, Tailwind
4. Build and run the application: 4. Build and run the application:
```bash ```bash
go run main.go go run .
``` ```
The application will be accessible at `http://localhost:8787`, or whatever port you set in your configuration. The application will be accessible at `http://localhost:8787`, or whatever port you set in your configuration.

View File

@ -1,23 +1,15 @@
package routes package routes
import ( import (
"html/template" "GoStart/src/utils"
"net/http" "net/http"
) )
func ExampleHandler(w http.ResponseWriter, r *http.Request) { func ExampleHandler(w http.ResponseWriter, r *http.Request) {
data := PageData{ data := utils.PageData{
Title: "Example Page", Title: "Example Page",
} }
tmpl, err := template.ParseFiles("web/views/#layout.html", "web/views/example.html") // Call RenderTemplate with the specific template for this route
if err != nil { utils.RenderTemplate(w, data, "example.html")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.ExecuteTemplate(w, "layout", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} }

View File

@ -1,27 +1,13 @@
package routes package routes
import ( import (
"html/template" "GoStart/src/utils"
"net/http" "net/http"
) )
type PageData struct {
Title string
}
func RootHandler(w http.ResponseWriter, r *http.Request) { func RootHandler(w http.ResponseWriter, r *http.Request) {
data := PageData{ data := utils.PageData{
Title: "Home Page", Title: "Home Page",
} }
utils.RenderTemplate(w, data, "index.html")
tmpl, err := template.ParseFiles("web/views/#layout.html", "web/views/index.html", "web/views/#header.html", "web/views/#footer.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.ExecuteTemplate(w, "layout", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} }

View File

@ -1,4 +1,4 @@
package util package utils
import ( import (
"encoding/json" "encoding/json"
@ -8,7 +8,7 @@ import (
type Config struct { type Config struct {
Port int `json:"port"` Port int `json:"port"`
Development string `json:"development"` Development string `json:"development"`
} }
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {

10
src/utils/helpers.go Normal file
View File

@ -0,0 +1,10 @@
package utils
// Helper function to prepend a directory path to a list of filenames
func PrependDir(dir string, files []string) []string {
var fullPaths []string
for _, file := range files {
fullPaths = append(fullPaths, dir+file)
}
return fullPaths
}

46
src/utils/template.go Normal file
View File

@ -0,0 +1,46 @@
package utils
import (
"html/template"
"net/http"
)
type PageData struct {
Title string
Theme string
}
// Define the base directories for views and templates
const (
viewsDir = "web/views/"
templatesDir = "web/views/templates/"
)
// Define the common layout templates filenames
var templateFiles = []string{
"#layout.html",
"header.html",
"footer.html",
}
// Initialize the common templates with full paths
var layout = PrependDir(templatesDir, templateFiles)
func RenderTemplate(w http.ResponseWriter, data PageData, view string) {
// Append the specific template for the route
templates := append(layout, viewsDir+view)
// Parse all templates
tmpl, err := template.ParseFiles(templates...)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Execute the "layout" template
err = tmpl.ExecuteTemplate(w, "layout", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

1
web/static/custom.min.css vendored Normal file
View File

@ -0,0 +1 @@
/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}:root{--color-bgPrimary:#101010;--color-bgSecondary:#282828;--color-bgInverted:#e1e1e1;--color-textPrimary:#fff;--color-textSecondary:#ebebeb;--color-textMuted:#c8c8c8;--color-textInverted:#101010}:root[data-theme=light]{--color-bgPrimary:#c8c8c8;--color-bgSecondary:#e6e6e6;--color-bgInverted:#505050;--color-textPrimary:#000;--color-textSecondary:#141414;--color-textMuted:#646464;--color-textInverted:#fff}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.mb-8{margin-bottom:2rem}.mt-8{margin-top:2rem}.rounded-md{border-radius:.375rem}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity))}.bg-bgPrimary{background-color:var(--color-bgPrimary)}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity))}.p-2{padding:.5rem}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.font-bold{font-weight:700}.text-textMuted{color:var(--color-textMuted)}.text-textPrimary{color:var(--color-textPrimary)}.text-textSecondary{color:var(--color-textSecondary)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}

File diff suppressed because one or more lines are too long

View File

@ -3,3 +3,24 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer base {
:root {
--color-bgPrimary: rgb(16, 16, 16);
--color-bgSecondary: rgb(40, 40, 40);
--color-bgInverted: rgb(225, 225, 225);
--color-textPrimary: rgb(255, 255, 255);
--color-textSecondary: rgb(235, 235, 235);
--color-textMuted: rgb(200, 200, 200);
--color-textInverted: rgb(16, 16, 16);
}
:root[data-theme="light"] {
--color-bgPrimary: rgb(200, 200, 200);
--color-bgSecondary: rgb(230, 230, 230);
--color-bgInverted: rgb(80, 80, 80);
--color-textPrimary: rgb(0, 0, 0);
--color-textSecondary: rgb(20, 20, 20);
--color-textMuted: rgb(100, 100, 100);
--color-textInverted: rgb(255, 255, 255);
}
}

View File

@ -1,9 +1,25 @@
# Development # Information
For Tailwind to Rebuild the Output CSS, a watcher must be run to compile the new styling as pages are edited. This repository INCLUDES a minified version of the custom css used to style the web views with themes defined in the input.css. If you want to change anything about the configuration or the input, you will need to rebuild the custom minified css by using the [Tailwind standalone CLI Tool](https://github.com/tailwindlabs/tailwindcss/releases).
For Tailwind to Rebuild the CSS, Tailwind must be run to compile the new styling.
To do this run: To do this run:
```bash ```bash
tailwindcss -i web/style/input.css -o web/static/output.css --watch tailwindcss -i web/style/input.css -o web/static/custom.min.css --minify
``` ```
## Development
You can run a watcher while in development to automatically rebuild the `tailwind.min.css` whenever a file in the project directory is modified.
To do this run:
```bash
tailwindcss -i web/style/input.css -o web/static/custom.min.css --watch --minify
```
### Dark Mode
Yes... This framework is designed with "Dark Mode" as the default theme. As all things should be.

View File

@ -3,7 +3,15 @@ module.exports = {
content: ["./**/*.{html,js}"], content: ["./**/*.{html,js}"],
theme: { theme: {
extend: { extend: {
colors: {}, colors: {
bgPrimary: "var(--color-bgPrimary)",
bgSecondary: "var(--color-bgSecondary)",
bgInverted: "var(--color-bgInverted)",
textPrimary: "var(--color-textPrimary)",
textSecondary: "var(--color-textSecondary)",
textMuted: "var(--color-textMuted)",
textInverted: "var(--color-textInverted)",
},
}, },
}, },
plugins: [], plugins: [],

View File

@ -1,5 +0,0 @@
{{define "footer"}}
<footer>
<p>&copy; 2024 My Web App</p>
</footer>
{{end}}

View File

@ -1,7 +0,0 @@
{{define "header"}}
<header>
<h1 class="mt-8 mb-8 text-3xl font-bold text-blue-400">
Welcome to My GO Web App Framework {{.Title}}
</h1>
</header>
{{end}}

View File

@ -1,18 +0,0 @@
{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GO Web App - {{.Title}}</title>
<link rel="stylesheet" href="/static/output.css" />
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon" />
<script src="/static/htmx.min.js"></script>
</head>
<body class="text-center text-blue-300 bg-gray-800">
{{template "header" .}}
{{template "view" .}}
{{template "footer" .}}
</body>
</html>
{{end}}

View File

@ -1,17 +1,18 @@
{{define "view"}} {{define "view"}}
<header> <main>
<h1 class="mt-8 mb-8 text-3xl font-bold text-blue-300"> <div>You are now viewing the {{.Title}}</div>
This is an additional example route <h1 class="font-bold">Serve Static Files Like this Gopher</h1>
</h1> <div class="flex justify-center mb-4">
</header> <img src="/static/img/gopher.png" alt="alternate text" />
</div>
<main> <div>You can get back to the index view with this button</div>
<section> <button
<h1 class="font-bold">Serve Static Files Like this Globe</h1> hx-get="/"
<img src="/static/img/gopher.png" alt="alternate text"> hx-swap="outerHTML"
</main> hx-target="body"
class="p-2 text-white bg-blue-400 rounded-md"
<footer> >
<p>&copy; 2024 My Web App</p> Click Me 😀
</footer> </button>
{{end}} </main>
{{end}}

View File

@ -1,17 +1,46 @@
{{define "view"}} {{define "view"}}
<main> <main class="flex flex-col items-center justify-center p-8">
<section> <div class="mb-4">You are now viewing the {{.Title}}</div>
<button hx-get="/api/example" class="p-2 text-white bg-blue-400 rounded-md">
Click Me! <div class="mb-2">
</button> Clicking the blue button will replace the entire page with an additional
<h1 class="font-bold">API Example</h1> example view
<a href="/api/example" target="_blank">Access Example API</a> </div>
<h2>Content Section</h2> <button
<p> hx-get="/example"
This is the main content of your web app. You can add any HTML content class="p-2 mb-4 text-white bg-blue-400 rounded-md"
here. hx-swap="outerHTML"
hx-target="body"
>
Click Me 😀
</button>
<h1 class="mb-2 font-bold">
Clicking the orange button will replace the content in the box below with
the example API
</h1>
<button
hx-get="/api/example"
hx-swap="outerHTML"
class="p-2 mb-2 text-white bg-orange-400 rounded-md"
hx-target="#dynamic-content"
>
Click Me 😀
</button>
<div
class="w-full max-w-md p-4 mb-6 text-center border-2 border-white border-solid rounded-md shadow-inner bg-bgPrimary"
>
<!-- Content loaded via HTMX will appear here -->
<p id="dynamic-content" class="text-textPrimary">
Dynamic content will be displayed here.
</p> </p>
<a href="/example">Another Example Route can be found here</a> </div>
</section> <div class="space-y-2 text-center">
<a href="/api/example" target="_blank" class="block text-blue-400"
>You can still access traditional, json style api's</a
>
<a href="/example" class="block"
>You can also create traditional route links</a
>
</div>
</main> </main>
{{end}} {{end}}

View File

@ -0,0 +1,34 @@
{{define "layout"}}
<!DOCTYPE html>
<html lang="en" data-theme="{{.Theme}}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!--
CDNs are used for ease of development. In a Production enviornment,
build a proper minified custom CSS using the Tailwind CLI tool and serve
the latest available minified htmx and custom css directly from your server.
To download a copy of htmx: https://htmx.org/docs/#download-a-copy
<link href="/static/tailwind.min.css" rel="stylesheet" />
<link href="/static/htmx.min.js">
-->
<script src="https://cdn.tailwindcss.com"></script>
<script
src="https://unpkg.com/htmx.org@1.9.12"
integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"
crossorigin="anonymous"
></script>
<!--
link the custom minified styling included in this repo, built from the configuration
in the /web/style directory
-->
<link href="/static/custom.min.css" rel="stylesheet" />
<link rel="icon" href="/static/img/favicon.ico" type="image/x-icon" />
<title>GO Web App - {{.Title}}</title>
</head>
<body class="font-mono text-center text-textPrimary bg-bgPrimary">
{{template "header" .}} {{template "view" .}} {{template "footer" .}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,5 @@
{{define "footer"}}
<footer class="text-textMuted">
<p>&copy; 2024 My Web App</p>
</footer>
{{end}}

View File

@ -0,0 +1,7 @@
{{define "header"}}
<header>
<h1 class="mt-8 mb-8 text-3xl font-bold text-textSecondary">
Welcome to My GO Web App Framework
</h1>
</header>
{{end}}