remove sandbox, move todo-app to index page

This commit is contained in:
maddalax 2024-09-22 14:37:24 -05:00
parent 9e44fb5d52
commit 94f7d53a0d
30 changed files with 29 additions and 1110 deletions

View file

@ -15,6 +15,14 @@ func PushUrlHeader(url string) *Headers {
return NewHeaders(hx.PushUrlHeader, url)
}
func PushQsHeader(ctx *RequestContext, qs *Qs) *Headers {
parsed, err := url.Parse(ctx.currentBrowserUrl)
if err != nil {
return NewHeaders()
}
return NewHeaders(hx.ReplaceUrlHeader, SetQueryParams(parsed.Path, qs))
}
func CombineHeaders(headers ...*Headers) *Headers {
m := make(Headers)
for _, h := range headers {

View file

@ -1,7 +1,6 @@
package h
import (
"github.com/maddalax/htmgo/framework/hx"
"net/url"
"strings"
)
@ -49,14 +48,6 @@ func (q *Qs) ToString() string {
return builder.String()
}
func PushQsHeader(ctx *RequestContext, qs *Qs) *Headers {
parsed, err := url.Parse(ctx.currentBrowserUrl)
if err != nil {
return NewHeaders()
}
return NewHeaders(hx.ReplaceUrlHeader, SetQueryParams(parsed.Path, qs))
}
func GetQueryParam(ctx *RequestContext, key string) string {
value := ctx.QueryParam(key)
if value == "" {

View file

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -1,36 +0,0 @@
const {join} = require("node:path");
/** @type {import('tailwindcss').Config} */
const root = join(__dirname, "../../");
const contentGo = join(root, "**/*.go");
const contentJs = join(root, "**/pages/**/*.js");
module.exports = {
content: [contentGo, contentJs],
theme: {
extend: {
colors: {
background: 'hsl(224, 71.4%, 4.1%)',
foreground: 'hsl(0, 0%, 89%)',
card: 'hsl(224, 71.4%, 4.1%)',
cardForeground: 'hsl(0, 0%, 89%)',
popover: 'hsl(224, 71.4%, 4.1%)',
popoverForeground: 'hsl(0, 0%, 89%)',
primary: 'hsl(0, 0%, 89%)',
primaryForeground: 'hsl(220.9, 39.3%, 11%)',
secondary: 'hsl(215, 27.9%, 16.9%)',
secondaryForeground: 'hsl(0, 0%, 89%)',
muted: 'hsl(215, 27.9%, 16.9%)',
mutedForeground: 'hsl(217.9, 10.6%, 64.9%)',
accent: 'hsl(215, 27.9%, 16.9%)',
accentForeground: 'hsl(0, 0%, 89%)',
destructive: 'hsl(0, 62.8%, 30.6%)',
destructiveForeground: 'hsl(0, 0%, 89%)',
border: 'hsl(215, 27.9%, 16.9%)',
input: 'hsl(215, 27.9%, 16.9%)',
ring: 'hsl(216, 12.2%, 83.9%)',
},
},
},
plugins: [],
};

Binary file not shown.

View file

@ -1,136 +0,0 @@
package database
import (
"context"
"encoding/json"
"github.com/redis/go-redis/v9"
"sync"
"time"
)
var (
once sync.Once
rdb *redis.Client
)
func Connect() *redis.Client {
once.Do(func() {
var ctx = context.Background()
var err error
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
if err != nil {
panic(err)
}
cmd := rdb.Ping(ctx)
if cmd.Err() != nil {
panic(err)
}
})
return rdb
}
func Incr(key string) int64 {
db := Connect()
result := db.Incr(context.Background(), key)
return result.Val()
}
func Set[T any](key string, value T) error {
db := Connect()
serialized, err := json.Marshal(value)
if err != nil {
return err
}
result := db.Set(context.Background(), key, serialized, time.Duration(0))
return result.Err()
}
func HSet[T any](set string, key string, value T) error {
db := Connect()
serialized, err := json.Marshal(value)
if err != nil {
return err
}
result := db.HSet(context.Background(), set, key, serialized)
return result.Err()
}
func HIncr(set string, key string) int64 {
db := Connect()
result := db.HIncrBy(context.Background(), set, key, 1)
return result.Val()
}
func HGet[T any](set string, key string) *T {
db := Connect()
val, err := db.HGet(context.Background(), set, key).Result()
if err != nil || val == "" {
return nil
}
result := new(T)
err = json.Unmarshal([]byte(val), result)
if err != nil {
return nil
}
return result
}
func GetOrSet[T any](key string, cb func() T) (*T, error) {
db := Connect()
val, err := db.Get(context.Background(), key).Result()
if err == nil {
result := new(T)
err = json.Unmarshal([]byte(val), result)
if err != nil {
return nil, err
}
return result, nil
}
value := cb()
err = Set(key, value)
if err != nil {
return nil, err
}
return &value, nil
}
func Get[T any](key string) (*T, error) {
db := Connect()
val, err := db.Get(context.Background(), key).Result()
if err != nil {
return nil, err
}
result := new(T)
err = json.Unmarshal([]byte(val), result)
if err != nil {
return nil, err
}
return result, nil
}
func HList[T any](key string) ([]*T, error) {
db := Connect()
val, err := db.HGetAll(context.Background(), key).Result()
if err != nil {
return nil, err
}
result := make([]*T, len(val))
count := 0
for _, t := range val {
item := new(T)
err = json.Unmarshal([]byte(t), item)
if err != nil {
return nil, err
}
result[count] = item
count++
}
return result, nil
}

View file

@ -1,49 +0,0 @@
package patient
import (
"errors"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"starter-template/database"
"time"
)
type Patient struct {
Name string
ReasonForVisit string
AppointmentDate time.Time
LocationName string
}
type Service struct {
ctx echo.Context
}
func NewService(ctx echo.Context) *Service {
return &Service{}
}
type CreatePatientRequest struct {
Name string
ReasonForVisit string
LocationName string
}
func (c *Service) Create(request CreatePatientRequest) error {
time.Sleep(time.Second)
database.HSet("patients", uuid.New().String(), Patient{
Name: request.Name,
ReasonForVisit: request.ReasonForVisit,
AppointmentDate: time.Now(),
LocationName: "New York",
})
return errors.New("error creating patient")
}
func (c *Service) List() ([]*Patient, error) {
patients, err := database.HList[Patient]("patients")
if err != nil {
return nil, err
}
return patients, nil
}

View file

@ -1,25 +0,0 @@
module sandbox
go 1.23.0
require (
github.com/google/uuid v1.6.0
github.com/maddalax/htmgo/framework v0.0.0-20240914010415-2397bf9fb057
github.com/maddalax/htmgo/framework-ui v0.0.0-20240914003619-c256552b2143
github.com/redis/go-redis/v9 v9.6.1
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.25.0 // indirect
)

View file

@ -1,34 +0,0 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/labstack/echo/v4 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/labstack/echo/v4 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/maddalax/htmgo/framework v0.0.0-20240914010415-2397bf9fb057 h1:DTc3qPMvwrh6wPIusGmQ1jPxngy7T6maUDh5RwYf6H0=
github.com/maddalax/htmgo/framework v0.0.0-20240914010415-2397bf9fb057/go.mod h1:O8ogYQjCn5iD9CzjdRgfrJfP9uMpx8rrx6YD5N4AecY=
github.com/maddalax/htmgo/framework-ui v0.0.0-20240914003619-c256552b2143 h1:aGpoab8N9rzXJAHIP8WiW5CsHjpSCrGlnbd0bQCRS3g=
github.com/maddalax/htmgo/framework-ui v0.0.0-20240914003619-c256552b2143/go.mod h1:Z8Mym1vqAMNcgtu1sHbWwQIwTvtNr6VqRZhKBweiCNE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View file

@ -1,50 +0,0 @@
package main
import (
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
"log"
"starter-template/pages"
"starter-template/partials/load"
"time"
)
func main() {
f := echo.New()
f.Static("/public", "./assets/dist")
f.Use(func(ctx echo.Context) error {
if ctx.Cookies("htmgo-session") != "" {
return ctx.Next()
}
id := ctx.IP() + uuid.NewString()
ctx.Cookie(&echo.Cookie{
Name: "htmgo-session",
Value: id,
SessionOnly: true,
})
return ctx.Next()
})
f.Use(func(ctx echo.Context) error {
if ctx.Path() == "/livereload" {
return ctx.Next()
}
now := time.Now()
err := ctx.Next()
duration := time.Since(now)
ctx.Set("X-Response-Time", duration.String())
// Log or print the request method, URL, and duration
log.Printf("Request: %s %s took %dms", ctx.Method(), ctx.OriginalURL(), duration.Milliseconds())
return err
})
load.RegisterPartials(f)
pages.RegisterPages(f)
h.Start(f, h.App{
LiveReload: true,
})
}

View file

@ -1,58 +0,0 @@
package news
import (
"fmt"
"github.com/maddalax/htmgo/framework/util/httpjson"
"sync"
)
type Post struct {
By string `json:"by"`
Descendants int `json:"descendants"`
Id int `json:"id"`
Kids []int `json:"kids"`
Score int `json:"score"`
Time int `json:"time"`
Title string `json:"title"`
Type string `json:"type"`
Url string `json:"url"`
}
func List() ([]Post, error) {
responseIds, err := httpjson.Get[[]int64]("https://hacker-news.firebaseio.com/v0/topstories.json")
responseIds = responseIds[0:50]
if err != nil {
return []Post{}, err
}
var wg sync.WaitGroup
posts := make([]Post, len(responseIds))
for index, id := range responseIds {
wg.Add(1)
id := id
index := index
go func() {
defer wg.Done()
url := fmt.Sprintf("https://hacker-news.firebaseio.com/v0/item/%d.json", id)
post, err := httpjson.Get[Post](url)
if err != nil {
println(err.Error())
}
posts[index] = post
}()
}
wg.Wait()
return posts, nil
}
func Get(id string) (Post, error) {
url := fmt.Sprintf("https://hacker-news.firebaseio.com/v0/item/%s.json", id)
post, err := httpjson.Get[Post](url)
if err != nil {
return Post{}, err
}
return post, nil
}

View file

@ -1,44 +0,0 @@
package news
import (
"fmt"
"github.com/maddalax/htmgo/framework/h"
"starter-template/database"
"time"
)
func StoryList() h.Ren {
posts, _ := database.GetOrSet[[]Post]("posts", func() []Post {
p, _ := List()
return p
})
time.Sleep(200 * time.Millisecond)
if len(*posts) == 0 {
return h.Pf("No results found")
}
return h.Fragment(
h.Div(h.List(*posts, func(item Post, index int) h.Ren {
return StoryCard(item)
})),
)
}
func StoryCard(post Post) h.Ren {
url := fmt.Sprintf("/news/%d", post.Id)
return h.Div(
h.Class("items-center bg-indigo-200 p-4 rounded"),
h.A(h.Text(post.Title), h.Href(url)),
)
}
func StoryFull(id string) h.Ren {
post, err := Get(id)
if err != nil {
return h.Pf(err.Error())
}
return StoryCard(post)
}

View file

@ -1,25 +0,0 @@
package base
import (
"github.com/maddalax/htmgo/framework/h"
"starter-template/partials"
"starter-template/partials/sheet"
)
func RootPage(children ...h.Ren) h.Ren {
return h.Html(
h.HxExtension("path-deps, response-targets, mutation-error"),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
),
h.Body(
partials.NavBar(),
sheet.Closed(),
h.Div(
h.Class("flex flex-col gap-2 bg-white h-full"),
h.Fragment(children...),
),
),
)
}

View file

@ -1,20 +0,0 @@
// Package pages THIS FILE IS GENERATED. DO NOT EDIT.
package pages
import "github.com/labstack/echo/v4"
import "github.com/maddalax/htmgo/framework/h"
func RegisterPages(f *echo.Echo) {
f.Get("/", func(ctx echo.Context) error {
return h.HtmlView(ctx, IndexPage(ctx))
})
f.Get("/news/:id", func(ctx echo.Context) error {
return h.HtmlView(ctx, Test(ctx))
})
f.Get("/news", func(ctx echo.Context) error {
return h.HtmlView(ctx, ListPage(ctx))
})
f.Get("/patients", func(ctx echo.Context) error {
return h.HtmlView(ctx, PatientsIndex(ctx))
})
}

View file

@ -1,89 +0,0 @@
package pages
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
"os"
"time"
)
func IndexPage(c echo.Context) *h.Page {
return h.NewPage(h.Html(
h.Class("bg-background flex flex-col items-center"),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
h.Script("/public/scripts/shiki.js"),
),
h.Body(
h.Class("flex flex-col gap-3"),
// Navbar
h.Div(
h.Class("flex justify-between items-center w-full p-6"),
h.Div(
h.Class("text-white text-xl font-bold"),
h.Text("htmgo"),
),
h.Div(
h.Class("flex gap-4"),
h.A(h.Href("/pricing"), h.Class("text-white"), h.Text("Pricing")),
h.A(h.Href("/docs"), h.Class("text-white"), h.Text("Docs")),
h.A(h.Href("/app"), h.Class("text-white"), h.Text("App")),
),
),
// Hero Section
h.Div(
h.Class("flex flex-col items-center justify-center gap-6 p-12 bg-background text-center"),
h.H1(
h.Class("text-white text-4xl sm:text-5xl font-bold max-w-3xl"),
h.Text("Go and HTMX: The Simple Stack"),
),
h.P(
h.Class("text-white text-lg sm:text-xl max-w-2xl"),
h.Text("Combine the simplicity of Go with the power of HTMX for dynamic, JavaScript-light web development."),
),
h.A(h.Href("/get-started"),
h.Class("bg-white text-background px-6 py-3 rounded-md font-semibold mt-4"),
h.Text("Join the waitlist"),
),
),
// Explore Section
h.Div(
h.Class("w-full max-w-4xl"),
CodeExample(),
),
// Footer Section
h.Div(
h.Class("flex justify-center items-center py-6"),
h.Text(fmt.Sprintf("© %d htmgo", time.Now().Year())),
),
),
))
}
func CodeExample() h.Ren {
code, err := os.ReadFile("pages/assets/_example.go")
scriptSrc, err := os.ReadFile("pages/assets/shiki.js")
if err != nil {
return h.Pf("Error loading code example")
}
fmt.Printf("%s\n", code)
script := fmt.Sprintf(string(scriptSrc), string(code))
return h.Div(
h.Class("text-white rounded-lg"),
h.Pre(h.Id("foo")),
h.RawF(`
<script type="module">
%s
</script>
`, script),
)
}

View file

@ -1,14 +0,0 @@
package pages
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
)
func Test(ctx echo.Context) *h.Page {
text := fmt.Sprintf("News ID: %s", ctx.Params("id"))
return h.NewPage(
h.Div(h.Text(text)),
)
}

