From 30798ce7235ed1dc63cbe01021ab99269541ed40 Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 14 Dec 2025 15:08:13 +0100 Subject: [PATCH] feat: Enhance game and lobby components with improved state management and user notifications --- src/components/ingame/HandC.vue | 51 +++++++++++++++--------- src/components/lobby/LobbyComponent.vue | 39 +++++++++++++++--- src/composables/useWebsocket.ts | 2 + src/router/index.ts | 2 - src/views/Game.vue | 3 ++ src/views/LobbyView.vue | 53 ------------------------- 6 files changed, 72 insertions(+), 78 deletions(-) delete mode 100644 src/views/LobbyView.vue diff --git a/src/components/ingame/HandC.vue b/src/components/ingame/HandC.vue index b13c314..9a79b2d 100644 --- a/src/components/ingame/HandC.vue +++ b/src/components/ingame/HandC.vue @@ -3,35 +3,38 @@ import {useWebSocket} from "@/composables/useWebsocket.ts"; import {useIngame} from "@/composables/useIngame.ts"; import type {GameInfo} from "@/types/GameTypes.ts"; import {useQuasar} from "quasar"; +import { ref } from 'vue'; const wb = useWebSocket() const wi = useIngame() const $q = useQuasar(); +const wiggleIdx = ref(null) +let wiggleTimer: ReturnType | null = null + +function triggerWiggle(index: number) { + // clear previous timer if any + if (wiggleTimer) clearTimeout(wiggleTimer) + wiggleIdx.value = index + wiggleTimer = setTimeout(() => { + wiggleIdx.value = null + wiggleTimer = null + }, 700) +} + function handlePlayCard(index: number | null) { if (index === null) return - const wiggleKeyframes = [ - { transform: 'translateX(0)' }, - { transform: 'translateX(-5px)' }, - { transform: 'translateX(5px)' }, - { transform: 'translateX(-5px)' }, - { transform: 'translateX(0)' } - ]; - - const wiggleTiming = { - duration: 400, - iterations: 1, - easing: 'ease-in-out', - fill: 'forwards' - }; - wb.sendAndWait("PlayCard", { cardindex: index }) .catch((error) => { + triggerWiggle(index) + $q.notify({ - message: "Error playing card: " + error.message, - color: "negative" + message: error.message, + color: "negative", + position: "top" }) + }) } @@ -50,7 +53,7 @@ function getCardImagePath(cardPath: string) {
-
+
@@ -114,6 +117,18 @@ function getCardImagePath(cardPath: string) { width:120px; border-radius:6px } +.wiggle { + animation: wiggle 700ms ease-in-out; +} + +@keyframes wiggle { + 0% { transform: translateY(0) rotate(0deg); } + 15% { transform: translateY(-8px) rotate(-6deg); } + 35% { transform: translateY(0) rotate(6deg); } + 55% { transform: translateY(-4px) rotate(-3deg); } + 75% { transform: translateY(0) rotate(2deg); } + 100% { transform: translateY(0) rotate(0deg); } +} @media (max-height: 500px) { .card { diff --git a/src/components/lobby/LobbyComponent.vue b/src/components/lobby/LobbyComponent.vue index 6516e85..da96572 100644 --- a/src/components/lobby/LobbyComponent.vue +++ b/src/components/lobby/LobbyComponent.vue @@ -4,31 +4,60 @@ import {useIngame} from "@/composables/useIngame.ts"; import type {LobbyInfo} from "@/types/GameTypes.ts"; import {useWebSocket} from "@/composables/useWebsocket.ts"; import {computed} from "vue"; +import router from "@/router"; +import {useQuasar} from "quasar"; const wb = useWebSocket() const ig = useIngame(); +const $q = useQuasar(); + const maxPlayers = computed(() => (ig.data).maxPlayers); const isHost = computed(() => (ig.data).self.host); const players = computed(() => { return (ig.data).users; }) -const lobbyName = `${ig.data?.gameId}`; +const lobbyName = computed(() => { + return `${ig.data?.gameId}` +}); const handleKickPlayer = (user: User) => { if (isHost) { - emit('kick-player', user); + wb.send("KickPlayer", {playerId: user.id}) } }; const handleStartGame = () => { if (isHost) { - wb.send("StartGame", {}) + wb.sendAndWait("StartGame", {}) } }; const handleLeaveGame = (user: User) => { - emit('leave-game', user); + wb.sendAndWait("LeaveGame", {user: user}) }; + +wb.useEvent("SessionClosed", () => { + $q.notify({ + message: `You left the lobby.`, + color: "positive" + }) + router.replace("/") +}) +wb.useEvent("LeftEvent", () => { + $q.notify({ + message: `You left the lobby.`, + color: "positive" + }) + router.replace("/") +}) + +wb.useEvent("KickEvent", () => { + $q.notify({ + message: `You were kicked from the lobby!`, + color: "amber" + }) + router.replace("/") +}) const profileIcon = 'person'; @@ -75,7 +104,7 @@ const profileIcon = 'person'; v-if="player.id !== (ig.data).self.id" color="negative" label="Remove" - @click="handleKickPlayer((ig.data).self)" + @click="handleKickPlayer(player)" class="full-width" /> { + console.log("Registering event handler for " + event); onEvent(event, wrapped); }); onBeforeUnmount(() => { + console.log("Unregistering event handler for " + event); onEvent(event, () => {}); }); } diff --git a/src/router/index.ts b/src/router/index.ts index 4202bad..35cf7a6 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -7,8 +7,6 @@ import defaultMenu from "../components/DefaultMenu.vue" import axios from "axios"; import { useUserInfo } from "@/composables/useUserInfo"; import rulesView from "../components/Rules.vue"; -import LobbyView from "@/views/LobbyView.vue"; -import GameView from "@/views/Game.vue" import Game from "@/views/Game.vue"; const api = window?.__RUNTIME_CONFIG__?.API_URL; diff --git a/src/views/Game.vue b/src/views/Game.vue index 4072d50..3798bcc 100644 --- a/src/views/Game.vue +++ b/src/views/Game.vue @@ -6,6 +6,7 @@ import {useUserInfo} from "@/composables/useUserInfo.ts"; import LobbyComponent from "@/components/lobby/LobbyComponent.vue"; import {storeToRefs} from "pinia"; import {useQuasar} from "quasar"; +import router from "@/router"; const wb = useWebSocket() const ig = useIngame() @@ -13,6 +14,7 @@ const { state } = storeToRefs(ig) const ui = useUserInfo() const $q = useQuasar(); + ui.requestState().then(() => { if (ui.gameId == null) { $q.notify({ @@ -20,6 +22,7 @@ ui.requestState().then(() => { color: "negative" }) + router.replace("/") } else { ig.requestGame(ui.gameId).then(() => { wb.connect() diff --git a/src/views/LobbyView.vue b/src/views/LobbyView.vue deleted file mode 100644 index 1637d86..0000000 --- a/src/views/LobbyView.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - - -