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 {useIngame} from "@/composables/useIngame.ts";
|
||||||
import type {GameInfo} from "@/types/GameTypes.ts";
|
import type {GameInfo} from "@/types/GameTypes.ts";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const wb = useWebSocket()
|
const wb = useWebSocket()
|
||||||
const wi = useIngame()
|
const wi = useIngame()
|
||||||
const $q = useQuasar();
|
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) {
|
function handlePlayCard(index: number | null) {
|
||||||
if (index === null) return
|
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 })
|
wb.sendAndWait("PlayCard", { cardindex: index })
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
triggerWiggle(index)
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
message: "Error playing card: " + error.message,
|
message: error.message,
|
||||||
color: "negative"
|
color: "negative",
|
||||||
|
position: "top"
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ function getCardImagePath(cardPath: string) {
|
|||||||
<div class="hand-container">
|
<div class="hand-container">
|
||||||
<div id="card-slide" class="ingame-cards-slide">
|
<div id="card-slide" class="ingame-cards-slide">
|
||||||
<div class="cards-row">
|
<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">
|
<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" />
|
<q-img :src="getCardImagePath(card.path)" v-on:click="handlePlayCard(card.idx)" :alt="card.identifier" class="card" />
|
||||||
</div>
|
</div>
|
||||||
@@ -114,6 +117,18 @@ function getCardImagePath(cardPath: string) {
|
|||||||
width:120px;
|
width:120px;
|
||||||
border-radius:6px
|
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) {
|
@media (max-height: 500px) {
|
||||||
.card {
|
.card {
|
||||||
|
|||||||
@@ -4,31 +4,60 @@ import {useIngame} from "@/composables/useIngame.ts";
|
|||||||
import type {LobbyInfo} from "@/types/GameTypes.ts";
|
import type {LobbyInfo} from "@/types/GameTypes.ts";
|
||||||
import {useWebSocket} from "@/composables/useWebsocket.ts";
|
import {useWebSocket} from "@/composables/useWebsocket.ts";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
import router from "@/router";
|
||||||
|
import {useQuasar} from "quasar";
|
||||||
|
|
||||||
const wb = useWebSocket()
|
const wb = useWebSocket()
|
||||||
const ig = useIngame();
|
const ig = useIngame();
|
||||||
|
const $q = useQuasar();
|
||||||
|
|
||||||
const maxPlayers = computed(() => (<LobbyInfo>ig.data).maxPlayers);
|
const maxPlayers = computed(() => (<LobbyInfo>ig.data).maxPlayers);
|
||||||
const isHost = computed(() => (<LobbyInfo>ig.data).self.host);
|
const isHost = computed(() => (<LobbyInfo>ig.data).self.host);
|
||||||
const players = computed(() => {
|
const players = computed(() => {
|
||||||
return (<LobbyInfo>ig.data).users;
|
return (<LobbyInfo>ig.data).users;
|
||||||
})
|
})
|
||||||
const lobbyName = `${ig.data?.gameId}`;
|
const lobbyName = computed(() => {
|
||||||
|
return `${ig.data?.gameId}`
|
||||||
|
});
|
||||||
|
|
||||||
const handleKickPlayer = (user: User) => {
|
const handleKickPlayer = (user: User) => {
|
||||||
if (isHost) {
|
if (isHost) {
|
||||||
emit('kick-player', user);
|
wb.send("KickPlayer", {playerId: user.id})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartGame = () => {
|
const handleStartGame = () => {
|
||||||
if (isHost) {
|
if (isHost) {
|
||||||
wb.send("StartGame", {})
|
wb.sendAndWait("StartGame", {})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLeaveGame = (user: User) => {
|
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';
|
const profileIcon = 'person';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -75,7 +104,7 @@ const profileIcon = 'person';
|
|||||||
v-if="player.id !== (<LobbyInfo>ig.data).self.id"
|
v-if="player.id !== (<LobbyInfo>ig.data).self.id"
|
||||||
color="negative"
|
color="negative"
|
||||||
label="Remove"
|
label="Remove"
|
||||||
@click="handleKickPlayer((<LobbyInfo>ig.data).self)"
|
@click="handleKickPlayer(player)"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|||||||
@@ -37,10 +37,12 @@ export function useWebSocket() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
console.log("Registering event handler for " + event);
|
||||||
onEvent(event, wrapped);
|
onEvent(event, wrapped);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
console.log("Unregistering event handler for " + event);
|
||||||
onEvent(event, () => {});
|
onEvent(event, () => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import defaultMenu from "../components/DefaultMenu.vue"
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useUserInfo } from "@/composables/useUserInfo";
|
import { useUserInfo } from "@/composables/useUserInfo";
|
||||||
import rulesView from "../components/Rules.vue";
|
import rulesView from "../components/Rules.vue";
|
||||||
import LobbyView from "@/views/LobbyView.vue";
|
|
||||||
import GameView from "@/views/Game.vue"
|
|
||||||
import Game from "@/views/Game.vue";
|
import Game from "@/views/Game.vue";
|
||||||
const api = window?.__RUNTIME_CONFIG__?.API_URL;
|
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 LobbyComponent from "@/components/lobby/LobbyComponent.vue";
|
||||||
import {storeToRefs} from "pinia";
|
import {storeToRefs} from "pinia";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
const wb = useWebSocket()
|
const wb = useWebSocket()
|
||||||
const ig = useIngame()
|
const ig = useIngame()
|
||||||
@@ -13,6 +14,7 @@ const { state } = storeToRefs(ig)
|
|||||||
const ui = useUserInfo()
|
const ui = useUserInfo()
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
||||||
|
|
||||||
ui.requestState().then(() => {
|
ui.requestState().then(() => {
|
||||||
if (ui.gameId == null) {
|
if (ui.gameId == null) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
@@ -20,6 +22,7 @@ ui.requestState().then(() => {
|
|||||||
color: "negative"
|
color: "negative"
|
||||||
|
|
||||||
})
|
})
|
||||||
|
router.replace("/")
|
||||||
} else {
|
} else {
|
||||||
ig.requestGame(ui.gameId).then(() => {
|
ig.requestGame(ui.gameId).then(() => {
|
||||||
wb.connect()
|
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