View file

@ -1,29 +0,0 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
"starter-template/pages/base"
"starter-template/partials"
)
func ListPage(ctx echo.Context) *h.Page {
return h.NewPage(base.RootPage(
list(ctx),
))
}
func list(ctx echo.Context) h.Ren {
return h.Fragment(
h.ViewOnLoad(partials.NewsSheet),
h.Div(
h.Class("inline-flex flex-col gap-4 p-4"),
h.Div(
h.Class("max-w-md flex flex-col gap-4 "),
partials.OpenSheetButton(h.GetQueryParam(ctx, "open") == "true"),
),
h.Div(
h.ViewOnLoad(partials.NewsSheetOpenCount),
h.Text("you opened sheet 0 times")),
))
}

View file

@ -1,27 +0,0 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
"starter-template/pages/base"
"starter-template/partials/patient"
)
func PatientsIndex(ctx echo.Context) *h.Page {
return h.NewPage(base.RootPage(
h.Div(
h.Class("flex flex-col p-4 w-full"),
h.Div(
h.Div(
h.Class("flex justify-between items-center"),
h.P(h.Text("Manage Patients"), h.Class("text-lg font-bold")),
patient.AddPatientButton(),
),
h.View(patient.List, h.ReloadParams{
Triggers: h.CreateTriggers("load", "path-deps"),
Children: h.Children(h.Attribute("path-deps", h.GetPartialPath(patient.Create))),
}),
),
),
))
}

