some styling and validation
This commit is contained in:
parent
784995728c
commit
8abed86b7d
6 changed files with 77 additions and 28 deletions
|
|
@ -10,13 +10,16 @@ import (
|
||||||
func MessageRow(message *Message) *h.Element {
|
func MessageRow(message *Message) *h.Element {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Attribute("hx-swap-oob", "beforeend"),
|
h.Attribute("hx-swap-oob", "beforeend"),
|
||||||
h.Class("flex flex-col gap-2 w-full"),
|
h.Class("flex flex-col gap-4 w-full"),
|
||||||
h.Id("messages"),
|
h.Id("messages"),
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex gap-2 items-center"),
|
h.Class("flex flex-col gap-1"),
|
||||||
h.Pf(message.UserName),
|
h.Div(
|
||||||
h.Pf(message.CreatedAt.In(time.Local).Format("01/02 03:04 PM")),
|
h.Class("flex gap-2 items-center"),
|
||||||
h.Pf(message.Message),
|
h.Pf(message.UserName, h.Class("font-bold")),
|
||||||
|
h.Pf(message.CreatedAt.In(time.Local).Format("01/02 03:04 PM")),
|
||||||
|
),
|
||||||
|
h.P(h.Text(message.Message)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +29,7 @@ func ConnectedUsers(username string) *h.Element {
|
||||||
h.Attribute("hx-swap", "none"),
|
h.Attribute("hx-swap", "none"),
|
||||||
h.Attribute("hx-swap-oob", "beforeend"),
|
h.Attribute("hx-swap-oob", "beforeend"),
|
||||||
h.Id("connected-users"),
|
h.Id("connected-users"),
|
||||||
h.Class("flex flex-col gap-2"),
|
h.Class("flex flex-col"),
|
||||||
// This would be populated dynamically with connected users
|
// This would be populated dynamically with connected users
|
||||||
ConnectedUser(username, false),
|
ConnectedUser(username, false),
|
||||||
)
|
)
|
||||||
|
|
@ -39,7 +42,7 @@ func ConnectedUser(username string, remove bool) *h.Element {
|
||||||
}
|
}
|
||||||
return h.Li(
|
return h.Li(
|
||||||
h.Id(id),
|
h.Id(id),
|
||||||
h.Class("text-slate-700"),
|
h.Class("truncate text-slate-700"),
|
||||||
h.Text(username),
|
h.Text(username),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
"github.com/maddalax/htmgo/framework/js"
|
"github.com/maddalax/htmgo/framework/js"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ChatRoom(ctx *h.RequestContext) *h.Page {
|
func ChatRoom(ctx *h.RequestContext) *h.Page {
|
||||||
|
|
@ -29,8 +30,10 @@ func ChatRoom(ctx *h.RequestContext) *h.Page {
|
||||||
const reason = e.detail.event.reason
|
const reason = e.detail.event.reason
|
||||||
if(['invalid room', 'no session'].includes(reason)) {
|
if(['invalid room', 'no session'].includes(reason)) {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
|
} else if(e.detail.event.code === 1011) {
|
||||||
|
window.location.reload()
|
||||||
} else {
|
} else {
|
||||||
console.error('Connection closed:', e.detail.event)
|
console.error('Connection closed:', e.detail.event)
|
||||||
}
|
}
|
||||||
`),
|
`),
|
||||||
),
|
),
|
||||||
|
|
@ -40,26 +43,30 @@ func ChatRoom(ctx *h.RequestContext) *h.Page {
|
||||||
// Sidebar for connected users
|
// Sidebar for connected users
|
||||||
UserSidebar(),
|
UserSidebar(),
|
||||||
|
|
||||||
// Chat Area
|
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex flex-col flex-grow gap-4 bg-white rounded p-4"),
|
h.Class("flex flex-col flex-grow bg-white rounded p-4"),
|
||||||
|
|
||||||
|
// Room name at the top, fixed
|
||||||
|
CachedRoomHeader(ctx),
|
||||||
|
|
||||||
|
// Padding to push chat content below the fixed room name
|
||||||
|
h.Div(h.Class("pt-[50px]")),
|
||||||
|
|
||||||
h.HxAfterWsMessage(
|
h.HxAfterWsMessage(
|
||||||
js.EvalJsOnSibling("#messages",
|
js.EvalJsOnSibling("#messages",
|
||||||
// language=JavaScript
|
|
||||||
`element.scrollTop = element.scrollHeight;`),
|
`element.scrollTop = element.scrollHeight;`),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Chat Messages
|
// Chat Messages
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Id("messages"),
|
h.Id("messages"),
|
||||||
h.Class("flex flex-col gap-2 overflow-auto grow w-full"),
|
h.Class("flex flex-col gap-4 overflow-auto grow w-full"),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Chat Input at the bottom
|
// Chat Input at the bottom
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("mt-auto"),
|
h.Class("mt-auto"),
|
||||||
Form(ctx),
|
Form(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -67,11 +74,35 @@ func ChatRoom(ctx *h.RequestContext) *h.Page {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CachedRoomHeader = h.CachedT(time.Hour, func(ctx *h.RequestContext) *h.Element {
|
||||||
|
return roomNameHeader(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
func roomNameHeader(ctx *h.RequestContext) *h.Element {
|
||||||
|
roomId := chi.URLParam(ctx.Request, "id")
|
||||||
|
service := chat.NewService(ctx.ServiceLocator())
|
||||||
|
room, err := service.GetRoom(roomId)
|
||||||
|
if err != nil {
|
||||||
|
return h.Div()
|
||||||
|
}
|
||||||
|
return h.Div(
|
||||||
|
h.Class("bg-neutral-700 text-white p-3 shadow-sm w-full fixed top-0 left-0 flex justify-center z-10"),
|
||||||
|
h.H2F(room.Name, h.Class("text-lg font-bold")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func UserSidebar() *h.Element {
|
func UserSidebar() *h.Element {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("w-48 bg-slate-200 p-4 flex flex-col gap-3 rounded-l-lg"),
|
h.Class("pt-[67px] min-w-48 w-48 bg-neutral-200 p-4 flex flex-col justify-between gap-3 rounded-l-lg"),
|
||||||
h.H2F("Connected Users", h.Class("text-lg font-bold")),
|
h.Div(
|
||||||
chat.ConnectedUsers(""),
|
h.H3F("Connected Users", h.Class("text-lg font-bold")),
|
||||||
|
chat.ConnectedUsers(""),
|
||||||
|
),
|
||||||
|
h.A(
|
||||||
|
h.Class("cursor-pointer"),
|
||||||
|
h.Href("/"),
|
||||||
|
h.Text("Leave Room"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,8 +110,9 @@ func MessageInput() *h.Element {
|
||||||
return h.Input("text",
|
return h.Input("text",
|
||||||
h.Id("message-input"),
|
h.Id("message-input"),
|
||||||
h.Required(),
|
h.Required(),
|
||||||
h.Class("p-4 rounded-md border border-slate-200 w-full"),
|
h.Class("p-4 rounded-md border border-slate-200 w-full focus:outline-none focus:ring focus:ring-slate-200"),
|
||||||
h.Name("message"),
|
h.Name("message"),
|
||||||
|
h.MaxLength(1000),
|
||||||
h.Placeholder("Type a message..."),
|
h.Placeholder("Type a message..."),
|
||||||
h.HxAfterWsSend(
|
h.HxAfterWsSend(
|
||||||
js.SetValue(""),
|
js.SetValue(""),
|
||||||
|
|
@ -88,7 +120,7 @@ func MessageInput() *h.Element {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Form(ctx *h.RequestContext) *h.Element {
|
func Form() *h.Element {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("flex gap-4 items-center"),
|
h.Class("flex gap-4 items-center"),
|
||||||
h.Form(
|
h.Form(
|
||||||
|
|
@ -98,11 +130,3 @@ func Form(ctx *h.RequestContext) *h.Element {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Spinner(children ...h.Ren) *h.Element {
|
|
||||||
return h.Div(
|
|
||||||
h.Children(children...),
|
|
||||||
h.Class("spinner spinner-border animate-spin w-4 h-4 border-2 border-t-transparent"),
|
|
||||||
h.Attribute("role", "status"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page {
|
||||||
Name: "username",
|
Name: "username",
|
||||||
Label: "Username",
|
Label: "Username",
|
||||||
Required: true,
|
Required: true,
|
||||||
|
Children: []h.Ren{
|
||||||
|
h.Attribute("autocomplete", "off"),
|
||||||
|
h.MaxLength(15),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
h.Div(
|
h.Div(
|
||||||
|
|
@ -33,6 +37,10 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page {
|
||||||
Name: "new-chat-room",
|
Name: "new-chat-room",
|
||||||
Label: "Create a New Chat Room",
|
Label: "Create a New Chat Room",
|
||||||
Placeholder: "Chat Room Name",
|
Placeholder: "Chat Room Name",
|
||||||
|
Children: []h.Ren{
|
||||||
|
h.Attribute("autocomplete", "off"),
|
||||||
|
h.MaxLength(20),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
h.Div(
|
h.Div(
|
||||||
|
|
@ -47,6 +55,10 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page {
|
||||||
Name: "join-chat-room",
|
Name: "join-chat-room",
|
||||||
Label: "Join a Chat Room",
|
Label: "Join a Chat Room",
|
||||||
Placeholder: "Chat Room Id",
|
Placeholder: "Chat Room Id",
|
||||||
|
Children: []h.Ren{
|
||||||
|
h.Attribute("autocomplete", "off"),
|
||||||
|
h.MaxLength(100),
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ func CreateOrJoinRoom(ctx *h.RequestContext) *h.Partial {
|
||||||
return h.SwapPartial(ctx, components.FormError("Username is required"))
|
return h.SwapPartial(ctx, components.FormError("Username is required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(username) > 15 {
|
||||||
|
return h.SwapPartial(ctx, components.FormError("Username is too long"))
|
||||||
|
}
|
||||||
|
|
||||||
user, err := service.CreateUser(username)
|
user, err := service.CreateUser(username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -52,6 +56,11 @@ func CreateOrJoinRoom(ctx *h.RequestContext) *h.Partial {
|
||||||
}
|
}
|
||||||
|
|
||||||
chatRoomName := ctx.Request.FormValue("new-chat-room")
|
chatRoomName := ctx.Request.FormValue("new-chat-room")
|
||||||
|
|
||||||
|
if len(chatRoomName) > 20 {
|
||||||
|
return h.SwapPartial(ctx, components.FormError("Chat room name is too long"))
|
||||||
|
}
|
||||||
|
|
||||||
if chatRoomName != "" {
|
if chatRoomName != "" {
|
||||||
room, _ := service.CreateRoom(chatRoomName)
|
room, _ := service.CreateRoom(chatRoomName)
|
||||||
if room == nil {
|
if room == nil {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ func Handle() http.HandlerFunc {
|
||||||
|
|
||||||
c, err := websocket.Accept(w, r, nil)
|
c, err := websocket.Accept(w, r, nil)
|
||||||
|
|
||||||
|
// 2 mb
|
||||||
|
c.SetReadLimit(2 * 1024 * 1024)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package ws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
)
|
)
|
||||||
|
|
@ -142,7 +141,6 @@ func (manager *SocketManager) Broadcast(message []byte, messageType websocket.Me
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *SocketManager) BroadcastText(message string) {
|
func (manager *SocketManager) BroadcastText(message string) {
|
||||||
fmt.Printf("Broadcasting message: \n%s\n", message)
|
|
||||||
manager.Broadcast([]byte(message), websocket.MessageText)
|
manager.Broadcast([]byte(message), websocket.MessageText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue