22 Commits

Author SHA1 Message Date
20285b945f Merge branch 'main' of https://projects.cloudsucks.net/asco.fablab/BrainBlast 2024-09-11 15:41:02 +02:00
d0cc633264 Ajout d'un draft d'un quizz + un draft de l'exploitation des images via un webrequest sur pictureEngine.js 2024-09-11 15:38:13 +02:00
bfcca3619f recovering sound player 2024-08-05 16:04:01 +00:00
f2aa7e64a0 Changement de l'image dans la solution 2024-04-14 14:50:53 +02:00
9801182db0 Création du groupe de controle des scores dans la zone de boutton 2024-04-14 14:36:22 +02:00
3f078aacb7 Suppression du toggle de thème. Raison ? -> toute l'interface est faite pour fonctionner en version dark, c'est bien plus ergonomique, le logo est également prévu pour être affiché sur une version dark 2024-04-13 16:45:10 +02:00
74f71a0ca3 Refonte de la page de debug MQTT pour y ajouter un UX design, rajout également du timestamp dans le retour de console 2024-04-13 15:49:49 +02:00
08471a27b7 Ajout de deux paramètres dans la page de settings ces boutons permettent de jouer certains sons lors de l'ajout ou la suppression de score 2024-04-12 21:32:08 +02:00
2cc97c8de8 Ajout de la fonctionnalité de réduction de card lorsque l'on clique sur une des trois card dans le gamecontrol 2024-04-12 21:16:12 +02:00
3826a067e2 Décalage du boutton Debugger 2024-04-05 16:03:49 +02:00
76383c1a3d Ajustement du menu router pour cacher le debugger, il est désormais accessible depuis la page paramètre, onglet MQTT 2024-04-05 16:01:13 +02:00
8b012757f3 Ajustement de la section Son pourque les emplacements soient plus optimisés 2024-04-05 15:48:39 +02:00
0dea30e8a6 Modification du titre Brain Blast dans la barre de menu, désormais elle ne s'affiche que lorsque l'on ne se trouve pas sur la page d'accueil (sinon cela fait redondance avec le logo principal) 2024-04-05 15:14:28 +02:00
da4daae323 Suppression de la page About 2024-04-05 15:11:51 +02:00
ef4838bde1 Modification de l'écart de la carte solution par rapport aux autres 2024-04-05 15:03:58 +02:00
1edb73bf5f Création de la carte timer dans le GameStatus 2024-04-05 15:00:41 +02:00
5a983f2c7e Transformation des scores en card 2024-04-05 14:33:54 +02:00
96b7b1cbd7 Ajout de la card Timer pour créer un chronomètre de jeu 2024-04-05 14:29:08 +02:00
080601b792 Ajustement du placement des score, tout est bien aligné et centré 2024-04-05 14:12:22 +02:00
c3b86cb68b Modification des données affichée dans la gestion des scores, ajout de la notion Total et Manche (affichage de la manche courante et du score total) 2024-04-01 17:40:18 +02:00
44ce39bf3f Merge remote-tracking branch 'origin/soundboard-server' into DataWorking 2024-04-01 16:30:40 +02:00
995cca83ae Modification des données affichée dans la gestion des scores en vu d'une migration vers du dialog via WebSocket entre le serveur backend et le frontend 2024-04-01 15:03:27 +02:00
61 changed files with 2358 additions and 678 deletions

View File

@ -1,13 +1,7 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brain Blast</title>
<head> <meta charset="UTF-8"> <link rel="icon" href="/favicon.ico"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Brain Blast</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<body> <div id="app"></div> <script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -1,8 +1,5 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
"compilerOptions": { "paths": { "@/*": ["./src/*"] }
},
"exclude": ["node_modules", "dist"]
}

