players stay in run all timers mode

This commit is contained in:
cpu
2025-05-12 02:49:41 +02:00
parent c9273e65c6
commit 7746e5f833
3 changed files with 63 additions and 72 deletions

View File

@@ -1,10 +1,10 @@
<template>
<div
:class="['player-list-item flex items-center justify-between p-3 my-2 rounded-lg shadow cursor-pointer',
itemBgClass,
{ 'opacity-60': player.isSkipped,
'animate-pulsePositive': player.isTimerRunning && player.currentTimerSec >=0,
}]"
:class="['player-list-item flex items-center justify-between p-3 my-2 rounded-lg shadow cursor-pointer transition-all duration-200 ease-in-out',
itemStateClasses,
{ 'opacity-50 filter grayscale contrast-75': player.isSkipped },
{ 'opacity-75': !player.isTimerRunning && !player.isSkipped }
]"
@click="handleTap"
>
<div class="flex items-center">
@@ -12,17 +12,24 @@
v-if="player.avatar"
:src="player.avatar"
alt="Player Avatar"
class="w-12 h-12 rounded-full object-cover mr-3 border-2 flex-shrink-0"
:class="player.isSkipped ? 'border-gray-500 filter grayscale' : 'border-blue-400 dark:border-blue-300'"
class="w-12 h-12 rounded-full object-cover mr-3 border-2"
:class="player.isSkipped ? 'border-gray-400 dark:border-gray-600' :
player.isTimerRunning ? 'border-green-400 dark:border-green-500' :
'border-yellow-400 dark:border-yellow-500'"
/>
<DefaultAvatarIcon
v-else
class="w-12 h-12 rounded-full object-cover mr-3 border-2 text-gray-400 dark:text-gray-500 bg-gray-200 dark:bg-gray-700 p-1 flex-shrink-0"
:class="player.isSkipped ? 'border-gray-500 filter grayscale !text-gray-300 dark:!text-gray-600' : 'border-blue-400 dark:border-blue-300'"
class="w-12 h-12 rounded-full object-cover mr-3 border-2 p-1"
:class="player.isSkipped ? 'text-gray-400 bg-gray-200 border-gray-400 dark:text-gray-600 dark:bg-gray-700 dark:border-gray-600' :
player.isTimerRunning ? 'text-green-600 bg-green-100 border-green-400 dark:text-green-300 dark:bg-green-800 dark:border-green-500' :
'text-yellow-600 bg-yellow-50 border-yellow-400 dark:text-yellow-300 dark:bg-yellow-800 dark:border-yellow-500'"
/>
<div>
<h3 class="text-lg font-medium">{{ player.name }}</h3>
<h3 class="text-lg font-medium" :class="{'text-gray-500 dark:text-gray-400': !player.isTimerRunning && !player.isSkipped}">
{{ player.name }}
</h3>
<p v-if="player.isSkipped" class="text-xs text-red-500 dark:text-red-400">SKIPPED</p>
<p v-else-if="!player.isTimerRunning" class="text-xs text-yellow-600 dark:text-yellow-400">Paused</p>
</div>
</div>
<div class="flex flex-col items-end">
@@ -31,6 +38,7 @@
:is-negative="player.currentTimerSec < 0"
:is-pulsating="player.isTimerRunning"
class="text-2xl"
:class="{'opacity-80': !player.isTimerRunning && !player.isSkipped}"
/>
</div>
</div>
@@ -51,20 +59,22 @@ const props = defineProps({
const emit = defineEmits(['tapped']);
const handleTap = () => {
// Allow tapping only if not skipped, to pause/resume their timer
if (!props.player.isSkipped) {
emit('tapped');
}
};
const itemBgClass = computed(() => {
const itemStateClasses = computed(() => {
if (props.player.isSkipped) {
return 'bg-gray-200 dark:bg-gray-800 border border-gray-300 dark:border-gray-700';
}
if (props.player.isTimerRunning) {
return props.player.currentTimerSec < 0
? 'bg-red-100 dark:bg-red-900/80 border border-red-300 dark:border-red-700'
: 'bg-green-50 dark:bg-green-800/70 border border-green-200 dark:border-green-700';
? 'bg-red-100 dark:bg-red-900/80 border border-red-400 dark:border-red-600 animate-pulsePositive' // Pulsate background if running positive
: 'bg-green-50 dark:bg-green-900/70 border border-green-400 dark:border-green-600 animate-pulsePositive';
}
return 'bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600';
// Paused state
return 'bg-yellow-50 dark:bg-yellow-900/50 border border-yellow-300 dark:border-yellow-700 hover:bg-yellow-100 dark:hover:bg-yellow-900/70';
});
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex flex-col safe-area-height safe-area-padding overflow-hidden" :class="{'dark': theme === 'dark'}">
<div class="flex flex-col h-screen overflow-hidden" :class="{'dark': theme === 'dark'}">
<!-- Header Bar -->
<header class="p-3 bg-gray-100 dark:bg-gray-800 shadow-md flex justify-between items-center shrink-0">
<!-- ... header content ... -->
<div class="flex items-center space-x-2">
<button @click="navigateToSetup" class="btn btn-secondary text-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline mr-1" viewBox="0 0 20 20" fill="currentColor"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" /></svg>
@@ -28,9 +28,8 @@
</div>
</header>
<!-- Main content area should allow its children to use its full height -->
<main class="flex-grow overflow-hidden flex flex-col" ref="gameArea">
<!-- Normal Mode: This div itself needs to take full height of 'main' -->
<!-- Normal Mode -->
<div v-if="gameMode === 'normal' && currentPlayer && nextPlayer" class="h-full flex flex-col">
<PlayerDisplay
:player="currentPlayer"
@@ -52,20 +51,21 @@
<!-- All Timers Running Mode -->
<div v-if="gameMode === 'allTimers'" class="p-4 h-full flex flex-col">
<div class="mb-4 flex justify-start items-center">
<h2 class="text-2xl font-semibold">All Timers Running</h2>
<h2 class="text-2xl font-semibold">All Timers Mode</h2>
</div>
<div class="flex-grow overflow-y-auto space-y-2">
<!-- Use playersToListInAllTimersMode -->
<PlayerListItem
v-for="(player) in playersInAllTimersView"
v-for="(player) in playersToListInAllTimersMode"
:key="player.id"
:player="player"
@tapped="() => handlePlayerTapAllTimers(indexInFullList(player.id))"
/>
<p v-if="playersInAllTimersView.length === 0 && players.length > 0 && anyTimerCouldRun" class="text-center text-gray-500 dark:text-gray-400 py-6">
All active timers paused.
<p v-if="playersToListInAllTimersMode.length === 0 && players.length > 0 && anyTimerCouldRun" class="text-center text-gray-500 dark:text-gray-400 py-6">
All players are skipped or an issue occurred.
</p>
<p v-if="playersInAllTimersView.length === 0 && players.length > 0 && !anyTimerCouldRun" class="text-center text-gray-500 dark:text-gray-400 py-6">
No players eligible to run. (All skipped or issue)
<p v-if="playersToListInAllTimersMode.length === 0 && players.length > 0 && !anyTimerCouldRun" class="text-center text-gray-500 dark:text-gray-400 py-6">
All players are skipped.
</p>
<p v-if="players.length === 0" class="text-center text-gray-500 dark:text-gray-400 py-6">
No players to display.
@@ -98,14 +98,15 @@ const gameRunning = computed(() => store.getters.gameRunning);
let timerInterval = null;
const playersInAllTimersView = computed(() => {
if (gameMode.value === 'allTimers') {
if (!players.value) return [];
return players.value.filter(p => p.isTimerRunning && !p.isSkipped);
// Shows all non-skipped players when in 'allTimers' mode.
const playersToListInAllTimersMode = computed(() => {
if (gameMode.value === 'allTimers' && players.value) {
return players.value.filter(p => !p.isSkipped);
}
return [];
});
// This computed is still used for audio logic and auto-revert
const anyTimerRunningInAllMode = computed(() => {
if (!players.value) return false;
return players.value.some(p => p.isTimerRunning && !p.isSkipped);
@@ -122,7 +123,8 @@ const indexInFullList = (playerId) => {
return players.value.findIndex(p => p.id === playerId);
}
onMounted(() => {
// ... (onMounted, onUnmounted, watchers, navigation, and other methods remain the same)
onMounted(async () => {
if (!players.value || players.value.length < 2) {
router.push({ name: 'Setup' });
return;
@@ -132,9 +134,8 @@ onMounted(() => {
store.dispatch('tick');
}, 1000);
// Attempt to acquire wake lock if game is already running (e.g., page refresh)
if (gameRunning.value) {
WakeLockService.request();
await WakeLockService.request();
}
});
@@ -142,30 +143,19 @@ onUnmounted(async () => {
clearInterval(timerInterval);
AudioService.stopContinuousTick();
AudioService.cancelPassTurnSound();
await WakeLockService.release(); // Release wake lock when leaving the game view
await WakeLockService.release();
});
// Watch gameRunning state to acquire/release wake lock
watch(gameRunning, async (isRunning) => {
if (isRunning) {
await WakeLockService.request();
} else {
// Don't release immediately on pause, only if all timers stop and stay stopped.
// For simplicity, we'll rely on onUnmounted for now or if explicitly navigating away.
// If you want to release on extended pause, you'd need more logic here.
// Perhaps only release if gameMode is normal and current player is paused,
// or if allTimers mode and ALL are paused.
// For now, aggressive release on any gameRunning=false might be too much.
// Let's only release it when navigating away or explicitly stopped.
// The browser will also release it if the tab is hidden.
// Let's make it simpler: release if game isn't running
if (!WakeLockService.isActive()) return; // Avoid releasing if not active
// A short delay before releasing to avoid flickering if quickly pausing/resuming
if (!WakeLockService.isActive()) return;
setTimeout(async () => {
if (!store.getters.gameRunning && WakeLockService.isActive()) { // Check again before releasing
if (!store.getters.gameRunning && WakeLockService.isActive()) {
await WakeLockService.release();
}
}, 3000); // e.g., release after 3 seconds of no activity
}, 3000);
}
});
@@ -212,7 +202,7 @@ watch(() => currentPlayer.value?.isTimerRunning, (isRunning, wasRunning) => {
const navigateToSetup = async () => {
await WakeLockService.release(); // Release before navigating
await WakeLockService.release();
const isAnyTimerActive = store.getters.gameRunning;
if (isAnyTimerActive) {
if (window.confirm('Game is active. Going to Setup will pause all timers. Continue?')) {
@@ -225,21 +215,15 @@ const navigateToSetup = async () => {
};
const navigateToInfo = async () => {
await WakeLockService.release(); // Release before navigating
await WakeLockService.release();
if (store.getters.gameRunning) {
store.commit('PAUSE_ALL_TIMERS'); // Good practice to pause if navigating away
store.commit('PAUSE_ALL_TIMERS');
}
router.push({ name: 'Info' });
};
const toggleMute = () => {
store.dispatch('setMuted', !isMuted.value);
};
const handleCurrentPlayerTap = () => {
store.dispatch('toggleCurrentPlayerTimerNormalMode');
};
const toggleMute = () => { store.dispatch('setMuted', !isMuted.value); };
const handleCurrentPlayerTap = () => { store.dispatch('toggleCurrentPlayerTimerNormalMode'); };
const handlePassTurn = () => {
if(currentPlayer.value && !currentPlayer.value.isSkipped) {
AudioService.cancelPassTurnSound();
@@ -252,29 +236,26 @@ const handlePassTurn = () => {
});
}
};
const handlePlayerTapAllTimers = (playerIndex) => { store.dispatch('togglePlayerTimerAllTimersMode', playerIndex); };
const switchToAllTimersMode = () => { store.dispatch('switchToAllTimersMode'); };
const switchToNormalMode = () => { store.dispatch('switchToNormalMode'); };
const handlePlayerTapAllTimers = (playerIndex) => {
store.dispatch('togglePlayerTimerAllTimersMode', playerIndex);
};
const switchToAllTimersMode = () => {
store.dispatch('switchToAllTimersMode');
};
const switchToNormalMode = () => {
store.dispatch('switchToNormalMode');
};
// Auto-revert logic
watch(anyTimerRunningInAllMode, (anyRunning) => {
// Only revert if we are IN allTimers mode and NO timers are running
if (gameMode.value === 'allTimers' && !anyRunning && players.value && players.value.length > 0) {
const nonSkippedPlayersExist = players.value.some(p => !p.isSkipped);
if (nonSkippedPlayersExist) {
setTimeout(() => {
// Double check condition before switching, state might change rapidly
if(gameMode.value === 'allTimers' && !store.getters.players.some(p => p.isTimerRunning && !p.isSkipped)){
console.log("All timers paused in AllTimersMode, reverting to Normal Mode.");
store.dispatch('switchToNormalMode');
}
}, 200);
}, 250); // A small delay to prevent flickering if a timer is immediately restarted
} else {
// All players are skipped, so stay in all timers mode but paused.
console.log("All players skipped in AllTimersMode, staying paused.");
AudioService.stopContinuousTick();
}
}