PWA fixed
This commit is contained in:
146
src/components/PlayerDisplay.vue
Normal file
146
src/components/PlayerDisplay.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<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-6 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>
|
||||
<p v-if="isNextPlayerArea && !player.isSkipped" class="mt-1 md:mt-2 text-xs md:text-sm text-gray-600 dark:text-gray-400">(Swipe up to pass turn)</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 < 960) {
|
||||
size = Math.min(availableHeight * 0.7, screenWidth * 0.6, 250);
|
||||
} else if (screenWidth < 1024) {
|
||||
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>
|
||||
Reference in New Issue
Block a user