717 lines
28 KiB
JavaScript
717 lines
28 KiB
JavaScript
// ==================== MAP CODE ====================
|
|
(function () {
|
|
// ==================== STATE =====================
|
|
let map;
|
|
let currentJourney = {
|
|
id: null,
|
|
title: "",
|
|
description: "",
|
|
markers: [],
|
|
};
|
|
let availableJourneys = [];
|
|
let journeyPath;
|
|
let activeMarker = null;
|
|
|
|
// ==================== MAP INIT ==================
|
|
function initMap() {
|
|
map = L.map("map", {
|
|
worldCopyJump: true,
|
|
}).setView([46.8182, 8.2275], 8);
|
|
L.tileLayer(
|
|
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
{
|
|
attribution:
|
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
|
},
|
|
).addTo(map);
|
|
|
|
journeyPath = L.polyline([], {
|
|
color: "#4263eb",
|
|
weight: 4,
|
|
}).addTo(map);
|
|
|
|
map.on("click", function (e) {
|
|
if (
|
|
document
|
|
.getElementById("mode-create")
|
|
.classList.contains("active")
|
|
) {
|
|
createMarker(normalizeLatLng(e.latlng), { title: "New Marker" });
|
|
}
|
|
});
|
|
|
|
setTimeout(() => map.invalidateSize(), 300);
|
|
}
|
|
|
|
// ==================== MARKER FUNCTIONS ==========
|
|
function normalizeLatLng(latlng) {
|
|
return L.latLng(
|
|
latlng.lat,
|
|
((((latlng.lng + 180) % 360) + 360) % 360) - 180,
|
|
);
|
|
}
|
|
|
|
function createMarker(latlng, content = {}) {
|
|
latlng = normalizeLatLng(latlng);
|
|
const marker = L.marker(latlng, {
|
|
draggable: true,
|
|
title: content.title || "Untitled",
|
|
}).addTo(map);
|
|
|
|
marker.bindPopup(
|
|
`<strong>${escapeHtml(content.title || "Untitled")}</strong>`,
|
|
);
|
|
marker.on("click", () => openMarkerEditor(marker));
|
|
marker.on("dragend", () => {
|
|
marker.setLatLng(normalizeLatLng(marker.getLatLng()));
|
|
updateJourneyPath();
|
|
});
|
|
|
|
marker._content = {
|
|
...content,
|
|
lat: latlng.lat,
|
|
lng: latlng.lng,
|
|
};
|
|
currentJourney.markers.push(marker);
|
|
updateJourneyPath();
|
|
addMarkerToList(marker, latlng, content);
|
|
return marker;
|
|
}
|
|
|
|
function addMarkerToList(marker, latlng, content) {
|
|
const container = document.getElementById(
|
|
"current-markers-container",
|
|
);
|
|
const empty = container.querySelector(".empty-message");
|
|
if (empty) empty.remove();
|
|
|
|
const div = document.createElement("div");
|
|
div.className = "marker-item";
|
|
div.dataset.lat = latlng.lat;
|
|
div.dataset.lng = latlng.lng;
|
|
div.innerHTML = `
|
|
<div class="marker-title">${escapeHtml(content.title || "Untitled")}</div>
|
|
<div class="marker-coords">${latlng.lat.toFixed(4)}, ${latlng.lng.toFixed(4)}</div>
|
|
`;
|
|
div.addEventListener("click", () => {
|
|
map.flyTo(latlng, 12);
|
|
openMarkerEditor(marker);
|
|
});
|
|
container.appendChild(div);
|
|
}
|
|
|
|
function updateJourneyPath() {
|
|
const coords = currentJourney.markers.map((m) => [
|
|
m.getLatLng().lat,
|
|
m.getLatLng().lng,
|
|
]);
|
|
journeyPath.setLatLngs(coords);
|
|
}
|
|
|
|
function getUploadUrl(path) {
|
|
if (!path) return "";
|
|
if (path.startsWith("http")) return sanitizeDisplayUrl(path);
|
|
return API_BASE.replace("/api", path);
|
|
}
|
|
|
|
function renderMarkerImages(images = []) {
|
|
const preview = document.getElementById("marker-image-preview");
|
|
preview.innerHTML = "";
|
|
|
|
images.forEach((image, index) => {
|
|
const item = document.createElement("div");
|
|
item.className = "image-preview";
|
|
const imagePath = typeof image === "string" ? image : image.url;
|
|
const imageName = typeof image === "string" ? "Marker image" : image.originalName;
|
|
const url = sanitizeDisplayUrl(getUploadUrl(imagePath));
|
|
const alt = escapeAttribute(imageName || "Marker image");
|
|
if (!url) return;
|
|
item.innerHTML = `
|
|
<img src="${escapeAttribute(url)}" alt="${alt}">
|
|
<button type="button" class="remove-image-btn" data-index="${index}" aria-label="Remove image">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
item.querySelector(".remove-image-btn").addEventListener("click", () => {
|
|
if (!activeMarker) return;
|
|
const content = activeMarker._content || {};
|
|
content.images = (content.images || []).filter((_, i) => i !== index);
|
|
activeMarker._content = content;
|
|
renderMarkerImages(content.images);
|
|
});
|
|
preview.appendChild(item);
|
|
});
|
|
}
|
|
|
|
function updateMarkerMarkdownPreview(markdown) {
|
|
const preview = document.getElementById("marker-markdown-preview");
|
|
if (!preview) return;
|
|
preview.innerHTML = renderMarkdown(markdown || "") || '<p class="markdown-empty">Nothing to preview yet.</p>';
|
|
}
|
|
|
|
async function uploadMarkerImages(files) {
|
|
if (!files.length) return [];
|
|
|
|
const formData = new FormData();
|
|
files.forEach((file) => formData.append("images", file));
|
|
|
|
const res = await fetch(`${API_BASE}/uploads/images`, {
|
|
method: "POST",
|
|
body: formData,
|
|
credentials: "include",
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || "Image upload failed");
|
|
return data.images || [];
|
|
}
|
|
|
|
// ==================== MODAL =====================
|
|
function openMarkerEditor(marker) {
|
|
activeMarker = marker;
|
|
const latlng = marker.getLatLng();
|
|
const content = marker._content || {};
|
|
|
|
document.getElementById("marker-title").value =
|
|
content.title || "";
|
|
document.getElementById("marker-date").value =
|
|
content.date || "";
|
|
document.getElementById("marker-text").value =
|
|
content.description || "";
|
|
updateMarkerMarkdownPreview(content.description || "");
|
|
document.getElementById("marker-images").value = "";
|
|
renderMarkerImages(content.images || []);
|
|
document.getElementById("marker-coords").value =
|
|
`${latlng.lat.toFixed(6)}, ${latlng.lng.toFixed(6)}`;
|
|
document
|
|
.getElementById("marker-modal")
|
|
.classList.add("active");
|
|
}
|
|
|
|
function closeModal() {
|
|
document
|
|
.getElementById("marker-modal")
|
|
.classList.remove("active");
|
|
activeMarker = null;
|
|
}
|
|
|
|
async function saveMarker() {
|
|
if (!activeMarker) return;
|
|
const title = document.getElementById("marker-title").value || "Untitled";
|
|
const date = document.getElementById("marker-date").value;
|
|
const description = document.getElementById("marker-text").value;
|
|
const imageInput = document.getElementById("marker-images");
|
|
const existingImages = activeMarker._content?.images || [];
|
|
let images = existingImages;
|
|
|
|
try {
|
|
const uploadedImages = await uploadMarkerImages(Array.from(imageInput.files));
|
|
images = [...existingImages, ...uploadedImages];
|
|
} catch (err) {
|
|
showToast(err.message, "error");
|
|
return;
|
|
}
|
|
|
|
// Update marker's tooltip title and popup
|
|
activeMarker.options.title = title;
|
|
activeMarker.setPopupContent(`<strong>${escapeHtml(title)}</strong>`);
|
|
|
|
// Store content for saving
|
|
activeMarker._content = { title, date, description, images };
|
|
|
|
// Update the list item in the sidebar
|
|
const latlng = activeMarker.getLatLng();
|
|
const items = document.querySelectorAll("#current-markers-container .marker-item");
|
|
for (let item of items) {
|
|
if (item.dataset.lat == latlng.lat && item.dataset.lng == latlng.lng) {
|
|
item.querySelector(".marker-title").textContent = title;
|
|
break;
|
|
}
|
|
}
|
|
|
|
closeModal();
|
|
showToast("Saving journey...");
|
|
await saveJourneyToAPI();
|
|
}
|
|
|
|
function deleteMarkerFromMap() {
|
|
if (!activeMarker) return;
|
|
const latlng = activeMarker.getLatLng();
|
|
|
|
map.removeLayer(activeMarker);
|
|
const idx = currentJourney.markers.indexOf(activeMarker);
|
|
if (idx > -1) currentJourney.markers.splice(idx, 1);
|
|
|
|
const items = document.querySelectorAll(
|
|
"#current-markers-container .marker-item",
|
|
);
|
|
for (let item of items) {
|
|
if (
|
|
item.dataset.lat == latlng.lat &&
|
|
item.dataset.lng == latlng.lng
|
|
) {
|
|
item.remove();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (currentJourney.markers.length === 0) {
|
|
document.getElementById(
|
|
"current-markers-container",
|
|
).innerHTML =
|
|
'<p class="empty-message">No markers yet. Click on the map to add markers.</p>';
|
|
}
|
|
updateJourneyPath();
|
|
closeModal();
|
|
showToast("Marker deleted");
|
|
}
|
|
|
|
// ==================== API INTERACTION (with credentials) ====================
|
|
async function fetchJourneys() {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/journeys`, {
|
|
credentials: "include",
|
|
});
|
|
if (!res.ok) throw new Error("Failed to fetch");
|
|
const journeys = await res.json();
|
|
availableJourneys = journeys;
|
|
populateJourneySelect(journeys);
|
|
} catch (err) {
|
|
showToast("Error loading journeys", "error");
|
|
}
|
|
}
|
|
|
|
function populateJourneySelect(journeys) {
|
|
const select = document.getElementById("journey-select");
|
|
select.innerHTML =
|
|
'<option value="">-- Choose a journey --</option><option value="all">Show All Journeys</option>';
|
|
journeys.forEach((j) => {
|
|
const opt = document.createElement("option");
|
|
opt.value = j.id;
|
|
opt.textContent = j.title;
|
|
select.appendChild(opt);
|
|
});
|
|
}
|
|
|
|
async function loadJourney(journeyId) {
|
|
try {
|
|
const res = await fetch(
|
|
`${API_BASE}/journeys/${journeyId}`,
|
|
{ credentials: "include" },
|
|
);
|
|
if (!res.ok) throw new Error("Failed to load");
|
|
const journey = await res.json();
|
|
displayJourneyInfo(journey);
|
|
} catch (err) {
|
|
showToast("Error loading journey", "error");
|
|
}
|
|
}
|
|
|
|
function displayJourneyInfo(journey) {
|
|
document.getElementById("info-title").textContent =
|
|
journey.title;
|
|
document.getElementById("info-description").textContent =
|
|
journey.description || "-";
|
|
document.getElementById("info-marker-count").textContent =
|
|
journey.markers ? journey.markers.length : 0;
|
|
document.getElementById("info-date").textContent =
|
|
new Date(journey.created_at).toLocaleDateString() ||
|
|
"-";
|
|
document.getElementById(
|
|
"load-journey-btn",
|
|
).dataset.journey = JSON.stringify(journey);
|
|
document.getElementById("edit-journey-btn").disabled = false;
|
|
document.getElementById("delete-journey-btn").disabled = false;
|
|
}
|
|
|
|
function displayAllJourneysInfo() {
|
|
const markerCount = availableJourneys.reduce(
|
|
(total, journey) => total + (journey.markers ? journey.markers.length : 0),
|
|
0,
|
|
);
|
|
document.getElementById("info-title").textContent = "All Journeys";
|
|
document.getElementById("info-description").textContent =
|
|
`${availableJourneys.length} journeys selected`;
|
|
document.getElementById("info-marker-count").textContent = markerCount;
|
|
document.getElementById("info-date").textContent = "-";
|
|
document.getElementById("load-journey-btn").dataset.journey = JSON.stringify({
|
|
type: "all",
|
|
});
|
|
document.getElementById("edit-journey-btn").disabled = true;
|
|
document.getElementById("delete-journey-btn").disabled = true;
|
|
}
|
|
|
|
async function saveJourneyToAPI() {
|
|
const title = document
|
|
.getElementById("journey-title")
|
|
.value.trim();
|
|
if (!title) {
|
|
showToast("Please enter a title", "error");
|
|
return;
|
|
}
|
|
const description = document.getElementById(
|
|
"journey-description",
|
|
).value;
|
|
|
|
const markers = currentJourney.markers.map((m) => {
|
|
const latlng = normalizeLatLng(m.getLatLng());
|
|
m.setLatLng(latlng);
|
|
const content = m._content || {};
|
|
return {
|
|
lat: latlng.lat,
|
|
lng: latlng.lng,
|
|
title: content.title || "",
|
|
date: content.date || "",
|
|
description: content.description || "",
|
|
images: content.images || [],
|
|
};
|
|
});
|
|
|
|
const visibility = document.getElementById('journey-visibility').value;
|
|
|
|
const payload = { title, description, markers, visibility };
|
|
|
|
try {
|
|
let res;
|
|
if (currentJourney.id) {
|
|
res = await fetch(
|
|
`${API_BASE}/journeys/${currentJourney.id}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(payload),
|
|
credentials: "include",
|
|
},
|
|
);
|
|
} else {
|
|
res = await fetch(`${API_BASE}/journeys`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
credentials: "include",
|
|
});
|
|
}
|
|
if (!res.ok) throw new Error("Save failed");
|
|
const data = await res.json();
|
|
currentJourney.id = data.id;
|
|
showToast("Journey saved!");
|
|
fetchJourneys();
|
|
} catch (err) {
|
|
showToast("Error saving journey", "error");
|
|
}
|
|
}
|
|
|
|
async function deleteJourneyFromAPI(journeyId) {
|
|
if (!confirm("Delete this journey?")) return;
|
|
try {
|
|
const res = await fetch(
|
|
`${API_BASE}/journeys/${journeyId}`,
|
|
{
|
|
method: "DELETE",
|
|
credentials: "include",
|
|
},
|
|
);
|
|
if (!res.ok) throw new Error("Delete failed");
|
|
showToast("Journey deleted");
|
|
fetchJourneys();
|
|
document.getElementById("info-title").textContent = "-";
|
|
document.getElementById(
|
|
"info-description",
|
|
).textContent = "-";
|
|
document.getElementById(
|
|
"info-marker-count",
|
|
).textContent = "0";
|
|
document.getElementById("info-date").textContent = "-";
|
|
} catch (err) {
|
|
showToast("Error deleting journey", "error");
|
|
}
|
|
}
|
|
|
|
function loadJourneyMarkers(journey) {
|
|
currentJourney.markers.forEach((m) => map.removeLayer(m));
|
|
currentJourney.markers = [];
|
|
document.getElementById('journey-visibility').value = journey.visibility || 'private';
|
|
document.getElementById(
|
|
"current-markers-container",
|
|
).innerHTML =
|
|
'<p class="empty-message">No markers yet. Click on the map to add markers.</p>';
|
|
|
|
if (journey.markers) {
|
|
journey.markers.forEach((m) => {
|
|
const latlng = L.latLng(m.lat, m.lng);
|
|
const marker = createMarker(latlng, {
|
|
title: m.title,
|
|
date: m.date,
|
|
description: m.description,
|
|
images: m.images || [],
|
|
});
|
|
marker._content = { ...m };
|
|
});
|
|
}
|
|
currentJourney.id = journey.id;
|
|
document.getElementById("journey-title").value =
|
|
journey.title;
|
|
document.getElementById("journey-description").value =
|
|
journey.description || "";
|
|
setMode("create");
|
|
showToast("Journey loaded on map");
|
|
}
|
|
|
|
function loadAllJourneyMarkers() {
|
|
currentJourney.markers.forEach((m) => map.removeLayer(m));
|
|
currentJourney.markers = [];
|
|
currentJourney.id = null;
|
|
currentJourney.title = "All Journeys";
|
|
currentJourney.description = "";
|
|
document.getElementById("current-markers-container").innerHTML =
|
|
'<p class="empty-message">No markers found in the selected journeys.</p>';
|
|
|
|
availableJourneys.forEach((journey) => {
|
|
(journey.markers || []).forEach((m) => {
|
|
const latlng = L.latLng(m.lat, m.lng);
|
|
const markerTitle = m.title || "Untitled";
|
|
const displayTitle = `${journey.title}: ${markerTitle}`;
|
|
const marker = createMarker(latlng, {
|
|
title: displayTitle,
|
|
date: m.date,
|
|
description: m.description,
|
|
images: m.images || [],
|
|
});
|
|
marker._content = {
|
|
...m,
|
|
title: displayTitle,
|
|
journeyId: journey.id,
|
|
journeyTitle: journey.title,
|
|
};
|
|
});
|
|
});
|
|
|
|
document.getElementById("journey-title").value = "All Journeys";
|
|
document.getElementById("journey-description").value = "";
|
|
setMode("view");
|
|
if (currentJourney.markers.length > 0) {
|
|
journeyPath.setLatLngs([]);
|
|
map.fitBounds(L.featureGroup(currentJourney.markers).getBounds());
|
|
showToast("All journey markers loaded");
|
|
} else {
|
|
updateJourneyPath();
|
|
showToast("No markers found", "warning");
|
|
}
|
|
}
|
|
|
|
// ==================== UI HELPERS ================
|
|
function showToast(msg, type = "success") {
|
|
const toast = document.getElementById("toast");
|
|
document.getElementById("toast-message").textContent = msg;
|
|
toast.style.display = "block";
|
|
setTimeout(() => {
|
|
toast.style.display = "none";
|
|
}, 3000);
|
|
}
|
|
|
|
function setMode(mode) {
|
|
const createBtn = document.getElementById("mode-create");
|
|
const viewBtn = document.getElementById("mode-view");
|
|
const createPanel = document.getElementById("create-panel");
|
|
const viewPanel = document.getElementById("view-panel");
|
|
const indicator = document.getElementById("mode-indicator");
|
|
|
|
if (mode === "create") {
|
|
createBtn.classList.add("active");
|
|
viewBtn.classList.remove("active");
|
|
createPanel.classList.add("active-panel");
|
|
viewPanel.classList.remove("active-panel");
|
|
indicator.querySelector(".indicator-text").textContent =
|
|
"Creating: New Journey";
|
|
indicator.querySelector(".indicator-dot").className =
|
|
"indicator-dot creating";
|
|
} else {
|
|
viewBtn.classList.add("active");
|
|
createBtn.classList.remove("active");
|
|
viewPanel.classList.add("active-panel");
|
|
createPanel.classList.remove("active-panel");
|
|
indicator.querySelector(".indicator-text").textContent =
|
|
"Viewing Journeys";
|
|
indicator.querySelector(".indicator-dot").className =
|
|
"indicator-dot viewing";
|
|
}
|
|
}
|
|
|
|
function clearAllMarkers() {
|
|
currentJourney.markers.forEach((m) => map.removeLayer(m));
|
|
currentJourney.markers = [];
|
|
document.getElementById(
|
|
"current-markers-container",
|
|
).innerHTML =
|
|
'<p class="empty-message">No markers yet. Click on the map to add markers.</p>';
|
|
updateJourneyPath();
|
|
showToast("All markers cleared");
|
|
}
|
|
|
|
// ==================== EVENT LISTENERS (run after auth) ====================
|
|
function bindEventListeners() {
|
|
document
|
|
.getElementById("mode-create")
|
|
.addEventListener("click", () => setMode("create"));
|
|
document
|
|
.getElementById("mode-view")
|
|
.addEventListener("click", () => {
|
|
setMode("view");
|
|
fetchJourneys();
|
|
});
|
|
document
|
|
.getElementById("clear-markers-btn")
|
|
.addEventListener("click", clearAllMarkers);
|
|
document
|
|
.getElementById("save-journey-btn")
|
|
.addEventListener("click", saveJourneyToAPI);
|
|
document
|
|
.getElementById("close-modal")
|
|
.addEventListener("click", closeModal);
|
|
document
|
|
.getElementById("close-modal-footer")
|
|
.addEventListener("click", closeModal);
|
|
document
|
|
.getElementById("save-marker")
|
|
.addEventListener("click", saveMarker);
|
|
document
|
|
.getElementById("delete-marker")
|
|
.addEventListener("click", deleteMarkerFromMap);
|
|
document
|
|
.getElementById("toggle-sidebar")
|
|
.addEventListener("click", () => {
|
|
document
|
|
.querySelector(".sidebar")
|
|
.classList.toggle("collapsed");
|
|
});
|
|
document
|
|
.getElementById("zoom-in")
|
|
.addEventListener("click", () => map.zoomIn());
|
|
document
|
|
.getElementById("zoom-out")
|
|
.addEventListener("click", () => map.zoomOut());
|
|
document
|
|
.getElementById("fit-all")
|
|
.addEventListener("click", () => {
|
|
if (currentJourney.markers.length > 0) {
|
|
const group = L.featureGroup(
|
|
currentJourney.markers,
|
|
);
|
|
map.fitBounds(group.getBounds());
|
|
} else {
|
|
showToast("No markers to fit", "warning");
|
|
}
|
|
});
|
|
document
|
|
.getElementById("locate-me")
|
|
.addEventListener("click", () => {
|
|
map.locate({ setView: true, maxZoom: 16 });
|
|
});
|
|
document
|
|
.getElementById("journey-select")
|
|
.addEventListener("change", (e) => {
|
|
const val = e.target.value;
|
|
if (val === "all") {
|
|
displayAllJourneysInfo();
|
|
} else if (val) {
|
|
loadJourney(val);
|
|
}
|
|
});
|
|
document
|
|
.getElementById("load-journey-btn")
|
|
.addEventListener("click", () => {
|
|
const journeyData =
|
|
document.getElementById("load-journey-btn")
|
|
.dataset.journey;
|
|
if (!journeyData) return;
|
|
const journey = JSON.parse(journeyData);
|
|
if (journey.type === "all") {
|
|
loadAllJourneyMarkers();
|
|
} else {
|
|
loadJourneyMarkers(journey);
|
|
}
|
|
});
|
|
document
|
|
.getElementById("edit-journey-btn")
|
|
.addEventListener("click", () => {
|
|
const journeyData =
|
|
document.getElementById("load-journey-btn")
|
|
.dataset.journey;
|
|
if (journeyData)
|
|
loadJourneyMarkers(JSON.parse(journeyData));
|
|
});
|
|
document
|
|
.getElementById("delete-journey-btn")
|
|
.addEventListener("click", () => {
|
|
const journeyData =
|
|
document.getElementById("load-journey-btn")
|
|
.dataset.journey;
|
|
if (journeyData)
|
|
deleteJourneyFromAPI(
|
|
JSON.parse(journeyData).id,
|
|
);
|
|
});
|
|
document
|
|
.getElementById("add-marker-btn")
|
|
.addEventListener("click", () => {
|
|
setMode("create");
|
|
showToast("Click on the map to add a marker");
|
|
});
|
|
document
|
|
.getElementById("marker-text")
|
|
.addEventListener("input", (e) => updateMarkerMarkdownPreview(e.target.value));
|
|
}
|
|
|
|
// Expose functions globally (optional)
|
|
window.createMarker = createMarker;
|
|
window.updateJourneyPath = updateJourneyPath;
|
|
window.clearAllMarkers = clearAllMarkers;
|
|
window.setMode = setMode;
|
|
|
|
// ==================== START ====================
|
|
function startMap() {
|
|
initMap();
|
|
// Check for journey parameter
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const journeyToLoad = urlParams.get('journey');
|
|
if (journeyToLoad) {
|
|
fetch(`${API_BASE}/journeys/${journeyToLoad}`, { credentials: 'include' })
|
|
.then(res => res.json())
|
|
.then(journey => {
|
|
if (journey.can_edit) {
|
|
loadJourneyMarkers(journey);
|
|
} else {
|
|
showToast('You cannot edit this journey', true);
|
|
}
|
|
})
|
|
.catch(err => console.error(err));
|
|
}
|
|
bindEventListeners();
|
|
}
|
|
window.startMap = startMap;
|
|
})(); // end of IIFE
|
|
|
|
// ==================== SIDEBAR HELPER (outside IIFE) ====================
|
|
function initSidebar() {
|
|
const sidebar = document.querySelector('.sidebar');
|
|
if (!sidebar) return;
|
|
const isMobile = window.innerWidth <= 768;
|
|
if (isMobile) {
|
|
sidebar.classList.add('collapsed');
|
|
} else {
|
|
sidebar.classList.remove('collapsed');
|
|
}
|
|
}
|
|
|
|
// ==================== AUTHENTICATION CHECK ====================
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
const authenticated = await checkAuthAndRedirect();
|
|
if (authenticated) {
|
|
updateUserMenu();
|
|
window.addEventListener('resize', initSidebar);
|
|
initSidebar();
|
|
window.startMap();
|
|
}
|
|
});
|