Exporting is allowed for anyone with export permission or above. Pseudo-versions have been bumped in order to ensure no cache issues.
594 lines
19 KiB
JavaScript
594 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
import {
|
|
CRS,
|
|
DivIcon,
|
|
ImageOverlay,
|
|
LatLng,
|
|
LatLngBounds,
|
|
Map,
|
|
Marker,
|
|
Popup,
|
|
} from './leaflet/leaflet.js';
|
|
|
|
let cookies = {}
|
|
let map = null;
|
|
let active = null;
|
|
const markers = {}
|
|
|
|
window.addEventListener('DOMContentLoaded', async (event) => {
|
|
try {
|
|
await makeApiRequest('GET', '/');
|
|
cookies = get_cookies();
|
|
const settings = JSON.parse(atob(cookies['server_settings']));
|
|
document.querySelector('head > title')
|
|
.textContent = settings['site_name'];
|
|
document.getElementById('banner-sitename')
|
|
.textContent = settings['site_name'];
|
|
document.getElementById('banner-userid')
|
|
.textContent = cookies['username'];
|
|
|
|
map = setupMap('map', 'static/karta_plan3.png');
|
|
|
|
makeApiRequest('GET', '/points')
|
|
.then((data) => {
|
|
for(const [id, values] of Object.entries(data)) {
|
|
const pointData = {id: id,
|
|
position: new LatLng(values.latitude,
|
|
values.longitude),
|
|
placeName: values.placeName,
|
|
personName: values.personName,
|
|
personRole: values.personRole};
|
|
if(values.hasOwnProperty('comment')) {
|
|
pointData.comment = values.comment;
|
|
}
|
|
drawMarker(pointData);
|
|
}
|
|
});
|
|
|
|
const create_button = document.getElementById('create-workspace');
|
|
if(cookies['admin']) {
|
|
create_button.textContent = 'Skapa arbetsplatser';
|
|
create_button.addEventListener('click', (event) => {
|
|
create_button.classList.toggle('cancel');
|
|
if(create_button.classList.contains('cancel')) {
|
|
create_button.textContent = 'Sluta skapa arbetsplatser';
|
|
map.on('click', createMarker);
|
|
} else {
|
|
create_button.textContent = 'Skapa arbetsplatser';
|
|
map.off('click', createMarker);
|
|
}
|
|
});
|
|
} else {
|
|
create_button.remove();
|
|
}
|
|
|
|
const export_button = document.getElementById('export-workspaces');
|
|
if(cookies['export']) {
|
|
export_button.addEventListener('click', (event) => {
|
|
makeApiRequest('GET', '/export').then((result) => {
|
|
// Have to pass a name, but it won't be used so passing ''
|
|
const file = new File([result.csv_data], '');
|
|
const url = window.URL.createObjectURL(file);
|
|
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `export-dsv-karta.tsv`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
});
|
|
});
|
|
} else {
|
|
export_button.remove();
|
|
}
|
|
|
|
const toggle_button = document.getElementById('toggle-labels');
|
|
toggle_button.addEventListener('click', (event) => {
|
|
localStorage.setItem('label-visibility', !getVisibility());
|
|
updateLabels();
|
|
});
|
|
|
|
const search_button = document.getElementById('search-button');
|
|
search_button.addEventListener('click', (event) => {
|
|
const search_term = document.getElementById('search-term').value;
|
|
if(!search_term) {
|
|
clearSearchResults();
|
|
return;
|
|
}
|
|
|
|
clearSearchResults(true);
|
|
getSearchResults(search_term)
|
|
.then((result_ids) => prepareResultElements(result_ids))
|
|
.then((result_elements) => showSearchResults(result_elements));
|
|
});
|
|
|
|
document.querySelector('body').removeAttribute('style');
|
|
} catch(e) {
|
|
if(e.message === 'access denied') {
|
|
deny_access();
|
|
return;
|
|
}
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
function setupMap(element_id, map_image) {
|
|
const top_left = new LatLng(10, 0);
|
|
const bottom_right = new LatLng(0, 10);
|
|
const bounds = new LatLngBounds(top_left, bottom_right);
|
|
|
|
let zoom = 7;
|
|
const stored_zoom = localStorage.getItem('zoom-level');
|
|
if(stored_zoom) {
|
|
zoom = Number(stored_zoom);
|
|
}
|
|
|
|
let center = bounds.getCenter();
|
|
const latitude = localStorage.getItem('view-latitude');
|
|
const longitude = localStorage.getItem('view-longitude');
|
|
if(latitude) {
|
|
center = new LatLng(Number(latitude), Number(longitude));
|
|
}
|
|
|
|
const map = new Map(element_id, {
|
|
center: center,
|
|
maxBounds: bounds.pad(0.5),
|
|
zoom: zoom,
|
|
zoomSnap: 0.1});
|
|
const background = new ImageOverlay(map_image, bounds);
|
|
background.addTo(map);
|
|
|
|
map.on('zoomend', () => {
|
|
localStorage.setItem('zoom-level', map.getZoom());
|
|
});
|
|
|
|
map.on('moveend', (event) => {
|
|
const newPosition = map.getCenter();
|
|
localStorage.setItem('view-latitude', newPosition.lat);
|
|
localStorage.setItem('view-longitude', newPosition.lng);
|
|
});
|
|
|
|
map.on('click', (event) => {
|
|
clearActive();
|
|
});
|
|
|
|
return map;
|
|
}
|
|
|
|
function createMarker(event) {
|
|
const form = document.createElement('div');
|
|
form.appendChild(document.getElementById('pin-form')
|
|
.content.cloneNode(true));
|
|
form.querySelector('#save').addEventListener('click', () => {
|
|
const markerData = {
|
|
id: crypto.randomUUID(),
|
|
position: event.latlng,
|
|
placeName: form.querySelector('#workspace-name').value,
|
|
personName: form.querySelector('#person-name').value,
|
|
personRole: form.querySelector('#person-role').value,
|
|
comment: form.querySelector('#comment').value
|
|
}
|
|
saveMarker(markerData);
|
|
drawMarker(markerData);
|
|
map.closePopup();
|
|
});
|
|
|
|
const deleteButton = form.querySelector('#delete');
|
|
deleteButton.parentNode.removeChild(deleteButton);
|
|
|
|
map.openPopup(form, event.latlng);
|
|
form.querySelector('#workspace-name').focus();
|
|
}
|
|
|
|
function drawMarker(data) {
|
|
const markerId = data.id;
|
|
const position = data.position;
|
|
const placeName = data.placeName;
|
|
const personName = data.personName;
|
|
const personRole = data.personRole;
|
|
|
|
let comment = '';
|
|
if(cookies['export']) {
|
|
comment = data.comment;
|
|
}
|
|
|
|
const label = document.createElement('div');
|
|
|
|
let draggable = false;
|
|
if(cookies['admin']) {
|
|
draggable = true;
|
|
}
|
|
|
|
label.appendChild(document.getElementById('pin-template')
|
|
.content.cloneNode(true));
|
|
|
|
const icon_hack = new DivIcon({
|
|
className: 'map-icon',
|
|
iconSize: [20, 20],
|
|
html: label
|
|
});
|
|
const marker = new Marker(position, {
|
|
icon: icon_hack,
|
|
title: placeName,
|
|
draggable: draggable
|
|
});
|
|
|
|
const popup_div = document.createElement('div');
|
|
|
|
if(cookies['admin']) {
|
|
popup_div.appendChild(document.getElementById('pin-form')
|
|
.content.cloneNode(true));
|
|
const popup_placename = popup_div.querySelector('#workspace-name');
|
|
const popup_personname = popup_div.querySelector('#person-name');
|
|
const popup_personrole = popup_div.querySelector('#person-role');
|
|
const popup_comment = popup_div.querySelector('#comment');
|
|
popup_placename.value = placeName;
|
|
popup_personname.value = personName;
|
|
popup_personrole.value = personRole;
|
|
popup_comment.value = comment;
|
|
|
|
popup_div.querySelector('#save').addEventListener('click', () => {
|
|
markers[markerId].placeName = popup_placename.value;
|
|
markers[markerId].personName = popup_personname.value;
|
|
markers[markerId].personRole = popup_personrole.value;
|
|
markers[markerId].comment = popup_comment.value;
|
|
|
|
saveMarker({id: markerId,
|
|
position: markers[markerId].marker.getLatLng(),
|
|
placeName: popup_placename.value,
|
|
personName: popup_personname.value,
|
|
personRole: popup_personrole.value,
|
|
comment: popup_comment.value});
|
|
map.closePopup();
|
|
|
|
updateLabel(marker);
|
|
});
|
|
|
|
popup_div.querySelector('#delete').addEventListener('click', () => {
|
|
deleteMarker(markerId);
|
|
delete markers[markerId];
|
|
map.removeLayer(marker);
|
|
});
|
|
|
|
marker.on('popupopen', (event) => {
|
|
if(!popup_placename.value) {
|
|
popup_placename.focus();
|
|
} else {
|
|
popup_personname.focus();
|
|
}
|
|
});
|
|
|
|
marker.on('popupclose', (event) => {
|
|
if(!markers[markerId]) {
|
|
// Don't act on deleted markers
|
|
return;
|
|
}
|
|
popup_placename.value = markers[markerId].placeName;
|
|
popup_personname.value = markers[markerId].personName;
|
|
popup_personrole.value = markers[markerId].personRole;
|
|
popup_comment.value = markers[markerId].comment;
|
|
});
|
|
|
|
marker.on('move', (event) => {
|
|
saveMarker({id: markerId,
|
|
position: markers[markerId].marker.getLatLng(),
|
|
placeName: markers[markerId].placeName,
|
|
personName: markers[markerId].personName,
|
|
personRole: markers[markerId].personRole,
|
|
comment: markers[markerId].comment,
|
|
});
|
|
});
|
|
} else {
|
|
popup_div.appendChild(document.getElementById('pin-info')
|
|
.content.cloneNode(true));
|
|
popup_div.querySelector('.workspace-name').textContent = placeName;
|
|
popup_div.querySelector('.person-name').textContent = personName;
|
|
const role_element = popup_div.querySelector('.person-role');
|
|
if(personRole) {
|
|
role_element.textContent = personRole;
|
|
} else {
|
|
role_element.remove();
|
|
}
|
|
|
|
if(!comment) {
|
|
popup_div.querySelector('.comment').remove();
|
|
} else {
|
|
popup_div.querySelector('.comment-text').textContent = comment;
|
|
}
|
|
}
|
|
|
|
marker.on('popupopen', (event) => {
|
|
if(active && marker !== active.marker) {
|
|
clearActive();
|
|
}
|
|
});
|
|
|
|
const result_template = document.getElementById('result-template')
|
|
.content.cloneNode(true);
|
|
const search_element = result_template.querySelector('.search-result');
|
|
search_element.addEventListener('click', (event) => {
|
|
setActive(marker, search_element);
|
|
map.flyTo(marker.getLatLng());
|
|
});
|
|
|
|
search_element.addEventListener('dblclick', (event) => {
|
|
setActive(marker, search_element);
|
|
map.flyTo(marker.getLatLng());
|
|
marker.openPopup();
|
|
});
|
|
|
|
markers[markerId] = {
|
|
marker: marker,
|
|
search_element: search_element,
|
|
placeName: placeName,
|
|
personName: personName,
|
|
personRole: personRole,
|
|
comment: comment
|
|
};
|
|
marker.id = markerId;
|
|
|
|
const popup = new Popup();
|
|
popup.setContent(popup_div);
|
|
marker.bindPopup(popup);
|
|
marker.addTo(map);
|
|
updateLabel(marker);
|
|
}
|
|
|
|
function updateLabels(overrides) {
|
|
for(const [id, values] of Object.entries(markers)) {
|
|
updateLabel(values.marker, overrides);
|
|
}
|
|
}
|
|
|
|
function updateLabel(marker, overrides) {
|
|
if(!overrides) {
|
|
overrides = {};
|
|
}
|
|
const placeName = markers[marker.id].placeName;
|
|
const personName = markers[marker.id].personName;
|
|
const personRole = markers[marker.id].personRole;
|
|
const comment = markers[marker.id].comment;
|
|
const label = marker.getElement().querySelector('.map-flex');
|
|
const text_label = label.querySelector('.label');
|
|
let visible = getVisibility();
|
|
if(overrides.visible !== undefined) {
|
|
visible = overrides.visible;
|
|
}
|
|
|
|
let icon_src = './static/pin-red.svg';
|
|
let tooltip = personName;
|
|
if(!personName) {
|
|
icon_src = './static/pin-yellow.svg';
|
|
tooltip = placeName;
|
|
}
|
|
|
|
let z_offset = 0;
|
|
if(overrides.active) {
|
|
icon_src = './static/pin-blue.svg';
|
|
z_offset = 1000;
|
|
}
|
|
|
|
let dim = false;
|
|
if(overrides.dim !== undefined) {
|
|
dim = overrides.dim;
|
|
}
|
|
if(dim) {
|
|
label.classList.add('dim');
|
|
icon_src = './static/pin-gray.svg';
|
|
} else {
|
|
label.classList.remove('dim');
|
|
}
|
|
|
|
marker.setZIndexOffset(z_offset);
|
|
label.querySelector('img').setAttribute('src', icon_src);
|
|
marker.getElement().setAttribute('title', tooltip);
|
|
|
|
label.querySelector('.location').textContent = placeName;
|
|
label.querySelector('.person').textContent = personName;
|
|
|
|
// Avoiding use of .toggle() here so we guarantee consistency
|
|
// even if other actions have modified individual labels
|
|
if(visible) {
|
|
text_label.classList.remove('no-label');
|
|
} else {
|
|
text_label.classList.add('no-label');
|
|
}
|
|
|
|
const search_element = markers[marker.id].search_element;
|
|
search_element.querySelector('.location').textContent = placeName;
|
|
search_element.querySelector('.person').textContent = personName;
|
|
|
|
const role_element = search_element.querySelector('.role');
|
|
role_element.textContent = personRole;
|
|
if(!role_element.textContent) {
|
|
role_element.classList.add('hidden');
|
|
} else {
|
|
role_element.classList.remove('hidden');
|
|
}
|
|
|
|
const comment_element = search_element.querySelector('.comment');
|
|
comment_element.textContent = comment;
|
|
if(!comment_element.textContent) {
|
|
comment_element.classList.add('hidden');
|
|
} else {
|
|
comment_element.classList.remove('hidden');
|
|
}
|
|
|
|
}
|
|
|
|
function getVisibility() {
|
|
// This hack deals with localStorage only storing strings:
|
|
return localStorage.getItem('label-visibility') === 'true';
|
|
}
|
|
|
|
function clearSearchResults(doing_search) {
|
|
const results_div = document.getElementById('search-results');
|
|
while(results_div.firstChild) {
|
|
results_div.removeChild(results_div.lastChild);
|
|
}
|
|
|
|
if(doing_search) {
|
|
const spinner = document.createElement('img');
|
|
spinner.setAttribute('src', './static/spinner.svg');
|
|
spinner.id = 'search-spinner';
|
|
results_div.appendChild(spinner);
|
|
}
|
|
|
|
updateLabels();
|
|
|
|
return results_div;
|
|
}
|
|
|
|
async function getSearchResults(search_term) {
|
|
const lowercase_term = search_term.toLowerCase();
|
|
const result_ids = [];
|
|
for(const [id, values] of Object.entries(markers)) {
|
|
if(values.placeName.toLowerCase().includes(lowercase_term)
|
|
|| values.personName.toLowerCase().includes(lowercase_term)) {
|
|
result_ids.push(id);
|
|
}
|
|
}
|
|
return result_ids;
|
|
}
|
|
|
|
async function prepareResultElements(result_ids) {
|
|
const result_objects = [];
|
|
|
|
for(const result_id of result_ids) {
|
|
result_objects.push(markers[result_id]);
|
|
}
|
|
|
|
result_objects.sort((e1, e2) => {
|
|
const person1 = e1.personName.toLowerCase();
|
|
const person2 = e2.personName.toLowerCase();
|
|
|
|
if(person1 && !person2) {
|
|
return -1;
|
|
}
|
|
if(person2 && !person1) {
|
|
return 1;
|
|
}
|
|
if(person1 && person2) {
|
|
return person1.localeCompare(person2);
|
|
}
|
|
|
|
const location1 = e1.placeName.toLowerCase();
|
|
const location2 = e2.placeName.toLowerCase();
|
|
|
|
if(location1 && !location2) {
|
|
return -1;
|
|
}
|
|
if(location2 && !location1) {
|
|
return 1;
|
|
}
|
|
if(location1 && location2) {
|
|
return location1.localeCompare(location2);
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
return result_objects;
|
|
}
|
|
|
|
async function showSearchResults(result_objects) {
|
|
const results_div = clearSearchResults();
|
|
|
|
updateLabels({dim: true});
|
|
|
|
result_objects.forEach((result) => {
|
|
results_div.appendChild(result.search_element);
|
|
updateLabel(result.marker);
|
|
});
|
|
}
|
|
|
|
function setActive(marker, result_element) {
|
|
clearActive();
|
|
active = {marker: marker,
|
|
result: result_element};
|
|
updateLabel(marker, {active: true,
|
|
visible: true});
|
|
result_element.classList.add('active');
|
|
}
|
|
|
|
function clearActive() {
|
|
if(active) {
|
|
updateLabel(active.marker);
|
|
active.result.classList.remove('active');
|
|
active = null;
|
|
}
|
|
}
|
|
|
|
function deny_access() {
|
|
const body = document.querySelector('body');
|
|
const main = document.querySelector('main');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const header = document.getElementById('banner-sitename');
|
|
const message = document.createElement('p');
|
|
|
|
body.removeChild(main);
|
|
body.removeChild(sidebar);
|
|
|
|
header.textContent = 'Access denied';
|
|
message.textContent = 'You do not have access to this site.';
|
|
message.id = 'access-denied';
|
|
body.appendChild(message);
|
|
body.removeAttribute('style');
|
|
}
|
|
|
|
function get_cookies() {
|
|
var out = new Object();
|
|
const cookies = document.cookie.split('; ');
|
|
cookies.forEach((cookie) => {
|
|
const temp = cookie.split('=');
|
|
const name = temp[0];
|
|
const value = temp.slice(1).join('=');
|
|
out[name] = value;
|
|
});
|
|
return out;
|
|
}
|
|
|
|
function login() {
|
|
const current_path = window.location.pathname;
|
|
return window.location.replace('/api/login?return='
|
|
+ current_path);
|
|
}
|
|
|
|
async function saveMarker(data) {
|
|
const request_body = {id: data.id,
|
|
placeName: data.placeName,
|
|
personName: data.personName,
|
|
latitude: data.position.lat,
|
|
longitude: data.position.lng,
|
|
personRole: data.personRole,
|
|
comment: data.comment};
|
|
makeApiRequest('PUT', '/point', request_body);
|
|
}
|
|
|
|
async function deleteMarker(markerId) {
|
|
makeApiRequest('DELETE', '/point', {'id': markerId});
|
|
}
|
|
|
|
async function makeApiRequest(method, path, body) {
|
|
const data = {'method': method,
|
|
'headers': {'Content-Type': 'application/json'}};
|
|
if(method != 'GET') {
|
|
data['body'] = JSON.stringify(body);
|
|
}
|
|
const request = new Request('/api' + path, data);
|
|
return fetch(request)
|
|
.then((response) => {
|
|
if(response.status === 403) {
|
|
const access = cookies['access'];
|
|
if(access === 'denied') {
|
|
throw new Error('access denied');
|
|
}
|
|
login();
|
|
}
|
|
return response.json();
|
|
}, (error) => {
|
|
alert('A network error has occurred. Please reload the page.');
|
|
});
|
|
}
|