|
|
|
|
@@ -14,31 +14,31 @@
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
// API Endpoint as relative Path
|
|
|
|
|
var API_URL = 'api/contributions.php';
|
|
|
|
|
const API_URL = 'api/contributions.php';
|
|
|
|
|
|
|
|
|
|
// Current User Name, set via Login Modal, stored in sessionStorage
|
|
|
|
|
var currentUser = sessionStorage.getItem('webgis_user') || '';
|
|
|
|
|
let currentUser = sessionStorage.getItem('webgis_user') || '';
|
|
|
|
|
|
|
|
|
|
// Category Definitions with Labels, Icons, and Colors
|
|
|
|
|
var CATEGORIES = {
|
|
|
|
|
mobility: { label: 'Mobilität', icon: '🚲', color: '#1565C0', faIcon: 'fa-bicycle' },
|
|
|
|
|
building: { label: 'Bauen', icon: '🏗️', color: '#E65100', faIcon: 'fa-helmet-safety' },
|
|
|
|
|
energy: { label: 'Energie', icon: '⚡', color: '#F9A825', faIcon: 'fa-bolt' },
|
|
|
|
|
environment: { label: 'Umwelt', icon: '🌳', color: '#2E7D32', faIcon: 'fa-tree' },
|
|
|
|
|
industry: { label: 'Industrie', icon: '🏭', color: '#6A1B9A', faIcon: 'fa-industry' },
|
|
|
|
|
consumption: { label: 'Konsum', icon: '🛒', color: '#AD1457', faIcon: 'fa-cart-shopping' },
|
|
|
|
|
other: { label: 'Sonstiges', icon: '📌', color: '#546E7A', faIcon: 'fa-map-pin' }
|
|
|
|
|
const CATEGORIES = {
|
|
|
|
|
consumption: { label: 'Geschäfte', faIcon: 'fa-cart-shopping', color: '#C00000' },
|
|
|
|
|
building: { label: 'Bauen', faIcon: 'fa-building', color: '#E65100' },
|
|
|
|
|
energy: { label: 'Energie', faIcon: 'fa-bolt', color: '#FFC000' },
|
|
|
|
|
environment: { label: 'Umwelt', faIcon: 'fa-seedling', color: '#92D050' },
|
|
|
|
|
mobility: { label: 'Mobilität', faIcon: 'fa-bus', color: '#0070C0' },
|
|
|
|
|
industry: { label: 'Industrie', faIcon: 'fa-industry', color: '#7030A0' },
|
|
|
|
|
other: { label: 'Sonstiges', faIcon: 'fa-thumbtack', color: '#7F7F7F' }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Application State
|
|
|
|
|
var map; // Leaflet Map Instance
|
|
|
|
|
var sidebar; // Sidebar Instance
|
|
|
|
|
var contributionsLayer; // GeoJSON Layer holding all Contributions
|
|
|
|
|
var contributionsData = []; // Raw Contribution Data Array
|
|
|
|
|
var activeFilters = Object.keys(CATEGORIES); // Active Category Filters
|
|
|
|
|
var drawnGeometry = null; // Temporary Storage for Geometry drawn with Geoman
|
|
|
|
|
var drawnGeomType = null; // Temporary Storage for Geometry Type
|
|
|
|
|
var userVotes = {}; // Tracks User Votes
|
|
|
|
|
let map; // Leaflet Map Instance
|
|
|
|
|
let sidebar; // Sidebar Instance
|
|
|
|
|
let contributionsLayer; // GeoJSON Layer holding all Contributions
|
|
|
|
|
let contributionsData = []; // Raw Contribution Data Array
|
|
|
|
|
let activeFilters = Object.keys(CATEGORIES); // Active Category Filters
|
|
|
|
|
let drawnGeometry = null; // Temporary Storage for Geometry drawn with Geoman
|
|
|
|
|
let drawnGeomType = null; // Temporary Storage for Geometry Type
|
|
|
|
|
let userVotes = {}; // Tracks User Votes
|
|
|
|
|
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// Block 2: Map Initialization
|
|
|
|
|
@@ -63,17 +63,17 @@ map = L.map('map', {
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
// Basemap Tile Layers
|
|
|
|
|
var basemapOSM = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
|
|
|
const basemapOSM = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
|
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
|
|
|
|
maxZoom: 20
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var basemapCartoDB = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
|
|
|
|
attribution: '© <a href="https://carto.com/">CARTO</a>',
|
|
|
|
|
const basemapCartoDB = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
|
|
|
|
attribution: '© <a href="https://carto.com/">Carto</a>',
|
|
|
|
|
maxZoom: 20
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var basemapSatellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
|
|
|
|
const basemapSatellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
|
|
|
|
attribution: '© <a href="https://www.esri.com/">Esri</a>',
|
|
|
|
|
maxZoom: 20
|
|
|
|
|
});
|
|
|
|
|
@@ -82,15 +82,15 @@ var basemapSatellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/
|
|
|
|
|
basemapCartoDB.addTo(map);
|
|
|
|
|
|
|
|
|
|
// Layer Control
|
|
|
|
|
var basemaps = {
|
|
|
|
|
'OpenStreetMap': basemapOSM,
|
|
|
|
|
'CartoDB (hell)': basemapCartoDB,
|
|
|
|
|
'Satellit (Esri)': basemapSatellite,
|
|
|
|
|
const basemaps = {
|
|
|
|
|
'<i class="fa-solid fa-map" style="color:#404040;"></i> Hintergrundkarte (farbe)': basemapOSM,
|
|
|
|
|
'<i class="fa-solid fa-map" style="color:#404040;"></i> Hintergrundkarte (grau)': basemapCartoDB,
|
|
|
|
|
'<i class="fa-solid fa-satellite" style="color:#404040;"></i> Satellitenbild': basemapSatellite,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var overlays = {}; // Populated later with Contribution Layers
|
|
|
|
|
const overlays = {}; // Populated later with Contribution Layers
|
|
|
|
|
|
|
|
|
|
var layerControl = L.control.layers(basemaps, overlays, {
|
|
|
|
|
const layerControl = L.control.layers(basemaps, overlays, {
|
|
|
|
|
position: 'topright',
|
|
|
|
|
collapsed: true
|
|
|
|
|
}).addTo(map);
|
|
|
|
|
@@ -135,39 +135,39 @@ L.Control.geocoder({
|
|
|
|
|
}).addTo(map);
|
|
|
|
|
|
|
|
|
|
// Polyline Measure Tool
|
|
|
|
|
L.control.polylineMeasure({
|
|
|
|
|
position: 'topright',
|
|
|
|
|
unit: 'metres',
|
|
|
|
|
showBearings: false,
|
|
|
|
|
clearMeasurementsOnStop: false,
|
|
|
|
|
showClearControl: true
|
|
|
|
|
}).addTo(map);
|
|
|
|
|
// L.control.polylineMeasure({
|
|
|
|
|
// position: 'topright',
|
|
|
|
|
// unit: 'metres',
|
|
|
|
|
// showBearings: false,
|
|
|
|
|
// clearMeasurementsOnStop: false,
|
|
|
|
|
// showClearControl: true
|
|
|
|
|
// }).addTo(map);
|
|
|
|
|
|
|
|
|
|
// Mouse Position Display
|
|
|
|
|
var MousePositionControl = L.Control.extend({
|
|
|
|
|
options: { position: 'bottomright' },
|
|
|
|
|
// const MousePositionControl = L.Control.extend({
|
|
|
|
|
// options: { position: 'bottomright' },
|
|
|
|
|
|
|
|
|
|
onAdd: function () {
|
|
|
|
|
var container = L.DomUtil.create('div', 'mouse-position-display');
|
|
|
|
|
container.innerHTML = 'Lat: , Lng: ';
|
|
|
|
|
// onAdd: function () {
|
|
|
|
|
// const container = L.DomUtil.create('div', 'mouse-position-display');
|
|
|
|
|
// container.innerHTML = 'Lat: , Lng: ';
|
|
|
|
|
|
|
|
|
|
map.on('mousemove', function (e) {
|
|
|
|
|
container.innerHTML = 'Lat: ' + e.latlng.lat.toFixed(5) + ', Lng: ' + e.latlng.lng.toFixed(5);
|
|
|
|
|
});
|
|
|
|
|
// map.on('mousemove', function (e) {
|
|
|
|
|
// container.innerHTML = 'Lat: ' + e.latlng.lat.toFixed(5) + ', Lng: ' + e.latlng.lng.toFixed(5);
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
return container;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// return container;
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
new MousePositionControl().addTo(map);
|
|
|
|
|
// new MousePositionControl().addTo(map);
|
|
|
|
|
|
|
|
|
|
// GPS Location Button
|
|
|
|
|
var GpsControl = L.Control.extend({
|
|
|
|
|
const GpsControl = L.Control.extend({
|
|
|
|
|
options: { position: 'topright' },
|
|
|
|
|
|
|
|
|
|
onAdd: function () {
|
|
|
|
|
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
|
|
|
|
|
var button = L.DomUtil.create('a', 'gps-control-button', container);
|
|
|
|
|
const container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
|
|
|
|
|
const button = L.DomUtil.create('a', 'gps-control-button', container);
|
|
|
|
|
button.href = '#';
|
|
|
|
|
button.title = 'Mein Standort';
|
|
|
|
|
button.innerHTML = '<i class="fa-solid fa-location-crosshairs"></i>';
|
|
|
|
|
@@ -184,7 +184,7 @@ var GpsControl = L.Control.extend({
|
|
|
|
|
new GpsControl().addTo(map);
|
|
|
|
|
|
|
|
|
|
// GPS Location Found Handler
|
|
|
|
|
var gpsMarker = null;
|
|
|
|
|
let gpsMarker = null;
|
|
|
|
|
|
|
|
|
|
map.on('locationfound', function (e) {
|
|
|
|
|
if (gpsMarker) {
|
|
|
|
|
@@ -240,7 +240,7 @@ map.pm.setLang('de');
|
|
|
|
|
|
|
|
|
|
// Captures drawn Geometry and opens the Create Modal
|
|
|
|
|
map.on('pm:create', function (e) {
|
|
|
|
|
var geojson = e.layer.toGeoJSON().geometry;
|
|
|
|
|
const geojson = e.layer.toGeoJSON().geometry;
|
|
|
|
|
|
|
|
|
|
// Determines drawn Geometry Type and normalizes to simple Types
|
|
|
|
|
if (e.shape === 'Marker') {
|
|
|
|
|
@@ -280,8 +280,8 @@ map.on('pm:create', function (e) {
|
|
|
|
|
|
|
|
|
|
// Generic API Call Function
|
|
|
|
|
function apiCall(data, callback) {
|
|
|
|
|
var formData = new FormData();
|
|
|
|
|
for (var key in data) {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
for (const key in data) {
|
|
|
|
|
formData.append(key, data[key]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -324,8 +324,7 @@ function loadContributions() {
|
|
|
|
|
onEachFeature: bindFeaturePopup
|
|
|
|
|
}).addTo(map);
|
|
|
|
|
|
|
|
|
|
layerControl.addOverlay(contributionsLayer, 'Beiträge');
|
|
|
|
|
|
|
|
|
|
layerControl.addOverlay(contributionsLayer, '<i class="fa-solid fa-map-pin" style="color:#C00000;"></i> Beiträge');
|
|
|
|
|
// Update Sidebar List and Statistics
|
|
|
|
|
updateContributionsList();
|
|
|
|
|
updateStatistics();
|
|
|
|
|
@@ -339,7 +338,7 @@ function loadContributions() {
|
|
|
|
|
|
|
|
|
|
// Style for Point Features (CircleMarkers)
|
|
|
|
|
function stylePoint(feature, latlng) {
|
|
|
|
|
var cat = CATEGORIES[feature.properties.category] || CATEGORIES.other;
|
|
|
|
|
const cat = CATEGORIES[feature.properties.category] || CATEGORIES.other;
|
|
|
|
|
|
|
|
|
|
return L.circleMarker(latlng, {
|
|
|
|
|
radius: 8,
|
|
|
|
|
@@ -352,7 +351,7 @@ function stylePoint(feature, latlng) {
|
|
|
|
|
|
|
|
|
|
// Style for Line and Polygon Features
|
|
|
|
|
function styleLinePolygon(feature) {
|
|
|
|
|
var cat = CATEGORIES[feature.properties.category] || CATEGORIES.other;
|
|
|
|
|
const cat = CATEGORIES[feature.properties.category] || CATEGORIES.other;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
color: cat.color,
|
|
|
|
|
@@ -369,19 +368,19 @@ function styleLinePolygon(feature) {
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
function bindFeaturePopup(feature, layer) {
|
|
|
|
|
var props = feature.properties;
|
|
|
|
|
var cat = CATEGORIES[props.category] || CATEGORIES.other;
|
|
|
|
|
const props = feature.properties;
|
|
|
|
|
const cat = CATEGORIES[props.category] || CATEGORIES.other;
|
|
|
|
|
|
|
|
|
|
// Formats Date
|
|
|
|
|
var date = new Date(props.created_at);
|
|
|
|
|
var dateStr = date.toLocaleDateString('de-DE', {
|
|
|
|
|
const date = new Date(props.created_at);
|
|
|
|
|
const dateStr = date.toLocaleDateString('de-DE', {
|
|
|
|
|
day: '2-digit', month: '2-digit', year: 'numeric'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Builds Popup on Click
|
|
|
|
|
var html = '' +
|
|
|
|
|
const html = '' +
|
|
|
|
|
'<div class="popup-detail">' +
|
|
|
|
|
'<span class="popup-detail-category">' + cat.icon + ' ' + cat.label + '</span>' +
|
|
|
|
|
'<span class="popup-detail-category">' + categoryIcon(cat) + ' ' + cat.label + '</span>' +
|
|
|
|
|
'<div class="popup-detail-title">' + escapeHtml(props.title) + '</div>' +
|
|
|
|
|
(props.description ? '<div class="popup-detail-description">' + escapeHtml(props.description) + '</div>' : '') +
|
|
|
|
|
'<div class="popup-detail-meta">' +
|
|
|
|
|
@@ -406,7 +405,7 @@ function bindFeaturePopup(feature, layer) {
|
|
|
|
|
layer.bindPopup(html, { maxWidth: 320, minWidth: 240 });
|
|
|
|
|
|
|
|
|
|
// Builds Tooltip on Hover
|
|
|
|
|
layer.bindTooltip(cat.icon + ' ' + escapeHtml(props.title), {
|
|
|
|
|
layer.bindTooltip(categoryIcon(cat) + ' ' + escapeHtml(props.title), {
|
|
|
|
|
direction: 'top',
|
|
|
|
|
offset: [0, -10]
|
|
|
|
|
});
|
|
|
|
|
@@ -419,11 +418,11 @@ function bindFeaturePopup(feature, layer) {
|
|
|
|
|
|
|
|
|
|
// CREATE: Submits new Contributions from Modal
|
|
|
|
|
function submitCreate() {
|
|
|
|
|
var category = document.getElementById('create-category').value;
|
|
|
|
|
var title = document.getElementById('create-title').value.trim();
|
|
|
|
|
var description = document.getElementById('create-description').value.trim();
|
|
|
|
|
var geom = document.getElementById('create-geom').value;
|
|
|
|
|
var geomType = document.getElementById('create-geom-type').value;
|
|
|
|
|
const category = document.getElementById('create-category').value;
|
|
|
|
|
const title = document.getElementById('create-title').value.trim();
|
|
|
|
|
const description = document.getElementById('create-description').value.trim();
|
|
|
|
|
const geom = document.getElementById('create-geom').value;
|
|
|
|
|
const geomType = document.getElementById('create-geom-type').value;
|
|
|
|
|
|
|
|
|
|
// Validates
|
|
|
|
|
if (!category) {
|
|
|
|
|
@@ -479,13 +478,13 @@ function closeCreateModal() {
|
|
|
|
|
// UPDATE: Edits existing Contributions
|
|
|
|
|
function editContribution(contributionId) {
|
|
|
|
|
// Finds Contribution in local Data
|
|
|
|
|
var contribution = contributionsData.find(function (f) {
|
|
|
|
|
const contribution = contributionsData.find(function (f) {
|
|
|
|
|
return f.properties.contribution_id === contributionId;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!contribution) return;
|
|
|
|
|
|
|
|
|
|
var props = contribution.properties;
|
|
|
|
|
const props = contribution.properties;
|
|
|
|
|
|
|
|
|
|
Swal.fire({
|
|
|
|
|
title: 'Beitrag bearbeiten',
|
|
|
|
|
@@ -571,10 +570,10 @@ function voteContribution(contributionId, voteType) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Updates local Vote State
|
|
|
|
|
var likeBtn = document.getElementById('vote-like-' + contributionId);
|
|
|
|
|
var dislikeBtn = document.getElementById('vote-dislike-' + contributionId);
|
|
|
|
|
var likesSpan = document.getElementById('likes-' + contributionId);
|
|
|
|
|
var dislikesSpan = document.getElementById('dislikes-' + contributionId);
|
|
|
|
|
const likeBtn = document.getElementById('vote-like-' + contributionId);
|
|
|
|
|
const dislikeBtn = document.getElementById('vote-dislike-' + contributionId);
|
|
|
|
|
const likesSpan = document.getElementById('likes-' + contributionId);
|
|
|
|
|
const dislikesSpan = document.getElementById('dislikes-' + contributionId);
|
|
|
|
|
|
|
|
|
|
if (response.action === 'created') {
|
|
|
|
|
// New Vote — Highlights Button and updates Count
|
|
|
|
|
@@ -620,16 +619,16 @@ function voteContribution(contributionId, voteType) {
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
function updateContributionsList() {
|
|
|
|
|
var container = document.getElementById('contributions-list');
|
|
|
|
|
var searchInput = document.getElementById('list-search-input');
|
|
|
|
|
var searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
|
|
|
|
const container = document.getElementById('contributions-list');
|
|
|
|
|
const searchInput = document.getElementById('list-search-input');
|
|
|
|
|
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
|
|
|
|
|
|
|
|
|
|
// Filters by Categories and Search Term
|
|
|
|
|
var filtered = contributionsData.filter(function (f) {
|
|
|
|
|
var props = f.properties;
|
|
|
|
|
var matchesCategory = activeFilters.indexOf(props.category) !== -1;
|
|
|
|
|
var cat = CATEGORIES[props.category] || CATEGORIES.other;
|
|
|
|
|
var matchesSearch = !searchTerm ||
|
|
|
|
|
const filtered = contributionsData.filter(function (f) {
|
|
|
|
|
const props = f.properties;
|
|
|
|
|
const matchesCategory = activeFilters.indexOf(props.category) !== -1;
|
|
|
|
|
const cat = CATEGORIES[props.category] || CATEGORIES.other;
|
|
|
|
|
const matchesSearch = !searchTerm ||
|
|
|
|
|
props.title.toLowerCase().indexOf(searchTerm) !== -1 ||
|
|
|
|
|
(props.description && props.description.toLowerCase().indexOf(searchTerm) !== -1) ||
|
|
|
|
|
props.author_name.toLowerCase().indexOf(searchTerm) !== -1 ||
|
|
|
|
|
@@ -648,16 +647,16 @@ function updateContributionsList() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var html = '';
|
|
|
|
|
let html = '';
|
|
|
|
|
filtered.forEach(function (f) {
|
|
|
|
|
var props = f.properties;
|
|
|
|
|
var cat = CATEGORIES[props.category] || CATEGORIES.other;
|
|
|
|
|
var date = new Date(props.created_at).toLocaleDateString('de-DE');
|
|
|
|
|
const props = f.properties;
|
|
|
|
|
const cat = CATEGORIES[props.category] || CATEGORIES.other;
|
|
|
|
|
const date = new Date(props.created_at).toLocaleDateString('de-DE');
|
|
|
|
|
|
|
|
|
|
html += '' +
|
|
|
|
|
'<div class="contribution-card" onclick="flyToContribution(' + props.contribution_id + ')">' +
|
|
|
|
|
'<div class="contribution-card-header">' +
|
|
|
|
|
'<span class="contribution-card-category">' + cat.icon + ' ' + cat.label + '</span>' +
|
|
|
|
|
'<span class="contribution-card-category">' + categoryIcon(cat) + ' ' + cat.label + '</span>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'<div class="contribution-card-title">' + escapeHtml(props.title) + '</div>' +
|
|
|
|
|
'<div class="contribution-card-meta">' +
|
|
|
|
|
@@ -709,18 +708,17 @@ document.getElementById('list-search-input').addEventListener('input', function
|
|
|
|
|
|
|
|
|
|
// Builds Category Filter Checkboxes
|
|
|
|
|
function buildCategoryFilter() {
|
|
|
|
|
var container = document.getElementById('category-filter');
|
|
|
|
|
var html = '';
|
|
|
|
|
const container = document.getElementById('category-filter');
|
|
|
|
|
let html = '';
|
|
|
|
|
|
|
|
|
|
for (var key in CATEGORIES) {
|
|
|
|
|
var cat = CATEGORIES[key];
|
|
|
|
|
var checked = activeFilters.indexOf(key) !== -1 ? 'checked' : '';
|
|
|
|
|
for (const key in CATEGORIES) {
|
|
|
|
|
const cat = CATEGORIES[key];
|
|
|
|
|
const checked = activeFilters.indexOf(key) !== -1 ? 'checked' : '';
|
|
|
|
|
|
|
|
|
|
html += '' +
|
|
|
|
|
'<label style="display:flex;align-items:center;gap:8px;margin-bottom:6px;cursor:pointer;">' +
|
|
|
|
|
'<input type="checkbox" value="' + key + '" ' + checked + ' onchange="toggleCategoryFilter(this)">' +
|
|
|
|
|
'<span style="display:inline-block;width:12px;height:12px;border-radius:50%;background:' + cat.color + ';"></span>' +
|
|
|
|
|
'<span>' + cat.icon + ' ' + cat.label + '</span>' +
|
|
|
|
|
'<span>' + categoryIcon(cat) + ' ' + cat.label + '</span>' +
|
|
|
|
|
'</label>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -729,7 +727,7 @@ function buildCategoryFilter() {
|
|
|
|
|
|
|
|
|
|
// Toggles a Category Filter on or off
|
|
|
|
|
function toggleCategoryFilter(checkbox) {
|
|
|
|
|
var category = checkbox.value;
|
|
|
|
|
const category = checkbox.value;
|
|
|
|
|
|
|
|
|
|
if (checkbox.checked) {
|
|
|
|
|
if (activeFilters.indexOf(category) === -1) {
|
|
|
|
|
@@ -739,11 +737,11 @@ function toggleCategoryFilter(checkbox) {
|
|
|
|
|
activeFilters = activeFilters.filter(function (c) { return c !== category; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Refilters Map Layer
|
|
|
|
|
// Refilters Map Layer
|
|
|
|
|
if (contributionsLayer) {
|
|
|
|
|
contributionsLayer.eachLayer(function (layer) {
|
|
|
|
|
if (layer.feature) {
|
|
|
|
|
var cat = layer.feature.properties.category;
|
|
|
|
|
const cat = layer.feature.properties.category;
|
|
|
|
|
if (activeFilters.indexOf(cat) !== -1) {
|
|
|
|
|
layer.setStyle({ opacity: 1, fillOpacity: layer.feature.geometry.type === 'Point' ? 0.9 : 0.25 });
|
|
|
|
|
if (layer.setRadius) layer.setRadius(8);
|
|
|
|
|
@@ -765,24 +763,24 @@ function toggleCategoryFilter(checkbox) {
|
|
|
|
|
|
|
|
|
|
// Updates Statistics in Home Tab
|
|
|
|
|
function updateStatistics() {
|
|
|
|
|
var container = document.getElementById('stats-container');
|
|
|
|
|
var total = contributionsData.length;
|
|
|
|
|
const container = document.getElementById('stats-container');
|
|
|
|
|
const total = contributionsData.length;
|
|
|
|
|
|
|
|
|
|
// Counts per Category
|
|
|
|
|
var counts = {};
|
|
|
|
|
const counts = {};
|
|
|
|
|
contributionsData.forEach(function (f) {
|
|
|
|
|
var cat = f.properties.category;
|
|
|
|
|
const cat = f.properties.category;
|
|
|
|
|
counts[cat] = (counts[cat] || 0) + 1;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var html = '<p style="font-size:0.9rem;"><strong>' + total + '</strong> Beiträge insgesamt</p>';
|
|
|
|
|
let html = '<p style="font-size:0.9rem;"><strong>' + total + '</strong> Beiträge insgesamt</p>';
|
|
|
|
|
|
|
|
|
|
for (var key in CATEGORIES) {
|
|
|
|
|
var cat = CATEGORIES[key];
|
|
|
|
|
var count = counts[key] || 0;
|
|
|
|
|
for (const key in CATEGORIES) {
|
|
|
|
|
const cat = CATEGORIES[key];
|
|
|
|
|
const count = counts[key] || 0;
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
html += '<div style="display:flex;align-items:center;gap:8px;margin:4px 0;font-size:0.85rem;">' +
|
|
|
|
|
'<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:' + cat.color + ';"></span>' +
|
|
|
|
|
categoryIcon(cat) + ' ' +
|
|
|
|
|
cat.label + ': ' + count +
|
|
|
|
|
'</div>';
|
|
|
|
|
}
|
|
|
|
|
@@ -798,7 +796,7 @@ function updateStatistics() {
|
|
|
|
|
|
|
|
|
|
// Welcome Modal shows on new Visits
|
|
|
|
|
function checkWelcomeModal() {
|
|
|
|
|
var hasVisited = localStorage.getItem('webgis_welcomed');
|
|
|
|
|
const hasVisited = localStorage.getItem('webgis_welcomed');
|
|
|
|
|
if (!hasVisited) {
|
|
|
|
|
document.getElementById('welcome-modal').style.display = 'flex';
|
|
|
|
|
}
|
|
|
|
|
@@ -818,7 +816,7 @@ function showLoginModal() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function submitLogin() {
|
|
|
|
|
var name = document.getElementById('user-name-input').value.trim();
|
|
|
|
|
const name = document.getElementById('user-name-input').value.trim();
|
|
|
|
|
if (!name) {
|
|
|
|
|
Swal.fire('Name eingeben', 'Bitte geben Sie Ihren Namen ein.', 'warning');
|
|
|
|
|
return;
|
|
|
|
|
@@ -882,14 +880,14 @@ function showImprintModal() {
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
function toggleMobileNav() {
|
|
|
|
|
var nav = document.querySelector('.header-nav');
|
|
|
|
|
const nav = document.querySelector('.header-nav');
|
|
|
|
|
nav.classList.toggle('open');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Closes Mobile Nav when clicking outside
|
|
|
|
|
document.addEventListener('click', function (e) {
|
|
|
|
|
var nav = document.querySelector('.header-nav');
|
|
|
|
|
var toggle = document.querySelector('.header-menu-toggle');
|
|
|
|
|
const nav = document.querySelector('.header-nav');
|
|
|
|
|
const toggle = document.querySelector('.header-menu-toggle');
|
|
|
|
|
|
|
|
|
|
if (nav.classList.contains('open') && !nav.contains(e.target) && !toggle.contains(e.target)) {
|
|
|
|
|
nav.classList.remove('open');
|
|
|
|
|
@@ -913,11 +911,16 @@ document.addEventListener('keydown', function (e) {
|
|
|
|
|
// Escapes HTML to prevent Cross-Site Scripting (XSS) in Popups and Lists
|
|
|
|
|
function escapeHtml(text) {
|
|
|
|
|
if (!text) return '';
|
|
|
|
|
var div = document.createElement('div');
|
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
div.appendChild(document.createTextNode(text));
|
|
|
|
|
return div.innerHTML;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns a colored Font Awesome Icon HTML String for a Category
|
|
|
|
|
function categoryIcon(cat) {
|
|
|
|
|
return '<i class="fa-solid ' + cat.faIcon + '" style="color:' + cat.color + ';"></i>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// Block 16: Application Startup
|
|
|
|
|
@@ -925,12 +928,13 @@ function escapeHtml(text) {
|
|
|
|
|
|
|
|
|
|
// Populates Category Dropdown in Create Modal from Categories Object
|
|
|
|
|
function buildCategoryDropdown() {
|
|
|
|
|
var select = document.getElementById('create-category');
|
|
|
|
|
for (var key in CATEGORIES) {
|
|
|
|
|
var cat = CATEGORIES[key];
|
|
|
|
|
var option = document.createElement('option');
|
|
|
|
|
const select = document.getElementById('create-category');
|
|
|
|
|
for (const key in CATEGORIES) {
|
|
|
|
|
const cat = CATEGORIES[key];
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
option.value = key;
|
|
|
|
|
option.textContent = cat.icon + ' ' + cat.label;
|
|
|
|
|
option.textContent = cat.label;
|
|
|
|
|
option.dataset.icon = cat.faIcon;
|
|
|
|
|
select.appendChild(option);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|