overlay for the hotkey

This commit is contained in:
cpu
2025-05-09 13:11:49 +02:00
parent 599d6aad3c
commit 216f0043e5
5 changed files with 273 additions and 203 deletions

View File

@@ -187,8 +187,7 @@ npm run dev
Open it in your browser:
[http://localhost:8080/](http://localhost:8080/)
When done, do not forget to update the cache version in the `service-worker.js`
When done, do not forget to update the `CACHE_VERSION` in the `service-worker.js`. It is the indicator for the PWA that the new version is available.
```bash
ver=$(grep -oP "CACHE_VERSION = 'nexus-timer-cache-v\K[0-9]+" public/service-worker.js)
sed -i "s/nexus-timer-cache-v$ver/nexus-timer-cache-v$((ver+1))/" public/service-worker.js
@@ -212,4 +211,4 @@ View real-time logs
```bash
journalctl -fu virt-nexus-timer.service
```
The previously installed PWA should now offer an upgrade
The previously installed PWA should update automatically or offer an upgrade

View File

@@ -1,4 +1,4 @@
const CACHE_VERSION = 'nexus-timer-cache-v4';
const CACHE_VERSION = 'nexus-timer-cache-v5';
const APP_SHELL_URLS = [
// '/', // Let NetworkFirst handle '/'
'/manifest.json',

View File

@@ -0,0 +1,79 @@
<template>
<div
v-if="visible"
class="fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-[100]"
@click.self="cancel"
@keydown.esc="cancel"
tabindex="0"
ref="overlay"
>
<div class="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-xl text-center">
<h3 class="text-2xl font-semibold text-gray-800 dark:text-gray-100 mb-4">
Press a Key
</h3>
<p class="text-gray-600 dark:text-gray-300 mb-6">
Waiting for a single key press to assign as the hotkey.
<br>
(Esc to cancel)
</p>
<div class="animate-pulse text-blue-500 dark:text-blue-400 text-xl">
Listening...
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick, onUnmounted } from 'vue';
const props = defineProps({
visible: Boolean,
});
const emit = defineEmits(['captured', 'cancel']);
const overlay = ref(null);
const handleKeyPress = (event) => {
if (!props.visible) return;
event.preventDefault();
event.stopPropagation(); // Prevent further propagation if overlay is active
let key = event.key;
if (event.code === 'Space') {
key = ' ';
} else if (key.length > 1 && key !== ' ') { // Ignore modifiers, Enter, Tab etc.
if (key === 'Escape') { // Handle Escape specifically for cancellation
cancel();
}
return;
}
key = key.toLowerCase();
emit('captured', key);
};
const cancel = () => {
emit('cancel');
};
watch(() => props.visible, async (newValue) => {
if (newValue) {
await nextTick(); // Ensure overlay is in DOM
overlay.value?.focus(); // Focus the overlay to capture keydown events directly
document.addEventListener('keydown', handleKeyPress, { capture: true }); // Use capture phase
} else {
document.removeEventListener('keydown', handleKeyPress, { capture: true });
}
});
onUnmounted(() => {
document.removeEventListener('keydown', handleKeyPress, { capture: true });
});
</script>
<style scoped>
/* Ensure overlay is above other modals if any (PlayerForm uses z-50) */
.z-\[100\] {
z-index: 100;
}
</style>

View File

@@ -3,20 +3,18 @@
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl w-full max-w-md">
<h2 class="text-2xl font-semibold mb-4">{{ isEditing ? 'Edit Player' : 'Add New Player' }}</h2>
<form @submit.prevent="submitForm">
<!-- Name -->
<!-- ... (Name, Remaining Time, Avatar sections remain the same) ... -->
<div class="mb-4">
<label for="playerName" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Name</label>
<input type="text" id="playerName" v-model="editablePlayer.name" required class="input-base mt-1">
</div>
<!-- Remaining Time -->
<div class="mb-4">
<label for="remainingTime" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Remaining Time (MM:SS or -MM:SS)</label>
<input type="text" id="remainingTime" v-model="currentTimeFormatted" @blur="validateCurrentTimeFormat" placeholder="e.g., 55:30 or -02:15" required class="input-base mt-1">
<p v-if="currentTimeFormatError" class="text-red-500 text-xs mt-1">{{ currentTimeFormatError }}</p>
</div>
<!-- Avatar -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">Avatar</label>
<div class="mt-1 flex items-center">
@@ -36,22 +34,22 @@
<p v-if="cameraError" class="text-red-500 text-xs mt-1">{{ cameraError }}</p>
</div>
<!-- "Pass Turn / My Pause" Hotkey -->
<div class="flex items-center justify-between mb-6"> <!-- Added flex container and margin -->
<label for="playerHotkey" class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
<div class="flex items-center justify-between mb-6">
<label for="playerHotkeyDisplay" class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
"Pass Turn / My Pause" Hotkey:
</label>
<div class="flex items-center"> <!-- Group input and clear button -->
<input
type="text"
id="playerHotkey"
:value="editablePlayer.hotkey ? editablePlayer.hotkey.toUpperCase() : ''"
@keydown.prevent="captureHotkey($event, 'player')"
placeholder="-"
class="input-base w-12 h-8 text-center font-mono text-lg p-0"
readonly
maxlength="1"
<div class="flex items-center">
<!-- Display field - not an input anymore, triggers overlay -->
<button
type="button"
id="playerHotkeyDisplay"
@click="startCapturePlayerHotkey"
class="input-base w-12 h-8 text-center font-mono text-lg p-0 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
>
{{ editablePlayer.hotkey ? editablePlayer.hotkey.toUpperCase() : '-' }}
</button>
<button
type="button"
@click="clearHotkey('player')"
@@ -62,13 +60,19 @@
</div>
</div>
<!-- Action Buttons -->
<div class="flex justify-end space-x-3 mt-6">
<button type="button" @click="closeModal" class="btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary" :disabled="!!currentTimeFormatError">{{ isEditing ? 'Save Changes' : 'Add Player' }}</button>
</div>
</form>
</div>
<!-- Hotkey Capture Overlay for Player Hotkey -->
<HotkeyCaptureOverlay
:visible="isCapturingPlayerHotkey"
@captured="handlePlayerHotkeyCaptured"
@cancel="cancelCapturePlayerHotkey"
/>
</div>
</template>
@@ -78,32 +82,65 @@ import { useStore } from 'vuex';
import { CameraService } from '../services/CameraService';
import { formatTime, parseTime } from '../utils/timeFormatter';
import DefaultAvatarIcon from './DefaultAvatarIcon.vue';
import HotkeyCaptureOverlay from './HotkeyCaptureOverlay.vue'; // Import overlay
import { AudioService } from '../services/AudioService';
const props = defineProps({
player: Object,
});
const props = defineProps({ player: Object });
const emit = defineEmits(['close', 'save']);
const store = useStore();
const DEFAULT_AVATAR_MARKER = null;
const isEditing = computed(() => !!props.player);
const editablePlayer = reactive({
id: null,
name: '',
avatar: DEFAULT_AVATAR_MARKER,
initialTimerSec: 3600,
currentTimerSec: 3600,
hotkey: '',
const editablePlayer = reactive({ /* ... same as before ... */
id: null, name: '', avatar: DEFAULT_AVATAR_MARKER, initialTimerSec: 3600, currentTimerSec: 3600, hotkey: '',
});
const currentTimeFormatted = ref('60:00');
const currentTimeFormatError = ref('');
const cameraError = ref('');
const maxNegativeSeconds = computed(() => store.getters.maxNegativeTimeReached);
// --- State for Player Hotkey Capture ---
const isCapturingPlayerHotkey = ref(false);
const startCapturePlayerHotkey = () => {
isCapturingPlayerHotkey.value = true;
};
const handlePlayerHotkeyCaptured = (key) => {
isCapturingPlayerHotkey.value = false;
// Validate the captured key (conflicts etc.)
const existingPlayerHotkey = store.state.players.some(p => p.hotkey === key && p.id !== editablePlayer.id);
const globalHotkeyInUseStopPause = store.state.globalHotkeyStopPause === key;
const globalHotkeyInUseRunAll = store.state.globalHotkeyRunAll === key;
if (existingPlayerHotkey) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned to another player.`);
return;
}
if (globalHotkeyInUseStopPause) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned as the Global Stop/Pause hotkey.`);
return;
}
if (globalHotkeyInUseRunAll) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned as the Global Run All Timers hotkey.`);
return;
}
editablePlayer.hotkey = key;
};
const cancelCapturePlayerHotkey = () => {
isCapturingPlayerHotkey.value = false;
};
// --- End Player Hotkey Capture ---
// ... (onMounted, watch for currentTimeFormatted, validateCurrentTimeFormat, capturePhoto, useDefaultAvatar, submitForm, closeModal remain the same)
// No change to captureHotkey itself, as the overlay handles the key capture event
// The clearHotkey method is still relevant
function clearHotkey(type) { // 'type' is 'player' here
if (type === 'player') {
editablePlayer.hotkey = '';
}
}
onMounted(() => {
if (isEditing.value && props.player) {
@@ -167,60 +204,18 @@ function useDefaultAvatar() {
editablePlayer.avatar = DEFAULT_AVATAR_MARKER;
}
function captureHotkey(event, type) {
event.preventDefault();
let key = event.key;
if (event.code === 'Space') {
key = ' ';
} else if (key.length > 1 && key !== ' ') {
return;
}
key = key.toLowerCase();
if (type === 'player') {
const existingPlayerHotkey = store.state.players.some(p => p.hotkey === key && p.id !== editablePlayer.id);
const globalHotkeyInUseStopPause = store.state.globalHotkeyStopPause === key;
const globalHotkeyInUseRunAll = store.state.globalHotkeyRunAll === key;
if (existingPlayerHotkey) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned to another player.`);
return;
}
if (globalHotkeyInUseStopPause) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned as the Global Stop/Pause hotkey.`);
return;
}
if (globalHotkeyInUseRunAll) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned as the Global Run All Timers hotkey.`);
return;
}
editablePlayer.hotkey = key;
}
}
function clearHotkey(type) {
if (type === 'player') {
editablePlayer.hotkey = '';
}
}
function submitForm() {
validateCurrentTimeFormat();
if (currentTimeFormatError.value) return;
const playerPayload = { ...editablePlayer };
if (playerPayload.currentTimerSec <= maxNegativeSeconds.value) {
playerPayload.isSkipped = true;
} else if (playerPayload.isSkipped && playerPayload.currentTimerSec > maxNegativeSeconds.value) {
playerPayload.isSkipped = false;
}
if (!isEditing.value) {
playerPayload.initialTimerSec = playerPayload.currentTimerSec;
}
emit('save', playerPayload);
closeModal();
}
@@ -228,4 +223,5 @@ function submitForm() {
function closeModal() {
emit('close');
}
</script>

View File

@@ -1,10 +1,10 @@
<template>
<div class="flex-grow flex flex-col p-4 md:p-6 lg:p-8 items-center dark:bg-gray-800">
<!-- ... (header, Player Management section remain the same) ... -->
<header class="w-full max-w-3xl mb-6 text-center">
<h1 class="text-4xl font-bold text-blue-600 dark:text-blue-400">Nexus Timer Setup</h1>
</header>
<!-- Player Management -->
<section class="w-full max-w-3xl bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-semibold">Players ({{ players.length }})</h2>
@@ -18,24 +18,21 @@
<div v-if="players.length > 0" class="space-y-3 mb-4">
<div v-for="(player, index) in players" :key="player.id" class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-600 rounded shadow-sm">
<div class="flex items-center flex-wrap">
<!-- *** START AVATAR LOGIC *** -->
<img
v-if="player.avatar"
:src="player.avatar"
alt="Player Avatar"
class="w-10 h-10 rounded-full object-cover mr-3 flex-shrink-0"
class="w-10 h-10 rounded-full object-cover mr-3"
/>
<DefaultAvatarIcon
v-else
class="w-10 h-10 rounded-full object-cover mr-3 text-gray-400 bg-gray-200 dark:bg-gray-700 p-0.5 flex-shrink-0"
class="w-10 h-10 rounded-full object-cover mr-3 text-gray-400 bg-gray-200 p-0.5"
/>
<!-- *** END AVATAR LOGIC *** -->
<span class="font-medium mr-2">{{ player.name }}</span>
<span class="mr-2 text-xs text-gray-500 dark:text-gray-400">({{ formatTime(player.currentTimerSec) }})</span>
<span v-if="player.hotkey" class="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-700 text-blue-700 dark:text-blue-200 rounded">Hotkey: {{ player.hotkey.toUpperCase() }}</span>
</div>
<div class="space-x-1 flex items-center flex-shrink-0">
<!-- Action buttons -->
<button @click="movePlayerUp(index)" :disabled="index === 0" class="btn-icon text-gray-600 dark:text-gray-300 hover:text-blue-500 disabled:opacity-50 disabled:cursor-not-allowed">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 15l7-7 7 7" />
@@ -64,51 +61,48 @@
</div>
</section>
<!-- Game Settings -->
<!-- Global Hotkeys Section -->
<section class="w-full max-w-3xl bg-white dark:bg-gray-700 p-6 rounded-lg shadow-md mb-6">
<!-- ... Global hotkey inputs ... -->
<div class="flex items-center justify-between mb-3">
<label for="globalHotkeyStopPause" class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
<label for="globalHotkeyStopPauseDisplay" class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
Global "Stop/Pause All" Hotkey:
</label>
<div class="flex items-center">
<input
type="text"
id="globalHotkeyStopPause"
:value="globalHotkeyStopPauseDisplay"
@keydown.prevent="captureGlobalHotkey($event, 'stopPause')"
placeholder="-"
class="input-base w-12 h-8 text-center font-mono text-lg p-0"
readonly
maxlength="1"
<button
type="button"
id="globalHotkeyStopPauseDisplay"
@click="startCaptureGlobalHotkey('stopPause')"
class="input-base w-12 h-8 text-center font-mono text-lg p-0 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
>
{{ globalHotkeyStopPauseDisplay || '-' }}
</button>
<button type="button" @click="clearGlobalHotkey('stopPause')" class="text-xs text-blue-500 hover:underline ml-2 px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-600">
Clear
</button>
</div>
</div>
<div class="flex items-center justify-between mb-6">
<label for="globalHotkeyRunAll" class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
<label for="globalHotkeyRunAllDisplay" class="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
Global "Run All Timers" Hotkey:
</label>
<div class="flex items-center">
<input
type="text"
id="globalHotkeyRunAll"
:value="globalHotkeyRunAllDisplay"
@keydown.prevent="captureGlobalHotkey($event, 'runAll')"
placeholder="-"
class="input-base w-12 h-8 text-center font-mono text-lg p-0"
readonly
maxlength="1"
<button
type="button"
id="globalHotkeyRunAllDisplay"
@click="startCaptureGlobalHotkey('runAll')"
class="input-base w-12 h-8 text-center font-mono text-lg p-0 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
>
{{ globalHotkeyRunAllDisplay || '-' }}
</button>
<button type="button" @click="clearGlobalHotkey('runAll')" class="text-xs text-blue-500 hover:underline ml-2 px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-600">
Clear
</button>
</div>
</div>
<!-- ... Dark Mode / Mute ... -->
<div class="flex items-center justify-between mb-4">
<!-- Dark Mode and Mute Audio -->
<div class="flex items-center justify-between mb-4 mt-6">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Dark Mode</span>
<button @click="toggleTheme" class="px-3 py-1.5 rounded-md text-sm font-medium" :class="theme === 'dark' ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-800'">
{{ theme === 'dark' ? 'On' : 'Off' }}
@@ -122,7 +116,7 @@
</div>
</section>
<!-- Actions -->
<!-- ... (Action Buttons section remains the same) ... -->
<section class="w-full max-w-3xl mt-4 space-y-3">
<button @click="saveAndClose" class="w-full btn btn-primary btn-lg text-xl py-3" :disabled="players.length < 2 && players.length !==0">
Save & Close
@@ -135,13 +129,19 @@
</button>
</section>
<!-- Player Form Modal -->
<PlayerForm
v-if="showPlayerModal"
:player="editingPlayer"
@close="closePlayerModal"
@save="savePlayer"
/>
<!-- Hotkey Capture Overlay for Global Hotkeys -->
<HotkeyCaptureOverlay
:visible="isCapturingGlobalHotkey"
@captured="handleGlobalHotkeyCaptured"
@cancel="cancelCaptureGlobalHotkey"
/>
</div>
</template>
@@ -152,6 +152,7 @@ import { useRouter } from 'vue-router';
import PlayerForm from '../components/PlayerForm.vue';
import { formatTime } from '../utils/timeFormatter';
import DefaultAvatarIcon from '../components/DefaultAvatarIcon.vue';
import HotkeyCaptureOverlay from '../components/HotkeyCaptureOverlay.vue'; // Import overlay
const store = useStore();
const router = useRouter();
@@ -159,94 +160,29 @@ const router = useRouter();
const players = computed(() => store.getters.players);
const theme = computed(() => store.getters.theme);
const isMuted = computed(() => store.getters.isMuted);
const globalHotkeyStopPause = computed(() => store.getters.globalHotkeyStopPause);
const globalHotkeyStopPauseDisplay = computed(() => globalHotkeyStopPause.value ? globalHotkeyStopPause.value.toUpperCase() : '');
const globalHotkeyRunAll = computed(() => store.getters.globalHotkeyRunAll);
const globalHotkeyRunAllDisplay = computed(() => globalHotkeyRunAll.value ? globalHotkeyRunAll.value.toUpperCase() : '');
const showPlayerModal = ref(false);
const editingPlayer = ref(null);
const openAddPlayerModal = () => {
if (players.value.length < 99) {
editingPlayer.value = null;
showPlayerModal.value = true;
} else {
alert("Maximum player limit (99) reached.");
}
// --- State for Global Hotkey Capture ---
const isCapturingGlobalHotkey = ref(false);
const currentGlobalHotkeyType = ref(null); // 'stopPause' or 'runAll'
const startCaptureGlobalHotkey = (type) => {
currentGlobalHotkeyType.value = type;
isCapturingGlobalHotkey.value = true;
};
const openEditPlayerModal = (player) => {
editingPlayer.value = player;
showPlayerModal.value = true;
};
const closePlayerModal = () => {
showPlayerModal.value = false;
editingPlayer.value = null;
};
const savePlayer = (playerData) => {
if (playerData.id) {
store.dispatch('updatePlayer', playerData);
} else {
store.dispatch('addPlayer', {
name: playerData.name,
avatar: playerData.avatar,
initialTimerSec: playerData.initialTimerSec,
currentTimerSec: playerData.currentTimerSec,
hotkey: playerData.hotkey
});
}
closePlayerModal();
};
const confirmDeletePlayer = (playerId) => {
if (window.confirm('Are you sure you want to delete this player?')) {
store.dispatch('deletePlayer', playerId);
}
};
const shufflePlayers = () => {
store.dispatch('shufflePlayers');
};
const reversePlayers = () => {
store.dispatch('reversePlayers');
};
const movePlayerUp = (index) => {
if (index > 0) {
const newPlayersOrder = [...players.value];
const temp = newPlayersOrder[index];
newPlayersOrder[index] = newPlayersOrder[index - 1];
newPlayersOrder[index - 1] = temp;
store.dispatch('reorderPlayers', newPlayersOrder);
}
};
const movePlayerDown = (index) => {
if (index < players.value.length - 1) {
const newPlayersOrder = [...players.value];
const temp = newPlayersOrder[index];
newPlayersOrder[index] = newPlayersOrder[index + 1];
newPlayersOrder[index + 1] = temp;
store.dispatch('reorderPlayers', newPlayersOrder);
}
};
const captureGlobalHotkey = (event, type) => {
event.preventDefault();
let key = event.key;
if (event.code === 'Space') {
key = ' ';
} else if (key.length > 1 && key !== ' ') {
return;
}
key = key.toLowerCase();
const handleGlobalHotkeyCaptured = (key) => {
isCapturingGlobalHotkey.value = false;
const type = currentGlobalHotkeyType.value;
if (!type) return;
// Validate captured key
const isPlayerHotkey = store.state.players.some(p => p.hotkey === key);
if (isPlayerHotkey) {
alert(`Hotkey "${key.toUpperCase()}" is already assigned to a player.`);
@@ -266,8 +202,74 @@ const captureGlobalHotkey = (event, type) => {
}
store.dispatch('setGlobalHotkeyRunAll', key);
}
currentGlobalHotkeyType.value = null; // Reset type
};
const cancelCaptureGlobalHotkey = () => {
isCapturingGlobalHotkey.value = false;
currentGlobalHotkeyType.value = null;
};
// --- End Global Hotkey Capture ---
// ... (openAddPlayerModal, openEditPlayerModal, closePlayerModal, savePlayer, confirmDeletePlayer, shufflePlayers, reversePlayers, movePlayerUp, movePlayerDown remain the same)
const openAddPlayerModal = () => {
if (players.value.length < 99) {
editingPlayer.value = null;
showPlayerModal.value = true;
} else {
alert("Maximum player limit (99) reached.");
}
};
const openEditPlayerModal = (player) => {
editingPlayer.value = player;
showPlayerModal.value = true;
};
const closePlayerModal = () => {
showPlayerModal.value = false;
editingPlayer.value = null;
};
const savePlayer = (playerData) => {
if (playerData.id) {
store.dispatch('updatePlayer', playerData);
} else {
store.dispatch('addPlayer', {
name: playerData.name,
avatar: playerData.avatar,
initialTimerSec: playerData.initialTimerSec,
currentTimerSec: playerData.currentTimerSec,
hotkey: playerData.hotkey
});
}
closePlayerModal();
};
const confirmDeletePlayer = (playerId) => {
if (window.confirm('Are you sure you want to delete this player?')) {
store.dispatch('deletePlayer', playerId);
}
};
const shufflePlayers = () => { store.dispatch('shufflePlayers'); };
const reversePlayers = () => { store.dispatch('reversePlayers'); };
const movePlayerUp = (index) => { /* ... as before ... */
if (index > 0) {
const newPlayersOrder = [...players.value];
const temp = newPlayersOrder[index];
newPlayersOrder[index] = newPlayersOrder[index - 1];
newPlayersOrder[index - 1] = temp;
store.dispatch('reorderPlayers', newPlayersOrder);
}
};
const movePlayerDown = (index) => { /* ... as before ... */
if (index < players.value.length - 1) {
const newPlayersOrder = [...players.value];
const temp = newPlayersOrder[index];
newPlayersOrder[index] = newPlayersOrder[index + 1];
newPlayersOrder[index + 1] = temp;
store.dispatch('reorderPlayers', newPlayersOrder);
}
};
// The old captureGlobalHotkey is replaced by startCaptureGlobalHotkey and handleGlobalHotkeyCaptured
const clearGlobalHotkey = (type) => {
if (type === 'stopPause') {
store.dispatch('setGlobalHotkeyStopPause', null);
@@ -276,15 +278,10 @@ const clearGlobalHotkey = (type) => {
}
};
const toggleTheme = () => {
store.dispatch('toggleTheme');
};
const toggleMute = () => {
store.dispatch('setMuted', !isMuted.value);
};
const saveAndClose = () => {
// ... (toggleTheme, toggleMute, saveAndClose, resetPlayerTimersConfirm, fullResetAppConfirm remain the same)
const toggleTheme = () => { store.dispatch('toggleTheme'); };
const toggleMute = () => { store.dispatch('setMuted', !isMuted.value); };
const saveAndClose = () => { /* ... as before ... */
store.dispatch('saveState');
if (players.value.length >= 2) {
if (store.state.currentPlayerIndex >= players.value.length || store.state.currentPlayerIndex < 0) {
@@ -299,16 +296,15 @@ const saveAndClose = () => {
alert('At least 2 players are required to start a game.');
}
};
const resetPlayerTimersConfirm = () => {
const resetPlayerTimersConfirm = () => { /* ... as before ... */
if (window.confirm('Are you sure you want to reset all current players\' timers to their initial values? This will not delete players.')) {
store.dispatch('resetGame');
}
};
const fullResetAppConfirm = () => {
const fullResetAppConfirm = () => { /* ... as before ... */
if (window.confirm('Are you sure you want to reset all app data? This includes all players, settings, and timer states. The app will revert to its default state with 2 predefined players.')) {
store.dispatch('fullResetApp');
}
};
</script>