diff --git a/Makefile b/Makefile index b43179c..e8f0b69 100644 --- a/Makefile +++ b/Makefile @@ -144,8 +144,8 @@ nginx-dev/nginx-selfsigned.crt nginx-dev/nginx-selfsigned.key &: fnginx: nginx-dev/nginx nginx-dev/nginx-selfsigned.crt nginx-dev/nginx-selfsigned.key nginx-dev/nginx -p ./nginx-dev -c nginx.conf -e /dev/null >/dev/null 2>/dev/null & - -(cd ./frontend && pnpm exec tsc --noEmit --watch --preserveWatchOutput) & - -(cd ./frontend && pnpm exec vite --clearScreen false) + -(cd ./frontend && npx pnpm exec tsc --noEmit --watch --preserveWatchOutput) & + -(cd ./frontend && npx pnpm exec vite --clearScreen false) wait # phony diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index 7bf4f15..5257cd1 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -3,6 +3,7 @@ apis/index.ts index.ts models/AllowGuestMessage200Response.ts models/AllowGuestMessage403Response.ts +models/ApiChatBroadcastPostRequest.ts models/ChangeDesc200Response.ts models/ChangeDesc400Response.ts models/ChangeDesc403Response.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index 9762acd..f53b65d 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -17,6 +17,7 @@ import * as runtime from '../runtime'; import type { AllowGuestMessage200Response, AllowGuestMessage403Response, + ApiChatBroadcastPostRequest, ChangeDesc200Response, ChangeDesc400Response, ChangeDesc403Response, @@ -71,6 +72,8 @@ import { AllowGuestMessage200ResponseToJSON, AllowGuestMessage403ResponseFromJSON, AllowGuestMessage403ResponseToJSON, + ApiChatBroadcastPostRequestFromJSON, + ApiChatBroadcastPostRequestToJSON, ChangeDesc200ResponseFromJSON, ChangeDesc200ResponseToJSON, ChangeDesc400ResponseFromJSON, @@ -169,6 +172,10 @@ import { TttHistory404ResponseToJSON, } from '../models/index'; +export interface ApiChatBroadcastPostOperationRequest { + apiChatBroadcastPostRequest: ApiChatBroadcastPostRequest; +} + export interface ChangeDescOperationRequest { changeDescRequest: ChangeDescRequest; } @@ -260,6 +267,53 @@ export class OpenapiOtherApi extends runtime.BaseAPI { return await response.value(); } + /** + */ + async apiChatBroadcastPostRaw(requestParameters: ApiChatBroadcastPostOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['apiChatBroadcastPostRequest'] == null) { + throw new runtime.RequiredError( + 'apiChatBroadcastPostRequest', + 'Required parameter "apiChatBroadcastPostRequest" was null or undefined when calling apiChatBroadcastPost().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + + let urlPath = `/api/chat/broadcast`; + + const response = await this.request({ + path: urlPath, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: ApiChatBroadcastPostRequestToJSON(requestParameters['apiChatBroadcastPostRequest']), + }, initOverrides); + + // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses + // This allows typed access to error responses (4xx, 5xx) and other status codes. + // The code routes responses based on the actual HTTP status code and returns + // appropriately typed ApiResponse wrappers for each status code. + if (response.status === 200) { + // No body response for status 200 + return new runtime.VoidApiResponse(response); + } + // CHANGED: Throw error if status code is not handled by any of the defined responses + // This ensures all code paths return a value and provides clear error messages for unexpected status codes + // Only throw if responses were defined but none matched the actual status code + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200`); + } + + /** + */ + async apiChatBroadcastPost(requestParameters: ApiChatBroadcastPostOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiChatBroadcastPostRaw(requestParameters, initOverrides); + } + /** */ async changeDescRaw(requestParameters: ChangeDescOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index 07e430b..17618ee 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -2,6 +2,7 @@ /* eslint-disable */ export * from './AllowGuestMessage200Response'; export * from './AllowGuestMessage403Response'; +export * from './ApiChatBroadcastPostRequest'; export * from './ChangeDesc200Response'; export * from './ChangeDesc400Response'; export * from './ChangeDesc403Response'; diff --git a/frontend/src/pages/ttt/ttt.html b/frontend/src/pages/ttt/ttt.html index c756223..82b0f8f 100644 --- a/frontend/src/pages/ttt/ttt.html +++ b/frontend/src/pages/ttt/ttt.html @@ -1,6 +1,6 @@
- +

Tic-tac-toe Box


