207 lines
7.4 KiB
JavaScript
207 lines
7.4 KiB
JavaScript
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 = '<img src="/static/pin.svg" alt="general pin" />';
|
|
}
|
|
if (pin.pin_type == "town"){
|
|
el.innerHTML = '<img src="/static/town.png" alt="town pin" />';
|
|
}
|
|
if (pin.pin_type == "dungeon"){
|
|
el.innerHTML = '<img src="/static/dungeon.png" alt="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 = `
|
|
<td>${idx+1}</td>
|
|
<td>${escapeHTML(pin.label)}</td>
|
|
<td><a href="${pin.url}" target="_blank">${pin.url}</a></td>
|
|
<td>${pin.x.toFixed(2)}</td>
|
|
<td>${pin.y.toFixed(2)}</td>
|
|
<td>
|
|
<button class="uk-button uk-button-small uk-button-default" onclick="centerMapOnPin(${pin.x},${pin.y})">Center</button>
|
|
<button class="uk-button uk-button-small uk-button-primary" onclick="editPin('${pin.id}')">Edit</button>
|
|
<button class="uk-button uk-button-small uk-button-danger" onclick="deletePin('${pin.id}')">Delete</button>
|
|
</td>
|
|
`;
|
|
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;
|
|
}); |