// 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 = `

${data.username}

Position mise à jour: ${this.formatTimestamp(data.timestamp)}

${!this.friends.includes(data.username) ? ` ` : ''}
`; 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(); });