function renderPins(pins) { const pinsDiv = document.getElementById("pins"); pinsDiv.innerHTML = ''; const img = document.getElementById("main-map"); const imgWidth = img.clientWidth; const imgHeight = img.clientHeight; pins.forEach(pin => { console.log(pin) const el = document.createElement('a'); el.className = "pin"; el.href = pin.url; el.title = pin.label; el.target = "_blank"; el.style.left = (imgWidth * (pin.x / 100)) + 'px'; el.style.top = (imgHeight * (pin.y / 100)) + 'px'; el.dataset.pinid = pin.id; el.dataset.x = pin.x; el.dataset.y = pin.y; if (pin.pin_type == "general"){ el.innerHTML = 'general pin'; } if (pin.pin_type == "town"){ el.innerHTML = 'town pin'; } if (pin.pin_type == "dungeon"){ el.innerHTML = 'dungeon pin'; } const label = document.createElement('div'); label.className = 'pin-label'; label.innerText = pin.label; el.appendChild(label); pinsDiv.appendChild(el); }); renderPinsTable(pins) } function fetchPins() { fetch("/api/pins/") .then(r => { if (!r.ok) throw new Error("Pin fetch failed"); return r.json(); }) .then(renderPins) .catch(() => { // For demo/development: Show example renderPins([ {x:15, y:30, label:'Demo Pin', url:'/'} ]); }); } function renderPinsTable(pins) { const tbody = document.querySelector('#pins-table tbody'); tbody.innerHTML = ''; pins.forEach((pin, idx) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${idx+1} ${escapeHTML(pin.label)} ${pin.url} ${pin.x.toFixed(2)} ${pin.y.toFixed(2)} `; tbody.appendChild(tr); }); } function escapeHTML(str) { // Prevent breaking the DOM if user enters < > or & etc return (str||'').replace(/[&<>"'`]/g, s => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''', '`':'`' }[s])); } function centerMapOnPin(xPercent, yPercent) { const mapImg = document.getElementById('main-map'); const container = document.getElementById('map-container'); // Calculate pixel pos relative to image const imgWidth = mapImg.clientWidth; const imgHeight = mapImg.clientHeight; const pinX = imgWidth * (xPercent / 100); const pinY = imgHeight * (yPercent / 100); // Center in the scrollable container const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; container.scrollLeft = Math.max(0, pinX - containerWidth / 2); container.scrollTop = Math.max(0, pinY - containerHeight / 2); // Highlight the right pin const pinEl = Array.from(document.querySelectorAll('.pin')).find( el => Math.abs(parseFloat(el.dataset.x) - xPercent) < 0.0001 && Math.abs(parseFloat(el.dataset.y) - yPercent) < 0.0001 ); if(pinEl) { pinEl.classList.add('pin-highlight'); setTimeout(()=>pinEl.classList.remove('pin-highlight'), 1200); } } function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } fetchPins(); document.getElementById('map-container').addEventListener('contextmenu', function(e) { e.preventDefault(); const mapImg = document.getElementById('main-map'); const rect = mapImg.getBoundingClientRect(); if (!(e.target === mapImg || e.target.id === 'pins')) return; const x = 100 * (e.clientX - rect.left) / rect.width; const y = 100 * (e.clientY - rect.top) / rect.height; document.getElementById('pin-x').value = x; document.getElementById('pin-y').value = y; document.getElementById('add-pin-form').reset(); UIkit.modal('#add-pin-modal').show(); }); document.getElementById('add-pin-form').addEventListener('submit', function(e) { e.preventDefault(); const x = parseFloat(document.getElementById('pin-x').value); const y = parseFloat(document.getElementById('pin-y').value); const label = document.getElementById('pin-label').value; const url = document.getElementById('pin-url').value; const pin_type = document.getElementById('pin-type').value; fetch('/api/pins/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({x, y, label, url, pin_type}) }).then(resp => { if (resp.ok) { UIkit.modal('#add-pin-modal').hide(); fetchPins(); } else { resp.json().then(data => alert(JSON.stringify(data))); } }); }); function resizePinsDiv() { var mapImg = document.getElementById('main-map'); var pinsDiv = document.getElementById('pins'); // Set pins div to same px size as image pinsDiv.style.width = mapImg.width + 'px'; pinsDiv.style.height = mapImg.height + 'px'; pinsDiv.style.position = 'absolute'; pinsDiv.style.top = '0'; pinsDiv.style.left = '0'; } document.getElementById('main-map').addEventListener('load', resizePinsDiv); window.addEventListener('resize', resizePinsDiv); if (document.getElementById('main-map').complete) resizePinsDiv(); // This code handles the dragging of the map const container = document.getElementById('map-container'); let isDragging = false; let startX, startY, scrollLeft, scrollTop; container.addEventListener('mousedown', function(e) { isDragging = true; container.classList.add('dragging'); startX = e.pageX - container.offsetLeft; startY = e.pageY - container.offsetTop; scrollLeft = container.scrollLeft; scrollTop = container.scrollTop; }); container.addEventListener('mouseleave', function() { isDragging = false; container.classList.remove('dragging'); }); container.addEventListener('mouseup', function() { isDragging = false; container.classList.remove('dragging'); }); container.addEventListener('mousemove', function(e) { if (!isDragging) return; e.preventDefault(); const x = e.pageX - container.offsetLeft; const y = e.pageY - container.offsetTop; const walkX = x - startX; const walkY = y - startY; container.scrollLeft = scrollLeft - walkX; container.scrollTop = scrollTop - walkY; });