Feat: add backlog
This commit is contained in:
		
							
								
								
									
										17
									
								
								components/actionhistory.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								components/actionhistory.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | {{define "actionhistory"}} | ||||||
|  | <div class="overflow-y-auto max-h-96 border-2 border-gray-300 p-4 rounded-lg space-y-2"> | ||||||
|  |   Backlog: | ||||||
|  |   {{range .}} | ||||||
|  |     <div class="flex items-center justify-between p-2 rounded"> | ||||||
|  |       <span class="font-mono text-sm"> | ||||||
|  |       <span class="text-{{.ActorColor}}-600">{{.Actor}}:</span> | ||||||
|  |         <span class="text-gray-600">{{.Action}}:</span> | ||||||
|  |         <span class="text-{{.WordColor}}-500 font-medium">{{.Word}}</span> | ||||||
|  |         {{if .Number}} | ||||||
|  |         <span class="text-gray-400">- {{.Number}}</span> | ||||||
|  |         {{end}} | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   {{end}} | ||||||
|  | </div> | ||||||
|  | {{end}} | ||||||
| @@ -1,53 +0,0 @@ | |||||||
| {{define "cluepopup"}} |  | ||||||
| <div hx-sse="swap:newClue" hx-swap="none" |  | ||||||
|     _="on htmx:sseMessage(mimeclue_{{.ID}}) |  | ||||||
|           -- Update content |  | ||||||
|         set match to event.detail.match(/(.*?)(\d+)$/) |  | ||||||
|           if match |  | ||||||
|             set clue to match[1] |  | ||||||
|             set number to match[2] |  | ||||||
|           else |  | ||||||
|             set clue to 'Invalid clue' |  | ||||||
|             set number to '0' |  | ||||||
|           end |  | ||||||
|            |  | ||||||
|           -- Cancel any previous timeout |  | ||||||
|           if window.clueTimeout then clearTimeout(window.clueTimeout) |  | ||||||
|            |  | ||||||
|           -- Show with animation |  | ||||||
|           remove .hidden from #clueModal |  | ||||||
|           remove .opacity-0.-translate-y-4 from #clueModal div |  | ||||||
|           add .opacity-100.translate-y-0 to #clueModal div |  | ||||||
|            |  | ||||||
|           -- Auto-hide after 3 seconds |  | ||||||
|           set window.clueTimeout to setTimeout(() =>  |  | ||||||
|             trigger closeModal |  | ||||||
|           end, 3000) |  | ||||||
|          |  | ||||||
|         on closeModal |  | ||||||
|           -- Hide with animation |  | ||||||
|           add .opacity-0.-translate-y-4 to #clueModal div |  | ||||||
|           wait 300ms |  | ||||||
|           add .hidden to #clueModal"> |  | ||||||
| <div id="clueModal"  |  | ||||||
|      class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full transition-opacity duration-300 ease-in-out"> |  | ||||||
|   <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white  |  | ||||||
|               transform transition-all duration-300 ease-out  |  | ||||||
|               opacity-0 -translate-y-4"> |  | ||||||
|     <div class="mt-3 text-center"> |  | ||||||
|       <h3 class="text-lg leading-6 font-medium text-gray-900">New Clue Received! 🔍</h3> |  | ||||||
|       <div class="mt-2 px-7 py-3"> |  | ||||||
|         <p class="text-sm text-gray-500">Clue: <span id="modalClue" class="font-medium"></span></p> |  | ||||||
|         <p class="text-sm text-gray-500">Number: <span id="modalNumber" class="font-medium"></span></p> |  | ||||||
|       </div> |  | ||||||
|       <div class="items-center px-4 py-3"> |  | ||||||
|         <button _="on click trigger closeModal" |  | ||||||
|                 class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-700 transition-colors"> |  | ||||||
|           OK |  | ||||||
|         </button> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
| </div> |  | ||||||
| {{end}} |  | ||||||
| @@ -45,10 +45,8 @@ | |||||||
|     {{template "mimeclue"}} |     {{template "mimeclue"}} | ||||||
|     {{end}} |     {{end}} | ||||||
|   </div> |   </div> | ||||||
|   <div> |   <div hx-get="/actionhistory" hx-trigger="sse:backlog_{{.Room.ID}}"> | ||||||
|   {{if .Room.MimeDone}} |   {{template "actionhistory" .Room.ActionHistory}} | ||||||
|   {{template "cluepop"}} |  | ||||||
|   {{end}} |  | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
| {{end}} | {{end}} | ||||||
|   | |||||||
| @@ -66,6 +66,14 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { | |||||||
| 	} | 	} | ||||||
| 	fi.Room.RevealSpecificWord(word) | 	fi.Room.RevealSpecificWord(word) | ||||||
| 	fi.Room.UpdateCounter() | 	fi.Room.UpdateCounter() | ||||||
|  | 	action := models.Action{ | ||||||
|  | 		Actor:      fi.State.Username, | ||||||
|  | 		ActorColor: string(fi.State.Team), | ||||||
|  | 		WordColor:  color, | ||||||
|  | 		Action:     "guessed", | ||||||
|  | 		Word:       word, | ||||||
|  | 	} | ||||||
|  | 	fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) | ||||||
| 	// if opened card is of color of opp team, change turn | 	// if opened card is of color of opp team, change turn | ||||||
| 	switch color { | 	switch color { | ||||||
| 	case "black": | 	case "black": | ||||||
| @@ -83,3 +91,17 @@ func HandleShowColor(w http.ResponseWriter, r *http.Request) { | |||||||
| 	notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "") | 	notify(models.NotifyRoomUpdatePrefix+fi.Room.ID, "") | ||||||
| 	tmpl.ExecuteTemplate(w, "cardword", cardword) | 	tmpl.ExecuteTemplate(w, "cardword", cardword) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func HandleActionHistory(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	fi, err := getFullInfoByCtx(r.Context()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		abortWithError(w, err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	tmpl, err := template.ParseGlob("components/*.html") | ||||||
|  | 	if err != nil { | ||||||
|  | 		abortWithError(w, err.Error()) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	tmpl.ExecuteTemplate(w, "actionhistory", fi.Room.ActionHistory) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -253,16 +253,19 @@ func HandleGiveClue(w http.ResponseWriter, r *http.Request) { | |||||||
| 		abortWithError(w, err.Error()) | 		abortWithError(w, err.Error()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	action := models.Action{ | ||||||
|  | 		Actor:      fi.State.Username, | ||||||
|  | 		ActorColor: string(fi.State.Team), | ||||||
|  | 		WordColor:  string(fi.State.Team), | ||||||
|  | 		Action:     "gave clue", | ||||||
|  | 		Word:       clue, | ||||||
|  | 		Number:     num, | ||||||
|  | 	} | ||||||
|  | 	fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) | ||||||
| 	fi.Room.MimeDone = true | 	fi.Room.MimeDone = true | ||||||
| 	notify(models.NotifyMimePrefix+fi.Room.ID, clue+num) | 	notify(models.NotifyBacklogPrefix+fi.Room.ID, clue+num) | ||||||
| 	if err := saveFullInfo(fi); err != nil { | 	if err := saveFullInfo(fi); err != nil { | ||||||
| 		abortWithError(w, err.Error()) | 		abortWithError(w, err.Error()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	// tmpl, err := template.ParseGlob("components/*.html") |  | ||||||
| 	// if err != nil { |  | ||||||
| 	// 	abortWithError(w, err.Error()) |  | ||||||
| 	// 	return |  | ||||||
| 	// } |  | ||||||
| 	// tmpl.ExecuteTemplate(w, "room", fi) |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.go
									
									
									
									
									
								
							| @@ -31,6 +31,7 @@ func ListenToRequests(port string) error { | |||||||
| 	mux.HandleFunc("GET /room-join", handlers.HandleJoinRoom) | 	mux.HandleFunc("GET /room-join", handlers.HandleJoinRoom) | ||||||
| 	mux.HandleFunc("POST /give-clue", handlers.HandleGiveClue) | 	mux.HandleFunc("POST /give-clue", handlers.HandleGiveClue) | ||||||
| 	//elements | 	//elements | ||||||
|  | 	mux.HandleFunc("GET /actionhistory", handlers.HandleActionHistory) | ||||||
| 	mux.HandleFunc("GET /room/createform", handlers.HandleShowCreateForm) | 	mux.HandleFunc("GET /room/createform", handlers.HandleShowCreateForm) | ||||||
| 	mux.HandleFunc("GET /room/hideform", handlers.HandleHideCreateForm) | 	mux.HandleFunc("GET /room/hideform", handlers.HandleHideCreateForm) | ||||||
| 	mux.HandleFunc("GET /word/show-color", handlers.HandleShowColor) | 	mux.HandleFunc("GET /word/show-color", handlers.HandleShowColor) | ||||||
|   | |||||||
| @@ -9,5 +9,5 @@ var ( | |||||||
| 	CacheStatePrefix = "state-" | 	CacheStatePrefix = "state-" | ||||||
| 	// sse | 	// sse | ||||||
| 	NotifyRoomUpdatePrefix = "roomupdate_" | 	NotifyRoomUpdatePrefix = "roomupdate_" | ||||||
| 	NotifyMimePrefix       = "mimeclue_" | 	NotifyBacklogPrefix    = "backlog_" | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -37,24 +37,34 @@ type Team struct { | |||||||
| 	Color    string | 	Color    string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type Action struct { | ||||||
|  | 	Actor      string | ||||||
|  | 	ActorColor string | ||||||
|  | 	Action     string // clue | guess | ||||||
|  | 	Word       string | ||||||
|  | 	WordColor  string | ||||||
|  | 	Number     string // for clue | ||||||
|  | } | ||||||
|  |  | ||||||
| type Room struct { | type Room struct { | ||||||
| 	ID        string    `json:"id" db:"id"` | 	ID        string    `json:"id" db:"id"` | ||||||
| 	CreatedAt time.Time `json:"created_at" db:"created_at"` | 	CreatedAt time.Time `json:"created_at" db:"created_at"` | ||||||
| 	// RoomName     string    `json:"room_name"` | 	// RoomName     string    `json:"room_name"` | ||||||
| 	RoomPass    string `json:"room_pass"` | 	RoomPass      string `json:"room_pass"` | ||||||
| 	RoomLink    string | 	RoomLink      string | ||||||
| 	CreatorName string   `json:"creator_name"` | 	CreatorName   string   `json:"creator_name"` | ||||||
| 	PlayerList  []string `json:"player_list"` | 	PlayerList    []string `json:"player_list"` | ||||||
| 	TeamTurn    string | 	ActionHistory []Action | ||||||
| 	RedTeam     Team | 	TeamTurn      string | ||||||
| 	BlueTeam    Team | 	RedTeam       Team | ||||||
| 	Cards       []WordCard | 	BlueTeam      Team | ||||||
| 	Result      uint8 // 0 for unknown; 1 is win for red; 2 if for blue; | 	Cards         []WordCard | ||||||
| 	BlueCounter uint8 | 	Result        uint8 // 0 for unknown; 1 is win for red; 2 if for blue; | ||||||
| 	RedCounter  uint8 | 	BlueCounter   uint8 | ||||||
| 	RedTurn     bool // false is blue turn | 	RedCounter    uint8 | ||||||
| 	MimeDone    bool | 	RedTurn       bool // false is blue turn | ||||||
| 	IsPublic    bool | 	MimeDone      bool | ||||||
|  | 	IsPublic      bool | ||||||
| 	// GameSettings *GameSettings `json:"settings"` | 	// GameSettings *GameSettings `json:"settings"` | ||||||
| 	IsRunning bool   `json:"is_running"` | 	IsRunning bool   `json:"is_running"` | ||||||
| 	Language  string `json:"language" example:"en" form:"language"` | 	Language  string `json:"language" example:"en" form:"language"` | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Grail Finder
					Grail Finder