1
0
forked from jchomaz/Vulture

Tracking de l'application VApp (IHM du jeu)

This commit is contained in:
2025-05-11 18:04:12 +02:00
commit 89e9db9b62
17763 changed files with 3718499 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<template>
<v-app-bar :collapse="$route.name === 'Game Display (Projection)'" :elevation="5" height="50">
<RouterMenu />
<v-app-bar-title v-if="$route.name !== 'Accueil'">Brain Blast</v-app-bar-title>
</v-app-bar>
</template>
<script setup>
import RouterMenu from '@/components/RouterMenu.vue'
</script>

View File

@@ -0,0 +1,120 @@
<template>
<div class="label-pos">
<v-label class="labelTitle-style">Buzzer connectés</v-label>
</div>
<v-row no-gutters justify="space-around" class="button-pos">
<v-icon v-bind:color="redBuzzerState == 1 ? 'RedBuzzer' : 'DisconnectedBuzzer'">mdi-radiobox-marked</v-icon>
<v-icon v-bind:color="blueBuzzerState == 1 ? 'BlueBuzzer' : 'DisconnectedBuzzer'">mdi-radiobox-marked</v-icon>
<v-icon v-bind:color="yellowBuzzerState == 1 ? 'YellowBuzzer' : 'DisconnectedBuzzer'">mdi-radiobox-marked</v-icon>
<v-icon v-bind:color="greenBuzzerState == 1 ? 'GreenBuzzer' : 'DisconnectedBuzzer'">mdi-radiobox-marked</v-icon>
</v-row>
<v-divider :thickness="2" class="border-opacity-100" color="primary"/>
</template>
<script setup>
import { subscribeToTopic } from '@/services/mqttService'
import { onMounted, ref, onUnmounted } from 'vue';
// États réactifs pour chaque buzzer
let redBuzzerState = ref(0);
let blueBuzzerState = ref(0);
let greenBuzzerState = ref(0);
let yellowBuzzerState = ref(0);
// État pour surveiller la connexion générale
let connectionStatus = ref("connected"); // "connected" ou "disconnected"
// Variable pour gérer le timeout global
let globalTimeoutHandle = null;
// Fonction pour réinitialiser le timeout global
function resetGlobalTimeout() {
// Effacer le timeout précédent, s'il existe
if (globalTimeoutHandle) {
clearTimeout(globalTimeoutHandle);
}
// Redémarrer un timeout de 5 minutes (300000 ms)
globalTimeoutHandle = setTimeout(() => {
handleGlobalTimeout(); // Appel si aucun message MQTT reçu depuis 5 minutes
}, 60000);
}
// Fonction à exécuter si le timeout global est atteint
function handleGlobalTimeout() {
console.log("Aucun message MQTT reçu depuis plus de 5 minutes !");
connectionStatus.value = "disconnected"; // Indiquer que la connexion est perdue
redBuzzerState.value = 0;
blueBuzzerState.value = 0;
yellowBuzzerState.value = 0;
greenBuzzerState.value = 0;
// Tu peux ajouter ici d'autres actions, comme afficher une alerte
}
// Fonction pour traiter chaque message reçu et réinitialiser le timeout
function handleMessage(topic, message) {
let parsedMessage;
try {
parsedMessage = JSON.parse(message);
} catch (e) {
console.error("Erreur d'analyse JSON:", e);
return;
}
// Extraire les informations
const { buzzer, status } = parsedMessage;
// Mettre à jour l'état des buzzers en fonction des messages
switch (buzzer) {
case 'redBuzzerIP':
redBuzzerState.value = status === "online" ? 1 : 0;
break;
case 'blueBuzzerIP':
blueBuzzerState.value = status === "online" ? 1 : 0;
break;
case 'yellowBuzzerIP':
yellowBuzzerState.value = status === "online" ? 1 : 0;
break;
case 'greenBuzzerIP':
greenBuzzerState.value = status === "online" ? 1 : 0;
break;
}
// Réinitialiser le timeout global car un message a été reçu
connectionStatus.value = "connected"; // Rétablir le statut de connexion
resetGlobalTimeout();
}
// S'abonner au topic lorsque le composant est monté
onMounted(() => {
subscribeToTopic('buzzer/watcher', (topic, message) => {
handleMessage(topic, message);
resetGlobalTimeout(); // Réinitialiser le timeout global au démarrage
});
});
// Nettoyer le timeout global lorsque le composant est démonté
onUnmounted(() => {
if (globalTimeoutHandle) {
clearTimeout(globalTimeoutHandle);
}
});
</script>
<style>
.label-pos {
padding-top: 15px;
text-align: center;
}
.labelTitle-style {
font-size: 20px !important;
font-weight: 500;
color: #e91e1e !important;
opacity: 90% !important;
}
.button-pos {
padding-top: 10px;
padding-bottom: 15px;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<v-card tile outlined :class="{ 'card--reduced': isCardReduced }">
<v-card-title class="card__title primary" @click="toggleCardSize">
<v-icon left class="white--text pr-5 pl-2" size="40">mdi-calculator-variant</v-icon>
Gestion des scores
</v-card-title>
<v-container class="text-center">
<v-row justify="center">
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn red xs12 sm6 md3" topic="game/score/update" message='{"Red": "-2"}'>
<v-icon left size="40">mdi-minus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn red card xs12 sm6 md3" topic="game/score/update" message='{"Red": "-1"}'>
<v-icon left size="40">mdi-minus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn red card xs12 sm6 md3" topic="game/score/update" message='{"Red": "+1"}'>
<v-icon left size="40">mdi-plus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn red card xs12 sm6 md3 " topic="game/score/update" message='{"Red": "+2"}'>
<v-icon left size="40">mdi-plus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn blue card xs12 sm6 md3 " topic="game/score/update" message='{"Blue": "-2"}'>
<v-icon left size="40">mdi-minus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn blue card xs12 sm6 md3 " topic="game/score/update" message='{"Blue": "-1"}'>
<v-icon left size="40">mdi-minus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn blue card xs12 sm6 md3 " topic="game/score/update" message='{"Blue": "+1"}'>
<v-icon left size="40">mdi-plus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn blue card xs12 sm6 md3 " topic="game/score/update" message='{"Blue": "+2"}'>
<v-icon left size="40">mdi-plus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn yellow card xs12 sm6 md3 " topic="game/score/update" message='{"Yellow": "-2"}'>
<v-icon left size="40">mdi-minus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn yellow card xs12 sm6 md3 " topic="game/score/update" message='{"Yellow": "-1"}'>
<v-icon left size="40">mdi-minus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn yellow card xs12 sm6 md3 " topic="game/score/update" message='{"Yellow": "+1"}'>
<v-icon left size="40">mdi-plus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn yellow card xs12 sm6 md3 " topic="game/score/update" message='{"Yellow": "+2"}'>
<v-icon left size="40">mdi-plus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn green card xs12 sm6 md3 " topic="game/score/update" message='{"Green": "-2"}'>
<v-icon left size="40">mdi-minus-box-multiple</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn green card xs12 sm6 md3 " topic="game/score/update" message='{"Green": "-1"}'>
<v-icon left size="40">mdi-minus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="4" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn green card xs12 sm6 md3 " topic="game/score/update" message='{"Green": "+1"}'>
<v-icon left size="40">mdi-plus-box</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="3">
<mqtt-button width="120" height="60" class="btn green card xs12 sm6 md3 " topic="game/score/update" message='{"Green": "2"}'>
<v-icon left size="40">mdi-plus-box-multiple</v-icon>
</mqtt-button>
</v-col>
</v-row>
</v-container>
</v-card>
</template>
<script setup>
import MqttButton from './MqttButton.vue';
import { ref } from 'vue';
// Variable pour contrôler l'état de la carte
const isCardReduced = ref(false);
// Méthode pour basculer l'état de la carte
function toggleCardSize() {
isCardReduced.value = !isCardReduced.value;
}
</script>
<style>
.card--reduced {
height: 56px; /* Réglez la hauteur réduite selon vos besoins */
width: 170px;
overflow: hidden;
transition: height 0.3s ease-in-out;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<v-card tile outlined :class="{ 'card--reduced': isCardReduced }">
<v-card-title class="card__title primary" @click="toggleCardSize">
<v-icon left class="white--text pr-5 pl-2" size="40">mdi-camera-control</v-icon>
Contrôle du jeu
</v-card-title>
<v-container class="text-center">
<v-row justify="center">
<v-col cols="12" sm="6" md="5" class="mt-4">
<mqtt-button width="150" height="90" class="btn red" topic="/display/control" message="previous">
<v-icon left size="60">mdi-skip-previous</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="5" class="mt-4">
<mqtt-button width="150" height="90" class="btn red card" topic="/display/control" message="next">
<v-icon left size="60">mdi-skip-next</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="5" class="mb-4">
<mqtt-button width="150" height="90" class="btn red card" topic="/display/control" message="pause">
<v-icon left size="60">mdi-pause</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="5" class="mb-4">
<mqtt-button width="150" height="90" class="btn red card" topic="/display/control" message="play">
<v-icon left size="60">mdi-play</v-icon>
</mqtt-button>
</v-col>
</v-row>
</v-container>
</v-card>
</template>
<script setup>
import MqttButton from './MqttButton.vue';
import { ref } from 'vue';
// Variable pour contrôler l'état de la carte
const isCardReduced = ref(false);
// Méthode pour basculer l'état de la carte
function toggleCardSize() { isCardReduced.value = !isCardReduced.value;
}
</script>
<style>
.card--reduced {
height: 56px; /* Réglez la hauteur réduite selon vos besoins */
width: 170px;
overflow: hidden;
transition: height 0.3s ease-in-out;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<div class="label-pos">
<v-label class="labelTitle-style pb-4">Nom du Quizz</v-label>
</div>
<!-- Sélection + Bouton -->
<div class="select-style-div">
<v-select
label="Select"
:items="quizzList"
density="compact"
variant="outlined"
class="v-select-color"
/>
<v-btn color="primary" @click="publisCollectMessage" text="Mettre à jour"></v-btn>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { subscribeToTopic, publishMessage } from '@/services/mqttService';
const mqttQuizzCollectorList = 'game/quizz-collector/list';
const mqttQuizzCollectorCmd = 'game/quizz-collector/cmd';
const quizzList = ref([]);
// Fonction pour mettre à jour la liste
const handleMessage = (topic, message) => {
try {
quizzList.value = JSON.parse(message.toString());
} catch (error) {
console.error('Erreur de parsing JSON:', error);
}
};
// Fonction pour republier la commande "Collect"
const publisCollectMessage = () => {
publishMessage(mqttQuizzCollectorCmd, 'Collect');
};
// Au montage du composant
onMounted(() => {
subscribeToTopic(mqttQuizzCollectorList, (topic, message) => {
handleMessage(topic, message);
});
setTimeout(() => {
publisCollectMessage();
}, 1000);
});
</script>
<style scoped>
.select-style-div {
padding: 2% 10% 2% 10%;
text-align: center;
}
.v-select-color {
color: rgba(var(--v-theme-primary), 1.0);
opacity: 100%;
}
</style>

View File

@@ -0,0 +1,218 @@
<template>
<div class="label-pos">
<v-label class="labelTitle-style pb-4">Scores</v-label>
</div>
<!-- Équipes Rouges et Bleues côte à côte -->
<v-row no-gutters class="scorebox-pos"> <!-- Équipe Rouge -->
<v-col cols="6"> <!-- Colonnes de taille 6 pour chaque équipe -->
<v-row no-gutters>
<v-col class="scorediv-style-red">
<div>
<v-label class="labelRoundScore-style pt-3">Manche</v-label>
<div>
<v-label class="labelRoundScore-style">{{ scores.RedRoundScore }}</v-label>
</div>
</div>
<v-divider color="background"/>
<div>
<v-label class="labelTotalScore-style pt-3">Total</v-label>
<div>
<v-label class="labelTotalScore-style pb-3">{{ scores.RedTotalScore }}</v-label>
</div>
</div>
</v-col>
</v-row>
</v-col>
<!-- Équipe Bleue -->
<v-col cols="6">
<v-row no-gutters>
<v-col class="scorediv-style-blue">
<div>
<v-label class="labelRoundScore-style pt-3">Manche</v-label>
<div>
<v-label class="labelRoundScore-style">{{ scores.BlueRoundScore }}</v-label>
</div>
</div>
<v-divider color="background"/>
<div>
<v-label class="labelTotalScore-style pt-3">Total</v-label>
<div>
<v-label class="labelTotalScore-style pb-3">{{ scores.BlueTotalScore }}</v-label>
</div>
</div>
</v-col>
</v-row>
</v-col>
</v-row>
<!-- Équipes Oranges et Vertes côte à côte -->
<v-row no-gutters class="scorebox-pos">
<!-- Équipe Orange -->
<v-col cols="6">
<v-row no-gutters>
<v-col class="scorediv-style-yellow">
<div>
<v-label class="labelRoundScore-style pt-3">Manche</v-label>
<div>
<v-label class="labelRoundScore-style">{{ scores.OrangeRoundScore }}</v-label>
</div>
</div>
<v-divider color="background"/>
<div>
<v-label class="labelTotalScore-style pt-3">Total</v-label>
<div>
<v-label class="labelTotalScore-style pb-3">{{ scores.OrangeTotalScore }}</v-label>
</div>
</div>
</v-col>
</v-row>
</v-col>
<!-- Équipe Verte -->
<v-col cols="6">
<v-row no-gutters>
<v-col class="scorediv-style-green">
<div>
<v-label class="labelRoundScore-style pt-3">Manche</v-label>
<div>
<v-label class="labelRoundScore-style">{{ scores.GreenRoundScore }}</v-label>
</div>
</div>
<v-divider color="background"/>
<div>
<v-label class="labelTotalScore-style pt-3">Total</v-label>
<div>
<v-label class="labelTotalScore-style pb-3">{{ scores.GreenTotalScore }}</v-label>
</div>
</div>
</v-col>
</v-row>
</v-col>
</v-row>
</template>
<script setup>
import { onMounted, ref, reactive } from 'vue';
import variables from '@/variables.js';
import mqtt from 'mqtt'
import config from '@/config.js'
const mqttBrokerUrl = config.mqttBrokerUrl
// Créer une instance de client MQTT
const client = mqtt.connect(mqttBrokerUrl)
// Déclaration des variables locales pour les scores
const scores = reactive({
RedTotalScore: 0, // Propriétés réactives
BlueTotalScore: 0, // Propriétés réactives
OrangeTotalScore: 0, // Propriétés réactives
GreenTotalScore: 0, // Propriétés réactives
RedRoundScore: 0, // Propriétés réactives
BlueRoundScore: 0, // Propriétés réactives
OrangeRoundScore: 0, // Propriétés réactives
GreenRoundScore: 0, // Propriétés réactives
});
// Fonction pour traiter chaque message reçu et réinitialiser le timeout
function handleMessage(topic, message) {
let parsedMessage;
try {
parsedMessage = JSON.parse(message);
} catch (e) {
console.error("Erreur d'analyse JSON:", e);
return;
}
// Extraire les informations
//const { TEAM, Name } = parsedMessage;
scores.RedTotalScore = parsedMessage.TEAM.Red.TotalScore
scores.BlueTotalScore = parsedMessage.TEAM.Blue.TotalScore
scores.YellowTotalScore = parsedMessage.TEAM.Yellow.TotalScore
scores.GreenTotalScore = parsedMessage.TEAM.Green.TotalScore
scores.RedRoundScore = parsedMessage.TEAM.Red.RoundScore
scores.BlueRoundScore = parsedMessage.TEAM.Blue.RoundScore
scores.YellowRoundScore = parsedMessage.TEAM.Yellow.RoundScore
scores.GreenRoundScore = parsedMessage.TEAM.Green.RoundScore
// Mettre à jour l'état des buzzers en fonction des messages
/*
switch (buzzer) {
case 'redBuzzerIP':
redBuzzerState.value = status === "online" ? 1 : 0;
break;
case 'blueBuzzerIP':
blueBuzzerState.value = status === "online" ? 1 : 0;
break;
case 'yellowBuzzerIP':
yellowBuzzerState.value = status === "online" ? 1 : 0;
break;
case 'greenBuzzerIP':
greenBuzzerState.value = status === "online" ? 1 : 0;
break;
}
*/
}
function subscribeToTopic(topic, callback) {
client.subscribe(topic)
client.on('message', (receivedTopic, message) => { callback(receivedTopic.toString(), message.toString())
})
}
// S'abonner au topic lorsque le composant est monté
onMounted(() => {
subscribeToTopic('game/score', (topic, message) => {
handleMessage(topic, message);
});
});
</script>
<style>
.label-pos {
padding-top: 15px;
text-align: center;
}
.labelTitle-style {
font-size: 20px !important;
font-weight: 500;
color: #d42828 !important;
opacity: 90% !important;
}
.labelRoundScore-style {
opacity: 100% !important;
font-size: 25px !important;
font-weight: 500;
}
.labelTotalScore-style {
opacity: 100% !important;
font-size: 15px !important;
font-weight: 500;
}
.button-pos {
padding-top: 10px;
padding-bottom: 15px;
}
.scorebox-pos {
text-align: center;
}
.scorediv-style-red {
background-color: #d42828 !important;
padding: 15px;
border-top-left-radius: 10%;
}
.scorediv-style-yellow {
background-color: #d4d100 !important;
padding: 15px;
border-bottom-left-radius: 10%;
}
.scorediv-style-blue {
background-color: #2867d4 !important;
padding: 15px;
border-top-right-radius: 10%;
}
.scorediv-style-green {
background-color: #28d42e !important;
padding: 15px;
border-bottom-right-radius: 10%;
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<v-card tile outlined :class="{ 'card--reduced': isCardReduced }">
<v-card-title class="card__title primary" @click="toggleCardSize">
<v-icon left class="white--text pr-5 pl-2" size="40">mdi-play-network-outline</v-icon>
Solution </v-card-title>
<v-container class="text-center">
<v-row justify="center">
<v-container class="text-center"> <!-- Utilisation de styles CSS personnalisés pour centrer l'image -->
<v-img width="450" src="@/assets/copilot-solution-FULL-HD.jpg" style="margin: 0 auto;">
</v-img>
</v-container>
</v-row>
</v-container>
</v-card>
</template>
<style>
@media (min-width: 1024px) {
.image-container {
width: 300px; overflow: hidden; /* Pour masquer le dépassement de l'image */
border: 1px solid #ccc; /* Bordure de l'image */
}
.image-container img {
width: 100%; /* Pour remplir complètement le conteneur */
height: auto; /* Pour maintenir le ratio d'aspect de l'image */
display: block; /* Pour éviter l'espace réservé pour les images */
}
}
.card--reduced {
height: 56px; /* Réglez la hauteur réduite selon vos besoins */
width: 160px;
overflow: hidden;
transition: height 0.6s ease-in-out;
}
</style>
<script setup>
import { ref } from 'vue';
// Variable pour contrôler l'état de la carte
const isCardReduced = ref(false);
// Méthode pour basculer l'état de la carte
function toggleCardSize() {
isCardReduced.value = !isCardReduced.value;
}
</script>

View File

@@ -0,0 +1,61 @@
<template>
<v-card tile outlined :class="{ 'card--reduced': isCardReduced }">
<v-card-title class="card__title primary" @click="toggleCardSize">
<v-icon left class="white--text pr-5 pl-2" size="40">mdi-music-box-multiple</v-icon>
Soundboard
</v-card-title>
<v-container class="text-center">
<v-row justify="center">
<v-col cols="12" sm="6" md="4" class="mt-4">
<mqtt-button class="btn red" width="150" height="90" topic="/sound/playsound" message="good-response" rounded>
<v-icon size="60">mdi-check-circle-outline</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="4" class="mt-4">
<mqtt-button class="btn red" width="150" height="90" topic="/sound/playsound" message="bad-response" rounded>
<v-icon size="60">mdi-close-circle-outline</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="4" class="mt-4">
<mqtt-button class="btn red" width="150" height="90" topic="/sound/playsound" message="timer" rounded>
<v-icon size="60">mdi-timer-outline</v-icon>
</mqtt-button>
</v-col>
</v-row>
<v-row justify="center">
<v-col cols="12" sm="6" md="4" class="mb-4">
<mqtt-button class="btn red" width="150" height="90" topic="/sound/playsound" message="applause" rounded>
<v-icon size="60">mdi-human-handsup</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="4" class="mb-4">
<mqtt-button class="btn red" width="150" height="90" topic="/sound/playsound" message="bell" rounded>
<v-icon size="60">mdi-bell-outline</v-icon>
</mqtt-button>
</v-col>
</v-row>
</v-container>
</v-card>
</template>
<script setup>
import MqttButton from './MqttButton.vue';
import { ref } from 'vue';
// Variable pour contrôler l'état de la carte
const isCardReduced = ref(false);
// Méthode pour basculer l'état de la carte
function toggleCardSize() {
isCardReduced.value = !isCardReduced.value;
}
</script>
<style scoped>
.card--reduced {
height: 56px; /* Réglez la hauteur réduite selon vos besoins */
width: 190px;
overflow: hidden;
transition: height 0.3s ease-in-out;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class="container">
<div class="timer">
<v-label color="primary" class="labelTime-style" >{{ formatTime }}</v-label>
</div>
<v-row no-gutters justify="space-around" >
<v-btn class="buttons" color="primary" icon="mdi-play" @click="startTimer"></v-btn>
<v-btn color="primary" icon="mdi-pause" @click="pauseTimer"></v-btn>
<v-btn color="primary" icon="mdi-restart" @click="resetTimer"></v-btn>
</v-row>
</div>
</template>
<script setup>
import { ref, computed, onBeforeUnmount } from 'vue';
const timerActive = ref(false);
const startTime = ref(null);
const currentTime = ref(null);
const elapsedTime = ref(0);
const formatTime = computed(() => {
let seconds = Math.floor(elapsedTime.value / 1000);
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
seconds = seconds % 60; minutes = minutes % 60;
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
});
const pad = (number) => {
return (number < 10 ? "0" : "") + number;
};
const startTimer = () => {
if (!timerActive.value) {
timerActive.value = true;
startTime.value = Date.now() - elapsedTime.value;
updateTimer(); }
};
const pauseTimer = () => {
if (timerActive.value) {
timerActive.value = false;
clearInterval(currentTime.value); }
};
const resetTimer = () => {
elapsedTime.value = 0;
timerActive.value = false;
clearInterval(currentTime.value);
};
const updateTimer = () => {
currentTime.value = setInterval(() => {elapsedTime.value = Date.now() - startTime.value; }, 1000);
};
onBeforeUnmount(() => { clearInterval(currentTime.value);
});
</script>
<script>
const startTimer = () => {
if (!timerActive.value) {
timerActive.value = true;
startTime.value = Date.now() - elapsedTime.value;
updateTimer(); } };
const pauseTimer = () => {
if (timerActive.value) {
timerActive.value = false;
clearInterval(currentTime.value); } };
const resetTimer = () => {
elapsedTime.value = 0;
timerActive.value = false;
clearInterval(currentTime.value); };
export { startTimer, pauseTimer, resetTimer };
</script>
<style>
.container {
text-align: center;
margin-top: auto; /* Place le container en bas de son parent */
margin-bottom: 1px; /* Marge en bas pour un espacement */
position: fixed; /* Le positionne de manière fixe */
left: 0;
right: 0;
bottom: 0;
padding: 20px;
}
.timer {
margin-bottom: 15px;
}
.labelTime-style {
font-size: 30px !important;
font-weight: 500;
color: #d42828 !important;
opacity: 90% !important;
}
.buttons{
background-color: rgb(255, 255, 255);
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<v-navigation-drawer width="250">
<BuzzerWatcherCard/>
<CardScore/>
<CardCurrentQuizz/>
<CardTimer/>
</v-navigation-drawer>
</template>
<script setup>
import CardTimer from '@/components/CardTimer.vue'
import CardScore from '@/components/CardScore.vue'
import BuzzerWatcherCard from '@/components/BuzzerWatcherCard.vue'
import CardCurrentQuizz from '@/components/CardCurrentQuizz.vue'
</script>
<style>
</style>

View File

@@ -0,0 +1,118 @@
<template>
<v-container class="v-container-style">
<v-card tile outlined width="500">
<v-card-title class="card__title primary centered-title">
<v-icon left class="pr-5 pl-2" size="30">mdi-send</v-icon>
Publier une couleur
</v-card-title>
<div class="input-style">
<v-select
label="Topic"
v-model="selectedTopic"
:items="topics"
prepend-icon="mdi-target"
></v-select>
</div>
<v-row>
<v-col cols="6" class="color-picker-style">
<div>
<v-color-picker
mode="hex"
v-model="selectedColor"
border="md"
width="250"
></v-color-picker>
<v-btn
class="v-btn-style-validate"
height="35"
@click="publishCustomColor"
>
Publier
</v-btn>
</div>
</v-col>
<v-col cols="4" class="button-container-2">
<v-btn color="#D42828" class="team-button" @click="publishButtonColor('#D42828')">Team Rouge</v-btn>
<v-btn color="#00FF1F" class="team-button" @click="publishButtonColor('#00FF1F')">Team Verte</v-btn>
<v-btn color="#007AFF" class="team-button" @click="publishButtonColor('#007AFF')">Team Bleue</v-btn>
<v-btn color="#FFFC00" class="team-button" @click="publishButtonColor('#FFFC00')">Team Jaune</v-btn>
</v-col>
</v-row>
</v-card>
</v-container>
</template>
<script setup>
import { ref } from 'vue';
import { publishMessage } from '@/services/mqttService';
const selectedTopic = ref('Selectionnez un topic');
const selectedColor = ref('#FF0000');
const topics = ref([
'/wled/all',
'/wled/1',
'/wled/2',
'/wled/3',
'/wled/4',
'/wled/5',
]);
const publishCustomColor = () => {
if (selectedTopic.value && selectedColor.value) {
publishMessage(selectedTopic.value, selectedColor.value);
} else {
console.warn('Topic ou couleur non sélectionné !');
}
};
const publishButtonColor = (color) => {
if (selectedTopic.value) {
publishMessage(selectedTopic.value, color);
} else {
console.warn('Topic non sélectionné !');
}
};
</script>
<style scoped>
.v-container-style {
align-items: center;
justify-content: center;
}
.input-style {
margin: 20px;
}
.v-btn-style-validate {
align-items: center;
justify-content: center;
width: 100%;
background-color: rgba(var(--v-theme-primary));
border-top-right-radius: 0%;
border-top-left-radius: 0%;
}
.centered-title {
text-align: center;
}
.color-picker-style {
margin-left: 5%;
margin-bottom: 5%;
display: flex;
}
.button-container-2 {
text-align: center;
margin-top: 12px;
margin-bottom: 27px;
margin-left: 15px;
margin-right: 13px;
}
.team-button {
width: 140px;
display: block;
margin: 12% auto 0;
background-color: rgba(var(--v-theme-primary));
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<v-container class="v-container-style-console">
<v-card tile outlined width="500">
<v-card-title class="card__title primary centered-title">
<v-icon left class="pr-5 pl-2" size="40">mdi-console-line</v-icon>
Console MQTT
</v-card-title>
<div class="div_topic">
<v-select
density="compact"
label="Topic"
v-model="selectedTopic"
:items="topics"
prepend-icon="mdi-target"
></v-select>
<div class="button_div_style">
<v-btn rounded @click="resetTopicFilter" color="primary">Unfilter</v-btn>
<v-btn
rounded
:class="{ 'scrolling-paused': scrollingState, 'scrolling-active': !scrollingState }"
@click="toggleScrollingState"
>
<v-icon>{{ scrollingState ? 'mdi-pause' : 'mdi-play' }}</v-icon>
</v-btn>
</div>
</div>
<v-container class="text-center">
<div v-for="(log, index) in filteredLogs" :key="index">
<v-label class="v-label-timestamp">{{ log.timestamp }} -&nbsp;</v-label>
<v-label class="v-label-topic-message-title">Topic :&nbsp;</v-label>
<v-label class="v-label-topic-message">{{ log.topic }}&nbsp;</v-label>
<v-label class="v-label-topic-message-title">Msg :&nbsp;</v-label>
<v-label class="v-label-topic-message">{{ log.message }}</v-label>
</div>
</v-container>
</v-card>
</v-container>
</template>
<script setup>
import { ref, computed } from 'vue';
import { subscribeToTopic } from '@/services/mqttService';
// Data
const messageLogs = ref([]);
const selectedTopic = ref('');
const topics = ref([]);
const scrollingState = ref(true);
// Methods
const handleMessage = (topic, message) => {
const timestamp = new Date().toLocaleString('fr-FR', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
if (scrollingState.value) {
if (!topics.value.includes(topic)) {
topics.value.push(topic);
}
messageLogs.value.unshift({ timestamp, topic, message });
if (messageLogs.value.length > 20) {
messageLogs.value.pop();
}
}
};
const resetTopicFilter = () => {
selectedTopic.value = '';
};
const toggleScrollingState = () => {
scrollingState.value = !scrollingState.value;
};
// Computed
const filteredLogs = computed(() => {
if (!selectedTopic.value) {
return messageLogs.value;
}
return messageLogs.value.filter((log) => log.topic === selectedTopic.value);
});
// Lifecycle
subscribeToTopic('#', (topic, message) => {
handleMessage(topic, message);
});
</script>
<style scoped>
.scrolling-paused {
background-color: rgba(var(--v-theme-primary)) !important;
}
.scrolling-active {
background-color: rgba(var(--v-theme-success)) !important;
}
.button_div_style {
justify-content: space-evenly;
display: flex;
}
.div_topic {
text-align: center !important;
padding: 5% 4% 1% 4%;
}
.v-container-style-console {
display: flex;
justify-content: center;
position: sticky;
top: 50px;
}
.centered-title {
text-align: center;
}
.v-label-timestamp {
opacity: 100%;
font-style: oblique;
font-weight: 400;
color: #838383;
}
.v-label-topic-message-title {
opacity: 100%;
font-weight: 700;
color: rgba(var(--v-theme-primary));
}
.v-label-topic-message {
font-weight: 300;
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<v-container class="v-container-style">
<v-card tile outlined width="500">
<v-card-title class="card__title primary centered-title">
<v-icon left class="pr-5 pl-2" size="30">mdi-send</v-icon>
Publier un message
</v-card-title>
<div class="input-style">
<v-select
label="Topic"
v-model="selectedTopic"
:items="topics"
prepend-icon="mdi-target"
></v-select>
<v-text-field
label="Message"
v-model="message"
prepend-icon="mdi-text-box"
></v-text-field>
</div>
<v-btn
rounded
class="v-btn-style-standalone"
height="40"
@click="publishBuzzerUnblock"
>
Déblocage<br>Buzzer
</v-btn>
<v-btn
class="v-btn-style-validate"
height="50"
@click="publisCustomMessage"
>
Publier
</v-btn>
</v-card>
</v-container>
</template>
<script setup>
import { ref } from 'vue';
import { publishMessage } from '@/services/mqttService';
// Reactive data
const message = ref('');
const selectedTopic = ref('Selectionnez un topic');
const topics = [
'wled/all',
'display/control',
'sound/playsound',
'game/score/update',
'game/score'
];
// Methods
const publisCustomMessage = () => {
publishMessage(selectedTopic.value, message.value);
};
const publishBuzzerUnblock = () => {
publishMessage('brainblast/buzzer/unlock', "0");
};
</script>
<style scoped>
.v-container-style {
align-items: center;
justify-content: center;
}
.input-style {
margin: 20px;
}
.v-btn-style-standalone {
background-color: rgba(var(--v-theme-primary));
margin-bottom: 5%;
margin-left: 5%;
}
.v-btn-style-validate {
align-items: center;
justify-content: center;
width: 100%;
background-color: rgba(var(--v-theme-primary));
border-top-right-radius: 0%;
border-top-left-radius: 0%;
}
.centered-title {
text-align: center;
}
</style>

View File

@@ -0,0 +1,22 @@
<template>
<v-btn @click="_publishMessage" v-bind="$attrs">
<slot/>
</v-btn>
</template>
<script setup>
import { publishMessage } from '@/services/mqttService'
import { ref, defineProps } from 'vue'
const props = defineProps({
topic: String,
message: null
})
const disabled = ref(false)
const _publishMessage = () => {
publishMessage(props.topic, props.message)
disabled.value = true
}
</script>

View File

@@ -0,0 +1,29 @@
<template>
<v-app-bar-nav-icon v-on:click="menu = !menu"></v-app-bar-nav-icon>
<v-menu v-model="menu" class="menu-below-bar">
<v-list>
<v-list-item v-for="route in filteredRoutes" :key="route.name" :to="route.path">{{ route.name }}</v-list-item>
</v-list>
</v-menu>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const routes = router.options.routes;
let menu = ref(false);
// Filtrer les routes pour masquer une route spécifique (par exemple, 'RouteA')
const filteredRoutes = computed(() => {
return routes.filter(route => route.name !== 'Debugger MQTT');
});
</script>
<style scoped>
.menu-below-bar {
margin-top: 48px; /* La hauteur de la barre d'application */
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<v-container class="v-container-style-console-dmx">
<v-card tile outlined width="500">
<v-card-title class="card__title primary centered-title">
<v-icon left class="pr-5 pl-2" size="40">mdi-console-line</v-icon>
Console DMX
</v-card-title>
<v-container class="text-center">
<v-label v-if="!isConnected">Connecting...</v-label>
<div class="button-container">
<v-btn class="v-btn-dmx white" @click="handleButtonPress(2, 1)" :disabled="!isConnected">Rouge</v-btn>
<v-btn class="v-btn-dmx red" @click="handleButtonPress(7, 1)" :disabled="!isConnected">Rouge</v-btn>
<v-btn class="v-btn-dmx green" @click="handleButtonPress(8, 1)" :disabled="!isConnected">Vert</v-btn>
<v-btn class="v-btn-dmx blue" @click="handleButtonPress(9, 1)" :disabled="!isConnected">Blue</v-btn>
</div>
</v-container>
</v-card>
</v-container>
</template>
<script>
import { ref, onMounted } from 'vue';
import { connectWebSocket, sendButtonPress } from '@/services/light-manager-DMX.js';
export default {
setup() {
const websocketUrl = 'ws://192.168.1.30:9999/qlcplusWS';
const isConnected = ref(false);
// Fonction pour connecter le WebSocket et mettre à jour l'état de la connexion
const connect = () => {
connectWebSocket(websocketUrl);
isConnected.value = true; // Mettre à jour l'état de la connexion
};
onMounted(() => {
connect();
});
const handleButtonPress = (id, state) => {
sendButtonPress(id, state);
// Pause de 2 secondes
setTimeout(() => {
sendButtonPress(id, !state);
}, 500); // 2000 millisecondes = 2 secondes
};
return {
handleButtonPress,
isConnected,
};
},
};
</script>
<style scoped>
.v-container-style-console-dmx {
justify-content: center;
position: sticky;
top: 50px; /* Distance depuis le haut de la fenêtre avant de "coller" */
}
.centered-title {
text-align: center;
}
.v-label-timestamp{
opacity: 100%;
font-style: oblique;
font-weight: 400;
color: #838383;
}
.button-container{
display: flex; /* Active le mode Flexbox */
justify-content: space-around; /* Répartit les boutons équitablement */
align-items: center; /* Aligne les boutons verticalement */
text-align: center;
margin-top: 12px;
margin-bottom: 27px;
margin-left: 15px;
margin-right: 13px;
}
.v-btn-dmx{
width: 100px;
margin: 2% auto 0; /* 5% de marge en bas pour espacer les boutons */
}
.v-btn-dmx.white{
background-color:#ffffff;
color: #000000;
}
.v-btn-dmx.red{
background-color: rgba(var(--v-theme-primary));
}
.v-btn-dmx.green{
background-color: rgba(var(--v-theme-GreenBuzzer));
}
.v-btn-dmx.blue{
background-color: rgba(var(--v-theme-BlueBuzzer));
}
</style>