1497
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,11 @@
{
"name": "brain-blast",
"version": "0.0.0",
"private": true,
"private": false,
"type": "module",
"scripts": {
"dev": "vite",
"dev-complete": "concurrently \"vite\" \"node ./src/services/buzzerWatcher.js\"",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
@ -12,15 +13,19 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"express": "^5.0.0",
"mqtt": "^5.3.5",
"ping": "^0.4.4",
"roboto-fontface": "^0.10.0",
"vue": "^3.4.19",
"vue-router": "^4.2.5"
"vue-router": "^4.2.5",
"vuex": "^4.1.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^8.0.0",
"concurrently": "^8.2.2",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^3.0.3",

View File

@ -1,11 +1,11 @@
<template>
<v-app>
<BrainBlastBar />
<GameStatus v-if="$route.name === 'Game Control (Présentateur)'"></GameStatus>
<v-main>
<RouterView />
</v-main>
<!-- <v-footer class="footer" :elevation=12 border><v-row justify="center">© 2024 - ASCO section Fablab</v-row></v-footer> -->
<v-app>
<BrainBlastBar />
<GameStatus v-if="$route.name === 'Game Control (Présentateur)'">
</GameStatus>
<v-main>
<RouterView />
</v-main> <!-- <v-footer class="footer" :elevation=12 border><v-row justify="center">© 2024 - ASCO section Fablab</v-row></v-footer> -->
</v-app>
</template>
@ -17,11 +17,11 @@ import GameStatus from '@/components/GameStatus.vue'
</script>
<style scoped>
.footer {
position: fixed; /* Fixe le footer en bas de la page */
bottom: 0; /* Aligne le footer en bas de la page */
left: 0; /* Aligne le footer à gauche */
width: 100%; /* Ajuste la largeur du footer en fonction de la largeur de l'écran */
.footer {
position: fixed; /* Fixe le footer en bas de la page */
bottom: 0; /* Aligne le footer en bas de la page */
left: 0; /* Aligne le footer à gauche */
width: 100%; /* Ajuste la largeur du footer en fonction de la largeur de l'écran */
z-index: 1000; /* Assure que le footer est au-dessus des autres éléments */
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
ui/src/assets/design.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

View File

@ -1,27 +1,10 @@
<template>
<v-app-bar :elevation="5" height="50">
<RouterMenu />
<v-app-bar-title>Brain Blast</v-app-bar-title>
<template v-slot:append>
<v-btn icon @click="toggleTheme">
<v-icon>{{ darkTheme ? 'mdi-white-balance-sunny' : 'mdi-moon-waning-crescent' }}</v-icon>
</v-btn>
</template>
<v-app-bar :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 { ref } from 'vue'
import { useTheme } from 'vuetify'
import RouterMenu from '@/components/RouterMenu.vue'
const theme = useTheme()
const darkTheme = ref(true)
function toggleTheme() {
darkTheme.value = !darkTheme.value
theme.global.name.value = theme.global.current.value.dark ? 'CustomThemeLight' : 'CustomThemeDark'
}
</script>

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" message="TEAM_RED-2"rounded>
<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" message="TEAM_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" message="TEAM_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" message="TEAM_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" message="TEAM_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" message="TEAM_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" message="TEAM_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" message="TEAM_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 orange card xs12 sm6 md3 " topic="/game/score" message="TEAM_ORANGE-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 orange card xs12 sm6 md3 " topic="/game/score" message="TEAM_ORANGE-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 orange card xs12 sm6 md3 " topic="/game/score" message="TEAM_ORANGE+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 orange card xs12 sm6 md3 " topic="/game/score" message="TEAM_ORANGE+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" message="TEAM_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" message="TEAM_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" message="TEAM_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" message="TEAM_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

@ -1,36 +1,34 @@
<template>
<v-card tile outlined class="card">
<v-card-title class="card__title primary">
<v-icon left class="white--text pr-5 pl-2" size="40">mdi-camera-control</v-icon>
Contrôles
</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="220" height="110" class="btn xs12 sm6 md3" topic="/display/control" message="previous">
<v-icon left size="80">mdi-skip-previous</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="5" class="mt-4">
<mqtt-button width="240" height="110" class="btn card xs12 sm6 md3" topic="/display/control" message="next">
<v-icon left size="80">mdi-skip-next</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="5" class="mb-4">
<mqtt-button width="240" height="110" class="btn card xs12 sm6 md3" topic="/display/control" message="pause">
<v-icon left size="80">mdi-pause</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="5" class="mb-4">
<mqtt-button width="240" height="110" class="btn card xs12 sm6 md3 " topic="/display/control" message="play">
<v-icon left size="80">mdi-play</v-icon>
</mqtt-button>
</v-col>
</v-row>
</v-container>
<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,45 @@
<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">{{ 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">{{ 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">{{ 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">{{ 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-orange"> <div> <v-label class="labelRoundScore-style pt-3">Manche</v-label> <div> <v-label class="labelRoundScore-style">{{ 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">{{ 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">{{ 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">{{ GreenTotalScore }}</v-label> </div> </div> </v-col> </v-row> </v-col> </v-row>
</template>
<script setup>
import { ref } from 'vue'; // Import des fonctions de Vue 3
import variables from '@/variables.js';
// Déclaration des variables locales pour les scores
const RedTotalScore = ref(variables.RedTotalScore);
const BlueTotalScore = ref(variables.BlueTotalScore);
const OrangeTotalScore = ref(variables.OrangeTotalScore);
const GreenTotalScore = ref(variables.GreenTotalScore);
const RedRoundScore = ref(variables.RedRoundScore);
const BlueRoundScore = ref(variables.BlueRoundScore);
const OrangeRoundScore = ref(variables.OrangeRoundScore);
const GreenRoundScore = ref(variables.GreenRoundScore);
</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-orange { background-color: #d48f28 !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

@ -1,30 +1,46 @@
<template>
<v-card tile outlined class="card">
<v-card-title class="card__title feedback">
<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="400" src="https://c4.wallpaperflare.com/wallpaper/908/893/291/funny-middle-finger-black-background-wallpaper-preview.jpg" style="margin: 0 auto;"></v-img>
</v-container>
</v-row>
</v-container>
</v-card>
<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 */
}
}
.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

@ -1,43 +1,61 @@
<template>
<v-card tile outlined class="card">
<v-card-title class="card__title primary">
<v-icon left class="white--text pr-5 pl-2" size="40">mdi-music-box-multiple</v-icon>
Soundboard
</v-card-title>
<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" width="200" height="110" topic="/sound/playsound" message="good-response" rounded>
<v-icon size="70">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" width="200" height="110" topic="/sound/playsound" message="bad-response" rounded>
<v-icon size="70">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" width="200" height="110" topic="/sound/playsound" message="timer" rounded>
<v-icon size="70">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" width="220" height="110" topic="/sound/playsound" message="applause" rounded>
<v-icon size="70">mdi-human-handsup</v-icon>
</mqtt-button>
</v-col>
<v-col cols="12" sm="6" md="4" class="mb-4">
<mqtt-button class="btn" width="220" height="110" topic="/sound/playsound" message="bell" rounded>
<v-icon size="70">mdi-bell-outline</v-icon>
</mqtt-button>
</v-col>
</v-row>
<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

@ -1,95 +1,41 @@
<template>
<v-navigation-drawer width="250">
<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 color="BuzzerRed">mdi-radiobox-marked</v-icon>
<v-icon color="BuzzerBlue">mdi-radiobox-marked</v-icon>
<v-icon color="BuzzerOrange">mdi-radiobox-marked</v-icon>
<v-icon color="BuzzerGreen">mdi-radiobox-marked</v-icon>
</v-row>
<v-divider :thickness="2" class="border-opacity-100" color="primary"/>
<div class="label-pos">
<v-label class="labelTitle-style pb-7">Scores</v-label>
</div>
<div>
<v-row no-gutters justify="space-around" class="scorebox-1-pos">
<v-row>
<v-col class="align-start scorediv-style-red pr-1">
<v-label class="labelScore-style">{{ localStorageVars.RedScore }}</v-label>
</v-col>
<v-col class="align-start scorediv-style-blue pl-1">
<v-label class="labelScore-style">{{ localStorageVars.BlueScore }}</v-label>
</v-col>
</v-row>
</v-row>
</div>
<div>
<v-row no-gutters justify="space-around" class="scorebox-2-pos mb-0">
<v-row>
<v-col class="align-start scorediv-style-orange pr-1">
<v-label class="labelScore-style">{{ localStorageVars.OrangeScore }}</v-label>
</v-col>
<v-col class="align-start scorediv-style-green pl-1">
<v-label class="labelScore-style">{{ localStorageVars.GreenScore }}</v-label>
</v-col>
</v-row>
</v-row>
</div>
</v-navigation-drawer>
<v-navigation-drawer width="250"> <!-- Augmenter la largeur pour deux équipes côte à côte -->
<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 color="BuzzerRed">mdi-radiobox-marked</v-icon>
<v-icon color="BuzzerBlue">mdi-radiobox-marked</v-icon>
<v-icon color="BuzzerOrange">mdi-radiobox-marked</v-icon>
<v-icon color="BuzzerGreen">mdi-radiobox-marked</v-icon>
</v-row>
<v-divider :thickness="2" class="border-opacity-100" color="primary"/>
<CardScore/>
<CardTimer/>
</v-navigation-drawer>
</template>
<script setup>
import { localStorageVars } from '@/variables.js';
localStorage.setItem('RedScore', 10);
localStorage.setItem('BlueScore', 11);
localStorage.setItem('OrangeScore', 12);
localStorage.setItem('GreenScore', 13);
import CardTimer from '@/components/CardTimer.vue'
import CardScore from '@/components/CardScore.vue'
import { ref } from 'vue'; // Import des fonctions de Vue 3
import variables from '@/variables.js';
</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;
}
.labelScore-style{
opacity: 100% !important;
}
.button-pos{
padding-top: 10px;
padding-bottom: 15px;
}
.scorebox-1-pos{
padding-bottom: 15px;
text-align: center;
margin:auto;
}
.scorebox-2-pos{
padding-top: 9px;
text-align: center;
margin:auto;
}
.scorediv-style-red{
background-color: #d42828 !important;
}
.scorediv-style-orange{
background-color: #d48f28 !important;
}
.scorediv-style-blue{
background-color: #2867d4 !important;
}
.scorediv-style-green{
background-color: #28d42e !important;
}
.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

@ -1,23 +1,58 @@
<template>
<div>
<h1>Console MQTT</h1>
<div v-for="(message, index) in messages" :key="index">{{ message }}</div>
</div>
<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="40">mdi-console-line</v-icon>
Console MQTT
</v-card-title>
<v-container class="text-center">
<div v-for="(log, index) in messageLogs" :key="index">
<v-label class="v-label-timestamp">{{ log.timestamp }} </v-label> -
<v-label>{{ log.message }}</v-label>
</div>
</v-container>
</v-card>
</v-container>
</template>
<script>
import { subscribeToTopic } from '@/services/mqttService'
import { subscribeToTopic } from '@/services/mqttService'
export default {
data() {
return {
messages: [] // Initialiser un tableau pour stocker les messages MQTT
}
},
created() {
subscribeToTopic('#', (topic, message) => {
this.messages.push(`Topic: ${topic}, Message: ${message}`) // Ajouter le message à la liste des messages
}) // S'abonner à tous les topics MQTT
}
}
export default {
data() { return {
messageLogs: [] // Initialiser un tableau pour stocker les messages MQTT avec horodatage
}
},
created() { subscribeToTopic('#', (topic, message) => { // Obtenir l'horodatage actuel
const timestamp = new Date().toLocaleString('fr-FR', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// Ajouter le message avec l'horodatage à la liste des messages
this.messageLogs.push({ timestamp, message: `Topic: ${topic}, Message: ${message}` });
// Limiter la liste à 10 messages
if (this.messageLogs.length > 10) {
this.messageLogs.shift(); // Supprimer le premier élément (le plus ancien)
} }) // S'abonner à tous les topics MQTT
}
}
</script>
<style>
.v-container-style {
display: flex;
justify-content: center;
}
.centered-title {
text-align: center;
}
.v-label-timestamp{
opacity: 100%;
font-weight: 700;
color: #d42828;
}
</style>

View File

@ -1,40 +1,52 @@
<template>
<div>
<h1>Publier sur MQTT</h1>
<select v-model="selectedTopic">
<option v-for="topic in topics" :key="topic">{{ topic }}</option>
</select>
<input type="text" v-model="message" placeholder="Saisissez votre message" />
<button @click="publishMessage">Publier sur MQTT</button>
</div>
<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 class="v-btn-style" height="50" @click="publishMessage">Publier</v-btn>
</v-card>
</v-container>
</template>
<script>
import { publishMessage } from '@/services/mqttService'
export default {
data() {
return {
message: '', // Initialiser la variable message
selectedTopic: 'topic1',
topics: [
'topic1',
'topic2',
'topic3',
'topic4',
'topic5',
'topic6',
'topic7',
'topic8',
'topic9',
'topic10'
] // Liste des topics
}
},
methods: {
publishMessage() {
publishMessage(this.selectedTopic, this.message)
}
data() { return { message: '', // Initialiser la variable message
selectedTopic: 'topic1',
topics: [ '/display/control', '/sound/playsound', 'topic3', 'topic4', 'topic5', 'topic6', 'topic7', 'topic8', 'topic9', 'topic10' ] // Liste des topics
}
},
methods: {
publishMessage() {
publishMessage(this.selectedTopic, this.message) }
}
}
</script>
<style>
.v-container-style {
align-items: center;
justify-content: center;
}
.input-style{
margin: 20px;
}
.v-btn-style{
align-items: center;
justify-content: center;
width: 100%;
background-color: #d42828; /* Changez la couleur en fonction de votre thème */
border-top-right-radius: 0%;
border-top-left-radius: 0%;
}
.centered-title {
text-align: center;
}
</style>

View File

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

View File

@ -1,24 +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 routes" :key="route.name" :to="route.path">{{ route.name }}</v-list-item>
<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 } from 'vue'
import { useRouter } from 'vue-router'
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter()
const routes = router.options.routes
const router = useRouter();
const routes = router.options.routes;
let menu = ref(false)
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>
.menu-below-bar {
margin-top: 48px; /* La hauteur de la barre d'application */
}
</style>

View File

@ -4,5 +4,13 @@
// config.js
export default {
mqttBrokerUrl: 'ws://localhost:9001'
mqttBrokerUrl: 'ws://192.168.1.78:9001',
// Buzzer
redBuzzerIP: '',
blueBuzzerIP: '',
orangeBuzzerIP: '',
greenBuzzerIP: ''
// Light
};

View File

@ -3,40 +3,30 @@ import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'Home',
component: HomeView
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('@/views/AboutView.vue')
},
{
path: '/game/control',
name: 'Game Control (Présentateur)',
component: () => import('@/views/GameControl.vue')
},
{
path: '/game/display',
name: 'Game Display (Projection)',
component: () => import('@/views/GameDisplay.vue')
},
{
path: '/mqtt-debugger',
name: 'Debugger MQTT',
component: () => import('@/views/MQTTDebugView.vue')
},
{
path: '/settings',
name: 'Paramètres',
component: () => import('@/views/SettingsView.vue')
}
routes: [ {
path: '/',
name: 'Accueil',
component: HomeView
},
{
path: '/game/control',
name: 'Game Control (Présentateur)',
component: () => import('@/views/GameControl.vue')
},
{
path: '/game/display',
name: 'Game Display (Projection)',
component: () => import('@/views/GameDisplay.vue')
},
{
path: '/mqtt-debugger',
name: 'Debugger MQTT',
component: () => import('@/views/MQTTDebugView.vue')
},
{
path: '/settings',
name: 'Paramètres',
component: () => import('@/views/SettingsView.vue') }
]
})

View File

@ -13,42 +13,45 @@ import { createVuetify } from 'vuetify'
const CustomThemeDark = {
dark: true,
colors: {
background: '#121212',
primary: '#e91e1e',
secondary: '#F44336',
accent: '#FFC107',
error: '#e91e1e',
warning: '#FFC107',
info: '#607D8B',
success: '#e91e1e',
BuzzerBlue: '#2867d4',
BuzzerOrange: '#d48f28',
BuzzerRed: '#d42828',
colors: {
background: '#121212',
primary: '#d42828',
secondary: '#F44336',
accent: '#FFC107',
error: '#e91e1e',
warning: '#FFC107',
info: '#607D8B',
success: '#e91e1e',
BuzzerBlue: '#2867d4',
BuzzerOrange: '#d48f28',
BuzzerRed: '#d42828',
BuzzerGreen: '#28d42e',
}
}
const CustomThemeLight = {
dark: false,
colors: {
background: '#ffffff',
primary: '#e91e1e',
secondary: '#F44336',
accent: '#FFC107',
error: '#e91e1e',
warning: '#FFC107',
info: '#607D8B',
success: '#4CAF50'
colors: {
background: '#ffffff',
primary: '#d42828',
secondary: '#F44336',
accent: '#FFC107',
error: '#e91e1e',
warning: '#FFC107',
info: '#607D8B',
success: '#4CAF50',
BuzzerBlue: '#2867d4',
BuzzerOrange: '#d48f28',
BuzzerRed: '#d42828',
BuzzerGreen: '#28d42e',
}
}
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
theme: {
defaultTheme: 'CustomThemeDark',
themes: {
CustomThemeDark,
CustomThemeLight,
},
theme: {
defaultTheme: 'CustomThemeDark',
themes: {
CustomThemeDark,
CustomThemeLight, },
},
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -0,0 +1,27 @@
name: "Histoire & Géographie"
questions:
1:
Q: "Quelle bataille célèbre s'est déroulée en 1815, marquant la défaite de Napoléon Bonaparte ?"
T: "Elle a eu lieu en Belgique"
R: "La bataille de Waterloo."
P: "Q-1.jpeg"
2:
Q: "Quelle est la capitale de l'Australie ?"
T: "Le nom de cette ville commence par la lettre 'C'"
R: "Canberra."
P: "Q-2.jpeg"
3:
Q: "En quelle année la Seconde Guerre mondiale a-t-elle pris fin ?"
T: "C'est au milieu des années 40."
R: "En 1945."
P: "Q-3.jpeg"
4:
Q: "Quel fleuve traverse la ville du Caire en Égypte ?"
T: "C'est l'un des plus longs fleuves du monde"
R: "Le Nil."
P: "Q-4.jpeg"
5:
Q: "Quel pays a été divisé par un mur de 1961 à 1989 ?"
T: "Sa chute a marqué la fin de la guerre froide."
R: "L'Allemagne (le mur de Berlin)."
P: "Q-5.jpeg"

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

View File

View File

View File

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

@ -0,0 +1,27 @@
name: "Jeux vidéos"
questions:
1:
Q: "Quel personnage de jeu vidéo est un plombier moustachu qui saute sur des ennemis pour sauver une princesse ?"
T: "Cest le personnage le plus célèbre de Nintendo, son nom commence par 'M'."
R: "Mario."
P: "Q-1.jpeg"
2:
Q: "Quel jeu vidéo multijoueur de football avec des voitures est très populaire ?"
T: "Il s'agit d'un mélange de sport et de voitures rapides."
R: "Rocket League."
P: "Q-2.jpeg"
3:
Q: "Quel jeu vidéo mobile consiste à faire exploser des bonbons en alignant trois pièces identiques ?"
T: "Son nom fait référence aux bonbons."
R: "Candy Crush Saga."
P: "Q-3.jpeg"
4:
Q: "Quel est le nom du célèbre personnage bleu de SEGA qui court à une vitesse incroyable ?"
T: "Son nom commence par la lettre 'S' et c'est un hérisson."
R: "Sonic"
P: "Q-4.jpeg"
5:
Q: "Quel jeu permet de construire et explorer un monde fait de blocs, tout en survivant face à des monstres ?"
T: "Le monde est entièrement fait de blocs carrés."
R: "Minecraft."
P: "Q-5.jpeg"

View File

@ -0,0 +1,64 @@
// index.js
import Vue from 'vue';
import Vuex from 'vuex';
import ping from 'ping';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
buzzerRedState: '',
buzzerBlueState: '',
buzzerOrangeState: '',
buzzerGreenState: ''
},
mutations: {
setBuzzerRedState(state, newState) {
state.buzzerRedState = newState;
},
setBuzzerBlueState(state, newState) {
state.buzzerBlueState = newState;
},
setBuzzerOrangeState(state, newState) {
state.buzzerOrangeState = newState;
},
setBuzzerGreenState(state, newState) {
state.buzzerGreenState = newState;
}
},
actions: {
updateBuzzerStates({ commit }, { index, newState }) {
switch (index) {
case 0:
commit('setBuzzerRedState', newState);
break;
case 1:
commit('setBuzzerBlueState', newState);
break;
case 2:
commit('setBuzzerOrangeState', newState);
break;
case 3:
commit('setBuzzerGreenState', newState);
break;
default:
console.error('Index de buzzer invalide:', index);
}
}
}
});
const hosts = ['192.168.1.1', '192.168.1.2', '192.168.1.3', '192.168.1.4'];
async function pingHosts() {
hosts.forEach(async (host, index) => { try {
const res = await ping.promise.probe(host);
store.dispatch('updateBuzzerStates', {index, newState: res.alive ? 'ON' : 'OFF' }); }
catch (error) {
console.error(`Erreur lors du ping de ${host}:`, error.message); }
});
}
pingHosts();
module.exports = store;

View File

@ -13,7 +13,6 @@ export function publishMessage(topic, message) {
// Fonction pour s'abonner à un topic MQTT et écouter les messages entrants
export function subscribeToTopic(topic, callback) {
client.subscribe(topic)
client.on('message', (receivedTopic, message) => {
callback(receivedTopic.toString(), message.toString())
client.on('message', (receivedTopic, message) => { callback(receivedTopic.toString(), message.toString())
})
}

View File

@ -0,0 +1,43 @@
import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const app = express();
const port = 3000;
// Obtenir le chemin du répertoire parent
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Middleware pour gérer les requêtes depuis le frontend
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
next();
});
// Le dossier assets est situé un niveau au-dessus du dossier services
const assetsDir = path.join(__dirname, '..', 'quizz/geography-history');
console.log(assetsDir)
// Middleware pour servir les fichiers statiques
app.use('/images', express.static(assetsDir));
// API pour lister les fichiers d'image
app.get('/images-list', async (req, res) => {
try {
const files = await fs.readdir(assetsDir);
// Filtrer pour ne renvoyer que les fichiers d'image (par ex : .jpg, .png)
const images = files.filter(file => /\.(jpg|jpeg|png|gif)$/.test(file));
res.json(images);
} catch (err) {
res.status(500).send('Erreur lors de la lecture du dossier');
}
});
app.listen(port, () => {
console.log(`Serveur démarré sur http://localhost:${port}`);
});

View File

@ -1,8 +1,27 @@
export default {
// Gestion des score et des Buzzers
// Scores totaux
RedTotalScore: 11,
BlueTotalScore: 22,
GreenTotalScore: 33,
OrangeTotalScore: 44,
// Score de la manche courante
RedRoundScore: 1,
BlueRoundScore: 2,
OrangeRoundScore: 3,
GreenRoundScore: 4,
//Etat des buzzer
BuzzerRed: false,
BuzzerBlue: false,
BuzzerOrange: false,
BuzzerGreen: false,
// Ajoutez d'autres variables globales ici
};
// Variables localStorage
export const localStorageVars = {
// Exemple de variable localStorage
RedScore: localStorage.getItem('RedScore') || '',
BlueScore: localStorage.getItem('BlueScore') || '',
OrangeScore: localStorage.getItem('OrangeScore') || '',
GreenScore: localStorage.getItem('GreenScore') || '',
export const localStorageVars = { // Exemple de variable localStorage RedScorelocal: localStorage.getItem('RedScore') || '', BlueScorelocal: localStorage.getItem('BlueScore') || '', OrangeScorelocal: localStorage.getItem('OrangeScore') || '', GreenScorelocal: localStorage.getItem('GreenScore') || '',
};

View File

@ -1,24 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
<hr/>
<p>
Exemple d'une page
</p><p>
Ou il y a un scroll
</p><p>
Pour mettre en valeur leu footer fixe en bas de page
</p><p>
Cette page n'a aucun interet et pourra etre supprimée
</p>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
align-items: center;
}
}
</style>

View File

@ -1,21 +1,24 @@
<template>
<v-container>
<v-row no-gutters>
<v-col class="align-start">
<card-control />
</v-col>
<v-col class="pl-3">
<card-soundboard />
<v-row no-gutters>
<v-col class="align-start">
<card-control />
</v-col>
<v-col class="pl-3">
<card-soundboard />
</v-col>
</v-row>
</v-container>
<v-container>
<v-row no-gutters>
<v-col>
<card-solution />
</v-col>
<v-row no-gutters class="pr-4 pl-4">
<v-row no-gutters>
<v-col class="align-start">
<CardButtonScore />
</v-col>
<v-col class="pl-3">
<card-solution />
</v-col>
</v-row>
</v-row>
</v-container>
</template>
<script setup>
@ -23,20 +26,52 @@
import CardSolution from '@/components/CardSolution.vue'
import CardControl from '@/components/CardControl.vue'
import CardSoundboard from '@/components/CardSoundboard.vue';
import CardButtonScore from '@/components/CardButtonScore.vue'
</script>
<style>
@media (min-width: 1024px) {
.card__title.primary {
background-color: #e91e1e; /* Changez la couleur en fonction de votre thème */
.card__title.primary {
background-color: #d42828; /* Changez la couleur en fonction de votre thème */
}
.card__title.feedback {
background-color: #2E7D32; /* Changez la couleur en fonction de votre thème */
.card__title.feedback {
background-color: #2E7D32; /* Changez la couleur en fonction de votre thème */
}
.btn{
border-radius:30px!important;
background-color: #e91e1e; /* Changez la couleur en fonction de votre thème */
.btn{
border-radius:20px!important;
}
.btn.red {
background-color: #d42828; /* Changez la couleur en fonction de votre thème */
}
.btn.blue {
background-color: #2867d4; /* Changez la couleur en fonction de votre thème */
}
.btn.orange {
background-color: #d48f28; /* Changez la couleur en fonction de votre thème */
}
.btn.green {
background-color: #28d42e; /* Changez la couleur en fonction de votre thème */
}
.scorediv-style-red {
background-color: #d42828 !important;
padding: 15px;
border-top-left-radius: 10%;
}
.scorediv-style-orange {
background-color: #d48f28 !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,53 @@
<template>
<v-container>
<v-row>
<v-col
v-for="image in images"
:key="image"
cols="12"
sm="6"
md="4"
>
<v-card>
<v-img
:src="`http://localhost:3000/images/${image}`"
:alt="image"
class="image-thumbnail"
/>
<v-card-title>{{ image }}</v-card-title>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// Déclare une variable réactive pour stocker les images
const images = ref([]);
// Fonction pour récupérer les images depuis l'API
const fetchImages = async () => {
try {
const response = await fetch('http://localhost:3000/images-list');
const data = await response.json();
images.value = data; // Met à jour les images
} catch (error) {
console.error('Erreur lors de la récupération des images :', error);
}
};
// Appelle la fonction fetchImages lorsque le composant est monté
onMounted(() => {
fetchImages();
});
</script>
<style scoped>
.image-thumbnail {
max-width: 100%;
height: auto;
}
</style>

View File

@ -1,10 +1,6 @@
<template>
<div>
<!-- Zone pour publier sur MQTT -->
<PublishMQTTComponent />
<!-- Zone pour afficher la console MQTT -->
<MQTTConsoleComponent />
<div> <!-- Zone pour publier sur MQTT --> <PublishMQTTComponent />
<!-- Zone pour afficher la console MQTT --> <MQTTConsoleComponent />
</div>
</template>
@ -13,9 +9,18 @@ import PublishMQTTComponent from '@/components/MQTTDebugPublish.vue' // Importer
import MQTTConsoleComponent from '@/components/MQTTDebugConsole.vue' // Importer le composant pour la console MQTT
export default {
components: {
PublishMQTTComponent, // Composant pour publier sur MQTT
MQTTConsoleComponent // Composant pour la console MQTT
components: { PublishMQTTComponent, // Composant pour publier sur MQTT MQTTConsoleComponent // Composant pour la console MQTT
}
}
</script>
<style>
@media (min-width: 1024px) {
.card__title.primary { background-color: #d42828; /* Changez la couleur en fonction de votre thème */
}
.card__title.feedback { background-color: #2E7D32; /* Changez la couleur en fonction de votre thème */
}
.btn{ border-radius:30px!important; background-color: #d42828; /* Changez la couleur en fonction de votre thème */
}
}
</style>

View File

@ -1,71 +1,132 @@
<template>
<template>
<v-label class="title-style-1">Paramètres</v-label>
<v-divider :thickness="2" class="border-opacity-100" color="primary"/>
<v-label class="title-style-2">Son</v-label>
<div class="mutltiple-per-line">
<v-switch hide-details label="Activer le son intégré" v-model="EmbeddedSound" class="ml-15" color="primary"/>
<div>
<v-slider hide-details class="v-slider-style ml-15" :disabled="EmbeddedSound === false" v-model="EmbeddedSoundVolume" color="primary"/>
</div>
</div>
<v-switch label="Activer le son MQTT" v-model="MQTTSound" class="ml-15" color="primary"/>
<v-divider />
<v-label class="title-style-2">Affichage</v-label>
<div>
<v-switch hide-details label="Activer l'affichage des sattelites" v-model="SattelitesDisplay" class="ml-15 pb-3" color="primary"/>
</div>
<v-divider />
<v-label class="title-style-2">MQTT</v-label>
<div class="mutltiple-per-line">
<v-icon v-model="MQTTBrokerState" class="ml-15 mb-5" color="error" icon="record">mdi-record</v-icon>
<v-label class="ml-2 mb-10 mt-5">Etat du serveur MQTT</v-label>
<v-btn class="ml-10 mb-5" color="primary" @click="goToDebugRoute">Debugger</v-btn>
</div>
<v-divider />
<v-label class="title-style-2">Jeu</v-label>
<div class="mutltiple-per-line">
<v-switch hide-details label='Jouer le son de succès lorsque des points sont ajoutés' v-model="SuccessPlay" class="ml-15" color="primary"/>
</div>
<v-switch hide-details label='Jouer le son de erreur lorsque des points sont enlevés' v-model="ErrorPlay" class="ml-15" color="primary"/>
<h1 class="title mb-4 ml-5 mt-5">Paramètres</h1>
<v-divider :thickness="2" class="border-opacity-100" color="primary"/>
<h2 class="title ml-10 mb-5 mt-5">Son</h2>
<div style="display: flex; align-items: center;">
<v-switch label="Activer le son intégré" v-model="EmbeddedSound" class="ml-15" color="primary"/>
<div style="width: 250px; margin-left: 16px;">
<v-slider class="ml-15" :disabled="EmbeddedSound === false" v-model="EmbeddedSoundVolume" color="primary"/>
</div>
</div>
<v-switch label="Activer le son MQTT" v-model="MQTTSound" class="ml-15" color="primary"/>
<v-divider />
<h2 class="title ml-10 mb-5 mt-5">Affichage</h2>
<v-switch label="Activer l'affichage des sattelites" v-model="SattelitesDisplay" class="ml-15" color="primary"/>
<v-divider />
<h2 class="title ml-10 mb-5 mt-5">MQTT</h2>
<div style="display: flex; align-items: center;">
<v-icon v-model="MQTTBrokerState" class="ml-15 mb-5" color="error" icon="record">mdi-record</v-icon>
<v-label class="ml-2 mb-10 mt-5">Etat du serveur MQTT</v-label>
</div>
<v-divider />
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
const EmbeddedSound = ref(false); // Définition d'une référence pour la case à cocher. Initialement décochée.
const EmbeddedSoundVolume = ref(50); // Définition d'une référence pour la case à cocher. Initialement décochée.
const MQTTSound = ref(false); // Définition d'une référence pour la case à cocher. Initialement décochée.
const MQTTBrokerState = ref(false); // Définition d'une référence pour la case à cocher. Initialement décochée.
const SattelitesDisplay = ref(false);
const SuccessPlay = ref(false);
const ErrorPlay = ref(false);
const router = useRouter();
onMounted(() => {
if (localStorage.getItem('EmbeddedSound')) {
EmbeddedSound.value = localStorage.getItem('EmbeddedSound') === 'true'; // Si l'état de la case à cocher est stocké, le mettre dans la référence
}
if (localStorage.getItem('MQTTSound')) {
MQTTSound.value = localStorage.getItem('MQTTSound') === 'true'; // Si l'état de la case à cocher est stocké, le mettre dans la référence
}
if (localStorage.getItem('EmbeddedSoundVolume')) {
EmbeddedSoundVolume.value = localStorage.getItem('EmbeddedSoundVolume'); // Si l'état de la case à cocher est stocké, le mettre dans la référence
}
if (localStorage.getItem('SattelitesDisplay')) {
SattelitesDisplay.value = localStorage.getItem('SattelitesDisplay') === 'true'; // Added a default value for this switch
}
});
watch(EmbeddedSound, (EmbeddedSoundNewValue) => {
if (EmbeddedSoundNewValue !== null) {
localStorage.setItem('EmbeddedSound', EmbeddedSoundNewValue); // Mettre à jour l'état de la case à cocher dans le LocalStorage chaque fois qu'il change.
}
});
watch(EmbeddedSoundVolume, (EmbeddedSoundVolumeNewValue) => {
if (EmbeddedSoundVolumeNewValue !== null) {
localStorage.setItem('EmbeddedSoundVolume', EmbeddedSoundVolumeNewValue); // Mettre à jour l'état de la case à cocher dans le LocalStorage chaque fois qu'il change.
}
});
watch(MQTTSound, (MQTTSoundNewValue) => {
const goToDebugRoute = () => {
router.push({
name: 'Debugger MQTT'
}); // Redirige vers la route nommée 'debugger'
};
if (MQTTSoundNewValue !== null) {
localStorage.setItem('MQTTSound', MQTTSoundNewValue); // Mettre à jour l'état de la case à cocher dans le LocalStorage chaque fois qu'il change.
}
onMounted(() => {
if (localStorage.getItem('EmbeddedSound')) {
EmbeddedSound.value = localStorage.getItem('EmbeddedSound') === 'true'; // Si l'état de la case à cocher est stocké, le mettre dans la référence
}
if (localStorage.getItem('MQTTSound')) {
MQTTSound.value = localStorage.getItem('MQTTSound') === 'true'; // Si l'état de la case à cocher est stocké, le mettre dans la référence
}
if (localStorage.getItem('EmbeddedSoundVolume')) {
EmbeddedSoundVolume.value = localStorage.getItem('EmbeddedSoundVolume'); // Si l'état de la case à cocher est stocké, le mettre dans la référence
}
if (localStorage.getItem('SattelitesDisplay')) {
SattelitesDisplay.value = localStorage.getItem('SattelitesDisplay') === 'true'; // Added a default value for this switch
}
if (localStorage.getItem('SuccessPlay')) {
SuccessPlay.value = localStorage.getItem('SuccessPlay') === 'true'; // Added a default value for this switch
}
if (localStorage.getItem('ErrorPlay')) {
ErrorPlay.value = localStorage.getItem('ErrorPlay') === 'true'; // Added a default value for this switch
}
});
watch(SattelitesDisplay, (SattelitesDisplaynewValue) => {
if (SattelitesDisplaynewValue !== null) {
localStorage.setItem('SattelitesDisplay', SattelitesDisplaynewValue); // Added a default value for this switch
}
});
</script>
watch(EmbeddedSound, (EmbeddedSoundNewValue) => {
if (EmbeddedSoundNewValue !== null) {
localStorage.setItem('EmbeddedSound', EmbeddedSoundNewValue); // Mettre à jour l'état de la case à cocher dans le LocalStorage chaque fois qu'il change.
}
});
watch(EmbeddedSoundVolume, (EmbeddedSoundVolumeNewValue) => {
if (EmbeddedSoundVolumeNewValue !== null) {
localStorage.setItem('EmbeddedSoundVolume', EmbeddedSoundVolumeNewValue); // Mettre à jour l'état de la case à cocher dans le LocalStorage chaque fois qu'il change.
}
});
watch(MQTTSound, (MQTTSoundNewValue) => {
if (MQTTSoundNewValue !== null) {
localStorage.setItem('MQTTSound', MQTTSoundNewValue); // Mettre à jour l'état de la case à cocher dans le LocalStorage chaque fois qu'il change.
}
});
watch(SattelitesDisplay, (SattelitesDisplaynewValue) => {
if (SattelitesDisplaynewValue !== null) {
localStorage.setItem('SattelitesDisplay', SattelitesDisplaynewValue); // Added a default value for this switch
}
});
watch(SuccessPlay, (SuccessPlaynewValue) => {
if (SuccessPlaynewValue !== null) {
localStorage.setItem('SuccessPlay', SuccessPlaynewValue); // Added a default value for this switch
}
});
watch(ErrorPlay, (ErrorPlaynewValue) => {
if (ErrorPlaynewValue !== null) {
localStorage.setItem('ErrorPlay', ErrorPlaynewValue); // Added a default value for this switch
}
});
</script>
<style>
.title-style-1{
margin-top: 20px;
margin-bottom: 16px;
margin-left: 20px;
font-size: 30px;
opacity: 100%;
font-weight: 500;
}
.title-style-2{
margin-top: 20px;
margin-bottom: 10px;
margin-left: 40px;
font-size: 25px;
opacity: 100%;
font-weight: 500;
}
.mutltiple-per-line{
display: flex;
align-items: center;
}
.v-slider-style{
width: 250px;
margin-left: 16px;
padding-top: px;
}
</style>

View File

@ -9,24 +9,9 @@ import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
template: { transformAssetUrls }
}),
Vuetify(),
ViteFonts({
google: {
families: [{
name: 'Roboto',
styles: 'wght@100;300;400;500;700;900',
}]
}
}),
plugins: [ vue({ template: { transformAssetUrls } }), Vuetify(), ViteFonts({ google: { families: [{ name: 'Roboto', styles: 'wght@100;300;400;500;700;900', }] } }),
],
define: { 'process.env': {} },
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }
}
})