From 5e14d364efa18b4074382130922fea8e458316fa Mon Sep 17 00:00:00 2001 From: Matteo Gheza Date: Fri, 13 Nov 2020 15:22:26 +0100 Subject: [PATCH] Support for location picker --- server/install/installHelper.php | 1 + server/resources/src/main.js | 44 +++++-- server/resources/src/maps.css | 37 ++++++ server/resources/src/maps.js | 192 ++++++++++++++++++++++++++++- server/resources/src/sw.js | 2 +- server/templates/base.html | 2 +- server/templates/edit_service.html | 12 ++ server/templates/services.html | 3 +- server/templates/trainings.html | 3 +- server/translations/en/base.php | 5 +- server/translations/it/base.php | 5 +- 11 files changed, 286 insertions(+), 20 deletions(-) create mode 100644 server/resources/src/maps.css diff --git a/server/install/installHelper.php b/server/install/installHelper.php index 8accdb9..e916c1a 100644 --- a/server/install/installHelper.php +++ b/server/install/installHelper.php @@ -474,6 +474,7 @@ INSERT INTO `".$prefix."_options` (`id`, `name`, `value`, `enabled`, `created_ti INSERT INTO `".$prefix."_options` (`id`, `name`, `value`, `enabled`, `created_time`, `last_edit`, `user_id`) VALUES (NULL, 'service_remove', 1, 1, current_timestamp(), current_timestamp(), '1'); INSERT INTO `".$prefix."_options` (`id`, `name`, `value`, `enabled`, `created_time`, `last_edit`, `user_id`) VALUES (NULL, 'training_edit', 1, 1, current_timestamp(), current_timestamp(), '1'); INSERT INTO `".$prefix."_options` (`id`, `name`, `value`, `enabled`, `created_time`, `last_edit`, `user_id`) VALUES (NULL, 'training_remove', 1, 1, current_timestamp(), current_timestamp(), '1'); +INSERT INTO `".$prefix."_options` (`id`, `name`, `value`, `enabled`, `created_time`, `last_edit`, `user_id`) VALUES (NULL, 'use_location_picker', 1, 1, current_timestamp(), current_timestamp(), '1'); $option_check_cf_ip"); mt_srand(10); $prep->bindValue(':hidden', ($visible ? 0 : 1), PDO::PARAM_INT); diff --git a/server/resources/src/main.js b/server/resources/src/main.js index fcc9c6e..92603a1 100644 --- a/server/resources/src/main.js +++ b/server/resources/src/main.js @@ -11,7 +11,7 @@ import '../node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css' import 'time-input-polyfill/auto'; import 'jquery-pjax'; -$(document).pjax('a', '#content'); +$(document).pjax('a', '#content', {timeout: 100000}); $(document).on('pjax:start', function() { if(window.loadTable_interval !== undefined){ clearInterval(window.loadTable_interval); @@ -71,6 +71,7 @@ $( document ).ready(function() { window.dispatchEvent(new Event("cookieAlertAccept")) }); }); + if (getCookie("authenticated")) { if ('serviceWorker' in navigator) { window.addEventListener('load', () => { @@ -83,15 +84,29 @@ if (getCookie("authenticated")) { } } -function fillTable(data){ +function fillTable(data, replaceLatLngWithMap=false){ $("#table_body").empty(); - $.each(data, function(num, item) { - var row = document.createElement("tr"); - $.each(item, function(num, i) { + $.each(data, function(row_num, item) { + let row = document.createElement("tr"); + $.each(item, function(cell_num, i) { if(i !== null){ - var cell = document.createElement("td"); - cell.innerHTML = i; - row.appendChild(cell); + if(replaceLatLngWithMap && i.match(/[+-]?\d+([.]\d+)?[;][+-]?\d+([.]\d+)?/gm)){ + let lat = i.split(";")[0]; + let lng = i.split(";")[1]; + let mapDiv = document.createElement("div"); + mapDiv.className = "map"; + mapDiv.id = "map-"+row_num; + var mapScript = document.createElement("script"); + mapScript.appendChild(document.createTextNode("load_map("+lat+", "+lng+", \"map-"+row_num+"\", false)")); + mapDiv.appendChild(mapScript); + let cell = document.createElement("td"); + cell.appendChild(mapDiv); + row.appendChild(cell); + } else { + let cell = document.createElement("td"); + cell.innerHTML = i; + row.appendChild(cell); + } } }); document.getElementById("table_body").appendChild(row); @@ -101,8 +116,9 @@ function fillTable(data){ var offline = false; var loadTable_interval = undefined; function loadTable(table_page, set_interval=true, interval=10000, onlineReload=false){ + let replaceLatLngWithMap = table_page == "services" || table_page == "trainings"; $.getJSON( "resources/ajax/ajax_"+table_page+".php", function( data, status, xhr ) { - fillTable(data); + fillTable(data, replaceLatLngWithMap); var headers = new Headers(); headers.append('date', Date.now()); caches.open('tables-1').then((cache) => { @@ -119,14 +135,14 @@ function loadTable(table_page, set_interval=true, interval=10000, onlineReload=f } }).fail(function(data, status) { if(status == "parsererror"){ - if($("#table_body").children().length == 0) { - loadTable(table_page, set_interval, interval); + if($("#table_body").children().length == 0) { //this is a server-side authentication error on some cheap hosting providers + loadTable(table_page, set_interval, interval); //retry } // else nothing } else { caches.open('tables-1').then(cache => { cache.match("/table_"+table_page+".json").then(response => { response.json().then(data => { - fillTable(data); + fillTable(data, replaceLatLngWithMap); console.log("Table loaded from cache"); $("#offline_update").text(new Date(parseInt(response.headers.get("date"))).toLocaleString()); }); @@ -147,4 +163,6 @@ function loadTable(table_page, set_interval=true, interval=10000, onlineReload=f window.loadTable_interval = loadTable_interval; window.fillTable = fillTable; -window.loadTable = loadTable; \ No newline at end of file +window.loadTable = loadTable; +window.setCookie = setCookie; +window.getCookie = getCookie; \ No newline at end of file diff --git a/server/resources/src/maps.css b/server/resources/src/maps.css new file mode 100644 index 0000000..ed596e9 --- /dev/null +++ b/server/resources/src/maps.css @@ -0,0 +1,37 @@ +div#map { + width: 100%; + height: 500px; +} + +.map { + width: 100%; + height: 500px; +} + +div#search { + background-color: rgba(255, 255, 255, 0.4); + bottom: 40px; + left: 40px; + width: auto; + height: auto; + padding: 10px; +} + +div#results { + font-style: sans-serif; + color: black; + font-size: 75%; +} + +.fa{ + vertical-align: middle; + font-size: 25px; +} + +.fa.fa-map-marker-alt,.fa.fa-spinner.fa-spin { + line-height: inherit; +} + +.leaflet-pane.leaflet-shadow-pane { + display: none; +} \ No newline at end of file diff --git a/server/resources/src/maps.js b/server/resources/src/maps.js index d37e8d7..1e49d8c 100644 --- a/server/resources/src/maps.js +++ b/server/resources/src/maps.js @@ -2,10 +2,200 @@ import L from 'leaflet'; import 'leaflet.locatecontrol'; import '../node_modules/leaflet.locatecontrol/dist/L.Control.Locate.min.css' import '../node_modules/leaflet/dist/leaflet.css'; +import './maps.css'; delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'resources/dist/marker-icon-2x.png', iconUrl: 'resources/dist/marker-icon.png', shadowUrl: 'resources/dist/marker-shadow.png', -}); \ No newline at end of file +}); + +var marker; +var map; +function set_marker(LatLng){ + if(marker){ + console.log("Marker exists"); + //console.log(marker); + marker.remove(); + } + console.log(LatLng); + if($("input[name='luogo']").val() !== undefined){ + $("input[name='luogo']").val(LatLng.lat + ";" + LatLng.lng); + } + marker = L.marker(LatLng).addTo(map); +} + +function load_map(lat=undefined, lng=undefined, selector_id=undefined, select=true) { + if(lat == undefined && lng == undefined){ + lat = 45.5285; //TODO: replace hard-coded into cookie reading + lng = 10.2956; + } + if(selector_id == undefined){ + selector_id = "map"; + } + let zoom = select ? 10 : 17; + let latLng = new L.LatLng(lat, lng); + map = new L.Map(selector_id, {zoomControl: true}); + + let osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + osmAttribution = 'Map data © 2012 OpenStreetMap contributors', + osm = new L.TileLayer(osmUrl, {maxZoom: 20, attribution: osmAttribution}); + + map.setView(latLng, zoom).addLayer(osm); + + if(select){ + map.on('click', function(e) { + set_marker(e.latlng); + }); + + L.Control.CustomLocate = L.Control.Locate.extend({ + _drawMarker: function() { + set_marker(this._event.latlng); + }, + _onDrag: function(){}, + _onZoom: function(){}, + _onZoomEnd: function(){}, + }); + + let lc = new L.Control.CustomLocate({ + icon: "fa fa-map-marker", + cacheLocation: false, //disabled for privacy reasons + }).addTo(map); + + if($("#addr").val() !== undefined){ + document.getElementById("addr").addEventListener("keydown", function(event) { + if (event.key === "Enter") { + event.preventDefault(); + document.querySelector("#search > button").click(); + } + }); + } + + if(getCookie("experimental_read_clipboard")){ + window.addEventListener("focus", function(event) { + if($("#addr").val() == ""){ + console.log("Loading location from clipboard"); + navigator.clipboard.readText().then(text => { + $("#addr").val(text); + if(!addr_search()){ + $("#addr").val(""); + } + }).catch(err => { + console.error('Failed to read clipboard contents: ', err); + }); + } + }); + } + } else { + set_marker(latLng); + } + map.invalidateSize(); +} + +// from unknown source in the Internet +function chooseAddr(lat, lng, zoom=undefined, lat1=undefined, lng1=undefined, lat2=undefined, lng2=undefined, osm_type=undefined) { + let lat = lat.replace(",", "."); + let lng = lng.replace(",", "."); + if(lat1 !== undefined && lng1 !== undefined && lat2 !== undefined && lng2 !== undefined && osm_type !== undefined){ + let loc1 = new L.LatLng(lat1, lng1); + let loc2 = new L.LatLng(lat2, lng2); + let bounds = new L.LatLngBounds(loc1, loc2); + console.log(lat1, lng1, lat2, lng2, osm_type); + set_marker(new L.LatLng(lat, lng)); + if (feature) { + map.removeLayer(feature); + } + if (osm_type == "node") { + map.fitBounds(bounds); + map.setZoom(18); + } else { + let loc3 = new L.LatLng(lat1, lng2); + let loc4 = new L.LatLng(lat2, lng1); + feature = L.polyline( [loc1, loc4, loc2, loc3, loc1], {color: 'red'}).addTo(map); + map.fitBounds(bounds); + } + } else if (lat !== undefined && lng !== undefined){ + let loc = new L.LatLng(lat, lng); + console.log(loc); + set_marker(loc); + if(zoom !== undefined){ + map.setView(loc, zoom); + } else { + map.setView(loc); + } + } +} + +// started from https://derickrethans.nl/leaflet-and-nominatim.html +function addr_search(string_results_found=undefined, string_results_not_found=undefined) { + function searchError(error, checkClipboard){ + if(!checkClipboard){ + $('

', { html: string_results_not_found }).appendTo('#results'); + console.error(error); + } + return false; + } + let inp = document.getElementById("addr").value; + //if translation strings are not defined, skip the nominatim step and don't log errors (no console.error) + let checkClipboard = string_results_found==undefined && string_results_not_found==undefined; + $('#results').empty(); + + if(inp.match("\@(-?[\d\.]*)")){ //Google Maps + try { + inp = inp.split("@")[1].split(","); + chooseAddr(inp[0], inp[1]); + return true; + } catch (error) { + searchError(error, checkClipboard); + } + } else if(inp.includes("#map=")) { //OpenStreetMap website + try { + inp = inp.split("#map=")[1].split("/"); + chooseAddr(inp[1], inp[2], inp[0]); + return true; + } catch (error) { + searchError(error, checkClipboard); + } + } else if(inp.match(/[0-9]+,\s[0-9]+/)) { //Bing + try { + inp = inp.split(", "); + chooseAddr(inp[0], inp[1]); + return true; + } catch (error) { + searchError(error, checkClipboard); + } + } else if(inp.match(/[0-9]+;[0-9]+/)) { //DB dump + try { + inp = inp.split(";"); + chooseAddr(inp[0], inp[1]); + return true; + } catch (error) { + searchError(error, checkClipboard); + } + } else if(!checkClipboard) { + $.getJSON('https://nominatim.openstreetmap.org/search?format=json&limit=5&q=' + inp, function(data) { + let items = []; + + $.each(data, function(key, val) { + items.push("

  • " + val.display_name + '
  • '); + }); + + if (items.length != 0) { + $('

    ', { html: string_results_found+":" }).appendTo('#results'); + $('