View file

@ -1,26 +0,0 @@
package partials
import (
"github.com/maddalax/htmgo/framework-ui/ui"
"github.com/maddalax/htmgo/framework/h"
)
func OpenSheetButton(open bool, children ...h.Ren) h.Ren {
if open {
return ui.PrimaryButton(ui.ButtonProps{
Id: "open-sheet",
Text: "Close NewsSheet",
Target: "#sheet-partial",
Get: h.GetPartialPathWithQs(NewsSheet, "open=false"),
Children: children,
})
} else {
return ui.PrimaryButton(ui.ButtonProps{
Id: "open-sheet",
Text: "Open NewsSheet",
Target: "#sheet-partial",
Get: h.GetPartialPathWithQs(NewsSheet, "open=true"),
Children: children,
})
}
}

View file

@ -1,44 +0,0 @@
// Package partials THIS FILE IS GENERATED. DO NOT EDIT.
package load
import "github.com/maddalax/htmgo/framework/h"
import "github.com/labstack/echo/v4"
import "starter-template/partials"
import "starter-template/partials/patient"
import "starter-template/partials/sheet"
func GetPartialFromContext(ctx echo.Context) *h.Partial {
path := ctx.Path()
if path == "NewsSheet" || path == "/starter-template/partials.NewsSheet" {
return partials.NewsSheet(ctx)
}
if path == "NewsSheetOpenCount" || path == "/starter-template/partials.NewsSheetOpenCount" {
return partials.NewsSheetOpenCount(ctx)
}
if path == "Create" || path == "/starter-template/partials/patient.Create" {
return patient.Create(ctx)
}
if path == "List" || path == "/starter-template/partials/patient.List" {
return patient.List(ctx)
}
if path == "AddPatientSheetPartial" || path == "/starter-template/partials/patient.AddPatientSheetPartial" {
return patient.AddPatientSheetPartial(ctx)
}
if path == "ValidateForm" || path == "/starter-template/partials/patient.ValidateForm" {
return patient.ValidateForm(ctx)
}
if path == "Close" || path == "/starter-template/partials/sheet.Close" {
return sheet.Close(ctx)
}
return nil
}
func RegisterPartials(f *echo.Echo) {
f.All("starter-template/partials*", func(ctx echo.Context) error {
partial := GetPartialFromContext(ctx)
if partial == nil {
return ctx.SendStatus(404)
}
return h.PartialView(ctx, partial)
})
}

