Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 13 additions & 21 deletions avatar-upload/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,7 @@ type ProfileStore struct {
AvatarURL string
}

// Change implements the Store interface
func (s *ProfileStore) Change(ctx *livetemplate.ActionContext) error {
log.Printf("DEBUG: Change called with action: %s", ctx.Action)
switch ctx.Action {
case "UpdateProfile":
return s.UpdateProfile(ctx)
case "upload:avatar:complete":
// Auto-triggered when avatar upload completes
log.Printf("DEBUG: Processing auto-triggered upload")
return s.ProcessAvatarUpload(ctx)
default:
return fmt.Errorf("unknown action: %s", ctx.Action)
}
}

// UpdateProfile handles profile update form submission
// UpdateProfile handles the "UpdateProfile" action for profile update form submission
func (s *ProfileStore) UpdateProfile(ctx *livetemplate.ActionContext) error {
name, _ := ctx.Data.GetStringOk("name")
email, _ := ctx.Data.GetStringOk("email")
Expand All @@ -47,7 +32,7 @@ func (s *ProfileStore) UpdateProfile(ctx *livetemplate.ActionContext) error {

// Also process avatar if it was uploaded with the form
if ctx.HasUploads("avatar") {
if err := s.ProcessAvatarUpload(ctx); err != nil {
if err := s.processAvatarUpload(ctx); err != nil {
return err
}
}
Expand All @@ -56,10 +41,17 @@ func (s *ProfileStore) UpdateProfile(ctx *livetemplate.ActionContext) error {
return nil
}

// ProcessAvatarUpload handles avatar upload processing
// Called either automatically when upload completes (upload:avatar:complete action)
// UploadAvatarComplete handles the "upload_avatar_complete" action.
// Auto-triggered when avatar upload completes.
func (s *ProfileStore) UploadAvatarComplete(ctx *livetemplate.ActionContext) error {
log.Printf("DEBUG: Processing auto-triggered upload")
return s.processAvatarUpload(ctx)
}

// processAvatarUpload handles avatar upload processing
// Called either automatically when upload completes (upload_avatar_complete action)
// or during explicit form submission (UpdateProfile action)
func (s *ProfileStore) ProcessAvatarUpload(ctx *livetemplate.ActionContext) error {
func (s *ProfileStore) processAvatarUpload(ctx *livetemplate.ActionContext) error {
// Get completed uploads from ActionContext
uploads := ctx.GetCompletedUploads("avatar")
log.Printf("DEBUG: ProcessAvatarUpload called, found %d completed uploads", len(uploads))
Expand Down Expand Up @@ -167,7 +159,7 @@ func main() {
log.Printf("📸 Upload an avatar to see the upload feature in action!")
log.Printf("📁 Uploaded files will be saved to ./uploads/")
log.Printf("✨ Upload processing happens automatically when upload completes")
log.Printf(" (via upload:avatar:complete action) or during form submission")
log.Printf(" (via upload_avatar_complete action) or during form submission")

if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatal(err)
Expand Down
108 changes: 57 additions & 51 deletions chat/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,75 +32,81 @@ type User struct {
IsOnline bool
}

func (s *ChatState) Change(ctx *livetemplate.ActionContext) error {
// Send handles the "send" action to send a chat message
func (s *ChatState) Send(ctx *livetemplate.ActionContext) error {
s.mu.Lock()
defer s.mu.Unlock()

switch ctx.Action {
case "send":
var data struct {
Message string `json:"message"`
}
var data struct {
Message string `json:"message"`
}

if err := ctx.Bind(&data); err != nil {
log.Printf("Failed to bind message data: %v", err)
return nil
}
if err := ctx.Bind(&data); err != nil {
log.Printf("Failed to bind message data: %v", err)
return nil
}

if data.Message == "" {
return nil
}
if data.Message == "" {
return nil
}

s.TotalMessages++
msg := Message{
ID: s.TotalMessages,
Username: s.CurrentUser,
Text: data.Message,
Timestamp: time.Now().Format("15:04:05"),
}
s.TotalMessages++
msg := Message{
ID: s.TotalMessages,
Username: s.CurrentUser,
Text: data.Message,
Timestamp: time.Now().Format("15:04:05"),
}

s.Messages = append(s.Messages, msg)
s.Messages = append(s.Messages, msg)

// Auto-broadcast handles syncing to other tabs automatically
return nil
// Auto-broadcast handles syncing to other tabs automatically
return nil
}

case "join":
var data struct {
Username string `json:"username"`
}
// Join handles the "join" action when a user joins the chat
func (s *ChatState) Join(ctx *livetemplate.ActionContext) error {
s.mu.Lock()
defer s.mu.Unlock()

if err := ctx.Bind(&data); err != nil {
log.Printf("Failed to bind join data: %v", err)
return nil
}
var data struct {
Username string `json:"username"`
}

if data.Username == "" {
return nil
}
if err := ctx.Bind(&data); err != nil {
log.Printf("Failed to bind join data: %v", err)
return nil
}

s.CurrentUser = data.Username
if data.Username == "" {
return nil
}

if _, exists := s.Users[data.Username]; !exists {
s.Users[data.Username] = &User{
Username: data.Username,
JoinedAt: time.Now(),
IsOnline: true,
}
s.updateOnlineCount()
s.CurrentUser = data.Username

if _, exists := s.Users[data.Username]; !exists {
s.Users[data.Username] = &User{
Username: data.Username,
JoinedAt: time.Now(),
IsOnline: true,
}
s.updateOnlineCount()
}

return nil
return nil
}

// Leave handles the "leave" action when a user leaves the chat
func (s *ChatState) Leave(_ *livetemplate.ActionContext) error {
s.mu.Lock()
defer s.mu.Unlock()

case "leave":
if s.CurrentUser != "" {
if user, exists := s.Users[s.CurrentUser]; exists {
user.IsOnline = false
}
s.updateOnlineCount()
if s.CurrentUser != "" {
if user, exists := s.Users[s.CurrentUser]; exists {
user.IsOnline = false
}
return nil
s.updateOnlineCount()
}

return nil
}

Expand Down
28 changes: 16 additions & 12 deletions graceful-shutdown/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@ type CounterState struct {
LastUpdated string `json:"last_updated"`
}

func (s *CounterState) Change(ctx *livetemplate.ActionContext) error {
switch ctx.Action {
case "increment":
s.Counter++
case "decrement":
s.Counter--
case "reset":
s.Counter = 0
default:
log.Printf("Unknown action: %s", ctx.Action)
return nil
}
// Increment handles the "increment" action
func (s *CounterState) Increment(_ *livetemplate.ActionContext) error {
s.Counter++
s.LastUpdated = formatTime()
return nil
}

// Decrement handles the "decrement" action
func (s *CounterState) Decrement(_ *livetemplate.ActionContext) error {
s.Counter--
s.LastUpdated = formatTime()
return nil
}

// Reset handles the "reset" action
func (s *CounterState) Reset(_ *livetemplate.ActionContext) error {
s.Counter = 0
s.LastUpdated = formatTime()
return nil
}
Expand Down
24 changes: 6 additions & 18 deletions login/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,8 @@ type AuthState struct {
mu sync.Mutex
}

// Change handles authentication actions
func (s *AuthState) Change(ctx *livetemplate.ActionContext) error {
switch ctx.Action {
case "login":
return s.handleLogin(ctx)
case "logout":
return s.handleLogout(ctx)
case "serverWelcome":
return s.handleServerWelcome(ctx)
default:
return fmt.Errorf("unknown action: %s", ctx.Action)
}
}

func (s *AuthState) handleLogin(ctx *livetemplate.ActionContext) error {
// Login handles the "login" action
func (s *AuthState) Login(ctx *livetemplate.ActionContext) error {
username := ctx.GetString("username")
password := ctx.GetString("password")

Expand Down Expand Up @@ -82,7 +69,8 @@ func (s *AuthState) handleLogin(ctx *livetemplate.ActionContext) error {
return ctx.Redirect("/", http.StatusSeeOther)
}

func (s *AuthState) handleLogout(ctx *livetemplate.ActionContext) error {
// Logout handles the "logout" action
func (s *AuthState) Logout(ctx *livetemplate.ActionContext) error {
s.mu.Lock()
s.Username = ""
s.IsLoggedIn = false
Expand All @@ -100,9 +88,9 @@ func (s *AuthState) handleLogout(ctx *livetemplate.ActionContext) error {
return ctx.Redirect("/", http.StatusSeeOther)
}

// handleServerWelcome handles server-initiated welcome messages.
// ServerWelcome handles the "serverWelcome" action (server-initiated welcome messages).
// This is triggered by TriggerAction from sendWelcomeMessage.
func (s *AuthState) handleServerWelcome(ctx *livetemplate.ActionContext) error {
func (s *AuthState) ServerWelcome(ctx *livetemplate.ActionContext) error {
message := ctx.GetString("message")
s.mu.Lock()
s.ServerMessage = message
Expand Down
28 changes: 16 additions & 12 deletions observability/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,23 @@ type CounterState struct {
LastUpdated string `json:"last_updated"`
}

func (s *CounterState) Change(ctx *livetemplate.ActionContext) error {
switch ctx.Action {
case "increment":
s.Counter++
case "decrement":
s.Counter--
case "reset":
s.Counter = 0
default:
log.Printf("Unknown action: %s", ctx.Action)
return nil
}
// Increment handles the "increment" action
func (s *CounterState) Increment(_ *livetemplate.ActionContext) error {
s.Counter++
s.LastUpdated = formatTime()
return nil
}

// Decrement handles the "decrement" action
func (s *CounterState) Decrement(_ *livetemplate.ActionContext) error {
s.Counter--
s.LastUpdated = formatTime()
return nil
}

// Reset handles the "reset" action
func (s *CounterState) Reset(_ *livetemplate.ActionContext) error {
s.Counter = 0
s.LastUpdated = formatTime()
return nil
}
Expand Down
26 changes: 17 additions & 9 deletions production/single-host/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ type AppState struct {
LastUpdated string `json:"last_updated"`
}

func (s *AppState) Change(ctx *livetemplate.ActionContext) error {
switch ctx.Action {
case "increment":
s.Counter++
case "decrement":
s.Counter--
case "reset":
s.Counter = 0
}
// Increment handles the "increment" action
func (s *AppState) Increment(_ *livetemplate.ActionContext) error {
s.Counter++
s.LastUpdated = formatTime()
return nil
}

// Decrement handles the "decrement" action
func (s *AppState) Decrement(_ *livetemplate.ActionContext) error {
s.Counter--
s.LastUpdated = formatTime()
return nil
}

// Reset handles the "reset" action
func (s *AppState) Reset(_ *livetemplate.ActionContext) error {
s.Counter = 0
s.LastUpdated = formatTime()
return nil
}
Expand Down
6 changes: 1 addition & 5 deletions testing/01_basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ type PageState struct {
Count int
}

// Change implements livetemplate.Store interface
func (s *PageState) Change(ctx *livetemplate.ActionContext) error {
// No actions for this static page
return nil
}
// No action methods needed for this static page

func main() {
// Create template (will auto-discover welcome.tmpl)
Expand Down
Loading
Loading