145 lines
4.7 KiB
Vue
145 lines
4.7 KiB
Vue
<template>
|
|
<div
|
|
:class="['player-area p-2 md:p-4 flex flex-col items-center justify-center text-center relative no-select',
|
|
areaClass,
|
|
{ 'opacity-50': player.isSkipped,
|
|
'bg-green-100 dark:bg-green-800 animate-pulsePositive': player.isTimerRunning && player.currentTimerSec >=0 && !isNextPlayerArea,
|
|
'bg-red-100 dark:bg-red-900': player.isTimerRunning && player.currentTimerSec < 0 && !isNextPlayerArea,
|
|
}]"
|
|
@click="handleTap"
|
|
v-touch:swipe.up="handleSwipeUp"
|
|
>
|
|
<!-- Avatar -->
|
|
<div
|
|
class="mb-4 md:mb-4 relative"
|
|
:style="{
|
|
width: avatarSize.width,
|
|
height: avatarSize.height,
|
|
}"
|
|
>
|
|
<img
|
|
v-if="player.avatar"
|
|
:src="player.avatar"
|
|
alt="Player Avatar"
|
|
class="rounded-full object-cover border-2 md:border-4 shadow-lg w-full h-full"
|
|
:class="player.isSkipped ? 'border-gray-500 filter grayscale' : 'border-blue-500 dark:border-blue-400'"
|
|
/>
|
|
<DefaultAvatarIcon
|
|
v-else
|
|
class="rounded-full object-cover border-2 md:border-4 shadow-lg text-gray-400 dark:text-gray-500 bg-gray-200 dark:bg-gray-700 p-1 md:p-2 w-full h-full"
|
|
:class="player.isSkipped ? 'border-gray-500 filter grayscale !text-gray-300 dark:!text-gray-600' : 'border-blue-500 dark:border-blue-400'"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Player Name -->
|
|
<h2 class="font-semibold mb-4 md:mb-6 text-5xl sm:text-2xl md:text-3xl lg:text-5xl">
|
|
{{ player.name }}
|
|
</h2>
|
|
<TimerDisplay
|
|
:seconds="player.currentTimerSec"
|
|
:is-negative="player.currentTimerSec < 0"
|
|
:is-pulsating="player.isTimerRunning"
|
|
class="text-4xl sm:text-5xl md:text-6xl lg:text-6xl xl:text-5xl"
|
|
/>
|
|
<p v-if="player.isSkipped" class="text-red-500 dark:text-red-400 mt-1 md:mt-2 font-semibold text-sm md:text-base lg:text-lg">SKIPPED</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
|
import TimerDisplay from './TimerDisplay.vue';
|
|
import DefaultAvatarIcon from './DefaultAvatarIcon.vue';
|
|
|
|
const vTouch = {
|
|
mounted: (el, binding) => {
|
|
if (binding.arg === 'swipe' && binding.modifiers.up) {
|
|
let touchstartX = 0;
|
|
let touchstartY = 0;
|
|
let touchendX = 0;
|
|
let touchendY = 0;
|
|
const swipeThreshold = 50;
|
|
|
|
el.addEventListener('touchstart', function(event) {
|
|
touchstartX = event.changedTouches[0].screenX;
|
|
touchstartY = event.changedTouches[0].screenY;
|
|
}, { passive: true });
|
|
|
|
el.addEventListener('touchend', function(event) {
|
|
touchendX = event.changedTouches[0].screenX;
|
|
touchendY = event.changedTouches[0].screenY;
|
|
handleGesture();
|
|
}, { passive: true });
|
|
|
|
function handleGesture() {
|
|
const deltaY = touchstartY - touchendY;
|
|
const deltaX = Math.abs(touchendX - touchstartX);
|
|
|
|
if (deltaY > swipeThreshold && deltaY > deltaX) {
|
|
if (typeof binding.value === 'function') {
|
|
binding.value();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
const props = defineProps({
|
|
player: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
isCurrentPlayerArea: Boolean,
|
|
isNextPlayerArea: Boolean,
|
|
areaClass: String,
|
|
});
|
|
|
|
const emit = defineEmits(['tapped', 'swiped-up']);
|
|
|
|
const avatarSize = ref({ width: '120px', height: '120px' });
|
|
|
|
const calculateAvatarSize = () => {
|
|
const screenWidth = window.innerWidth;
|
|
const screenHeight = window.innerHeight;
|
|
const isNormalModeContext = document.querySelector('.player-area')?.parentElement?.classList.contains('flex-col');
|
|
|
|
if (isNormalModeContext) {
|
|
const availableHeight = screenHeight / 2;
|
|
let size = Math.min(availableHeight * 0.5, screenWidth * 0.4, 175);
|
|
|
|
if (screenWidth < 768) { // Mobile
|
|
size = Math.min(availableHeight * 0.7, screenWidth * 0.6, 230);
|
|
} else if (screenWidth < 1024) { // Tablet
|
|
size = Math.min(availableHeight * 0.55, screenWidth * 0.45, 180);
|
|
}
|
|
size = Math.max(size, 100);
|
|
|
|
avatarSize.value = { width: `${size}px`, height: `${size}px` };
|
|
} else {
|
|
avatarSize.value = { width: '120px', height: '120px' };
|
|
}
|
|
};
|
|
|
|
|
|
onMounted(() => {
|
|
calculateAvatarSize();
|
|
window.addEventListener('resize', calculateAvatarSize);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('resize', calculateAvatarSize);
|
|
});
|
|
|
|
const handleTap = () => {
|
|
if (props.isCurrentPlayerArea && !props.player.isSkipped) {
|
|
emit('tapped');
|
|
}
|
|
};
|
|
|
|
const handleSwipeUp = () => {
|
|
if (props.isNextPlayerArea && !props.player.isSkipped) {
|
|
emit('swiped-up');
|
|
}
|
|
};
|
|
</script> |