From bc45f3a604795a0c200e5b249413e67fb0821450 Mon Sep 17 00:00:00 2001 From: sepia Date: Wed, 23 Jul 2025 16:50:35 -0500 Subject: [PATCH] Add name editing. Add icons for all buttons. --- index.html | 43 +++++++++++--- public/icons/accept.svg | 4 ++ public/icons/decline.svg | 4 ++ public/icons/draw.svg | 4 ++ public/icons/resign.svg | 4 ++ public/icons/rotate-right.svg | 4 ++ public/icons/undo.svg | 4 ++ public/scripts/display-ws-connection.js | 2 +- public/scripts/profile-editor.js | 42 +++++++++++++ public/scripts/send-ws-messages.js | 6 ++ public/style.css | 63 ++++++++++++++++++++ src/messages.ts | 7 ++- src/web-socket-handler.tsx | 78 ++++++++++++++++++++++++- 13 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 public/icons/accept.svg create mode 100644 public/icons/decline.svg create mode 100644 public/icons/draw.svg create mode 100644 public/icons/resign.svg create mode 100644 public/icons/rotate-right.svg create mode 100644 public/icons/undo.svg create mode 100644 public/scripts/profile-editor.js diff --git a/index.html b/index.html index 0ccef96..72f4bbc 100644 --- a/index.html +++ b/index.html @@ -17,15 +17,40 @@ -
-
-
-
-
+
+
+ + Edit Display Name + +
- - - - +
+
+
+
+
+
+
+ + + + + + +
diff --git a/public/icons/accept.svg b/public/icons/accept.svg new file mode 100644 index 0000000..e007bdd --- /dev/null +++ b/public/icons/accept.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/decline.svg b/public/icons/decline.svg new file mode 100644 index 0000000..1b29df4 --- /dev/null +++ b/public/icons/decline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/draw.svg b/public/icons/draw.svg new file mode 100644 index 0000000..f8ab6a9 --- /dev/null +++ b/public/icons/draw.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/resign.svg b/public/icons/resign.svg new file mode 100644 index 0000000..16d6975 --- /dev/null +++ b/public/icons/resign.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/rotate-right.svg b/public/icons/rotate-right.svg new file mode 100644 index 0000000..5610fd2 --- /dev/null +++ b/public/icons/rotate-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/undo.svg b/public/icons/undo.svg new file mode 100644 index 0000000..e553e2e --- /dev/null +++ b/public/icons/undo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/scripts/display-ws-connection.js b/public/scripts/display-ws-connection.js index faf7aab..803ad28 100644 --- a/public/scripts/display-ws-connection.js +++ b/public/scripts/display-ws-connection.js @@ -11,7 +11,7 @@ const playerId = playerIdMeta.content; const wsUrl = `ws://${window.location.host}/ws?gameId=${gameId}&playerId=${playerId}`; // Get the game container element -const gameContainer = document.getElementById('game-container'); +const gameContainer = document.getElementById('ws-container'); // Set the ws-connect attribute gameContainer.setAttribute('ws-connect', wsUrl); diff --git a/public/scripts/profile-editor.js b/public/scripts/profile-editor.js new file mode 100644 index 0000000..c8d7018 --- /dev/null +++ b/public/scripts/profile-editor.js @@ -0,0 +1,42 @@ +document.addEventListener('DOMContentLoaded', () => { + const displayNameSpan = document.getElementById('display-name'); + const editIcon = document.getElementById('edit-display-name-icon'); + const editControls = document.getElementById('display-name-edit-controls'); + const displayNameInput = document.getElementById('display-name-input'); + const saveButton = document.getElementById('save-display-name-ws-button'); + const cancelButton = document.getElementById('cancel-display-name-button'); + + // Get playerId from meta tag + const playerIdMeta = document.querySelector('meta[name="playerId"]'); + const playerId = playerIdMeta ? playerIdMeta.content : 'UnknownPlayer'; + + // Initialize display name with player ID + displayNameSpan.textContent = playerId; + + function setEditMode(isEditing) { + if (isEditing) { + displayNameSpan.style.display = 'none'; + editIcon.style.display = 'none'; + editControls.style.display = 'flex'; + displayNameInput.value = displayNameSpan.textContent; + displayNameInput.focus(); + } else { + displayNameSpan.style.display = 'inline'; + editIcon.style.display = 'inline'; + editControls.style.display = 'none'; + } + } + + editIcon.addEventListener('click', () => setEditMode(true)); + cancelButton.addEventListener('click', () => setEditMode(false)); + + saveButton.addEventListener('click', () => { + // The actual sending of the message is handled by hx-trigger and send-ws-messages.js + // We just handle the optimistic UI update here. + const newName = displayNameInput.value.trim(); + if (newName && newName !== displayNameSpan.textContent) { + displayNameSpan.textContent = newName; // Optimistically update display + } + setEditMode(false); + }); +}); diff --git a/public/scripts/send-ws-messages.js b/public/scripts/send-ws-messages.js index 67624ac..40f93d6 100644 --- a/public/scripts/send-ws-messages.js +++ b/public/scripts/send-ws-messages.js @@ -73,5 +73,11 @@ document.addEventListener('htmx:wsConfigSend', function (e) { type: 'rematch', action: 'cancel', }; + } else if (e.target.id == 'save-display-name-ws-button') { + const displayNameInput = document.getElementById('display-name-input'); + e.detail.parameters = { + type: 'update_display_name', + displayName: displayNameInput ? displayNameInput.value.trim() : '', + }; } }); diff --git a/public/style.css b/public/style.css index d256f78..163e8cc 100644 --- a/public/style.css +++ b/public/style.css @@ -273,3 +273,66 @@ button:hover { #copy-link-button.copied-state { background-color: var(--color-success); } + +.player-profile-box { + position: absolute; + top: 20px; + right: 20px; + display: flex; + align-items: center; + gap: 10px; + background-color: var(--color-neutral-100); + color: var(--color-on-light); + padding: 8px 12px; + border-radius: 20px; + box-shadow: 0 2px 4px var(--color-shadow); +} + +.player-profile-box .edit-icon { + cursor: pointer; + width: 1.4em; + height: 1.4em; + opacity: 0.6; + transition: opacity 0.3s ease; +} + +.player-profile-box .edit-icon:hover { + opacity: 1; +} + +.player-profile-box #display-name-edit-controls { + display: flex; + gap: 5px; + align-items: center; +} + +.player-profile-box #display-name-input { + border: 1px solid var(--color-neutral-300); + border-radius: 5px; + padding: 5px 8px; + font-size: 1em; + width: 120px; +} + +.player-profile-box button { + background-color: var(--color-success); + color: var(--color-on-primary); + border: none; + border-radius: 5px; + padding: 5px 10px; + cursor: pointer; + font-size: 0.9em; + transition: background-color 0.3s ease; +} + +.player-profile-box button:hover { + background-color: var(--color-success-light); +} + +.player-profile-box button#cancel-display-name-button { + background-color: var(--color-error); +} + +.player-profile-box button#cancel-display-name-button:hover { + background-color: var(--color-error-light); +} diff --git a/src/messages.ts b/src/messages.ts index 6b7f26b..545a241 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -7,7 +7,12 @@ export interface Message { | 'takeback' | 'draw' | 'rematch' - | 'redirect_to_game'; + | 'redirect_to_game' + | 'update_display_name'; +} + +export interface UpdateDisplayNameMessage extends Message { + displayName: string; } export interface MakeMoveMessage extends Message { diff --git a/src/web-socket-handler.tsx b/src/web-socket-handler.tsx index ec09aca..9aca2a4 100644 --- a/src/web-socket-handler.tsx +++ b/src/web-socket-handler.tsx @@ -10,6 +10,7 @@ import { DrawMessage, RematchMessage, RedirectToGameMessage, + UpdateDisplayNameMessage, } from './messages'; import { v4 as uuidv4 } from 'uuid'; @@ -27,6 +28,7 @@ class PlayerConnection { } public sendMessage(severity: 'info' | 'error', message: string) { + console.log(`Sending message ${message} to player ${this.id}`); // TODO } } @@ -146,17 +148,26 @@ class GameServer { if (this.takebackRequesterId === conn.id) { buttons.push( , ); } else { buttons.push( , ); buttons.push( , ); @@ -165,17 +176,26 @@ class GameServer { if (this.drawRequesterId === conn.id) { buttons.push( , ); } else { buttons.push( , ); buttons.push( , ); @@ -183,16 +203,25 @@ class GameServer { } else { buttons.push( , ); buttons.push( , ); buttons.push( , ); @@ -202,17 +231,26 @@ class GameServer { if (this.rematchRequesterId === conn.id) { buttons.push( , ); } else { buttons.push( , ); buttons.push( , ); @@ -220,6 +258,9 @@ class GameServer { } else { buttons.push( , ); @@ -248,7 +289,7 @@ class GameServer { const classes = `player-name ${'player-' + color} ${turnClass}`.trim(); return ( - {playerId} + {this.connections.get(playerId)?.name} {connectionIcon} ); @@ -287,6 +328,10 @@ class GameServer { this.handleRematchMessage(conn, message as RematchMessage); break; } + case 'update_display_name': { + this.handleUpdateDisplayName(conn, message as UpdateDisplayNameMessage); + break; + } } } @@ -588,6 +633,37 @@ class GameServer { this.rematchRequesterId = null; this.broadcastButtons(); } + + private handleUpdateDisplayName( + conn: PlayerConnection, + message: UpdateDisplayNameMessage, + ): void { + const newDisplayName = message.displayName.trim(); + + if (!newDisplayName) { + conn.sendMessage('error', 'Display name cannot be empty.'); + return; + } + + if (newDisplayName.length > 20) { + conn.sendMessage( + 'error', + 'Display name cannot be longer than 20 characters.', + ); + return; + } + + if (newDisplayName === conn.name) { + return; // No change, do nothing + } + + conn.name = newDisplayName; + this.broadcastTitle(); + conn.sendMessage( + 'info', + `Your display name has been updated to "${newDisplayName}".`, + ); + } } export class WebSocketHandler {