mirror of
https://github.com/ericasch6/scrum-poker-internal.git
synced 2026-10-15 15:46:09 +00:00
fix: refactored the codebase
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import RoomModeratorControls from '@/components/rooms/RoomModeratorControls.vue';
|
||||
import RoomSeat from '@/components/rooms/RoomSeat.vue';
|
||||
import { usePaperBallToss } from '@/composables/usePaperBallToss';
|
||||
import type {
|
||||
PositionedRoomPlayer,
|
||||
RoomTableProps,
|
||||
SeatStyle,
|
||||
} from '@/types/room';
|
||||
|
||||
const props = defineProps<RoomTableProps>();
|
||||
|
||||
const gameBoard = ref<HTMLElement | null>(null);
|
||||
const { activePaperBalls, hitPlayerIds, throwingAtPlayerId, throwPaperBall } =
|
||||
usePaperBallToss({
|
||||
roomId: () => props.room.id,
|
||||
currentPlayerId: () => props.currentPlayer.id,
|
||||
board: gameBoard,
|
||||
});
|
||||
|
||||
const positionedPlayers = computed<PositionedRoomPlayer[]>(() => {
|
||||
const currentPlayerIndex = props.room.players.findIndex(
|
||||
(player) => player.id === props.currentPlayer.id,
|
||||
);
|
||||
const orderedPlayers =
|
||||
currentPlayerIndex === -1
|
||||
? props.room.players
|
||||
: [
|
||||
...props.room.players.slice(currentPlayerIndex),
|
||||
...props.room.players.slice(0, currentPlayerIndex),
|
||||
];
|
||||
const playerCount = Math.max(orderedPlayers.length, 1);
|
||||
|
||||
return orderedPlayers.map((player, index) => {
|
||||
const angle = Math.PI / 2 + (index * Math.PI * 2) / playerCount;
|
||||
const horizontalRadius = 39;
|
||||
const verticalRadius = 40;
|
||||
const cardDistance = 96;
|
||||
|
||||
return {
|
||||
...player,
|
||||
isTopSeat: Math.sin(angle) < -0.25,
|
||||
seatStyle: {
|
||||
left: `${50 + Math.cos(angle) * horizontalRadius}%`,
|
||||
top: `${48 + Math.sin(angle) * verticalRadius}%`,
|
||||
'--card-x': `${-Math.cos(angle) * cardDistance}px`,
|
||||
'--card-y': `${-Math.sin(angle) * cardDistance}px`,
|
||||
} as SeatStyle,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const voteCount = computed(
|
||||
() => props.room.players.filter((player) => player.has_voted).length,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="mt-5 w-full">
|
||||
<div ref="gameBoard" class="game-board" aria-label="Scrum-Poker-Tisch">
|
||||
<div class="poker-table" aria-hidden="true">
|
||||
<div class="table-highlight"></div>
|
||||
<div class="table-center">
|
||||
<span
|
||||
v-if="!room.revealed"
|
||||
class="text-sm font-medium text-emerald-50/75"
|
||||
>
|
||||
{{ voteCount }} von {{ room.players.length }} haben
|
||||
gewählt
|
||||
</span>
|
||||
<div v-else class="grid grid-cols-2 gap-8 text-emerald-50">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs text-emerald-100/65">
|
||||
Durchschnitt
|
||||
</span>
|
||||
<strong class="text-xl">
|
||||
{{ room.statistics?.average ?? '–' }}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs text-emerald-100/65">
|
||||
Median
|
||||
</span>
|
||||
<strong class="text-xl">
|
||||
{{ room.statistics?.median ?? '–' }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="paperBall in activePaperBalls"
|
||||
:key="paperBall.id"
|
||||
class="paper-ball-flight"
|
||||
:style="paperBall.style"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span class="paper-ball"></span>
|
||||
</div>
|
||||
|
||||
<RoomSeat
|
||||
v-for="player in positionedPlayers"
|
||||
:key="player.id"
|
||||
:player="player"
|
||||
:current-player-id="currentPlayer.id"
|
||||
:current-vote="currentPlayer.current_vote"
|
||||
:cards="room.deck.cards"
|
||||
:revealed="room.revealed"
|
||||
:is-paper-ball-hit="hitPlayerIds.includes(player.id)"
|
||||
:throw-disabled="throwingAtPlayerId !== null"
|
||||
@throw-paper-ball="throwPaperBall"
|
||||
/>
|
||||
|
||||
<RoomModeratorControls
|
||||
v-if="currentPlayer.is_moderator"
|
||||
:room-id="room.id"
|
||||
:revealed="room.revealed"
|
||||
:has-votes="voteCount > 0"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.game-board {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: max(34rem, calc(100svh - 11rem));
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.poker-table {
|
||||
position: absolute;
|
||||
inset: 20% 15% 21%;
|
||||
overflow: hidden;
|
||||
border: 0.65rem solid #513b2d;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 50% 42%,
|
||||
rgb(16 126 91 / 38%),
|
||||
transparent 45%
|
||||
),
|
||||
linear-gradient(145deg, #136c54, #074c3d);
|
||||
box-shadow:
|
||||
inset 0 0 0 0.2rem rgb(255 255 255 / 8%),
|
||||
inset 0 -1.5rem 3rem rgb(0 0 0 / 22%),
|
||||
0 1.5rem 3rem rgb(0 0 0 / 35%);
|
||||
}
|
||||
|
||||
.table-highlight {
|
||||
position: absolute;
|
||||
inset: 6%;
|
||||
border: 1px solid rgb(255 255 255 / 8%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.table-center {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.paper-ball-flight {
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
width: 1.65rem;
|
||||
height: 1.65rem;
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
animation: paper-ball-flight 780ms linear forwards;
|
||||
}
|
||||
|
||||
.paper-ball {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid rgb(100 116 139 / 65%);
|
||||
border-radius: 48% 42% 54% 39%;
|
||||
background:
|
||||
linear-gradient(
|
||||
35deg,
|
||||
transparent 44%,
|
||||
rgb(100 116 139 / 35%) 46% 51%,
|
||||
transparent 53%
|
||||
),
|
||||
linear-gradient(
|
||||
120deg,
|
||||
transparent 35%,
|
||||
rgb(148 163 184 / 45%) 37% 42%,
|
||||
transparent 44%
|
||||
),
|
||||
#e2e8f0;
|
||||
}
|
||||
|
||||
@keyframes paper-ball-flight {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) translate(0, 0) rotate(0deg)
|
||||
scale(0.72);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--travel-quarter-x), var(--travel-quarter-y))
|
||||
rotate(195deg) scale(0.92);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--travel-mid-x), var(--travel-mid-y)) rotate(390deg)
|
||||
scale(1.08);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate(-50%, -50%)
|
||||
translate(
|
||||
var(--travel-three-quarter-x),
|
||||
var(--travel-three-quarter-y)
|
||||
)
|
||||
rotate(585deg) scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--travel-x), var(--travel-y)) rotate(780deg)
|
||||
scale(0.82);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.game-board {
|
||||
min-height: max(28rem, calc(100svh - 10rem));
|
||||
}
|
||||
|
||||
.poker-table {
|
||||
inset: 24% 17% 23%;
|
||||
border-width: 0.45rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.paper-ball-flight {
|
||||
animation-duration: 1ms;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user