oedb-backend/oedb/resources/demo/static/social.js
2025-09-26 11:57:54 +02:00

750 lines
28 KiB
JavaScript

// Fonctionnalités sociales pour OEDB
class OEDBSocial {
constructor() {
this.socket = null;
this.position = null;
this.username = '';
this.friends = [];
this.markers = {};
this.map = null;
this.lastPouetTime = 0;
this.showOnlyFriends = false;
// Charger les amis depuis le localStorage
this.loadFriends();
// Boutons pour l'interface sociale
this.createSocialUI();
}
// Initialiser la connexion WebSocket
init(map, username) {
this.map = map;
this.username = username || localStorage.getItem('oedb_social_username') || '';
if (!this.username) {
this.promptForUsername();
} else {
localStorage.setItem('oedb_social_username', this.username);
}
// Créer la connexion WebSocket
// Utiliser l'URL relative au serveur actuel
const wsProtocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
const wsUrl = `${wsProtocol}${window.location.host}/ws`;
this.socket = new WebSocket(wsUrl);
this.socket.onopen = () => {
console.log('Connexion WebSocket établie');
this.startSendingPosition();
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleSocketMessage(data);
};
this.socket.onclose = () => {
console.log('Connexion WebSocket fermée');
// Tentative de reconnexion après 5 secondes
setTimeout(() => this.init(this.map, this.username), 5000);
};
this.socket.onerror = (error) => {
console.error('Erreur WebSocket:', error);
this.showToast(`Erreur de connexion au serveur WebSocket. Vérifiez que le serveur est en cours d'exécution sur le port 8765.`, 'error');
};
}
// Demander le pseudo à l'utilisateur
promptForUsername() {
// Créer une boîte de dialogue modale pour demander le pseudo
const modalOverlay = document.createElement('div');
modalOverlay.className = 'modal-overlay';
modalOverlay.style.position = 'fixed';
modalOverlay.style.top = '0';
modalOverlay.style.left = '0';
modalOverlay.style.width = '100%';
modalOverlay.style.height = '100%';
modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
modalOverlay.style.zIndex = '1000';
modalOverlay.style.display = 'flex';
modalOverlay.style.justifyContent = 'center';
modalOverlay.style.alignItems = 'center';
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
modalContent.style.backgroundColor = '#fff';
modalContent.style.padding = '20px';
modalContent.style.borderRadius = '5px';
modalContent.style.maxWidth = '400px';
modalContent.style.width = '80%';
const title = document.createElement('h3');
title.textContent = 'Choisissez un pseudo';
title.style.marginBottom = '15px';
const form = document.createElement('form');
form.onsubmit = (e) => {
e.preventDefault();
const input = document.getElementById('username-input');
const username = input.value.trim();
if (username) {
this.username = username;
localStorage.setItem('oedb_social_username', username);
document.body.removeChild(modalOverlay);
this.startSendingPosition();
}
};
const input = document.createElement('input');
input.type = 'text';
input.id = 'username-input';
input.placeholder = 'Votre pseudo';
input.style.width = '100%';
input.style.padding = '8px';
input.style.marginBottom = '15px';
input.style.borderRadius = '4px';
input.style.border = '1px solid #ddd';
const button = document.createElement('button');
button.type = 'submit';
button.textContent = 'Valider';
button.style.padding = '8px 15px';
button.style.backgroundColor = '#0078ff';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.cursor = 'pointer';
form.appendChild(input);
form.appendChild(button);
modalContent.appendChild(title);
modalContent.appendChild(form);
modalOverlay.appendChild(modalContent);
document.body.appendChild(modalOverlay);
input.focus();
}
// Commencer à envoyer sa position
startSendingPosition() {
if (!this.username || !this.socket) return;
// Obtenir la position actuelle
this.getCurrentPosition();
// Mettre à jour la position toutes les 5 secondes
setInterval(() => {
this.getCurrentPosition();
}, 5000);
}
// Obtenir la position GPS actuelle
getCurrentPosition() {
if (navigator.geolocation && this.socket && this.socket.readyState === WebSocket.OPEN) {
navigator.geolocation.getCurrentPosition(
(position) => {
this.position = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
// Envoyer la position au serveur WebSocket
this.socket.send(JSON.stringify({
type: 'position',
username: this.username,
position: this.position,
timestamp: new Date().toISOString(),
showOnlyToFriends: this.showOnlyFriends
}));
},
(error) => {
console.error('Erreur lors de la récupération de la position:', error);
}
);
}
}
// Traiter les messages reçus par WebSocket
handleSocketMessage(data) {
switch (data.type) {
case 'position':
this.updateUserPosition(data);
break;
case 'pouet':
this.receivePouet(data);
break;
case 'friendRequest':
this.receiveFriendRequest(data);
break;
case 'users':
this.updateAllUsers(data.users);
break;
}
}
// Mettre à jour la position d'un utilisateur sur la carte
updateUserPosition(data) {
// Ignorer les mises à jour de notre propre position
if (data.username === this.username) return;
// Vérifier si l'utilisateur est visible uniquement pour ses amis
if (data.showOnlyToFriends && !this.friends.includes(data.username)) return;
// Supprimer l'ancien marqueur s'il existe
if (this.markers[data.username]) {
this.markers[data.username].remove();
}
// Créer un élément HTML personnalisé pour le marqueur
const el = document.createElement('div');
el.className = 'user-marker';
// Styles de base pour le marqueur
el.style.width = '40px';
el.style.height = '40px';
el.style.borderRadius = '50%';
el.style.backgroundColor = this.friends.includes(data.username) ? '#4CAF50' : '#0078ff';
el.style.border = '2px solid white';
el.style.boxShadow = '0 0 5px rgba(0,0,0,0.3)';
el.style.display = 'flex';
el.style.justifyContent = 'center';
el.style.alignItems = 'center';
el.style.color = 'white';
el.style.fontWeight = 'bold';
el.style.fontSize = '12px';
el.style.cursor = 'pointer';
// Ajouter les initiales de l'utilisateur
const initials = data.username.substring(0, 2).toUpperCase();
el.textContent = initials;
// Ajouter un tooltip avec le nom complet
el.title = data.username;
// Créer le popup
const popupContent = `
<div class="user-popup" style="padding: 10px; max-width: 200px;">
<h3 style="margin-top: 0;">${data.username}</h3>
<p style="margin-bottom: 10px;">Position mise à jour: ${this.formatTimestamp(data.timestamp)}</p>
<div style="display: flex; justify-content: space-between;">
<button class="pouet-btn" style="padding: 5px 10px; background-color: #FFC107; border: none; border-radius: 3px; cursor: pointer;">Pouet Pouet!</button>
${!this.friends.includes(data.username) ? `
<button class="add-friend-btn" style="padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer;">Ajouter ami</button>
` : ''}
</div>
</div>
`;
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: true
}).setHTML(popupContent);
// Ajouter des gestionnaires d'événements au popup
popup.on('open', () => {
// Gérer le clic sur le bouton Pouet Pouet
setTimeout(() => {
const pouetBtn = document.querySelector('.pouet-btn');
if (pouetBtn) {
pouetBtn.addEventListener('click', () => {
this.sendPouet(data.username);
});
}
// Gérer le clic sur le bouton Ajouter ami
const addFriendBtn = document.querySelector('.add-friend-btn');
if (addFriendBtn) {
addFriendBtn.addEventListener('click', () => {
this.sendFriendRequest(data.username);
});
}
}, 100);
});
// Créer le marqueur et l'ajouter à la carte
const marker = new maplibregl.Marker(el)
.setLngLat([data.position.lng, data.position.lat])
.setPopup(popup)
.addTo(this.map);
// Stocker le marqueur pour pouvoir le supprimer plus tard
this.markers[data.username] = marker;
}
// Mettre à jour tous les utilisateurs actifs
updateAllUsers(users) {
// Supprimer les marqueurs des utilisateurs qui ne sont plus actifs
Object.keys(this.markers).forEach(username => {
if (!users.find(user => user.username === username)) {
this.markers[username].remove();
delete this.markers[username];
}
});
}
// Envoyer un pouet à un utilisateur
sendPouet(username) {
const now = Date.now();
// Vérifier si on peut envoyer un pouet (limité à 1 toutes les 10 secondes)
if (now - this.lastPouetTime < 10000) {
const remainingTime = Math.ceil((10000 - (now - this.lastPouetTime)) / 1000);
this.showToast(`Merci d'attendre encore ${remainingTime} secondes avant d'envoyer un autre pouet!`, 'warning');
return;
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({
type: 'pouet',
from: this.username,
to: username,
timestamp: new Date().toISOString()
}));
this.lastPouetTime = now;
this.showToast(`Pouet pouet envoyé à ${username}!`, 'success');
}
}
// Recevoir un pouet
receivePouet(data) {
this.showToast(`${data.from} vous a envoyé un pouet pouet!`, 'info');
// Jouer un son
const audio = new Audio('/static/pouet.mp3');
audio.play().catch(e => console.log('Erreur lors de la lecture du son:', e));
}
// Envoyer une demande d'ami
sendFriendRequest(username) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({
type: 'friendRequest',
from: this.username,
to: username,
timestamp: new Date().toISOString()
}));
this.showToast(`Demande d'ami envoyée à ${username}!`, 'success');
}
}
// Recevoir une demande d'ami
receiveFriendRequest(data) {
// Créer une boîte de dialogue modale pour la demande d'ami
const modalOverlay = document.createElement('div');
modalOverlay.className = 'modal-overlay';
modalOverlay.style.position = 'fixed';
modalOverlay.style.top = '0';
modalOverlay.style.left = '0';
modalOverlay.style.width = '100%';
modalOverlay.style.height = '100%';
modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
modalOverlay.style.zIndex = '1000';
modalOverlay.style.display = 'flex';
modalOverlay.style.justifyContent = 'center';
modalOverlay.style.alignItems = 'center';
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
modalContent.style.backgroundColor = '#fff';
modalContent.style.padding = '20px';
modalContent.style.borderRadius = '5px';
modalContent.style.maxWidth = '400px';
modalContent.style.width = '80%';
const title = document.createElement('h3');
title.textContent = 'Demande d\'ami';
title.style.marginBottom = '15px';
const message = document.createElement('p');
message.textContent = `${data.from} souhaite vous ajouter à sa liste d'amis.`;
message.style.marginBottom = '20px';
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.justifyContent = 'space-between';
const acceptButton = document.createElement('button');
acceptButton.textContent = 'Accepter';
acceptButton.style.padding = '8px 15px';
acceptButton.style.backgroundColor = '#4CAF50';
acceptButton.style.color = 'white';
acceptButton.style.border = 'none';
acceptButton.style.borderRadius = '4px';
acceptButton.style.cursor = 'pointer';
acceptButton.onclick = () => {
this.addFriend(data.from);
document.body.removeChild(modalOverlay);
};
const rejectButton = document.createElement('button');
rejectButton.textContent = 'Refuser';
rejectButton.style.padding = '8px 15px';
rejectButton.style.backgroundColor = '#f44336';
rejectButton.style.color = 'white';
rejectButton.style.border = 'none';
rejectButton.style.borderRadius = '4px';
rejectButton.style.cursor = 'pointer';
rejectButton.onclick = () => {
document.body.removeChild(modalOverlay);
};
buttonsContainer.appendChild(acceptButton);
buttonsContainer.appendChild(rejectButton);
modalContent.appendChild(title);
modalContent.appendChild(message);
modalContent.appendChild(buttonsContainer);
modalOverlay.appendChild(modalContent);
document.body.appendChild(modalOverlay);
}
// Ajouter un ami à la liste d'amis
addFriend(username) {
if (!this.friends.includes(username)) {
this.friends.push(username);
this.saveFriends();
this.showToast(`${username} a été ajouté à votre liste d'amis!`, 'success');
// Mettre à jour le marqueur de cet ami s'il est visible
if (this.markers[username]) {
const position = this.markers[username].getLngLat();
this.markers[username].remove();
delete this.markers[username];
// Simuler une mise à jour de position pour recréer le marqueur
this.updateUserPosition({
username: username,
position: {
lng: position.lng,
lat: position.lat
},
timestamp: new Date().toISOString()
});
}
}
}
// Sauvegarder la liste d'amis dans le localStorage
saveFriends() {
localStorage.setItem('oedb_social_friends', JSON.stringify(this.friends));
}
// Charger la liste d'amis depuis le localStorage
loadFriends() {
try {
const friendsJson = localStorage.getItem('oedb_social_friends');
if (friendsJson) {
this.friends = JSON.parse(friendsJson);
}
} catch (e) {
console.error('Erreur lors du chargement des amis:', e);
this.friends = [];
}
}
// Créer l'interface utilisateur pour les fonctionnalités sociales
createSocialUI() {
// Conteneur principal pour les contrôles sociaux
const socialContainer = document.createElement('div');
socialContainer.className = 'social-controls';
socialContainer.style.position = 'absolute';
socialContainer.style.top = '10px';
socialContainer.style.right = '10px';
socialContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
socialContainer.style.padding = '10px';
socialContainer.style.borderRadius = '5px';
socialContainer.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
socialContainer.style.zIndex = '10';
socialContainer.style.display = 'flex';
socialContainer.style.flexDirection = 'column';
socialContainer.style.gap = '10px';
// Titre
const title = document.createElement('h3');
title.textContent = 'Mode Social';
title.style.margin = '0 0 10px 0';
title.style.textAlign = 'center';
// Bouton pour activer/désactiver le mode social
const toggleButton = document.createElement('button');
toggleButton.className = 'toggle-social-btn';
toggleButton.textContent = 'Activer le mode social';
toggleButton.style.padding = '8px';
toggleButton.style.backgroundColor = '#0078ff';
toggleButton.style.color = 'white';
toggleButton.style.border = 'none';
toggleButton.style.borderRadius = '4px';
toggleButton.style.cursor = 'pointer';
toggleButton.style.fontWeight = 'bold';
let socialActive = false;
toggleButton.addEventListener('click', () => {
socialActive = !socialActive;
if (socialActive) {
toggleButton.textContent = 'Désactiver le mode social';
toggleButton.style.backgroundColor = '#f44336';
this.init(this.map);
} else {
toggleButton.textContent = 'Activer le mode social';
toggleButton.style.backgroundColor = '#0078ff';
// Fermer la connexion WebSocket
if (this.socket) {
this.socket.close();
this.socket = null;
}
// Supprimer tous les marqueurs
Object.values(this.markers).forEach(marker => marker.remove());
this.markers = {};
}
// Afficher/masquer les options supplémentaires
optionsContainer.style.display = socialActive ? 'block' : 'none';
});
// Conteneur pour les options supplémentaires
const optionsContainer = document.createElement('div');
optionsContainer.className = 'social-options';
optionsContainer.style.display = 'none';
// Bouton pour changer de pseudo
const changeUsernameBtn = document.createElement('button');
changeUsernameBtn.textContent = 'Changer de pseudo';
changeUsernameBtn.style.width = '100%';
changeUsernameBtn.style.padding = '8px';
changeUsernameBtn.style.backgroundColor = '#FFC107';
changeUsernameBtn.style.border = 'none';
changeUsernameBtn.style.borderRadius = '4px';
changeUsernameBtn.style.marginBottom = '10px';
changeUsernameBtn.style.cursor = 'pointer';
changeUsernameBtn.addEventListener('click', () => {
this.promptForUsername();
});
// Case à cocher pour la visibilité uniquement aux amis
const visibilityContainer = document.createElement('div');
visibilityContainer.style.display = 'flex';
visibilityContainer.style.alignItems = 'center';
visibilityContainer.style.marginBottom = '10px';
const visibilityCheckbox = document.createElement('input');
visibilityCheckbox.type = 'checkbox';
visibilityCheckbox.id = 'visibility-checkbox';
visibilityCheckbox.checked = this.showOnlyFriends;
const visibilityLabel = document.createElement('label');
visibilityLabel.htmlFor = 'visibility-checkbox';
visibilityLabel.textContent = 'Visible uniquement par mes amis';
visibilityLabel.style.marginLeft = '5px';
visibilityContainer.appendChild(visibilityCheckbox);
visibilityContainer.appendChild(visibilityLabel);
visibilityCheckbox.addEventListener('change', () => {
this.showOnlyFriends = visibilityCheckbox.checked;
this.getCurrentPosition(); // Mettre à jour immédiatement avec le nouveau paramètre
});
// Gestionnaire d'amis
const friendsManager = document.createElement('div');
friendsManager.style.marginTop = '10px';
const friendsTitle = document.createElement('h4');
friendsTitle.textContent = 'Mes amis';
friendsTitle.style.margin = '0 0 5px 0';
const friendsList = document.createElement('ul');
friendsList.style.listStyle = 'none';
friendsList.style.padding = '0';
friendsList.style.margin = '0';
friendsList.style.maxHeight = '150px';
friendsList.style.overflowY = 'auto';
friendsList.style.border = '1px solid #ddd';
friendsList.style.borderRadius = '4px';
friendsList.style.padding = '5px';
// Fonction pour mettre à jour la liste d'amis
const updateFriendsList = () => {
friendsList.innerHTML = '';
if (this.friends.length === 0) {
const emptyItem = document.createElement('li');
emptyItem.textContent = 'Aucun ami pour l\'instant';
emptyItem.style.fontStyle = 'italic';
emptyItem.style.padding = '5px';
friendsList.appendChild(emptyItem);
} else {
this.friends.forEach(friend => {
const listItem = document.createElement('li');
listItem.style.display = 'flex';
listItem.style.justifyContent = 'space-between';
listItem.style.alignItems = 'center';
listItem.style.padding = '5px';
listItem.style.borderBottom = '1px solid #eee';
const friendName = document.createElement('span');
friendName.textContent = friend;
const removeBtn = document.createElement('button');
removeBtn.textContent = 'X';
removeBtn.style.backgroundColor = '#f44336';
removeBtn.style.color = 'white';
removeBtn.style.border = 'none';
removeBtn.style.borderRadius = '50%';
removeBtn.style.width = '20px';
removeBtn.style.height = '20px';
removeBtn.style.fontSize = '10px';
removeBtn.style.cursor = 'pointer';
removeBtn.style.display = 'flex';
removeBtn.style.justifyContent = 'center';
removeBtn.style.alignItems = 'center';
removeBtn.addEventListener('click', () => {
this.friends = this.friends.filter(f => f !== friend);
this.saveFriends();
updateFriendsList();
});
listItem.appendChild(friendName);
listItem.appendChild(removeBtn);
friendsList.appendChild(listItem);
});
}
};
// Initialiser la liste d'amis
updateFriendsList();
friendsManager.appendChild(friendsTitle);
friendsManager.appendChild(friendsList);
// Ajouter tous les éléments au conteneur d'options
optionsContainer.appendChild(changeUsernameBtn);
optionsContainer.appendChild(visibilityContainer);
optionsContainer.appendChild(friendsManager);
// Ajouter tous les éléments au conteneur principal
socialContainer.appendChild(title);
socialContainer.appendChild(toggleButton);
socialContainer.appendChild(optionsContainer);
// Ajouter le conteneur au document après le chargement du DOM
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(socialContainer);
});
}
// Afficher un toast (message flottant)
showToast(message, type = 'info') {
// Créer le conteneur de toast s'il n'existe pas
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
toastContainer.style.position = 'fixed';
toastContainer.style.top = '20px';
toastContainer.style.left = '50%';
toastContainer.style.transform = 'translateX(-50%)';
toastContainer.style.zIndex = '1000';
toastContainer.style.display = 'flex';
toastContainer.style.flexDirection = 'column';
toastContainer.style.alignItems = 'center';
toastContainer.style.gap = '10px';
document.body.appendChild(toastContainer);
}
// Créer le toast
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.style.padding = '10px 15px';
toast.style.borderRadius = '5px';
toast.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)';
toast.style.minWidth = '250px';
toast.style.textAlign = 'center';
toast.style.animation = 'fadeIn 0.3s, fadeOut 0.3s 2.7s';
toast.style.opacity = '0';
toast.style.maxWidth = '80vw';
// Définir la couleur en fonction du type
switch (type) {
case 'success':
toast.style.backgroundColor = '#4CAF50';
toast.style.color = 'white';
break;
case 'warning':
toast.style.backgroundColor = '#FFC107';
toast.style.color = 'black';
break;
case 'error':
toast.style.backgroundColor = '#f44336';
toast.style.color = 'white';
break;
default: // info
toast.style.backgroundColor = '#0078ff';
toast.style.color = 'white';
}
toast.textContent = message;
// Ajouter le style d'animation s'il n'existe pas
if (!document.getElementById('toast-style')) {
const style = document.createElement('style');
style.id = 'toast-style';
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-20px); }
}
`;
document.head.appendChild(style);
}
// Ajouter le toast au conteneur
toastContainer.appendChild(toast);
// Animer l'entrée
setTimeout(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateY(0)';
}, 10);
// Supprimer le toast après 3 secondes
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(-20px)';
setTimeout(() => {
toastContainer.removeChild(toast);
}, 300);
}, 3000);
}
// Formatter un timestamp en heure locale
formatTimestamp(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString();
}
}
// Initialiser l'objet social lorsque le DOM est chargé
document.addEventListener('DOMContentLoaded', () => {
// Créer l'instance sociale
window.oedbSocial = new OEDBSocial();
});