Sửa callout hiển thị scene và click vào scene để vào thẳng xem ảnh

This commit is contained in:
2026-06-08 07:58:46 +07:00
parent 5ba6e37039
commit 81de520071
4 changed files with 227 additions and 33 deletions
+111 -33
View File
@@ -2,6 +2,7 @@ const API_BASE_URL = 'http://localhost:5000/api';
let map;
let tempMarker = null;
let markerClusterGroup;
let currentSceneId = null;
let previousSceneId = null;
@@ -34,6 +35,27 @@ function initMap() {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Khởi tạo Marker Cluster Group CHỈ dành cho Scene (Ảnh mẹ)
markerClusterGroup = L.markerClusterGroup({
zoomToBoundsOnClick: false,
spiderfyOnMaxZoom: true,
maxClusterRadius: 50,
spiderfyDistanceMultiplier: 3.5, // Tăng thêm khoảng cách để callout ảnh mẹ tách rõ ràng khi tỏa ra
showCoverageOnHover: false,
iconCreateFunction: function(cluster) {
const childMarkers = cluster.getAllChildMarkers();
// Lấy icon của Scene đầu tiên trong nhóm để làm đại diện cho cả cụm
return childMarkers[0].options.icon;
}
});
// Khi click chuột trái vào một cụm callout, thực hiện tách chúng ra
markerClusterGroup.on('clusterclick', (a) => {
a.layer.spiderfy();
});
map.addLayer(markerClusterGroup);
// Lưu vị trí bản đồ mỗi khi người dùng di chuyển hoặc zoom xong
map.on('moveend', () => {
const center = map.getCenter();
@@ -284,45 +306,84 @@ async function loadScenes() {
if (!response.ok) throw new Error('Failed to load scenes');
const scenes = await response.json();
// Xóa sạch các layers cũ và chuẩn bị lọc tọa độ
markerClusterGroup.clearLayers();
const markersToAdd = [];
const activeSceneId = localStorage.getItem('activeSceneId');
const currentUserId = localStorage.getItem('userId');
const seenCoordinates = new Set();
// Clear existing markers (excluding tempMarker)
map.eachLayer((layer) => {
if (layer instanceof L.Marker && layer !== tempMarker) {
map.removeLayer(layer);
}
});
// Add Markers for each scene
// Chỉ lặp qua danh sách Scene mẹ, lọc bỏ các hotspots trùng tọa độ
scenes.forEach((scene) => {
const marker = L.marker([scene.lat, scene.lng]).addTo(map);
// Tạo khóa tọa độ để đảm bảo mỗi vị trí địa lý chỉ có 1 Marker duy nhất
const coordKey = `${scene.lat.toFixed(6)},${scene.lng.toFixed(6)}`;
if (seenCoordinates.has(coordKey)) return; // Bỏ qua nếu tọa độ này đã có Marker
seenCoordinates.add(coordKey);
let thumbUrl = `${API_BASE_URL}/assets/view/${scene.assetId._id}`;
if (token) thumbUrl += `?token=${token}`;
else if (scene.privacy === 'shared' && scene.shareToken) thumbUrl += `?token=${scene.shareToken}`;
const calloutIcon = L.divIcon({
className: 'custom-scene-marker',
html: `
<div class="scene-callout">
<div class="scene-img-wrapper">
<img src="${thumbUrl}" alt="${scene.title}">
</div>
</div>`,
iconSize: [64, 64],
iconAnchor: [32, 76] // Căn giữa ngang, đáy mũi tên tại tọa độ lat/lng
});
const marker = L.marker([scene.lat, scene.lng], {
icon: calloutIcon,
title: scene.title // Tooltip khi di chuột qua
});
// Generate Popup content with a View button
let popupContent = `
<div class="popup-box">
<h4>${scene.title}</h4>
<p>Owner: <strong>${scene.owner ? scene.owner.username : 'Unknown'}</strong></p>
<p>Privacy: <span class="badge">${scene.privacy.toUpperCase()}</span></p>
<button class="view-btn" onclick="openScene('${scene._id}', '${scene.privacy}', '${scene.shareToken || ''}')">View 360° Panorama</button>
// Tạo nội dung thông tin khi Hover (Tooltip)
const createdDate = scene.assetId?.createdAt ? new Date(scene.assetId.createdAt).toLocaleDateString('vi-VN') : 'N/A';
const tooltipContent = `
<div class="scene-hover-info">
<strong>${scene.title}</strong><br>
${scene.description ? `<small>${scene.description}</small><br>` : ''}
<span>Người tạo: ${scene.owner ? scene.owner.username : 'Ẩn danh'}</span><br>
<span>Ngày tạo: ${createdDate}</span>
</div>
`;
marker.bindPopup(popupContent);
// Nếu đây là scene đang xem, tự động mở popup
if (activeSceneId && scene._id === activeSceneId) {
marker.openPopup();
}
// Gán Tooltip cho sự kiện Hover
marker.bindTooltip(tooltipContent, {
direction: 'top',
offset: [0, -70],
className: 'custom-scene-tooltip'
});
// Sự kiện Click chuột trái: Vào thẳng trình xem 360
marker.on('click', () => {
openScene(scene._id, scene.privacy, scene.shareToken || '');
});
// Right-click on marker to Edit/Delete (Owner only)
marker.on('contextmenu', (e) => {
L.DomEvent.stopPropagation(e);
if (currentUserId && scene.owner && scene.owner._id === currentUserId) {
if (e.originalEvent) {
L.DomEvent.stop(e.originalEvent);
}
const currentUserId = localStorage.getItem('userId');
const ownerId = scene.owner?._id || scene.owner;
if (currentUserId && ownerId && ownerId.toString() === currentUserId.toString()) {
handleEditDeleteScene(scene);
} else {
alert("Bạn không có quyền chỉnh sửa scene này.");
}
});
markersToAdd.push(marker);
});
// Thêm danh sách marker đã lọc vào group
markerClusterGroup.addLayers(markersToAdd);
} catch (error) {
console.error('Error loading scenes:', error);
}
@@ -332,17 +393,34 @@ async function loadScenes() {
* Handles Edit/Delete options for a scene
*/
async function handleEditDeleteScene(scene) {
const action = confirm(`Bạn muốn làm gì với scene "${scene.title}"?\n\n- Nhấn OK để CHỈNH SỬA\n- Nhấn Cancel để XÓA`);
if (action) {
// EDIT MODE
const modal = document.getElementById('action-choice-modal');
const title = document.getElementById('action-modal-title');
const editBtn = document.getElementById('btn-edit-action');
const deleteBtn = document.getElementById('btn-delete-action');
title.innerText = `Scene: ${scene.title}`;
modal.style.display = 'flex';
// Gán sự kiện cho nút Sửa
editBtn.onclick = () => {
closeActionModal();
openEditSceneModal(scene);
} else {
// DELETE MODE
if (confirm(`Bạn có chắc chắn muốn xóa vĩnh viễn scene "${scene.title}"?`)) {
};
// Gán sự kiện cho nút Xóa
deleteBtn.onclick = async () => {
if (confirm(`Cảnh báo: Thao tác này sẽ xóa vĩnh viễn scene "${scene.title}" và tệp tin ảnh 360 liên quan. Bạn có chắc chắn?`)) {
closeActionModal();
await deleteScene(scene._id);
}
}
};
}
/**
* Closes the Action Choice Modal
*/
function closeActionModal() {
document.getElementById('action-choice-modal').style.display = 'none';
}
/**