const API_BASE_URL = 'http://localhost:5000/api'; let map; let tempMarker = null; // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', () => { initMap(); checkAuthStatus(); loadScenes(); }); /** * Initializes the full-screen Leaflet Map */ function initMap() { // Center of map defaults to Hanoi map = L.map('map').setView([21.0285, 105.8542], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap contributors' }).addTo(map); // Event listener for right-click on map to open modal map.on('contextmenu', (e) => { const { lat, lng } = e.latlng; openCreateSceneModal(lat, lng); }); } /** * Checks if user is logged in (via localStorage JWT) and updates UI */ function checkAuthStatus() { const token = localStorage.getItem('jwt'); const username = localStorage.getItem('username'); const role = localStorage.getItem('role'); const authGuest = document.getElementById('auth-guest'); const authLoggedIn = document.getElementById('auth-logged-in'); if (token && username) { authGuest.style.display = 'none'; authLoggedIn.style.display = 'block'; document.getElementById('logged-username').innerText = username; document.getElementById('logged-role').innerText = role || 'Thành viên'; } else { authGuest.style.display = 'block'; authLoggedIn.style.display = 'none'; } } /** * Handles user login */ async function handleLogin() { const username = document.getElementById('username-input').value.trim(); const password = document.getElementById('password-input').value.trim(); if (!username || !password) { alert('Please fill in both fields'); return; } try { const response = await fetch(`${API_BASE_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); if (!response.ok) throw new Error(data.message || 'Login failed'); localStorage.setItem('jwt', data.token); localStorage.setItem('username', data.user.username); localStorage.setItem('role', data.user.role); checkAuthStatus(); loadScenes(); // Reload scenes to show member/private scenes alert('Logged in successfully!'); } catch (error) { alert(error.message); } } /** * Handles user registration */ async function handleRegister() { const username = document.getElementById('username-input').value.trim(); const password = document.getElementById('password-input').value.trim(); if (!username || !password) { alert('Please fill in both fields'); return; } try { const response = await fetch(`${API_BASE_URL}/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, role: 'Thành viên' }) }); const data = await response.json(); if (!response.ok) throw new Error(data.message || 'Registration failed'); alert('Registration successful! You can now log in.'); } catch (error) { alert(error.message); } } /** * Handles user logout */ function handleLogout() { localStorage.removeItem('jwt'); localStorage.removeItem('username'); localStorage.removeItem('role'); checkAuthStatus(); loadScenes(); // Reload scenes to filter out private ones alert('Logged out successfully'); } /** * Opens Modal for creating a Scene and sets lat/lng inputs */ function openCreateSceneModal(lat, lng) { const token = localStorage.getItem('jwt'); if (!token) { alert('Please log in first to create a 3D scene.'); return; } // Place a temporary marker on the map if (tempMarker) map.removeLayer(tempMarker); tempMarker = L.marker([lat, lng]).addTo(map); document.getElementById('modal-lat').value = lat.toFixed(6); document.getElementById('modal-lng').value = lng.toFixed(6); document.getElementById('create-scene-modal').style.display = 'flex'; } /** * Closes the Create Scene Modal and removes temporary marker */ function closeModal() { document.getElementById('create-scene-modal').style.display = 'none'; if (tempMarker) { map.removeLayer(tempMarker); tempMarker = null; } document.getElementById('create-scene-form').reset(); document.getElementById('shared-with-group').style.display = 'none'; } /** * Toggles visibility of the shared users input based on privacy selection */ function toggleSharedUsers() { const privacy = document.getElementById('modal-privacy').value; const group = document.getElementById('shared-with-group'); if (privacy === 'shared') { group.style.display = 'block'; } else { group.style.display = 'none'; } } /** * Form submission for Scene creation (multipart/form-data) */ async function submitScene(e) { e.preventDefault(); const form = document.getElementById('create-scene-form'); const formData = new FormData(form); const token = localStorage.getItem('jwt'); if (!token) { alert('Please log in to submit'); return; } try { const response = await fetch(`${API_BASE_URL}/scenes`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const data = await response.json(); if (!response.ok) throw new Error(data.message || 'Failed to save scene'); alert('Scene created successfully!'); closeModal(); loadScenes(); } catch (error) { alert(error.message); } } /** * Loads and displays visible Scenes on the map */ async function loadScenes() { try { const token = localStorage.getItem('jwt'); const headers = {}; if (token) { headers['Authorization'] = `Bearer ${token}`; } const response = await fetch(`${API_BASE_URL}/scenes`, { method: 'GET', headers }); if (!response.ok) throw new Error('Failed to load scenes'); const scenes = await response.json(); // Clear existing markers (excluding tempMarker) map.eachLayer((layer) => { if (layer instanceof L.Marker && layer !== tempMarker) { map.removeLayer(layer); } }); // Add Markers for each scene scenes.forEach((scene) => { const marker = L.marker([scene.lat, scene.lng]).addTo(map); // Generate Popup content with a View button let popupContent = ` `; marker.bindPopup(popupContent); }); } catch (error) { console.error('Error loading scenes:', error); } } /** * Fetches secure scene details and triggers the Panorama viewer */ async function openScene(sceneId, privacy, shareToken) { try { const token = localStorage.getItem('jwt'); const headers = {}; if (token) { headers['Authorization'] = `Bearer ${token}`; } let url = `${API_BASE_URL}/scenes/${sceneId}`; if (privacy === 'shared' && shareToken) { url += `?token=${shareToken}`; } const response = await fetch(url, { method: 'GET', headers }); const scene = await response.json(); if (!response.ok) throw new Error(scene.message || 'Failed to fetch scene details'); // Construct secure image URL passing shareToken if applicable let secureImageUrl = `${API_BASE_URL}/assets/view/${scene.assetId._id}`; if (privacy === 'shared' && scene.shareToken) { secureImageUrl += `?token=${scene.shareToken}`; } // Initialize 3D Viewer with secure, referer-protected image stream initPanoramaViewer(secureImageUrl); } catch (error) { alert(error.message); } }