manque statut defi
@@ -47,7 +47,15 @@ Cette application est une interface web pour suivre des défis géolocalisés en
|
||||
```bash
|
||||
npm install
|
||||
|
||||
Installation des dépendances
|
||||
|
||||
Pour faire fonctionner le projet, utilisez npm pour installer express :
|
||||
|
||||
|
||||
```
|
||||
npm init -y
|
||||
npm install express
|
||||
```
|
||||
|
||||
|
||||
Création d'un nouveau dépôt en ligne de commande
|
||||
|
||||
@@ -2,9 +2,9 @@ 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'
|
||||
longitude: 4.0737720757147695
|
||||
latitude: 45.13940712256047
|
||||
last_update: '2024-12-17T16:52:19.548Z'
|
||||
defis:
|
||||
defi_1:
|
||||
geolocalisation:
|
||||
|
||||
@@ -1,27 +1,74 @@
|
||||
nom: Julien
|
||||
progression: defi 2/6
|
||||
avatar: "static/img/julien.jpg"
|
||||
progression: defi /6
|
||||
avatar: static/img/julien.png
|
||||
position_actuelle:
|
||||
longitude: 4.075159
|
||||
latitude: 45.141956
|
||||
longitude: 4.073772052242561
|
||||
latitude: 45.13940731006235
|
||||
last_update: '2024-12-17T17:10:00.056Z'
|
||||
defis:
|
||||
defi_1:
|
||||
geolocalisation:
|
||||
longitude: 4.076159
|
||||
latitude: 45.142956
|
||||
image_1: "static/img/imageaaabb03.jpg"
|
||||
text_found_1: "XYZ"
|
||||
longitude: 4.075368
|
||||
latitude: 45.141916
|
||||
image_1: static/img/imageaaabb01.png
|
||||
text_found_1: AZERT
|
||||
mode: reponse
|
||||
resolu: oui
|
||||
reception: 0
|
||||
contenu_reception: "réussi"
|
||||
contenu_reception: ceci est un test
|
||||
pin: static/img/pin1_j.png
|
||||
defi_2:
|
||||
geolocalisation:
|
||||
longitude: 4.077159
|
||||
latitude: 45.143956
|
||||
image_2: "static/img/imageaaabb04.jpg"
|
||||
text_found_2: "HELLO"
|
||||
mode: rien
|
||||
longitude: 4.076953
|
||||
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_j.png
|
||||
defi_3:
|
||||
geolocalisation:
|
||||
longitude: 4.073995
|
||||
latitude: 45.139302
|
||||
image_1: static/img/imageaaabb01.png
|
||||
text_found_1: AZERT
|
||||
mode: reponse
|
||||
resolu: non
|
||||
reception: 0
|
||||
contenu_reception: ""
|
||||
contenu_reception: ceci est un test
|
||||
pin: static/img/pin3_j.png
|
||||
defi_4:
|
||||
geolocalisation:
|
||||
longitude: 4.07935
|
||||
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_j.png
|
||||
defi_5:
|
||||
geolocalisation:
|
||||
longitude: 4.074903
|
||||
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_j.png
|
||||
defi_6:
|
||||
geolocalisation:
|
||||
longitude: 4.079286
|
||||
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_j.png
|
||||
|
||||
42
index_julien.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_julien.css">
|
||||
<script>
|
||||
const username = 'julien'; // 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_julien.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,14 +3,19 @@
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"description": "Cette application est une interface web pour suivre des défis géolocalisés en temps réel. Elle utilise Node.js pour le backend et JavaScript/HTML/CSS pour le frontend.",
|
||||
"dependencies": {
|
||||
"express": "^4.21.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://10.0.0.20:3004/gilles/geocaching_js.git"
|
||||
}
|
||||
}
|
||||
|
||||
59
server.js
@@ -25,29 +25,46 @@ app.get('/user/:username', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route pour mettre à jour la position dans baptiste.yaml
|
||||
// Route pour mettre à jour la position d'un utilisateur
|
||||
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);
|
||||
const { username, longitude, latitude, last_update } = req.body; // Extraction des données de la requête
|
||||
|
||||
if (!username || !longitude || !latitude || !last_update) {
|
||||
console.error("Requête invalide : données manquantes");
|
||||
return res.status(400).json({ error: "Les champs username, longitude, latitude et last_update sont requis." });
|
||||
}
|
||||
|
||||
const userFilePath = path.join(__dirname, `data/${username}.yaml`);
|
||||
|
||||
// Vérification que le fichier existe
|
||||
if (!fs.existsSync(userFilePath)) {
|
||||
console.error(`Fichier utilisateur non trouvé : ${userFilePath}`);
|
||||
return res.status(404).json({ error: `Utilisateur ${username} non trouvé.` });
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("Tentative de mise à jour des coordonnées :", longitude, latitude, last_update);
|
||||
console.log(`Tentative de mise à jour des coordonnées pour ${username} :`, longitude, latitude, last_update);
|
||||
|
||||
const userData = yaml.load(fs.readFileSync(userFilePath, 'utf8'));
|
||||
|
||||
// Mise à jour des données utilisateur
|
||||
userData.position_actuelle.longitude = longitude;
|
||||
userData.position_actuelle.latitude = latitude;
|
||||
userData.position_actuelle.last_update = last_update;
|
||||
|
||||
// Sauvegarde des données dans le fichier YAML
|
||||
fs.writeFileSync(userFilePath, yaml.dump(userData), 'utf8');
|
||||
console.log("Mise à jour réussie de baptiste.yaml");
|
||||
|
||||
console.log(`Mise à jour réussie de ${userFilePath}`);
|
||||
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" });
|
||||
console.error(`Erreur lors de la mise à jour pour ${username} :`, 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')));
|
||||
|
||||
@@ -79,3 +96,31 @@ userData.pin_offset = config.pin_offset;
|
||||
res.status(500).json({ error: "Impossible de récupérer les données" });
|
||||
}
|
||||
});
|
||||
|
||||
// Route pour la page de Julien
|
||||
app.get('/julien', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'index_julien.html'));
|
||||
});
|
||||
|
||||
/// Route pour récupérer les données de Julien
|
||||
app.get('/data/julien.yaml', (req, res) => {
|
||||
const userFilePath = path.join(__dirname, 'data/julien.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" });
|
||||
}
|
||||
});
|
||||
|
||||
// Servir les fichiers statiques des tuiles
|
||||
app.use('/tiles', express.static(path.join(__dirname, 'tiles')));
|
||||
|
||||
|
||||
|
||||
134
static/css/style_julien.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 */
|
||||
|
||||
|
||||
}
|
||||
@@ -51,6 +51,16 @@ html, body {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Section pour Baptiste */
|
||||
#section-top-right-baptiste {
|
||||
background-color: #82c4f3; /* Couleur bleutée */
|
||||
}
|
||||
|
||||
/* Section pour Julien */
|
||||
#section-top-right-julien {
|
||||
background-color: #a379e6; /* Couleur rosée */
|
||||
}
|
||||
|
||||
/* Responsivité : Pour les tablettes */
|
||||
@media (max-width: 1024px) {
|
||||
#container {
|
||||
@@ -76,7 +86,7 @@ html, body {
|
||||
border: 2px solid #333;
|
||||
}
|
||||
/* Section 2 - Organisation en colonne */
|
||||
#section-top-right {
|
||||
#section-top-right, #section-top-right-baptiste, #section-top-right-julien {
|
||||
display: flex; /* Active le flexbox */
|
||||
flex-direction: column; /* Aligne les éléments en colonne */
|
||||
align-items: center; /* Centre les éléments horizontalement */
|
||||
|
||||
BIN
static/img/julien.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
static/img/pin1_j.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
static/img/pin2_j.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
static/img/pin3_j.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
static/img/pin4_j.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
static/img/pin5_j.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
static/img/pin6_j.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
389
static/js/script copy.js
Normal file
@@ -0,0 +1,389 @@
|
||||
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
|
||||
const username = 'baptiste'; // Spécifiez le nom de l'utilisateur pour Julien
|
||||
try {
|
||||
const response = await fetch('/update-position', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: username, // Inclure le champ username
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -346,11 +346,13 @@ const positionUpdateInterval = 1000 * 5; // 5 secondes (à récupérer dynamique
|
||||
async function updateServerPosition(latitude, longitude) {
|
||||
|
||||
const now = new Date().toISOString(); // Format ISO 8601 pour last_update
|
||||
const username = 'baptiste'; // Spécifiez le nom de l'utilisateur pour Julien
|
||||
try {
|
||||
const response = await fetch('/update-position', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: username, // Inclure le champ username
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
last_update: now // Inclure le champ last_update
|
||||
|
||||
389
static/js/script_julien.js
Normal file
@@ -0,0 +1,389 @@
|
||||
const modeDebug = true; // 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
|
||||
const username = 'julien'; // Spécifiez le nom de l'utilisateur pour Julien
|
||||
try {
|
||||
const response = await fetch('/update-position', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: username, // Inclure le champ username
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,161 +1,202 @@
|
||||
let map; // Carte unique
|
||||
let baptisteMarker = null; // Marker de Baptiste
|
||||
let julienMarker = null; // Marker de Julien
|
||||
|
||||
// -------------------------------------
|
||||
// SECTION : Fonctions Utilitaires
|
||||
// -------------------------------------
|
||||
|
||||
// Calcul du temps d'inactivité en secondes
|
||||
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);
|
||||
const lastUpdateTime = new Date(lastUpdate).getTime();
|
||||
const currentTime = Date.now();
|
||||
return Math.floor((currentTime - lastUpdateTime) / 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();
|
||||
// Ajout d'un pin personnalisé
|
||||
function addCustomPin(coordinates, iconUrl, popupText, offsetX = 20, offsetY = 40) {
|
||||
const customIcon = L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [offsetX, offsetY],
|
||||
popupAnchor: [0, -40],
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
return L.marker(coordinates, { icon: customIcon }).bindPopup(popupText);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Initialisation de la carte
|
||||
function initializeMap(centerCoordinates = [45.141998, 4.0750724]) {
|
||||
// Initialiser la carte
|
||||
const map = L.map('map').setView(centerCoordinates, 20);
|
||||
map = L.map('map', {
|
||||
center: centerCoordinates, // Coordonnées du centre
|
||||
zoom: 18, // Niveau de zoom par défaut
|
||||
minZoom: 15, // Zoom minimum autorisé
|
||||
maxZoom: 20 // Zoom maximum autorisé
|
||||
});
|
||||
|
||||
// Définir les couches de carte
|
||||
// Définir les différentes couches de la 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'
|
||||
attribution: '© OpenStreetMap 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>'
|
||||
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'
|
||||
attribution: '© <a href="https://www.esri.com/">ESRI</a> World Imagery',
|
||||
});
|
||||
|
||||
// Ajouter la carte OpenStreetMap par défaut
|
||||
osmStandard.addTo(map);
|
||||
// Nouvelle couche de tuiles personnalisées
|
||||
const customTiles = L.tileLayer('https://jeu.maison43.duckdns.org/tiles/messinhac/{z}/{x}/{y}.png', {
|
||||
attribution: '© Mes Tuiles Personnalisées',
|
||||
maxZoom: 20, // Assure un zoom maximal compatible avec tes tuiles
|
||||
});
|
||||
|
||||
// Contrôle pour basculer entre les couches
|
||||
// Ajouter OpenStreetMap comme layer par défaut
|
||||
customTiles.addTo(map);
|
||||
|
||||
// Ajouter un contrôle pour basculer entre les couches
|
||||
const baseLayers = {
|
||||
"OpenStreetMap Standard": osmStandard,
|
||||
"CycleOSM": cycleOSM,
|
||||
"Satellite": satellite
|
||||
"Satellite": satellite,
|
||||
"Tuiles Personnalisées": customTiles, // Ajout de la nouvelle couche ici
|
||||
};
|
||||
|
||||
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
|
||||
});
|
||||
// -------------------------------------
|
||||
// SECTION : Gestion des Utilisateurs
|
||||
// -------------------------------------
|
||||
|
||||
return L.marker(coordinates, { icon: customIcon }).bindPopup(popupText);
|
||||
}
|
||||
// Fonction générique pour mettre à jour un utilisateur
|
||||
async function updateAvatarPosition(username, markerRefName) {
|
||||
try {
|
||||
const response = await fetch(`/data/${username}.yaml`);
|
||||
const userData = await response.json();
|
||||
|
||||
const newCoordinates = [
|
||||
userData.position_actuelle.latitude,
|
||||
userData.position_actuelle.longitude,
|
||||
];
|
||||
|
||||
// Référence globale au marker
|
||||
let marker = markerRefName === 'baptiste' ? baptisteMarker : julienMarker;
|
||||
|
||||
// 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();
|
||||
if (marker) {
|
||||
// Met à jour directement la position du marker
|
||||
marker.setLatLng(newCoordinates);
|
||||
} else {
|
||||
// Créez le marker pour la première fois
|
||||
const newMarker = addCustomPin(
|
||||
newCoordinates,
|
||||
userData.avatar || 'default-avatar.png',
|
||||
`${username} : ${userData.progression}`
|
||||
);
|
||||
newMarker.addTo(map);
|
||||
|
||||
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);
|
||||
// Met à jour la variable globale
|
||||
if (markerRefName === 'baptiste') {
|
||||
baptisteMarker = newMarker;
|
||||
} else if (markerRefName === 'julien') {
|
||||
julienMarker = newMarker;
|
||||
}
|
||||
|
||||
console.log("Position de l'avatar mise à jour :", newCoordinates);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la mise à jour de la position :", error);
|
||||
}
|
||||
|
||||
console.log(`Position de ${username} mise à jour :`, newCoordinates);
|
||||
} catch (error) {
|
||||
console.error(`Erreur lors de la mise à jour de la position de ${username} :`, 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();
|
||||
// Fonction générique pour charger les pins des défis
|
||||
async function loadChallengePins(username) {
|
||||
try {
|
||||
const response = await fetch(`/data/${username}.yaml`);
|
||||
const userData = await response.json();
|
||||
|
||||
const offsetX = userData.pin_offset?.x || 20;
|
||||
const offsetY = userData.pin_offset?.y || 40;
|
||||
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];
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const defi = userData.defis[`defi_${i}`];
|
||||
if (!defi) continue;
|
||||
|
||||
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);
|
||||
const defiMarker = addCustomPin(
|
||||
[defi.geolocalisation.latitude, defi.geolocalisation.longitude],
|
||||
defi.pin || 'default-pin.png',
|
||||
`Défi ${i} - ${defi.contenu_reception}`,
|
||||
offsetX,
|
||||
offsetY
|
||||
);
|
||||
defiMarker.addTo(map);
|
||||
}
|
||||
|
||||
console.log(`Pins des défis pour ${username} chargés avec succès.`);
|
||||
} catch (error) {
|
||||
console.error(`Erreur lors du chargement des défis pour ${username} :`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Charger les pins des défis (une seule fois)
|
||||
await loadChallengePins();
|
||||
// Fonction générique pour récupérer et afficher les données utilisateur
|
||||
async function fetchUserData(username, domElements) {
|
||||
try {
|
||||
const response = await fetch(`/data/${username}.yaml`);
|
||||
const userData = await response.json();
|
||||
|
||||
// Mettre à jour la position de l'avatar toutes les 3 secondes
|
||||
updateAvatarPosition(); // Premier appel pour l'initialisation
|
||||
setInterval(updateAvatarPosition, 3000);
|
||||
});
|
||||
const lastUpdate = userData.position_actuelle.last_update;
|
||||
const inactivitySeconds = calculateDeltaInSeconds(lastUpdate);
|
||||
|
||||
// Mise à jour des éléments HTML
|
||||
document.getElementById(domElements.name).textContent = `${userData.nom}`;
|
||||
document.getElementById(domElements.progression).textContent = `Progression : ${userData.progression}`;
|
||||
document.getElementById(domElements.avatar).src = userData.avatar;
|
||||
document.getElementById(domElements.longitude).textContent = userData.position_actuelle.longitude;
|
||||
document.getElementById(domElements.latitude).textContent = userData.position_actuelle.latitude;
|
||||
document.getElementById(domElements.inactive).textContent = `Inactif depuis : ${inactivitySeconds} s`;
|
||||
|
||||
console.log(`Données de ${username} mises à jour.`);
|
||||
} catch (error) {
|
||||
console.error(`Erreur lors de la récupération des données de ${username} :`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// SECTION : Événements DOM
|
||||
// -------------------------------------
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
initializeMap();
|
||||
|
||||
// Charger les pins des défis
|
||||
await loadChallengePins('baptiste');
|
||||
await loadChallengePins('julien');
|
||||
// Planifier les mises à jour des positions des utilisateurs
|
||||
setInterval(() => updateAvatarPosition('baptiste', 'baptiste'), 3000);
|
||||
setInterval(() => updateAvatarPosition('julien', 'julien'), 3000);
|
||||
|
||||
// Mettre à jour les avatars et les données utilisateur
|
||||
setInterval(() => updateAvatarPosition('baptiste', { value: baptisteMarker }), 3000);
|
||||
setInterval(() => updateAvatarPosition('julien', { value: julienMarker }), 3000);
|
||||
|
||||
setInterval(() => fetchUserData('baptiste', {
|
||||
name: 'user-name',
|
||||
progression: 'user-progression',
|
||||
avatar: 'user-avatar',
|
||||
longitude: 'user-longitude',
|
||||
latitude: 'user-latitude',
|
||||
inactive: 'user-inactive',
|
||||
}), 3000);
|
||||
|
||||
setInterval(() => fetchUserData('julien', {
|
||||
name: 'user-name-julien',
|
||||
progression: 'user-progression-julien',
|
||||
avatar: 'user-avatar-julien',
|
||||
longitude: 'user-longitude-julien',
|
||||
latitude: 'user-latitude-julien',
|
||||
inactive: 'user-inactive-julien',
|
||||
}), 3000);
|
||||
});
|
||||
|
||||
269
static/js/supervise_julien.js
Normal file
@@ -0,0 +1,269 @@
|
||||
let map; // Déclaration globale
|
||||
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 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 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 baptiste
|
||||
|
||||
|
||||
// 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 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);
|
||||
});
|
||||
|
||||
|
||||
// JULIEN
|
||||
|
||||
|
||||
// Fonction pour récupérer et afficher les données de Julien
|
||||
async function fetchUserDataJulien() {
|
||||
try {
|
||||
const response = await fetch('/data/julien.yaml');
|
||||
const userData = await response.json();
|
||||
|
||||
const lastUpdate = userData.position_actuelle.last_update;
|
||||
const inactivitySeconds = calculateDeltaInSeconds(lastUpdate);
|
||||
|
||||
// Mise à jour des éléments HTML pour Julien
|
||||
document.getElementById('user-name-julien').textContent = `Nom : ${userData.nom}`;
|
||||
document.getElementById('user-progression-julien').textContent = `Progression : ${userData.progression}`;
|
||||
document.getElementById('user-avatar-julien').src = userData.avatar;
|
||||
document.getElementById('user-longitude-julien').textContent = userData.position_actuelle.longitude;
|
||||
document.getElementById('user-latitude-julien').textContent = userData.position_actuelle.latitude;
|
||||
document.getElementById('user-inactive-julien').textContent = `Inactif depuis : ${inactivitySeconds} s`;
|
||||
|
||||
console.log("Données de Julien mises à jour !");
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des données de Julien :", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Déclaration globale pour le marker de Julien
|
||||
let julienMarker; // Variable pour gérer l'avatar de Julien sur la carte
|
||||
|
||||
// Fonction pour mettre à jour la position de l'avatar de Julien
|
||||
async function updateAvatarPositionJulien() {
|
||||
try {
|
||||
const response = await fetch('/data/julien.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à, mettez à jour sa position
|
||||
if (julienMarker) {
|
||||
julienMarker.setLatLng(newCoordinates); // Met à jour la position du pin existant
|
||||
} else {
|
||||
// Ajoutez le pin de l'avatar pour la première fois
|
||||
julienMarker = addCustomPin(newCoordinates, userData.avatar, `Julien : ${userData.progression}`);
|
||||
julienMarker.addTo(map); // Ajoutez le pin à la carte
|
||||
}
|
||||
|
||||
console.log("Position de l'avatar de Julien mise à jour :", newCoordinates);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la mise à jour de la position de Julien :", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fonction pour charger les pins des défis de Julien
|
||||
async function loadChallengePinsJulien() {
|
||||
try {
|
||||
const response = await fetch('/data/julien.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 de Julien chargés avec succès !");
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement des défis de Julien :", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour les données et pins pour Julien
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
// Charger les pins des défis de Julien (une seule fois)
|
||||
await loadChallengePinsJulien();
|
||||
|
||||
// Mettre à jour la position de Julien toutes les 3 secondes
|
||||
updateAvatarPositionJulien(); // Premier appel
|
||||
setInterval(updateAvatarPositionJulien, 3000);
|
||||
|
||||
// Mettre à jour les données de Julien toutes les 3 secondes
|
||||
fetchUserDataJulien(); // Premier appel
|
||||
setInterval(fetchUserDataJulien, 3000);
|
||||
});
|
||||
@@ -23,17 +23,23 @@
|
||||
|
||||
<!-- Deux sections à droite -->
|
||||
<div class="section-container">
|
||||
<div class="section small-section" id="section-top-right">
|
||||
<div class="section small-section" id="section-top-right-baptiste">
|
||||
<img id="user-avatar" src="" alt="Avatar" style="width: 50px; height: 50px; border-radius: 50%;">
|
||||
<h2 id="user-name">Nom : --</h2>
|
||||
<h2 id="user-name"> --</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>
|
||||
<!-- Section pour Julien -->
|
||||
<div class="section small-section" id="section-top-right-julien">
|
||||
<img id="user-avatar-julien" src="" alt="Avatar Julien" style="width: 50px; height: 50px; border-radius: 50%;">
|
||||
<h2 id="user-name-julien"> --</h2>
|
||||
<p id="user-progression-julien">Progression : --</p>
|
||||
<p>Longitude : <span id="user-longitude-julien">--</span></p>
|
||||
<p>Latitude : <span id="user-latitude-julien">--</span></p>
|
||||
<p id="user-inactive-julien">Inactif depuis : -- s</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
|
||||
BIN
tiles/messinhac/0/0/0.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/1/1/0.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
tiles/messinhac/10/523/367.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
tiles/messinhac/11/1047/735.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
tiles/messinhac/12/2094/1471.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
tiles/messinhac/13/4188/2942.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
tiles/messinhac/13/4189/2942.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
tiles/messinhac/14/8376/5884.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tiles/messinhac/14/8377/5884.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
tiles/messinhac/14/8378/5884.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
tiles/messinhac/15/16752/11768.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/15/16752/11769.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tiles/messinhac/15/16753/11768.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tiles/messinhac/15/16753/11769.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/15/16754/11768.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
tiles/messinhac/15/16754/11769.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
tiles/messinhac/15/16755/11768.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
tiles/messinhac/15/16755/11769.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
tiles/messinhac/15/16756/11768.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
tiles/messinhac/15/16756/11769.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
tiles/messinhac/16/33505/23537.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/16/33505/23538.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
tiles/messinhac/16/33505/23539.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/16/33506/23537.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
tiles/messinhac/16/33506/23538.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/16/33506/23539.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
tiles/messinhac/16/33507/23537.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/16/33507/23538.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
tiles/messinhac/16/33507/23539.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
tiles/messinhac/16/33508/23537.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/16/33508/23538.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tiles/messinhac/16/33508/23539.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/16/33509/23537.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tiles/messinhac/16/33509/23538.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
tiles/messinhac/16/33509/23539.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/16/33510/23537.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tiles/messinhac/16/33510/23538.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
tiles/messinhac/16/33510/23539.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
tiles/messinhac/16/33511/23537.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
tiles/messinhac/16/33511/23538.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tiles/messinhac/16/33511/23539.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tiles/messinhac/16/33512/23537.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
tiles/messinhac/16/33512/23538.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
tiles/messinhac/16/33512/23539.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
tiles/messinhac/16/33513/23537.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
tiles/messinhac/16/33513/23538.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tiles/messinhac/16/33513/23539.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
tiles/messinhac/17/67011/47074.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
tiles/messinhac/17/67011/47075.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/17/67011/47076.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
tiles/messinhac/17/67011/47077.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
tiles/messinhac/17/67011/47078.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
tiles/messinhac/17/67011/47079.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tiles/messinhac/17/67012/47074.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
tiles/messinhac/17/67012/47075.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/17/67012/47076.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tiles/messinhac/17/67012/47077.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/17/67012/47078.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
tiles/messinhac/17/67012/47079.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
tiles/messinhac/17/67013/47074.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tiles/messinhac/17/67013/47075.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tiles/messinhac/17/67013/47076.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tiles/messinhac/17/67013/47077.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tiles/messinhac/17/67013/47078.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
tiles/messinhac/17/67013/47079.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
tiles/messinhac/17/67014/47074.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tiles/messinhac/17/67014/47075.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
tiles/messinhac/17/67014/47076.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tiles/messinhac/17/67014/47077.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
tiles/messinhac/17/67014/47078.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
tiles/messinhac/17/67014/47079.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
tiles/messinhac/17/67015/47074.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tiles/messinhac/17/67015/47075.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tiles/messinhac/17/67015/47076.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
tiles/messinhac/17/67015/47077.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
tiles/messinhac/17/67015/47078.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tiles/messinhac/17/67015/47079.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tiles/messinhac/17/67016/47074.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
tiles/messinhac/17/67016/47075.png
Normal file
|
After Width: | Height: | Size: 114 KiB |