View file

@ -1,25 +0,0 @@
package partials
import "github.com/maddalax/htmgo/framework/h"
type Link struct {
Name string
Path string
}
func NavBar() h.Ren {
links := []Link{
{"Home", "/"},
{"News", "/news"},
{"Patients", "/patients"},
}
return h.Nav(h.Class("flex gap-4 items-center p-4 text-slate-600"),
h.Boost(),
h.Children(
h.Map(links, func(link Link) h.Ren {
return h.A(h.Text(link.Name), h.Href(link.Path), h.Class("cursor-pointer hover:text-blue-400"))
})...,
))
}

View file

@ -1,65 +0,0 @@
package partials
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework-ui/ui"
"github.com/maddalax/htmgo/framework/h"
"starter-template/news"
)
func NewsSheet(ctx echo.Context) *h.Partial {
open := h.GetQueryParam(ctx, "open") == "true"
return h.NewPartialWithHeaders(
&map[string]string{
"hx-trigger": "sheetOpened",
"hx-push-url": fmt.Sprintf("/news%s", h.Ternary(open, "?open=true", "")),
},
SheetWrapper(
h.IfElseLazy(open, SheetOpen, SheetClosed),
h.OobSwap(ctx, OpenSheetButton(open)),
h.OobSwap(ctx, NewsSheetOpenCount(ctx).Root),
),
)
}
func NewsSheetOpenCount(ctx echo.Context) *h.Partial {
open := h.GetQueryParam(ctx, "open") == "true"
return h.NewPartial(h.Div(
h.Id("sheet-open-count"),
h.IfElse(open,
h.Text(fmt.Sprintf("you opened sheet %d times", 1)),
h.Text("sheet is not open")),
),
)
}
func SheetWrapper(children ...h.Ren) h.Ren {
return h.Div(h.Id("sheet-partial"), h.Fragment(children...))
}
func SheetClosed() h.Ren {
return h.Div()
}
func SheetOpen() h.Ren {
return h.Fragment(h.Div(
h.Class(`fixed top-0 right-0 h-full w-96 bg-gray-100 shadow-lg z-50`),
h.Div(
h.Class("p-4 overflow-y-auto h-full w-full flex flex-col gap-4"),
h.P(h.Text("News Sheet"),
h.Class("text-lg font-bold"),
),
h.P(h.Text("Here are the latest news stories."),
h.Class("text-sm mt-2"),
),
ui.Button(ui.ButtonProps{
Text: "Close NewsSheet",
Target: "#sheet-partial",
Get: h.GetPartialPathWithQs(NewsSheet, "open=false"),
}),
news.StoryList(),
)))
}