@@ -9,18 +9,18 @@ X
-
${playerX.name}
-
${playerX.timer}
+
+
-
${currentPlayerTimer}
+
Waiting for match...
-
${playerO.name}
-
${playerO.timer}
+
+
O diff --git a/frontend/src/pages/ttt/ttt.ts b/frontend/src/pages/ttt/ttt.ts index 0b11318..d0a0fdc 100644 --- a/frontend/src/pages/ttt/ttt.ts +++ b/frontend/src/pages/ttt/ttt.ts @@ -15,6 +15,12 @@ declare module 'ft_state' { } } +enum QueueState { + InQueue = "In Queue", + InGame = "In Game", + Idle = "Join Queue", +}; + document.addEventListener("ft:pageChange", () => { if (window.__state.tttSock !== undefined) window.__state.tttSock.close(); if (window.__state.keepAliveInterval !== undefined) clearInterval(window.__state.keepAliveInterval); @@ -48,13 +54,27 @@ async function handleTTT(): Promise { const userXString = document.getElementById("playerX-name"); const userOString = document.getElementById("playerO-name"); - if (!userXString || !userOString) { - return showError('fatal error'); - } const currentPlayerIndicator = document.getElementById("currentPlayer"); - if (!currentPlayerIndicator) { + const joinQueueBtn = document.getElementById("JoinQueueBtn"); + const currentPlayerTimer = document.getElementById("currentPlayerTimer") + if (!userXString || !userOString || !currentPlayerIndicator || !joinQueueBtn || !currentPlayerTimer) { return showError('fatal error'); } + + joinQueueBtn.addEventListener("click", () => { + console.log('===== JOIN QUEUE BUTTON PRESSED ====='); + if (joinQueueBtn.innerText !== QueueState.Idle) { + console.log("== Entering in first if =="); + if (joinQueueBtn.innerText === QueueState.InQueue) { + console.log("== Entering in second if =="); + socket.emit("dequeue"); + joinQueueBtn.innerText = QueueState.Idle; + } + return; + } + joinQueueBtn.innerText = QueueState.InQueue; + socket.emit("enqueue"); + }); let curGame: CurrentGameInfo | null = null; let curGameX: {id: string, name: string} | null = null; @@ -64,6 +84,9 @@ async function handleTTT(): Promise { socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`)); socket.on('newGame', async (gameState) => { showInfo(`newGame: ${gameState.gameId}`) + + currentPlayerTimer.innerText = ""; + joinQueueBtn.innerText = QueueState.InGame; curGame = { ...gameState, lastState: null }; let resX = await client.getUser({user: curGame.playerX}); @@ -86,7 +109,8 @@ async function handleTTT(): Promise { userOString.classList.remove('text-gray-800'); userXString.classList.remove('text-red-800'); userXString.classList.add('text-gray-800'); - } else if (user.id === curGameX.id) { + } + else if (user.id === curGameX.id) { userXString.classList.add('text-red-800'); userXString.classList.remove('text-gray-800'); userOString.classList.remove('text-red-800'); @@ -95,7 +119,6 @@ async function handleTTT(): Promise { userXString.innerText = curGameX.name; userOString.innerText = curGameO.name; }); - socket.emit('enqueue'); const cells = app.querySelectorAll(".ttt-cell"); @@ -131,11 +154,10 @@ async function handleTTT(): Promise { socket.on('gameEnd', () => { curGame = null; - socket.emit('enqueue'); - showInfo('Game is finished, enqueuing directly') + currentPlayerTimer.innerText = "Waiting for match..."; + joinQueueBtn.innerText = QueueState.Idle; }) - socket.on('gameBoard', (u) => { if (curGame === null) { return showError('Got game State, but no in a game ?'); @@ -153,8 +175,6 @@ async function handleTTT(): Promise { updateUI(u.boardState); if (u.gameState && u.gameState !== "ongoing") { - // grid?.classList.add("pointer-events-none"); - if (u.gameState !== curGame.lastState) { curGame.lastState = u.gameState; switch (u.gameState) { diff --git a/src/chat/openapi.json b/src/chat/openapi.json index 38cd725..e45636a 100644 --- a/src/chat/openapi.json +++ b/src/chat/openapi.json @@ -7,7 +7,35 @@ "components": { "schemas": {} }, - "paths": {}, + "paths": { + "/api/chat/broadcast": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "nextGame" + ], + "properties": { + "nextGame": { + "type": "string" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response" + } + } + } + } + }, "servers": [ { "url": "https://local.maix.me:8888", diff --git a/src/openapi.json b/src/openapi.json index 503944a..95450ea 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -1917,6 +1917,36 @@ ] } }, + "/api/chat/broadcast": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "nextGame" + ], + "properties": { + "nextGame": { + "type": "string" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Default Response" + } + }, + "tags": [ + "openapi_other" + ] + } + }, "/api/ttt/history/{user}": { "get": { "operationId": "tttHistory",