Sửa callout hiển thị scene và click vào scene để vào thẳng xem ảnh
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 7.7 MiB |
@@ -213,3 +213,97 @@ html, body {
|
||||
|
||||
.save-btn { background: #28a745; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }
|
||||
.cancel-btn { background: #6c757d; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }
|
||||
|
||||
/* Action Modal Specifics */
|
||||
.action-modal-content {
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.edit-btn-large, .delete-btn-large {
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.edit-btn-large { background: #007bff; color: white; }
|
||||
.delete-btn-large { background: #dc3545; color: white; }
|
||||
.edit-btn-large:hover, .delete-btn-large:hover { opacity: 0.9; }
|
||||
.action-modal-content p { color: #666; margin-top: 5px; }
|
||||
|
||||
/* Scene Callout Bubble Marker */
|
||||
.scene-callout {
|
||||
background: white;
|
||||
border: 3px solid #007bff;
|
||||
border-radius: 10px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
position: relative;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
display: flex; /* Căn giữa nội dung bên trong */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scene-img-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden; /* Cắt ảnh nếu tràn ra ngoài border-radius */
|
||||
border-radius: 7px; /* Bo góc (10px - 3px border) */
|
||||
padding: 2px; /* Tạo khoảng hở giữa ảnh và viền xanh */
|
||||
}
|
||||
|
||||
.scene-callout:hover {
|
||||
transform: scale(1.1);
|
||||
border-color: #0056b3;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.scene-callout img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 5px; /* Bo góc ảnh nhẹ hơn wrapper một chút */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.scene-callout::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 12px 8px 0;
|
||||
border-style: solid;
|
||||
border-color: #007bff transparent transparent;
|
||||
}
|
||||
|
||||
/* Custom Tooltip Styling */
|
||||
.custom-scene-tooltip {
|
||||
background: rgba(0, 0, 0, 0.8) !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 13px !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.4) !important;
|
||||
}
|
||||
|
||||
.custom-scene-tooltip .scene-hover-info strong {
|
||||
font-size: 15px;
|
||||
color: #ffd700; /* Màu vàng cho tiêu đề */
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||||
<!-- Pannellum (3D Viewer) CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css"/>
|
||||
<!-- Leaflet MarkerCluster CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" />
|
||||
<!-- Custom Style -->
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
@@ -74,6 +77,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for Action Choice (Edit/Delete) -->
|
||||
<div id="action-choice-modal" class="modal">
|
||||
<div class="modal-content action-modal-content">
|
||||
<span class="close-btn" onclick="closeActionModal()">×</span>
|
||||
<h2 id="action-modal-title">Tùy chọn Scene</h2>
|
||||
<p id="action-modal-desc">Bạn muốn thực hiện thao tác gì với scene này?</p>
|
||||
<div class="action-buttons">
|
||||
<button id="btn-edit-action" class="edit-btn-large">
|
||||
<span class="icon">✏️</span> Chỉnh sửa thông tin
|
||||
</button>
|
||||
<button id="btn-delete-action" class="delete-btn-large">
|
||||
<span class="icon">🗑️</span> Xóa vĩnh viễn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3D Panorama Viewer Container -->
|
||||
<div id="viewer-container" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 2000; background: #000;">
|
||||
<div id="panorama-viewer"></div>
|
||||
@@ -163,6 +183,8 @@
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<!-- Pannellum JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.js"></script>
|
||||
<!-- Leaflet MarkerCluster JS -->
|
||||
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
||||
|
||||
<!-- Custom Scripts -->
|
||||
<script src="js/viewer360.js"></script>
|
||||
|
||||
+108
-30
@@ -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);
|
||||
}
|
||||
// Chỉ lặp qua danh sách Scene mẹ, lọc bỏ các hotspots trùng tọa độ
|
||||
scenes.forEach((scene) => {
|
||||
// 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
|
||||
});
|
||||
|
||||
// Add Markers for each scene
|
||||
scenes.forEach((scene) => {
|
||||
const marker = L.marker([scene.lat, scene.lng]).addTo(map);
|
||||
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`);
|
||||
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');
|
||||
|
||||
if (action) {
|
||||
// EDIT MODE
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user