Sauvegarde initiale de l'application
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Ignore le dossier node_modules
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Ignore les fichiers de logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Ignore les fichiers lock
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Ignore les fichiers temporaires ou cache
|
||||||
|
*.tmp
|
||||||
|
*.cache
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Ignore les fichiers sensibles (exemple)
|
||||||
|
#config.yaml
|
||||||
|
|
||||||
|
# Ignore les fichiers compilés
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Autres fichiers à ignorer
|
||||||
|
.env
|
||||||
15
config.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
port: 3000
|
||||||
|
targetCoordinates:
|
||||||
|
latitude: 45.141916
|
||||||
|
longitude: 4.075059
|
||||||
|
assets:
|
||||||
|
backgroundImage: "static/img/fond.jpg"
|
||||||
|
arrowApproaching: "static/img/fleche_rouge.png"
|
||||||
|
arrowMovingAway: "static/img/fleche_bleu.png"
|
||||||
|
bart: "static/img/bart.png"
|
||||||
|
userFile: "data/baptiste.yaml" # Chemin vers le fichier user.yaml
|
||||||
|
position_update: 2 # Temps en secondes entre chaque mise à jour de la position
|
||||||
|
pin_offset:
|
||||||
|
x: -0 # Décalage horizontal (en pixels)
|
||||||
|
y: -0 # Décalage vertical (en pixels)
|
||||||
|
nb_valeur_gps_moy: 5
|
||||||
74
data/baptiste.yaml
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
nom: Baptiste
|
||||||
|
progression: defi 3/6
|
||||||
|
avatar: static/img/bart.png
|
||||||
|
position_actuelle:
|
||||||
|
longitude: 4.0750673
|
||||||
|
latitude: 45.1420026
|
||||||
|
last_update: '2024-12-15T18:35:09.072Z'
|
||||||
|
defis:
|
||||||
|
defi_1:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.075068
|
||||||
|
latitude: 45.141916
|
||||||
|
image_1: static/img/imageaaabb01.png
|
||||||
|
text_found_1: AZERT
|
||||||
|
mode: reponse
|
||||||
|
resolu: oui
|
||||||
|
reception: 0
|
||||||
|
contenu_reception: ceci est un test
|
||||||
|
pin: static/img/pin1.png
|
||||||
|
defi_2:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.076653
|
||||||
|
latitude: 45.14198
|
||||||
|
image_1: static/img/imageaaabb02.png
|
||||||
|
text_found_1: QWERT
|
||||||
|
mode: message
|
||||||
|
resolu: oui
|
||||||
|
reception: 1
|
||||||
|
contenu_reception: un autre test
|
||||||
|
pin: static/img/pin2.png
|
||||||
|
defi_3:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.073695
|
||||||
|
latitude: 45.139302
|
||||||
|
image_1: static/img/imageaaabb01.png
|
||||||
|
text_found_1: AZERT
|
||||||
|
mode: reponse
|
||||||
|
resolu: non
|
||||||
|
reception: 0
|
||||||
|
contenu_reception: ceci est un test
|
||||||
|
pin: static/img/pin3.png
|
||||||
|
defi_4:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.07735
|
||||||
|
latitude: 45.141932
|
||||||
|
image_1: static/img/imageaaabb02.jpg
|
||||||
|
text_found_1: QWERT
|
||||||
|
mode: message
|
||||||
|
resolu: non
|
||||||
|
reception: 1
|
||||||
|
contenu_reception: un autre test
|
||||||
|
pin: static/img/pin4.png
|
||||||
|
defi_5:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.073903
|
||||||
|
latitude: 45.140061
|
||||||
|
image_1: static/img/imageaaabb01.jpg
|
||||||
|
text_found_1: AZERT
|
||||||
|
mode: reponse
|
||||||
|
resolu: non
|
||||||
|
reception: 0
|
||||||
|
contenu_reception: ceci est un test
|
||||||
|
pin: static/img/pin5.png
|
||||||
|
defi_6:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.077286
|
||||||
|
latitude: 45.142316
|
||||||
|
image_1: static/img/imageaaabb02.jpg
|
||||||
|
text_found_1: QWERT
|
||||||
|
mode: message
|
||||||
|
resolu: non
|
||||||
|
reception: 1
|
||||||
|
contenu_reception: un autre test
|
||||||
|
pin: static/img/pin6.png
|
||||||
27
data/julien.yaml
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
nom: Julien
|
||||||
|
progression: defi 2/6
|
||||||
|
avatar: "static/img/julien.jpg"
|
||||||
|
position_actuelle:
|
||||||
|
longitude: 4.075159
|
||||||
|
latitude: 45.141956
|
||||||
|
defis:
|
||||||
|
defi_1:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.076159
|
||||||
|
latitude: 45.142956
|
||||||
|
image_1: "static/img/imageaaabb03.jpg"
|
||||||
|
text_found_1: "XYZ"
|
||||||
|
mode: reponse
|
||||||
|
resolu: oui
|
||||||
|
reception: 0
|
||||||
|
contenu_reception: "réussi"
|
||||||
|
defi_2:
|
||||||
|
geolocalisation:
|
||||||
|
longitude: 4.077159
|
||||||
|
latitude: 45.143956
|
||||||
|
image_2: "static/img/imageaaabb04.jpg"
|
||||||
|
text_found_2: "HELLO"
|
||||||
|
mode: rien
|
||||||
|
resolu: non
|
||||||
|
reception: 0
|
||||||
|
contenu_reception: ""
|
||||||
42
index.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Jeu Smartphone</title>
|
||||||
|
<link rel="stylesheet" href="static/css/style.css">
|
||||||
|
<script>
|
||||||
|
const username = 'baptiste'; // Charge les données de Baptiste
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content0">
|
||||||
|
<p id="user-name">--</p>
|
||||||
|
<p id="challenge-number">défi n° : --</p>
|
||||||
|
<p id="distance">-- m</p>
|
||||||
|
</div>
|
||||||
|
<div data-debug="true" id="content">
|
||||||
|
<h1> <span id="user-name">--</span></h1>
|
||||||
|
<p data-debug="true">Latitude : <span id="latitude">--</span></p>
|
||||||
|
<p data-debug="true">Longitude : <span id="longitude">--</span></p>
|
||||||
|
<p>Distance : <span id="distance">--</span> mètres</p>
|
||||||
|
<p data-debug="true">Progression : <span id="user-progression">--</span></p>
|
||||||
|
|
||||||
|
<div data-debug="true" id="challenge-info">
|
||||||
|
<p id="challenge-number">Défi n° : --</p>
|
||||||
|
<p data-debug="true">Latitude cible : <span id="target-latitude">--</span></p>
|
||||||
|
<p data-debug="true">Longitude cible : <span id="target-longitude">--</span></p>
|
||||||
|
</div>
|
||||||
|
<div id="arrow-container">
|
||||||
|
<img id="arrow-black" src="static/img/fleche_noir.png" alt="Flèche directionnelle" data-debug="true" >
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img id="arrow-approaching" src="static/img/fleche_rouge.png" alt="Vous vous rapprochez">
|
||||||
|
<img id="arrow-moving-away" src="static/img/fleche_bleu.png" alt="Vous vous éloignez">
|
||||||
|
<img id="user-avatar" src="" alt="Avatar" style="width: 100px; border-radius: 50%;">
|
||||||
|
|
||||||
|
|
||||||
|
<script src="static/js/script.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "jeu_smartphone_js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"js-yaml": "^4.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
81
server.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Activer le middleware JSON pour parser le corps des requêtes
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: true })); // Si vous utilisez des requêtes POST de type formulaire
|
||||||
|
|
||||||
|
// Charger config.yaml
|
||||||
|
const config = yaml.load(fs.readFileSync('config.yaml', 'utf8'));
|
||||||
|
|
||||||
|
// Route pour récupérer les données utilisateur
|
||||||
|
app.get('/user/:username', (req, res) => {
|
||||||
|
const username = req.params.username;
|
||||||
|
const userFilePath = path.join(__dirname, `data/${username}.yaml`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userData = yaml.load(fs.readFileSync(userFilePath, 'utf8'));
|
||||||
|
res.json(userData);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(404).json({ error: "Utilisateur non trouvé" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route pour mettre à jour la position dans baptiste.yaml
|
||||||
|
app.post('/update-position', (req, res) => {
|
||||||
|
const { longitude, latitude, last_update } = req.body; // Problème résolu ici
|
||||||
|
const userFilePath = path.join(__dirname, config.userFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("Tentative de mise à jour des coordonnées :", longitude, latitude, last_update);
|
||||||
|
|
||||||
|
const userData = yaml.load(fs.readFileSync(userFilePath, 'utf8'));
|
||||||
|
userData.position_actuelle.longitude = longitude;
|
||||||
|
userData.position_actuelle.latitude = latitude;
|
||||||
|
userData.position_actuelle.last_update = last_update;
|
||||||
|
|
||||||
|
fs.writeFileSync(userFilePath, yaml.dump(userData), 'utf8');
|
||||||
|
console.log("Mise à jour réussie de baptiste.yaml");
|
||||||
|
|
||||||
|
res.json({ success: true, message: "Position mise à jour", data: userData });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la mise à jour :", error.message);
|
||||||
|
res.status(500).json({ error: "Impossible de mettre à jour la position" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Servir les fichiers statiques
|
||||||
|
app.use('/static', express.static(path.join(__dirname, 'static')));
|
||||||
|
|
||||||
|
// Page principale
|
||||||
|
app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'index.html')));
|
||||||
|
|
||||||
|
app.listen(config.port, () => {
|
||||||
|
console.log(`Serveur démarré sur http://localhost:${config.port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route pour servir la page de supervision
|
||||||
|
app.get('/supervise', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'supervise.html'));
|
||||||
|
});
|
||||||
|
// Route pour récupérer les données de Baptiste
|
||||||
|
app.get('/data/baptiste.yaml', (req, res) => {
|
||||||
|
const userFilePath = path.join(__dirname, 'data/baptiste.yaml');
|
||||||
|
const configFilePath = path.join(__dirname, 'config.yaml');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userData = yaml.load(fs.readFileSync(userFilePath, 'utf8'));
|
||||||
|
const config = yaml.load(fs.readFileSync(configFilePath, 'utf8'));
|
||||||
|
// Inclure le décalage des pins dans la réponse
|
||||||
|
userData.pin_offset = config.pin_offset;
|
||||||
|
|
||||||
|
res.json(userData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des données :", error.message);
|
||||||
|
res.status(500).json({ error: "Impossible de récupérer les données" });
|
||||||
|
}
|
||||||
|
});
|
||||||
BIN
static/css/img/fond.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
134
static/css/style.css
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
/* Fond d'écran */
|
||||||
|
background: url('img/fond.jpg') no-repeat center center;
|
||||||
|
background-size: cover; /* Adapte l'image à la taille de l'écran */
|
||||||
|
}
|
||||||
|
#content0 {
|
||||||
|
position: fixed; /* Fixe le conteneur en haut de la page */
|
||||||
|
top: 0; /* Place l'élément tout en haut */
|
||||||
|
margin: 0; /* Supprime les marges par défaut */
|
||||||
|
right: 0; /* Aligne à gauche */
|
||||||
|
width: 50%; /* Prend toute la largeur */
|
||||||
|
background: rgba(255, 255, 255, 0.8); /* Fond semi-transparent pour un effet header */
|
||||||
|
padding: 10px 10px; /* Espacement intérieur */
|
||||||
|
border-radius: 15px; /* Arrondit les angles */
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); /* Ajoute une légère ombre */
|
||||||
|
text-align: center; /* Centrage du texte */
|
||||||
|
border-bottom: 2px solid rgba(0, 0, 0, 0.1); /* Légère bordure en bas */
|
||||||
|
z-index: 1000; /* Assure que le header est au-dessus des autres éléments */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
position: relative; /* Nécessaire pour positionner des éléments enfants absolument */
|
||||||
|
background: rgba(255, 255, 255, 0.8); /* Fond semi-transparent */
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#position {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#distance {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style des flèches */
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
display: none; /* Masqué par défaut */
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow-approaching {
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow-moving-away {
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style de Avatar */
|
||||||
|
#avatar {
|
||||||
|
position: absolute;
|
||||||
|
width: 130px;
|
||||||
|
height: 130px;
|
||||||
|
background-color: rgba(27, 28, 29, 0.6); /* Couleur de remplissage interne */
|
||||||
|
border-radius: 50%; /* Pour arrondir l'intérieur */
|
||||||
|
box-shadow: 0 0 20px 10px rgba(27, 28, 29, 0.6); /* Halo par défaut */
|
||||||
|
transition: all 0.5s ease-in-out; /* Transition douce pour les changements */
|
||||||
|
}
|
||||||
|
/* Conteneur de la flèche */
|
||||||
|
#arrow-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100px; /* Taille de la boîte de la flèche */
|
||||||
|
height: 100px;
|
||||||
|
margin: 20px auto; /* Centre horizontalement dans le div #content */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flèche noire */
|
||||||
|
#arrow-black {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform-origin: center center; /* Centre de rotation */
|
||||||
|
transform: rotate(0deg); /* Rotation initiale */
|
||||||
|
transition: transform 0.1s linear; /* Animation fluide pour les rotations */
|
||||||
|
}
|
||||||
|
#user-avatar {
|
||||||
|
position: absolute; /* Permet de positionner avec left et top */
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%; /* Cercle */
|
||||||
|
object-fit: cover; /* Ajuste l'image */
|
||||||
|
transition: left 0.2s ease, top 0.2s ease; /* Animation fluide */
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-debug="true"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#user-name {
|
||||||
|
font-weight: bold; /* Rend le texte en gras */
|
||||||
|
margin: 0; /* Supprime les marges par défaut */
|
||||||
|
}
|
||||||
|
|
||||||
|
#challenge-number , #distance {
|
||||||
|
margin: 0; /* Supprime les marges par défaut */
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow-approaching, #arrow-moving-away {
|
||||||
|
position: fixed; /* Fixe le conteneur en haut de la page */
|
||||||
|
bottom: 0; /* Place l'élément tout en haut */
|
||||||
|
margin: 0; /* Supprime les marges par défaut */
|
||||||
|
left: 0; /* Aligne à gauche */
|
||||||
|
width: 25%; /* Prend toute la largeur */
|
||||||
|
background: rgba(255, 255, 255, 0.8); /* Fond semi-transparent pour un effet header */
|
||||||
|
padding: 10px 10px; /* Espacement intérieur */
|
||||||
|
border-radius: 15px; /* Arrondit les angles */
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); /* Ajoute une légère ombre */
|
||||||
|
text-align: center; /* Centrage du texte */
|
||||||
|
border-bottom: 2px solid rgba(0, 0, 0, 0.1); /* Légère bordure en bas */
|
||||||
|
z-index: 1000; /* Assure que le header est au-dessus des autres éléments */
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
106
static/css/supervise.css
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/* Réinitialisation de base */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conteneur principal */
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100vh; /* Vue entière */
|
||||||
|
gap: 10px; /* Espacement entre les sections */
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section principale (à gauche) */
|
||||||
|
.large-section {
|
||||||
|
flex: 2; /* Prend plus d'espace */
|
||||||
|
background-color: #ccc;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conteneur pour les sections de droite */
|
||||||
|
.section-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px; /* Espacement vertical */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Petites sections (à droite) */
|
||||||
|
.small-section {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #aaa;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsivité : Pour les tablettes */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
#container {
|
||||||
|
flex-direction: column; /* Colonne pour iPad portrait */
|
||||||
|
}
|
||||||
|
.large-section {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.section-container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#section-top-right h2,
|
||||||
|
#section-top-right p {
|
||||||
|
margin: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#user-avatar {
|
||||||
|
display: block;
|
||||||
|
margin: 10px auto;
|
||||||
|
border: 2px solid #333;
|
||||||
|
}
|
||||||
|
/* Section 2 - Organisation en colonne */
|
||||||
|
#section-top-right {
|
||||||
|
display: flex; /* Active le flexbox */
|
||||||
|
flex-direction: column; /* Aligne les éléments en colonne */
|
||||||
|
align-items: center; /* Centre les éléments horizontalement */
|
||||||
|
justify-content: center; /* Centre les éléments verticalement */
|
||||||
|
gap: 10px; /* Espacement entre les éléments */
|
||||||
|
font-size: 14px; /* Taille identique pour tous les caractères */
|
||||||
|
text-align: left; /* Alignement du texte */
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#section-top-right h2,
|
||||||
|
#section-top-right p {
|
||||||
|
margin: 0; /* Enlève les marges par défaut */
|
||||||
|
font-size: 14px; /* Taille identique */
|
||||||
|
}
|
||||||
|
|
||||||
|
#user-avatar {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%; /* Avatar en forme de cercle */
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid #333; /* Bordure autour de l'avatar */
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
BIN
static/img/bart.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
static/img/fleche_bleu.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
static/img/fleche_noir.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
static/img/fleche_rouge.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
static/img/fleche_verte.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
static/img/imageaaabb01.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
static/img/pin1.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
static/img/pin2.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
static/img/pin3.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
static/img/pin4.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
static/img/pin5.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
static/img/pin6.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
387
static/js/script.js
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
const modeDebug = false; // Passez à true pour activer le mode debug
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
toggleDebugMode(); // Appliquer le mode debug au chargement de la page
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleDebugMode() {
|
||||||
|
const debugElements = document.querySelectorAll('[data-debug="true"]');
|
||||||
|
|
||||||
|
debugElements.forEach(element => {
|
||||||
|
element.style.display = modeDebug ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Mode debug ${modeDebug ? 'activé' : 'désactivé'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Votre code ici
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
let wakeLock = null;
|
||||||
|
|
||||||
|
async function enableWakeLock() {
|
||||||
|
try {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
const wakeLock = await navigator.wakeLock.request('screen');
|
||||||
|
console.log('Verrouillage de l’écran activé.');
|
||||||
|
return wakeLock;
|
||||||
|
} else {
|
||||||
|
console.warn('API Wake Lock non prise en charge par ce navigateur.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de l’activation du verrouillage de l’écran :', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour réactiver le Wake Lock si la page redevient visible
|
||||||
|
function handleVisibilityChange() {
|
||||||
|
if (wakeLock !== null && document.visibilityState === 'visible') {
|
||||||
|
enableWakeLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activer le Wake Lock au chargement de la page
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
enableWakeLock();
|
||||||
|
});
|
||||||
|
|
||||||
|
function showPopup(challenge) {
|
||||||
|
if (!challenge) {
|
||||||
|
console.error("Aucune donnée de défi actif !");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { image, textFound, mode } = challenge;
|
||||||
|
|
||||||
|
// Créer l'élément popup
|
||||||
|
const popup = document.createElement('div');
|
||||||
|
popup.id = 'popup';
|
||||||
|
popup.style.position = 'fixed';
|
||||||
|
popup.style.top = '50%';
|
||||||
|
popup.style.left = '50%';
|
||||||
|
popup.style.transform = 'translate(-50%, -50%)';
|
||||||
|
popup.style.padding = '20px';
|
||||||
|
popup.style.backgroundColor = '#fff';
|
||||||
|
popup.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
|
||||||
|
popup.style.zIndex = '1000';
|
||||||
|
|
||||||
|
// Ajouter l'image
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = image;
|
||||||
|
img.alt = 'Défi actif';
|
||||||
|
img.style.width = '100%';
|
||||||
|
popup.appendChild(img);
|
||||||
|
|
||||||
|
// Ajouter le texte trouvé
|
||||||
|
const text = document.createElement('p');
|
||||||
|
text.textContent = `Texte à trouver : ${textFound}`;
|
||||||
|
popup.appendChild(text);
|
||||||
|
|
||||||
|
// Ajouter une action en fonction du mode
|
||||||
|
if (mode === 'message') {
|
||||||
|
const message = document.createElement('p');
|
||||||
|
message.textContent = "Mode : Message";
|
||||||
|
popup.appendChild(message);
|
||||||
|
} else if (mode === 'reponse') {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'text';
|
||||||
|
input.placeholder = 'Entrez votre réponse';
|
||||||
|
popup.appendChild(input);
|
||||||
|
|
||||||
|
const submitButton = document.createElement('button');
|
||||||
|
submitButton.textContent = 'Envoyer';
|
||||||
|
popup.appendChild(submitButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter un bouton pour fermer
|
||||||
|
const closeButton = document.createElement('button');
|
||||||
|
closeButton.textContent = 'Fermer';
|
||||||
|
closeButton.onclick = () => document.body.removeChild(popup);
|
||||||
|
popup.appendChild(closeButton);
|
||||||
|
|
||||||
|
// Ajouter le popup au corps de la page
|
||||||
|
document.body.appendChild(popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour charger les données utilisateur
|
||||||
|
async function fetchUserData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/user/${username}`);
|
||||||
|
const userData = await response.json();
|
||||||
|
|
||||||
|
console.log("Données utilisateur :", userData);
|
||||||
|
|
||||||
|
// Mettre à jour l'interface avec les données
|
||||||
|
document.getElementById('user-name').textContent = userData.nom;
|
||||||
|
document.getElementById('user-avatar').src = userData.avatar;
|
||||||
|
document.getElementById('user-progression').textContent = userData.progression;
|
||||||
|
|
||||||
|
// Trouver le premier défi non résolu
|
||||||
|
let firstUnresolvedChallenge = null;
|
||||||
|
let challengeNumber = null;
|
||||||
|
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
const challengeKey = `defi_${i}`;
|
||||||
|
const challenge = userData.defis[challengeKey];
|
||||||
|
|
||||||
|
if (challenge.resolu === "non") {
|
||||||
|
firstUnresolvedChallenge = challenge;
|
||||||
|
challengeNumber = i;
|
||||||
|
break; // On arrête dès qu'on trouve le premier défi non résolu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstUnresolvedChallenge) {
|
||||||
|
const { latitude, longitude } = firstUnresolvedChallenge.geolocalisation;
|
||||||
|
// Mettre à jour la variable globale target
|
||||||
|
target = { latitude, longitude };
|
||||||
|
|
||||||
|
// Extraire les informations du défi actif
|
||||||
|
const { image_1, text_found_1, mode } = firstUnresolvedChallenge;
|
||||||
|
|
||||||
|
// Stocker les informations du défi actif dans une variable globale
|
||||||
|
window.activeChallenge = {
|
||||||
|
image: image_1,
|
||||||
|
textFound: text_found_1,
|
||||||
|
mode: mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mettre à jour l'interface avec les coordonnées du défi
|
||||||
|
document.getElementById('target-latitude').textContent = latitude.toFixed(6);
|
||||||
|
document.getElementById('target-longitude').textContent = longitude.toFixed(6);
|
||||||
|
document.getElementById('challenge-number').textContent = `défi n°${challengeNumber}`;
|
||||||
|
} else {
|
||||||
|
console.log("Tous les défis sont résolus !");
|
||||||
|
target = null; // Réinitialisation de la cible
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors du chargement des données utilisateur :", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger les données utilisateur dès le chargement de la page
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
fetchUserData(); // Charger les données au démarrage
|
||||||
|
setInterval(fetchUserData, 10000); // Actualiser toutes les 10 secondes
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Coordonnées de la cible
|
||||||
|
//let target = { latitude: 45.141916, longitude: 4.075059 }; // Coordonnées de la cible
|
||||||
|
//let currentHeading = 0; // Orientation actuelle de l'appareil
|
||||||
|
let lastDistance = null;
|
||||||
|
|
||||||
|
// Variables pour les coins de la droite virtuelle
|
||||||
|
let topLeft = { x: 2, y: 2 }; // Coin supérieur gauche en pixels
|
||||||
|
let bottomRight = { x: window.innerWidth - 100, y: window.innerHeight - 250 }; // Coin inférieur droit en pixels
|
||||||
|
|
||||||
|
// Fonction pour recalculer les coins dynamiquement
|
||||||
|
function updateCorners() {
|
||||||
|
bottomRight = {
|
||||||
|
x: window.innerWidth - 100,
|
||||||
|
y: window.innerHeight - 250
|
||||||
|
};
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', updateCorners);
|
||||||
|
|
||||||
|
// Fonction utilitaire pour définir le halo
|
||||||
|
function setHaloColor(element, color) {
|
||||||
|
element.style.boxShadow = `0 0 20px 10px ${color}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour mettre à jour le style de Avatar
|
||||||
|
function updateAvatarStyle(distance) {
|
||||||
|
const avatar = document.getElementById('user-avatar');
|
||||||
|
|
||||||
|
if (distance > 20) {
|
||||||
|
setHaloColor(avatar, 'rgba(0, 0, 255, 0.6)'); // Halo bleu
|
||||||
|
} else if (distance <= 10) {
|
||||||
|
setHaloColor(avatar, 'rgba(255, 0, 0, 0.6)'); // Halo rouge
|
||||||
|
} else {
|
||||||
|
setHaloColor(avatar, 'rgba(27, 28, 29, 0.6)'); // Halo gris
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionAvatar(distance) {
|
||||||
|
const avatar = document.getElementById('user-avatar');
|
||||||
|
if (!avatar) {
|
||||||
|
console.error("L'élément 'user-avatar' n'existe pas dans le DOM.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance > 25) {
|
||||||
|
avatar.style.left = `${topLeft.x}px`;
|
||||||
|
avatar.style.top = `${topLeft.y}px`;
|
||||||
|
} else if (distance <= 5) {
|
||||||
|
avatar.style.left = `${bottomRight.x}px`;
|
||||||
|
avatar.style.top = `${bottomRight.y}px`;
|
||||||
|
} else {
|
||||||
|
const ratio = (25 - distance) / 20;
|
||||||
|
const newX = topLeft.x + (bottomRight.x - topLeft.x) * ratio;
|
||||||
|
const newY = topLeft.y + (bottomRight.y - topLeft.y) * ratio;
|
||||||
|
|
||||||
|
avatar.style.left = `${newX}px`;
|
||||||
|
avatar.style.top = `${newY}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour gérer les flèches directionnelles
|
||||||
|
function updateArrows(distance) {
|
||||||
|
const arrowApproaching = document.getElementById('arrow-approaching');
|
||||||
|
const arrowMovingAway = document.getElementById('arrow-moving-away');
|
||||||
|
|
||||||
|
if (lastDistance !== null) {
|
||||||
|
if (distance < lastDistance) {
|
||||||
|
arrowApproaching.style.display = 'block';
|
||||||
|
arrowMovingAway.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
arrowApproaching.style.display = 'none';
|
||||||
|
arrowMovingAway.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour calculer la direction vers la cible (bearing)
|
||||||
|
function calculateBearing(lat1, lon1, lat2, lon2) {
|
||||||
|
const toRad = (value) => (value * Math.PI) / 180;
|
||||||
|
const toDeg = (value) => (value * 180) / Math.PI;
|
||||||
|
|
||||||
|
const dLon = toRad(lon2 - lon1);
|
||||||
|
const y = Math.sin(dLon) * Math.cos(toRad(lat2));
|
||||||
|
const x = Math.cos(toRad(lat1)) * Math.sin(toRad(lat2)) -
|
||||||
|
Math.sin(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.cos(dLon);
|
||||||
|
|
||||||
|
return (toDeg(Math.atan2(y, x)) + 360) % 360; // Angle en degrés, ajusté pour être positif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour mettre à jour l'orientation de la flèche
|
||||||
|
function updateCompass(bearing) {
|
||||||
|
const arrow = document.getElementById('arrow-black');
|
||||||
|
const angle = (bearing - currentHeading + 360) % 360; // Ajuste selon l'orientation actuelle
|
||||||
|
arrow.style.transform = `rotate(${angle}deg)`; // Applique la rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Écoute les changements d'orientation de l'appareil
|
||||||
|
window.addEventListener('deviceorientation', (event) => {
|
||||||
|
currentHeading = event.alpha || 0; // Orientation absolue en degrés
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fonction principale pour mettre à jour les éléments
|
||||||
|
function updatePosition(position) {
|
||||||
|
const { latitude, longitude } = position.coords;
|
||||||
|
|
||||||
|
document.getElementById('latitude').textContent = latitude.toFixed(6);
|
||||||
|
document.getElementById('longitude').textContent = longitude.toFixed(6);
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
console.warn("Aucune cible définie. Impossible de calculer la distance ou la direction.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distance = calculateDistance(latitude, longitude, target.latitude, target.longitude);
|
||||||
|
document.getElementById('distance').textContent = `${distance.toFixed(0)} m`;
|
||||||
|
|
||||||
|
|
||||||
|
const bearing = calculateBearing(latitude, longitude, target.latitude, target.longitude);
|
||||||
|
|
||||||
|
positionAvatar(distance);
|
||||||
|
updateAvatarStyle(distance);
|
||||||
|
updateArrows(distance);
|
||||||
|
updateCompass(bearing);
|
||||||
|
|
||||||
|
// Afficher le popup si distance < 5m
|
||||||
|
if (distance < 4) {
|
||||||
|
showPopup(window.activeChallenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDistance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour calculer la distance entre deux points GPS (Haversine Formula)
|
||||||
|
function calculateDistance(lat1, lon1, lat2, lon2) {
|
||||||
|
const R = 6371000; // Rayon de la Terre en mètres
|
||||||
|
const toRad = (value) => (value * Math.PI) / 180;
|
||||||
|
const dLat = toRad(lat2 - lat1);
|
||||||
|
const dLon = toRad(lon2 - lon1);
|
||||||
|
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
|
||||||
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion des erreurs de géolocalisation
|
||||||
|
function handleError(error) {
|
||||||
|
const errorMessages = {
|
||||||
|
1: "Permission refusée",
|
||||||
|
2: "Position indisponible",
|
||||||
|
3: "Délai dépassé"
|
||||||
|
};
|
||||||
|
alert("Erreur de géolocalisation : " + errorMessages[error.code] || "Erreur inconnue");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialiser la géolocalisation en temps réel
|
||||||
|
function startTracking() {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
alert("La géolocalisation n'est pas prise en charge par votre navigateur.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.geolocation.watchPosition(updatePosition, handleError, {
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
maximumAge: 0,
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Démarrer le suivi dès le chargement de la page
|
||||||
|
startTracking();
|
||||||
|
|
||||||
|
const positionUpdateInterval = 1000 * 5; // 5 secondes (à récupérer dynamiquement depuis config.yaml)
|
||||||
|
|
||||||
|
// Fonction pour envoyer les nouvelles coordonnées au serveur
|
||||||
|
async function updateServerPosition(latitude, longitude) {
|
||||||
|
|
||||||
|
const now = new Date().toISOString(); // Format ISO 8601 pour last_update
|
||||||
|
try {
|
||||||
|
const response = await fetch('/update-position', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
last_update: now // Inclure le champ last_update
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
console.log("Position mise à jour :", result.message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la mise à jour de la position :", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour de la position toutes les X secondes
|
||||||
|
function startPositionUpdates() {
|
||||||
|
setInterval(() => {
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition(position => {
|
||||||
|
const { latitude, longitude } = position.coords;
|
||||||
|
updateServerPosition(latitude, longitude);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("La géolocalisation n'est pas prise en charge.");
|
||||||
|
}
|
||||||
|
}, positionUpdateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lancer le suivi dès le chargement de la page
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
startPositionUpdates();
|
||||||
|
startTracking(); // Fonction existante pour afficher les mises à jour côté client
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
161
static/js/supervise.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
|
||||||
|
function calculateDeltaInSeconds(lastUpdate) {
|
||||||
|
// Convertir la date de mise à jour en UTC
|
||||||
|
const lastUpdateTime = new Date(lastUpdate).getTime(); // En millisecondes
|
||||||
|
const currentTime = Date.now(); // Temps actuel en UTC (automatique)
|
||||||
|
|
||||||
|
const deltaInMilliseconds = currentTime - lastUpdateTime;
|
||||||
|
|
||||||
|
// Retourner la différence en secondes
|
||||||
|
return Math.floor(deltaInMilliseconds / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour récupérer les données et mettre à jour l'interface
|
||||||
|
async function fetchUserData() {
|
||||||
|
try {
|
||||||
|
// Appel à l'API pour récupérer les données
|
||||||
|
const response = await fetch('/data/baptiste.yaml');
|
||||||
|
const userData = await response.json();
|
||||||
|
|
||||||
|
// Calculer le temps d'inactivité
|
||||||
|
const lastUpdate = userData.position_actuelle.last_update; // Récupération du champ "last_update"
|
||||||
|
const inactivitySeconds = calculateDeltaInSeconds(lastUpdate); // Calcul du delta en secondes
|
||||||
|
|
||||||
|
// Vérification des données reçues
|
||||||
|
console.log("Données utilisateur :", userData);
|
||||||
|
|
||||||
|
// Mise à jour des éléments HTML
|
||||||
|
document.getElementById('user-name').textContent = `Nom : ${userData.nom}`;
|
||||||
|
document.getElementById('user-progression').textContent = `Progression : ${userData.progression}`;
|
||||||
|
document.getElementById('user-avatar').src = userData.avatar;
|
||||||
|
document.getElementById('user-longitude').textContent = userData.position_actuelle.longitude;
|
||||||
|
document.getElementById('user-latitude').textContent = userData.position_actuelle.latitude;
|
||||||
|
document.getElementById('user-inactive').textContent = `Inactif depuis : ${inactivitySeconds} s`;
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des données :", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualiser les données toutes les 3 secondes
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
fetchUserData(); // Appel initial pour charger les données au démarrage
|
||||||
|
setInterval(fetchUserData, 3000); // Appels périodiques toutes les 3 secondes
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
// Coordonnées par défaut pour centrer la carte
|
||||||
|
const centerCoordinates = [45.141998, 4.0750724];
|
||||||
|
let userMarker; // Variable pour stocker le pin de l'avatar
|
||||||
|
|
||||||
|
// Initialiser la carte
|
||||||
|
const map = L.map('map').setView(centerCoordinates, 20);
|
||||||
|
|
||||||
|
// Définir les couches de carte
|
||||||
|
const osmStandard = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
});
|
||||||
|
|
||||||
|
const cycleOSM = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.cyclosm.org/">CycleOSM</a>'
|
||||||
|
});
|
||||||
|
|
||||||
|
const satellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||||
|
attribution: '© <a href="https://www.esri.com/">ESRI</a> World Imagery'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajouter la carte OpenStreetMap par défaut
|
||||||
|
osmStandard.addTo(map);
|
||||||
|
|
||||||
|
// Contrôle pour basculer entre les couches
|
||||||
|
const baseLayers = {
|
||||||
|
"OpenStreetMap Standard": osmStandard,
|
||||||
|
"CycleOSM": cycleOSM,
|
||||||
|
"Satellite": satellite
|
||||||
|
};
|
||||||
|
L.control.layers(baseLayers).addTo(map);
|
||||||
|
|
||||||
|
// Fonction pour ajouter un pin personnalisé
|
||||||
|
function addCustomPin(coordinates, iconUrl, popupText, offsetX = 20, offsetY = 40) {
|
||||||
|
const customIcon = L.icon({
|
||||||
|
iconUrl: iconUrl,
|
||||||
|
iconSize: [40, 40], // Taille de l'icône
|
||||||
|
iconAnchor: [offsetX, offsetY], // Décalage en X et Y
|
||||||
|
popupAnchor: [0, -40] // Position du popup
|
||||||
|
});
|
||||||
|
|
||||||
|
return L.marker(coordinates, { icon: customIcon }).bindPopup(popupText);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Fonction pour mettre à jour la position de l'avatar
|
||||||
|
async function updateAvatarPosition() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/data/baptiste.yaml');
|
||||||
|
const userData = await response.json();
|
||||||
|
|
||||||
|
const newCoordinates = [
|
||||||
|
userData.position_actuelle.latitude,
|
||||||
|
userData.position_actuelle.longitude
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
const lastUpdate = userData.position_actuelle.last_update;
|
||||||
|
const inactivitySeconds = calculateDeltaInSeconds(lastUpdate);
|
||||||
|
|
||||||
|
// Si le pin existe déjà, on met à jour sa position
|
||||||
|
if (userMarker) {
|
||||||
|
userMarker.setLatLng(newCoordinates);
|
||||||
|
} else {
|
||||||
|
// Ajouter le pin de l'avatar pour la première fois
|
||||||
|
userMarker = addCustomPin(newCoordinates, userData.avatar, `Baptiste : ${userData.progression}`);
|
||||||
|
userMarker.addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Position de l'avatar mise à jour :", newCoordinates);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la mise à jour de la position :", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajouter les pins pour les défis (chargement initial uniquement)
|
||||||
|
async function loadChallengePins() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/data/baptiste.yaml');
|
||||||
|
const userData = await response.json();
|
||||||
|
|
||||||
|
const offsetX = userData.pin_offset?.x || 20;
|
||||||
|
const offsetY = userData.pin_offset?.y || 40;
|
||||||
|
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
const defiKey = `defi_${i}`;
|
||||||
|
const defi = userData.defis[defiKey];
|
||||||
|
|
||||||
|
const defiCoordinates = [
|
||||||
|
defi.geolocalisation.latitude,
|
||||||
|
defi.geolocalisation.longitude
|
||||||
|
];
|
||||||
|
|
||||||
|
const pinIcon = defi.pin;
|
||||||
|
const popupText = `Défi ${i} - ${defi.contenu_reception}`;
|
||||||
|
|
||||||
|
const defiMarker = addCustomPin(defiCoordinates, pinIcon, popupText, offsetX, offsetY);
|
||||||
|
defiMarker.addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Pins des défis chargés avec succès !");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors du chargement des défis :", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger les pins des défis (une seule fois)
|
||||||
|
await loadChallengePins();
|
||||||
|
|
||||||
|
// Mettre à jour la position de l'avatar toutes les 3 secondes
|
||||||
|
updateAvatarPosition(); // Premier appel pour l'initialisation
|
||||||
|
setInterval(updateAvatarPosition, 3000);
|
||||||
|
});
|
||||||
42
supervise.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Supervision</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<link rel="stylesheet" href="static/css/supervise.css">
|
||||||
|
<style>
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<!-- Section principale (à gauche) -->
|
||||||
|
<div class="section large-section" id="section-left">
|
||||||
|
<div id="map"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deux sections à droite -->
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="section small-section" id="section-top-right">
|
||||||
|
<img id="user-avatar" src="" alt="Avatar" style="width: 50px; height: 50px; border-radius: 50%;">
|
||||||
|
<h2 id="user-name">Nom : --</h2>
|
||||||
|
<p id="user-progression">Progression : --</p>
|
||||||
|
<p>Longitude : <span id="user-longitude">--</span></p>
|
||||||
|
<p>Latitude : <span id="user-latitude">--</span></p>
|
||||||
|
<p id="user-inactive">Inactif depuis : -- s</p>
|
||||||
|
</div>
|
||||||
|
<div class="section small-section" id="section-bottom-right">
|
||||||
|
<p>Section 3</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script src="static/js/supervise.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||