feat(ui): FRO-26 Tie selection
Added a nice tie selection ui
This commit is contained in:
@@ -4,7 +4,7 @@ import {useIngame} from "@/composables/useIngame.ts";
|
||||
import type {GameInfo, LobbyInfo, TieInfo, TrumpInfo} from "@/types/GameTypes.ts";
|
||||
import {useQuasar} from "quasar";
|
||||
import { ref } from 'vue';
|
||||
import { computed } from 'vue'
|
||||
import { computed, nextTick } from 'vue'
|
||||
|
||||
const wb = useWebSocket()
|
||||
const wi = useIngame()
|
||||
@@ -21,38 +21,36 @@ function getPlayerName(playerId: string) {
|
||||
return tieInf.value.tiedPlayers.find(p => p.id === playerId)?.name || 'Player';
|
||||
}
|
||||
const myRevealedCard = computed(() => {
|
||||
// 1. Get your own ID from the 'self' object in the DTO
|
||||
const myId = tieInf.value.self?.id;
|
||||
|
||||
// 2. Safety check: ensure we have an ID and the map exists
|
||||
if (!myId || !tieInf.value.selectedCards) return null;
|
||||
|
||||
// 3. Look up the CardDTO using your ID as the key
|
||||
const card = tieInf.value.selectedCards[myId];
|
||||
|
||||
// 4. If found, convert the relative path to a full URL
|
||||
return card ? getCardImagePath(card.path) : null;
|
||||
});
|
||||
|
||||
const isFlipping = ref(false);
|
||||
|
||||
// This replaces your v-if check.
|
||||
// It keeps the "Pick" screen visible while flipping.
|
||||
const showPickScreen = computed(() => {
|
||||
const isMyTurn = tieInf.value.self?.id === tieInf.value.currentPlayer?.id;
|
||||
return isMyTurn || isFlipping.value;
|
||||
return isFlipping.value || (isMyTurn && !showResultScreen.value);
|
||||
});
|
||||
|
||||
const showResultScreen = computed(() => {
|
||||
const allPicked = Object.keys(tieInf.value.selectedCards).length === tieInf.value.tiedPlayers.length;
|
||||
return allPicked && !isFlipping.value;
|
||||
});
|
||||
|
||||
|
||||
function selectTie(tieIndex: number) {
|
||||
// Use model.value because it's a ref
|
||||
isFlipping.value = true;
|
||||
nextTick(() => {
|
||||
wb.sendAndWait("PickTie", { cardIndex: tieIndex })
|
||||
.then(() => {
|
||||
console.log("Server accepted pick, starting animation...");
|
||||
console.log("CARD SELECTED: " + Object.keys(tieInf.value.selectedCards).length)
|
||||
|
||||
// 1. Trigger the animation state
|
||||
isFlipping.value = true;
|
||||
|
||||
// 2. Optional Notification
|
||||
$q.notify({
|
||||
message: "Card revealed!",
|
||||
color: "positive",
|
||||
@@ -60,13 +58,13 @@ function selectTie(tieIndex: number) {
|
||||
timeout: 1000
|
||||
});
|
||||
|
||||
// 3. Wait for animation to finish before switching to Waiting Screen
|
||||
setTimeout(() => {
|
||||
isFlipping.value = false;
|
||||
model.value = null;
|
||||
}, 2500);
|
||||
})
|
||||
.catch((error) => {
|
||||
isFlipping.value = false;
|
||||
console.error("Pick failed:", error);
|
||||
$q.notify({
|
||||
message: error.message || "Failed to pick card",
|
||||
@@ -74,6 +72,8 @@ function selectTie(tieIndex: number) {
|
||||
position: "top"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
const model = ref<number | null>(null)
|
||||
const options = computed(() => {
|
||||
@@ -115,7 +115,6 @@ const options = computed(() => {
|
||||
>
|
||||
<div class="card-inner">
|
||||
<q-img
|
||||
:key="myRevealedCard || 'hidden'"
|
||||
:src="(model === n && myRevealedCard) ? myRevealedCard : getCardImagePath(tieBlankCard)"
|
||||
class="card-image shadow-24"
|
||||
:class="{ 'animate-reveal': isFlipping && model === n }"
|
||||
@@ -147,7 +146,7 @@ const options = computed(() => {
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card v-else class="game-container text-center overflow-hidden">
|
||||
<q-card v-else-if="!showResultScreen" class="game-container text-center overflow-hidden">
|
||||
<q-card-section class="content-layer q-py-xl">
|
||||
<div class="text-overline text-primary letter-spacing-2">Tie-Break Round</div>
|
||||
|
||||
@@ -174,7 +173,7 @@ const options = computed(() => {
|
||||
</div>
|
||||
|
||||
<div class="q-mt-xl">
|
||||
<q-spinner-hourglass color="primary" size="4em" />
|
||||
<q-spinner-hourglass color="white" size="4em" />
|
||||
<div class="text-subtitle1 text-grey-5 q-mt-sm italic">
|
||||
Waiting for <span class="text-white">{{ tieInf.currentPlayer?.name }}</span> to pick a card...
|
||||
</div>
|
||||
@@ -182,6 +181,61 @@ const options = computed(() => {
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
</q-card>
|
||||
<q-card v-else class="game-container tie-break-card text-center overflow-hidden">
|
||||
<div class="absolute-full bg-gradient-dark opacity-80"></div>
|
||||
|
||||
<q-card-section class="content-layer q-py-xl">
|
||||
<div class="column items-center q-mb-xl">
|
||||
<q-icon
|
||||
:name="(tieInf.winners?.length || 0) > 1 ? 'auto_renew' : 'workspace_premium'"
|
||||
:color="(tieInf.winners?.length || 0) > 1 ? 'info' : 'warning'"
|
||||
size="64px"
|
||||
class="q-mb-sm"
|
||||
/>
|
||||
<div class="text-h3 text-weight-bolder text-white text-uppercase tracking-widest">
|
||||
{{ (tieInf.winners?.length || 0) > 1 ? 'Another Tie!' : 'Tie Broken' }}
|
||||
</div>
|
||||
<p class="text-subtitle1 text-grey-4">
|
||||
{{ (tieInf.winners?.length || 0) > 1 ? 'The high cards matched. Draw again!' : 'We have a winner.' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row justify-center items-end q-gutter-lg">
|
||||
<div
|
||||
v-for="(card, playerId) in tieInf.selectedCards"
|
||||
:key="playerId"
|
||||
class="revealed-card-wrapper"
|
||||
:class="{
|
||||
'winner-glow': tieInf.winners?.map(player => player.id).includes(playerId),
|
||||
'opacity-40': !tieInf.winners?.map(player => player.id).includes(playerId) && (tieInf.winners?.length || 0) > 0
|
||||
}"
|
||||
>
|
||||
<div class="player-tag q-mb-md">
|
||||
<div class="text-overline text-white">{{ getPlayerName(playerId) }}</div>
|
||||
<q-badge
|
||||
v-if="tieInf.winners?.map(player => player.id).includes(playerId)"
|
||||
:color="tieInf.winners.length > 1 ? 'info' : 'positive'"
|
||||
:label="tieInf.winners.length > 1 ? 'Still Tied' : 'Winner'"
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="card-stack">
|
||||
<q-img
|
||||
:src="getCardImagePath(card.path)"
|
||||
class="card-image-reveal shadow-24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column items-center q-mt-lg">
|
||||
<q-spinner-hourglass color="white" size="4em" />
|
||||
<div class="text-subtitle1 text-white q-mt-sm italic">
|
||||
Preparing trumpsuit selection...
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</transition>
|
||||
</template>
|
||||
@@ -397,4 +451,35 @@ const options = computed(() => {
|
||||
50% { transform: scale(1.45); box-shadow: 0 0 40px rgba(25, 118, 210, 0.6); }
|
||||
100% { transform: scale(1.4); }
|
||||
}
|
||||
/* Add to previous styles */
|
||||
|
||||
.winner-glow .card-image {
|
||||
border-color: #31ccec; /* Default 'info' blue for ongoing ties */
|
||||
transform: scale(1.05) translateY(-10px);
|
||||
box-shadow: 0 10px 40px rgba(49, 204, 236, 0.4) !important;
|
||||
}
|
||||
|
||||
/* If there is only one winner, change glow to Gold */
|
||||
.winner-glow:only-child .card-image,
|
||||
.tie-break-card .winner-glow:has(.bg-positive) .card-image {
|
||||
border-color: #f2c037;
|
||||
box-shadow: 0 10px 40px rgba(242, 192, 55, 0.5) !important;
|
||||
}
|
||||
|
||||
.opacity-40 {
|
||||
opacity: 0.4;
|
||||
filter: grayscale(0.8);
|
||||
transform: scale(0.9);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.card-stack {
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.card-image-reveal {
|
||||
width: 130px;
|
||||
border-radius: 10px;
|
||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,6 +33,7 @@ type TieInfo = {
|
||||
tiedPlayers: Player[]
|
||||
highestAmount: number
|
||||
selectedCards: Record<string, Card>
|
||||
winners: Player[] | null
|
||||
}
|
||||
|
||||
type TrumpInfo = {
|
||||
|
||||
Reference in New Issue
Block a user