entry page

This commit is contained in:
maddalax 2024-09-30 20:32:42 -05:00
parent ae983473b3
commit 787ccb4fc1
11 changed files with 313 additions and 120 deletions

View file

@ -293,6 +293,17 @@ func formatRoute(path string) string {
path = strings.ReplaceAll(path, "_", "/")
path = strings.ReplaceAll(path, ".", "/")
path = strings.ReplaceAll(path, "\\", "/")
parts := strings.Split(path, "/")
for i, part := range parts {
if strings.HasPrefix(part, ":") {
parts[i] = fmt.Sprintf("{%s}", part[1:])
}
}
path = strings.Join(parts, "/")
if path == "" {
return "/"
}

View file

@ -0,0 +1,25 @@
package chat
import (
"chat/internal/db"
"context"
"github.com/maddalax/htmgo/framework/service"
)
type Service struct {
queries *db.Queries
}
func NewService(locator *service.Locator) *Service {
return &Service{
queries: service.Get[db.Queries](locator),
}
}
func (s *Service) GetRoom(id string) (*db.ChatRoom, error) {
room, err := s.queries.GetChatRoom(context.Background(), id)
if err != nil {
return nil, err
}
return &room, nil
}

View file

@ -0,0 +1,41 @@
package components
import "github.com/maddalax/htmgo/framework/h"
type ButtonProps struct {
Id string
Text string
Target string
Type string
Trigger string
Get string
Class string
Children []h.Ren
}
func PrimaryButton(props ButtonProps) h.Ren {
props.Class = h.MergeClasses(props.Class, "border-slate-800 bg-slate-900 hover:bg-slate-800 text-white")
return Button(props)
}
func SecondaryButton(props ButtonProps) h.Ren {
props.Class = h.MergeClasses(props.Class, "border-gray-700 bg-gray-700 text-white")
return Button(props)
}
func Button(props ButtonProps) h.Ren {
text := h.Text(props.Text)
button := h.Button(
h.If(props.Id != "", h.Id(props.Id)),
h.If(props.Children != nil, h.Children(props.Children...)),
h.Class("flex gap-1 items-center justify-center border p-4 rounded cursor-hover", props.Class),
h.If(props.Get != "", h.Get(props.Get)),
h.If(props.Target != "", h.HxTarget(props.Target)),
h.IfElse(props.Type != "", h.Type(props.Type), h.Type("button")),
text,
)
return button
}

View file

@ -0,0 +1,11 @@
package components
import "github.com/maddalax/htmgo/framework/h"
func FormError(error string) *h.Element {
return h.Div(
h.Id("form-error"),
h.Text(error),
h.If(error != "", h.Class("p-4 bg-rose-400 text-white rounded")),
)
}

View file

@ -0,0 +1,54 @@
package components
import (
"github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/hx"
)
type InputProps struct {
Id string
Label string
Name string
Type string
DefaultValue string
Placeholder string
Required bool
ValidationPath string
Error string
Children []h.Ren
}
func Input(props InputProps) *h.Element {
validation := h.If(props.ValidationPath != "", h.Children(
h.Post(props.ValidationPath, hx.BlurEvent),
h.Attribute("hx-swap", "innerHTML transition:true"),
h.Attribute("hx-target", "next div"),
))
if props.Type == "" {
props.Type = "text"
}
input := h.Input(
props.Type,
h.Class("border p-2 rounded focus:outline-none focus:ring focus:ring-slate-800"),
h.If(props.Name != "", h.Name(props.Name)),
h.If(props.Children != nil, h.Children(props.Children...)),
h.If(props.Required, h.Required()),
h.If(props.DefaultValue != "", h.Attribute("value", props.DefaultValue)),
validation,
)
wrapped := h.Div(
h.If(props.Id != "", h.Id(props.Id)),
h.Class("flex flex-col gap-1"),
h.If(props.Label != "", h.Label(h.Text(props.Label))),
input,
h.Div(
h.Id(props.Id+"-error"),
h.Class("text-red-500"),
),
)
return wrapped
}

View file

@ -0,0 +1,91 @@
package pages
import (
"github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/hx"
"github.com/maddalax/htmgo/framework/js"
)
func ChatRoom(ctx *h.RequestContext) *h.Page {
return h.NewPage(
RootPage(
h.Div(
h.JoinExtensions(
h.TriggerChildren(),
h.HxExtension("ws"),
),
h.Attribute("ws-connect", "/chat"),
h.Class("flex flex-col gap-4 items-center pt-24 min-h-screen bg-neutral-100"),
Form(ctx),
h.Div(
h.Div(
h.Id("messages"),
h.Class("flex flex-col gap-2 w-full"),
),
),
),
),
)
}
func MessageInput() *h.Element {
return h.Input("text",
h.Id("message-input"),
h.Required(),
h.Class("p-4 rounded-md border border-slate-200"),
h.Name("message"),
h.Placeholder("Message"),
h.HxBeforeWsSend(
js.SetValue(""),
),
h.OnEvent(hx.KeyDownEvent, js.SubmitFormOnEnter()),
)
}
func Form(ctx *h.RequestContext) *h.Element {
return h.Div(
h.Class("flex flex-col items-center justify-center p-4 gap-6"),
h.H2F("Form submission with ws example", h.Class("text-2xl font-bold")),
h.Form(
h.Attribute("ws-send", ""),
h.Class("flex flex-col gap-2"),
h.LabelFor("name", "Your Message"),
MessageInput(),
SubmitButton(),
),
)
}
func SubmitButton() *h.Element {
buttonClasses := "rounded items-center px-3 py-2 bg-slate-800 text-white w-full text-center"
return h.Div(
h.HxBeforeRequest(
js.RemoveClassOnChildren(".loading", "hidden"),
js.SetClassOnChildren(".submit", "hidden"),
),
h.HxAfterRequest(
js.SetClassOnChildren(".loading", "hidden"),
js.RemoveClassOnChildren(".submit", "hidden"),
),
h.Class("flex gap-2 justify-center"),
h.Button(
h.Class("loading hidden relative text-center", buttonClasses),
Spinner(),
h.Disabled(),
h.Text("Submitting..."),
),
h.Button(
h.Type("submit"),
h.Class("submit", buttonClasses),
h.Text("Submit"),
),
)
}
func Spinner(children ...h.Ren) *h.Element {
return h.Div(
h.Children(children...),
h.Class("absolute left-1 spinner spinner-border animate-spin inline-block w-6 h-6 border-4 rounded-full border-slate-200 border-t-transparent"),
h.Attribute("role", "status"),
)
}

View file

@ -1,91 +1,63 @@
package pages
import (
"chat/components"
"chat/partials"
"github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/hx"
"github.com/maddalax/htmgo/framework/js"
)
func IndexPage(ctx *h.RequestContext) *h.Page {
func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page {
return h.NewPage(
RootPage(
h.Div(
h.JoinExtensions(
h.TriggerChildren(),
h.HxExtension("ws"),
),
h.Attribute("ws-connect", "/chat"),
h.Class("flex flex-col gap-4 items-center pt-24 min-h-screen bg-neutral-100"),
Form(ctx),
h.Class("flex flex-col items-center justify-center min-h-screen bg-neutral-100"),
h.Div(
h.Div(
h.Id("messages"),
h.Class("flex flex-col gap-2 w-full"),
h.Class("bg-white p-8 rounded-lg shadow-lg w-full max-w-md"),
h.H2F("htmgo chat", h.Class("text-3xl font-bold text-center mb-6")),
h.Form(
h.Attribute("hx-swap", "none"),
h.PostPartial(partials.CreateOrJoinRoom),
h.Class("flex flex-col gap-3"),
components.Input(components.InputProps{
Id: "username",
Name: "username",
Label: "Username",
Required: true,
}),
h.Div(
h.Class("mt-6 flex flex-col gap-3"),
components.Input(components.InputProps{
Name: "new-chat-room",
Label: "Create a New Chat Room",
Placeholder: "Chat Room Name",
}),
h.Div(
h.Class("flex items-center justify-center gap-4"),
h.Div(h.Class("border-t border-gray-300 flex-grow")),
h.P(h.Text("OR"), h.Class("text-gray-500")),
h.Div(h.Class("border-t border-gray-300 flex-grow")),
),
components.Input(components.InputProps{
Id: "join-chat-room",
Name: "join-chat-room",
Label: "Join a Chat Room",
Placeholder: "Chat Room Id",
}),
),
components.FormError(""),
components.PrimaryButton(components.ButtonProps{
Type: "submit",
Text: "Submit",
}),
),
),
),
),
)
}
func MessageInput() *h.Element {
return h.Input("text",
h.Id("message-input"),
h.Required(),
h.Class("p-4 rounded-md border border-slate-200"),
h.Name("message"),
h.Placeholder("Message"),
h.HxBeforeWsSend(
js.SetValue(""),
),
h.OnEvent(hx.KeyDownEvent, js.SubmitFormOnEnter()),
)
}
func Form(ctx *h.RequestContext) *h.Element {
return h.Div(
h.Class("flex flex-col items-center justify-center p-4 gap-6"),
h.H2F("Form submission with ws example", h.Class("text-2xl font-bold")),
h.Form(
h.Attribute("ws-send", ""),
h.Class("flex flex-col gap-2"),
h.LabelFor("name", "Your Message"),
MessageInput(),
SubmitButton(),
),
)
}
func SubmitButton() *h.Element {
buttonClasses := "rounded items-center px-3 py-2 bg-slate-800 text-white w-full text-center"
return h.Div(
h.HxBeforeRequest(
js.RemoveClassOnChildren(".loading", "hidden"),
js.SetClassOnChildren(".submit", "hidden"),
),
h.HxAfterRequest(
js.SetClassOnChildren(".loading", "hidden"),
js.RemoveClassOnChildren(".submit", "hidden"),
),
h.Class("flex gap-2 justify-center"),
h.Button(
h.Class("loading hidden relative text-center", buttonClasses),
Spinner(),
h.Disabled(),
h.Text("Submitting..."),
),
h.Button(
h.Type("submit"),
h.Class("submit", buttonClasses),
h.Text("Submit"),
),
)
}
func Spinner(children ...h.Ren) *h.Element {
return h.Div(
h.Children(children...),
h.Class("absolute left-1 spinner spinner-border animate-spin inline-block w-6 h-6 border-4 rounded-full border-slate-200 border-t-transparent"),
h.Attribute("role", "status"),
)
}

View file

@ -1,54 +1,30 @@
package partials
import (
"chat/chat"
"chat/components"
"github.com/maddalax/htmgo/framework/h"
"strconv"
)
func CounterPartial(ctx *h.RequestContext) *h.Partial {
count, err := strconv.ParseInt(ctx.FormValue("count"), 10, 64)
func CreateOrJoinRoom(ctx *h.RequestContext) *h.Partial {
locator := ctx.ServiceLocator()
service := chat.NewService(locator)
if err != nil {
count = 0
chatRoomId := ctx.FormValue("join-chat-room")
if chatRoomId != "" {
room, _ := service.GetRoom(chatRoomId)
if room == nil {
return h.SwapPartial(ctx, components.FormError("Room not found"))
} else {
return h.RedirectPartial("/chat/" + chatRoomId)
}
}
count++
chatRoomName := ctx.FormValue("chat-room-name")
if chatRoomName != "" {
// create room
}
return h.SwapManyPartial(
ctx,
CounterForm(int(count)),
h.ElementIf(count > 10, SubmitButton("New record!")),
)
}
func CounterForm(count int) *h.Element {
return h.Form(
h.Class("flex flex-col gap-3 items-center"),
h.Id("counter-form"),
h.PostPartial(CounterPartial),
h.Input("text",
h.Class("hidden"),
h.Value(count),
h.Name("count"),
),
h.P(
h.AttributePairs(
"id", "counter",
"class", "text-xl",
"name", "count",
"text", "count",
),
h.TextF("Count: %d", count),
),
SubmitButton("Increment"),
)
}
func SubmitButton(text string) *h.Element {
return h.Button(
h.Class("bg-rose-400 hover:bg-rose-500 text-white font-bold py-2 px-4 rounded"),
h.Id("swap-text"),
h.Type("submit"),
h.Text(text),
)
return h.SwapPartial(ctx, components.FormError("Create a new room or join an existing one"))
}

View file

@ -11,6 +11,8 @@ type InputProps struct {
Name string
Type string
DefaultValue string
Placeholder string
Required bool
ValidationPath string
Children []h.Ren
}
@ -22,19 +24,24 @@ func Input(props InputProps) h.Ren {
h.Attribute("hx-target", "next div"),
))
if props.Type == "" {
props.Type = "text"
}
input := h.Input(
props.Type,
h.Class("border p-2 rounded"),
h.If(props.Id != "", h.Id(props.Id)),
h.If(props.Name != "", h.Name(props.Name)),
h.If(props.Children != nil, h.Children(props.Children...)),
h.If(props.Required, h.Required()),
h.If(props.DefaultValue != "", h.Attribute("value", props.DefaultValue)),
validation,
)
wrapped := h.Div(
h.Class("flex flex-col gap-1"),
h.If(props.Label != "", h.Label(props.Label)),
h.If(props.Label != "", h.Label(h.Text(props.Label))),
input,
h.Div(h.Class("text-red-500")),
)

View file

@ -1,6 +1,7 @@
package h
import (
"github.com/maddalax/htmgo/framework/hx"
"html"
"net/http"
"reflect"
@ -56,6 +57,10 @@ func SwapPartial(ctx *RequestContext, swap *Element) *Partial {
SwapMany(ctx, swap))
}
func RedirectPartial(url string) *Partial {
return NewPartialWithHeaders(NewHeaders(hx.RedirectHeader, url), Fragment())
}
func SwapManyPartial(ctx *RequestContext, swaps ...*Element) *Partial {
return NewPartial(
SwapMany(ctx, swaps...),

View file

@ -65,7 +65,7 @@ func SwapMany(ctx *RequestContext, elements ...*Element) *Element {
for _, element := range elements {
element.AppendChild(outOfBandSwap(""))
}
return Template(Map(elements, func(arg *Element) Ren {
return Fragment(Map(elements, func(arg *Element) Ren {
return arg
})...)
}