feat: Enhance game and lobby components with improved state management and user notifications
This commit is contained in:
@@ -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<number | null>(null)
|
||||
let wiggleTimer: ReturnType<typeof setTimeout> | 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) {
|
||||
<div class="hand-container">
|
||||
<div id="card-slide" class="ingame-cards-slide">
|
||||
<div class="cards-row">
|
||||
<div v-for="card in (<GameInfo>wi.data)?.hand?.cards" :key="card.identifier" class="handcard">
|
||||
<div v-for="card in (<GameInfo>wi.data)?.hand?.cards" :key="card.identifier" :class="['handcard', { wiggle: wiggleIdx === card.idx }]">
|
||||
<div class="card-btn" aria-label="Play card">
|
||||
<q-img :src="getCardImagePath(card.path)" v-on:click="handlePlayCard(card.idx)" :alt="card.identifier" class="card" />
|
||||
</div>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(() => (<LobbyInfo>ig.data).maxPlayers);
|
||||
const isHost = computed(() => (<LobbyInfo>ig.data).self.host);
|
||||
const players = computed(() => {
|
||||
return (<LobbyInfo>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';
|
||||
</script>
|
||||
|
||||
@@ -75,7 +104,7 @@ const profileIcon = 'person';
|
||||
v-if="player.id !== (<LobbyInfo>ig.data).self.id"
|
||||
color="negative"
|
||||
label="Remove"
|
||||
@click="handleKickPlayer((<LobbyInfo>ig.data).self)"
|
||||
@click="handleKickPlayer(player)"
|
||||
class="full-width"
|
||||
/>
|
||||
<q-btn
|
||||
|
||||
@@ -37,10 +37,12 @@ export function useWebSocket() {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log("Registering event handler for " + event);
|
||||
onEvent(event, wrapped);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log("Unregistering event handler for " + event);
|
||||
onEvent(event, () => {});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue';
|
||||
import LobbyComponent from '../components/lobby/LobbyComponent.vue';
|
||||
import type {LobbyInfo} from "@/types/GameTypes.ts";
|
||||
import type {User} from "@/types/GameSubTypes.ts";
|
||||
import {useIngame} from "@/composables/useIngame.ts";
|
||||
import {sendEvent} from "@/services/ws.ts";
|
||||
|
||||
const ig = useIngame()
|
||||
|
||||
const lobbyInfo = computed<LobbyInfo | null>(() => {
|
||||
if (ig.state === 'Lobby' && ig.data) {
|
||||
return ig.data as LobbyInfo;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const handleKickPlayer = (user: User) => {
|
||||
sendEvent("KickEvent", {
|
||||
user: user
|
||||
})
|
||||
};
|
||||
|
||||
const handleStartGame = () => {
|
||||
//TODO: Implement start game
|
||||
};
|
||||
|
||||
const handleLeaveGame = (user: User) => {
|
||||
sendEvent("LeftEvent",{
|
||||
user: user
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page class="vh-100 column">
|
||||
<lobby-component
|
||||
v-if="lobbyInfo"
|
||||
:lobbyInfo="lobbyInfo"
|
||||
@kick-player="handleKickPlayer"
|
||||
@start-game="handleStartGame"
|
||||
@leave-game="handleLeaveGame"
|
||||
/>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user