View file

@ -1,38 +0,0 @@
package patient
import (
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
"starter-template/features/patient"
"starter-template/partials/sheet"
)
func Create(ctx echo.Context) *h.Partial {
name := ctx.FormValue("name")
reason := ctx.FormValue("reason-for-visit")
location := ctx.FormValue("location-name")
err := patient.NewService(ctx).Create(patient.CreatePatientRequest{
Name: name,
ReasonForVisit: reason,
LocationName: location,
})
if err != nil {
ctx.Status(500)
return h.NewPartialWithHeaders(h.NewHeaders(""),
h.Div(
h.Text("Error creating patient"),
h.Class("text-red-500"),
),
)
}
headers := h.CombineHeaders(h.PushQsHeader(ctx, "adding", ""), &map[string]string{
"HX-HxTrigger": "patient-added",
})
return h.NewPartialWithHeaders(
headers,
sheet.Close(ctx))
}

View file

@ -1,143 +0,0 @@
package patient
import (
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework-ui/ui"
"github.com/maddalax/htmgo/framework/h"
"starter-template/features/patient"
"starter-template/partials/sheet"
"strings"
)
func List(ctx echo.Context) *h.Partial {
patients, err := patient.NewService(ctx).List()
if err != nil {
return h.NewPartial(h.Div(
h.Class("patient-list"),
h.Pf("Error loading patients"),
))
}
if len(patients) == 0 {
return h.NewPartial(h.Div(
h.Class("patient-list"),
h.Pf("No patients found"),
))
}
return h.NewPartial(h.Div(
h.Class("mt-8"),
h.Id("patient-list"),
h.List(patients, Row),
))
}
func AddPatientSheetPartial(ctx echo.Context) *h.Partial {
closePathQs := h.GetQueryParam(ctx, "onClosePath")
return h.NewPartialWithHeaders(
h.PushQsHeader(ctx, "adding", "true"),
AddPatientSheet(
h.Ternary(closePathQs != "", closePathQs, h.CurrentPath(ctx)),
),
)
}
func AddPatientSheet(onClosePath string) h.Ren {
return sheet.Opened(
sheet.Props{
OnClosePath: onClosePath,
ClassName: "w-[400px] bg-gray-100 p-4",
Root: h.Div(
h.Class("flex flex-col gap-4"),
h.P(h.Text("Add Patient"), h.Class("text-lg font-bold")),
addPatientForm(),
),
})
}
func ValidateForm(ctx echo.Context) *h.Partial {
trigger := h.GetTriggerName(ctx)
value := ctx.FormValue(trigger)
if trigger == "name" {
if strings.ToLower(value) == "sydne" {
return h.NewPartial(h.Pf("that name is reserved"))
}
}
if trigger == "reason-for-visit" {
if strings.ToLower(value) == "arm hurts" {
return h.NewPartial(h.Pf("lol that reason is fake"))
}
}
if trigger == "location-name" {
if strings.ToLower(value) == "hospital" {
return h.NewPartial(h.Pf("that location is reserved"))
}
}
return h.NewPartial(h.Fragment())
}
func addPatientForm() h.Ren {
return h.Form(
h.HxExtension("debug, trigger-children"),
h.Attribute("hx-target-5*", "#submit-error"),
h.Post(h.GetPartialPath(Create)),
h.Class("flex flex-col gap-2"),
ui.Input(ui.InputProps{
Type: "text",
Label: "Name",
Name: "name",
DefaultValue: "",
ValidationPath: h.GetPartialPath(ValidateForm),
}),
ui.Input(ui.InputProps{
Type: "text",
Label: "Reason for visit",
Name: "reason-for-visit",
ValidationPath: h.GetPartialPath(ValidateForm),
}),
ui.Input(ui.InputProps{
Type: "date",
Label: "Appointment Date",
Name: "appointment-date",
}),
ui.Input(ui.InputProps{
Type: "text",
Label: "Location Name",
Name: "location-name",
ValidationPath: h.GetPartialPath(ValidateForm),
}),
ui.PrimaryButton(ui.ButtonProps{
Text: "Add Patient",
Class: "rounded p-2",
Type: "submit",
}),
h.Div(
h.Id("submit-error"),
h.Class("text-red-500"),
),
)
}
func Row(patient *patient.Patient, index int) h.Ren {
return h.Div(
h.Class("flex flex-col gap-2 rounded p-4", h.Ternary(index%2 == 0, "bg-red-100", "")),
h.Pf("Name: %s", patient.Name),
h.Pf("Reason for visit: %s", patient.ReasonForVisit),
)
}
func AddPatientButton() h.Ren {
return ui.Button(ui.ButtonProps{
Id: "add-patient",
Text: "Add Patient",
Class: "bg-blue-700 text-white rounded p-2 h-12",
Trigger: "qs:adding, click",
Target: sheet.Id,
Get: h.GetPartialPathWithQs(AddPatientSheetPartial, "onClosePath=/patients"),
})
}

View file

@ -1,47 +0,0 @@
package sheet
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
)
type Props struct {
ClassName string
Root h.Ren
OnClosePath string
}
var Id = "#active-modal"
func Opened(props Props) h.Ren {
return h.Fragment(h.Div(
h.Class(`fixed top-0 right-0 h-full shadow-lg z-50`,
h.Ternary(props.ClassName != "", props.ClassName, "w-96 bg-gray-100")),
closeButton(props),
h.Div(
props.Root,
)))
}
func Closed() h.Ren {
return h.Div(h.Id(Id))
}
func Close(ctx echo.Context) *h.Partial {
return h.NewPartialWithHeaders(
h.Ternary(ctx.Query("path") != "", h.ReplaceUrlHeader(ctx.Query("path")), h.NewHeaders()),
h.OobSwap(ctx, Closed()),
)
}
func closeButton(props Props) h.Ren {
return h.Div(
h.Class("absolute top-0 right-0 p-3"),
h.Button(
h.Class("text-gray-500"),
h.GetPartialWithQs(Close, fmt.Sprintf("path=%s", props.OnClosePath)),
h.Text("X"),
),
)
}

View file

@ -2,20 +2,11 @@ package base
import (
"github.com/maddalax/htmgo/framework/h"
"strings"
)
func Extensions() string {
extensions := []string{"path-deps", "response-targets", "mutation-error"}
if h.IsDevelopment() {
extensions = append(extensions, "livereload")
}
return strings.Join(extensions, ", ")
}
func RootPage(children ...h.Ren) h.Ren {
return h.Html(
h.HxExtension(Extensions()),
h.HxExtension(h.BaseExtensions()),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),

View file

@ -6,10 +6,6 @@ import "github.com/maddalax/htmgo/framework/h"
func RegisterPages(f *echo.Echo) {
f.GET("/", func(ctx echo.Context) error {
cc := ctx.(*h.RequestContext)
return h.HtmlView(ctx, IndexPage(cc))
})
f.GET("/tasks", func(ctx echo.Context) error {
cc := ctx.(*h.RequestContext)
return h.HtmlView(ctx, TaskListPage(cc))
})

View file

@ -1,40 +1,29 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
"todolist/pages/base"
"todolist/partials"
"todolist/partials/task"
"github.com/maddalax/htmgo/framework/h"
)
func IndexPage(c echo.Context) *h.Page {
return h.NewPage(h.Html(
h.HxExtension(base.Extensions()),
h.Class("bg-red-200 flex flex-col items-center h-full w-full"),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
func TaskListPage(ctx *h.RequestContext) *h.Page {
title := h.Div(
h.H1(h.Class("text-7xl font-extralight text-rose-500 tracking-wide"), h.Text("todos")),
)
return h.NewPage(base.RootPage(
h.Div(
h.Class("bg-neutral-100 min-h-screen"),
h.Div(
h.Class("flex flex-col gap-6 p-4 items-center max-w-xl mx-auto pb-12"),
title,
task.Card(ctx),
h.Children(
h.Div(h.Text("Double-click to edit a todo")),
),
h.Body(
h.Class("flex flex-col gap-4"),
h.Div(h.Class("flex gap-2 mt-6"),
Button(),
Button(),
Button(),
Button(),
),
),
))
}
func Button() h.Ren {
return h.Button(h.Class("btn bg-green-500 p-4 rounded text-white"),
h.Text("my button"),
h.AfterRequest(
h.SetDisabled(true),
h.RemoveClass("bg-red-600"),
h.AddClass("bg-gray-500"),
),
h.GetPartial(partials.SamplePartial),
)
}

View file

@ -1,29 +0,0 @@
package pages
import (
"todolist/pages/base"
"todolist/partials/task"
"github.com/maddalax/htmgo/framework/h"
)
func TaskListPage(ctx *h.RequestContext) *h.Page {
title := h.Div(
h.H1(h.Class("text-7xl font-extralight text-rose-500 tracking-wide"), h.Text("todos")),
)
return h.NewPage(base.RootPage(
h.Div(
h.Class("bg-neutral-100 min-h-screen"),
h.Div(
h.Class("flex flex-col gap-6 p-4 items-center max-w-xl mx-auto pb-12"),
title,
task.Card(ctx),
h.Children(
h.Div(h.Text("Double-click to edit a todo")),
),
),
),
))
}

View file

@ -314,7 +314,7 @@ func ChangeTab(ctx *h.RequestContext) *h.Partial {
tab := ctx.QueryParam("tab")
return h.SwapManyPartialWithHeaders(ctx,
h.PushUrlHeader(fmt.Sprintf("/tasks?tab=%s", tab)),
h.PushQsHeader(ctx, h.NewQs("tab", tab)),
List(list, tab),
Footer(list, tab),
)