Geo fields: Allow overriding existing values (#1978)

This commit is contained in:
Richard Schreiber
2021-02-26 09:55:23 +01:00
committed by GitHub
parent 73e7d407cd
commit 1c81792cd7
9 changed files with 208 additions and 213 deletions

View File

@@ -0,0 +1,33 @@
{% load i18n %}
{% load bootstrap3 %}
{% load static %}
<div class="geodata-section">
{% bootstrap_field form.location layout="control" %}
{% include "pretixcontrol/event/fragment_geodata_autoupdate.html" %}
<div class="form-group geodata-group"
data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}"
data-attrib="{{ global_settings.leaflet_tiles_attribution }}"
data-icon="{% static "leaflet/images/marker-icon.png" %}"
data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
{% load i18n %}
<span class="geodata-autoupdate">
<span class="optional" data-notification="error" title="{% trans "Failed to retrieve geo coordinates" %}"><i class="fa fa-warning"></i></span>
<span class="optional" data-notification="loading" title="{% trans "Retrieving geo coordinates " %}"><i class="fa fa-cog fa-spin"></i></span>
<span class="optional" data-notification="updated"><i class="fa fa-check"></i> {% trans "Geo coordinates updated" %}</span>
<span class="optional" data-notification="confirm">New geo coordinates. <button type="button" data-action="update" class="btn btn-link text-nowrap">{% trans "Update map?" %}</button></span>
</span>

View File

@@ -24,34 +24,7 @@
{% endif %} {% endif %}
{% bootstrap_field form.date_from layout="control" %} {% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %} {% bootstrap_field form.date_to layout="control" %}
<div class="geodata-section"> {% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group"
data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}"
data-attrib="{{ global_settings.leaflet_tiles_attribution }}"
data-icon="{% static "leaflet/images/marker-icon.png" %}"
data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.date_admission layout="control" %} {% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.currency layout="control" %} {% bootstrap_field form.currency layout="control" %}
{% bootstrap_field sform.contact_mail layout="control" %} {% bootstrap_field sform.contact_mail layout="control" %}

View File

@@ -39,30 +39,7 @@
{% if form.date_to %} {% if form.date_to %}
{% bootstrap_field form.date_to layout="control" %} {% bootstrap_field form.date_to layout="control" %}
{% endif %} {% endif %}
<div class="geodata-section"> {% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.currency layout="control" %} {% bootstrap_field form.currency layout="control" %}
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %} {% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
</fieldset> </fieldset>

View File

@@ -384,30 +384,7 @@
<legend>{% trans "General information" %}</legend> <legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %} {% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.active layout="control" %} {% bootstrap_field form.active layout="control" %}
<div class="geodata-section"> {% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.frontpage_text layout="control" %} {% bootstrap_field form.frontpage_text layout="control" %}
{% bootstrap_field form.is_public layout="control" %} {% bootstrap_field form.is_public layout="control" %}
{% if meta_forms %} {% if meta_forms %}

View File

@@ -32,6 +32,7 @@
{% bootstrap_field form.active layout="bulkedit" %} {% bootstrap_field form.active layout="bulkedit" %}
<div class="geodata-section"> <div class="geodata-section">
{% bootstrap_field form.location layout="bulkedit" %} {% bootstrap_field form.location layout="bulkedit" %}
{% include "pretixcontrol/event/fragment_geodata_autoupdate.html" %}
<div class="form-group geodata-group" <div class="form-group geodata-group"
data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}"
data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}"

View File

@@ -25,30 +25,7 @@
{% bootstrap_field form.active layout="control" %} {% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.date_from layout="control" %} {% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %} {% bootstrap_field form.date_to layout="control" %}
<div class="geodata-section"> {% include "pretixcontrol/event/fragment_geodata.html" %}
{% bootstrap_field form.location layout="control" %}
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
<label class="col-md-3 control-label">
{% trans "Geo coordinates" %}<br>
<span class="optional">{% trans "Optional" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %}
{% if global_settings.opencagedata_apikey %}
<p class="attrib">
<a href="https://openstreetmap.org/" target="_blank">
{% trans "Geocoding data © OpenStreetMap" %}
</a>
</p>
{% endif %}
</div>
<div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %}
</div>
<div class="col-md-1">
</div>
</div>
</div>
{% bootstrap_field form.date_admission layout="control" %} {% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.frontpage_text layout="control" %} {% bootstrap_field form.frontpage_text layout="control" %}
{% bootstrap_field form.is_public layout="control" %} {% bootstrap_field form.is_public layout="control" %}

View File

@@ -1,128 +1,160 @@
/*globals $*/ /*globals $*/
$(function () { $(function () {
var j = 0; function cleanup(l) {
$(".geodata-section").each(function () { return $.trim(l.replace(/\n/g, ", "));
// Geocoding
var $sec = $(this);
var $inp = $(this).find("textarea[lang=en], input[lang=en]").first();
if ($inp.length === 0) {
$inp = $(this).find("textarea, input").first();
} }
var timer, timer2; $(".geodata-section").each(function () {
var touched = $sec.find("input[name$=geo_lat]").val() !== ""; // Geocoding
// detach notifications and append them to first label (should be from location)
var $notifications = $(".geodata-autoupdate", this).detach().appendTo($("label", this).first());
var $lat = $("input[name$=geo_lat]", this).first();
var $lon = $("input[name$=geo_lon]", this).first();
var lat;
var lon;
var $updateButton = $("[data-action=update]", this);
var $location = $("textarea[lang=en], input[lang=en]", this).first();
if (!$location.length) $location = $("textarea, input[type=text]", this).first();
function load() { if (!$lat.length || !$lon.length || !$location.length) {
window.clearTimeout(timer); return;
var q = $.trim($inp.val().replace(/\n/g, ", "));
if ((touched && $sec.find("input[name$=geo_lat]").val() !== "") || $.trim(q) === "") {
return;
}
$sec.find(".col-md-1").html("<span class='fa fa-cog fa-spin'></span>");
$.getJSON('/control/geocode/?q=' + encodeURIComponent(q), function (res) {
var q2 = $.trim($inp.val().replace(/\n/g, ", "));
if (q2 !== q) {
return; // lost race
} }
if (res.results) {
$sec.find("input[name$=geo_lat]").val(res.results[0].lat); var debounceLoad, debounceLatLonChange, delayUpdateDismissal;
$sec.find("input[name$=geo_lon]").val(res.results[0].lon); var touched = $lat.val() !== "";
center(13); var xhr;
var lastLocation = cleanup($location.val());
function load() {
window.clearTimeout(debounceLoad);
if (xhr) {
xhr.abort();
xhr = null;
}
var q = cleanup($location.val());
if (q === "" || q === lastLocation) return;
lastLocation = q;
$notifications.attr("data-notify", "loading");
xhr = $.getJSON('/control/geocode/?q=' + encodeURIComponent(q), function (res) {
if (!res.results || !res.results.length) {
$notifications.attr("data-notify", "error");
return;
}
lat = res.results[0].lat;
lon = res.results[0].lon;
if (touched) {
$notifications.attr("data-notify", "confirm");
}
else {
$notifications.attr("data-notify", "");
$lat.val(lat);
$lon.val(lon);
center(13);
}
})
} }
$sec.find(".col-md-1").html("");
})
}
$sec.find("input[name$=geo_lat], input[name$=geo_lon]").change(function () { $lat.add($lon).change(function () {
touched = $sec.find("input[name$=geo_lat]").val() !== ""; if (this.value !== "") touched = true;
center(13); center(13);
}).keyup(function () { }).keyup(function () {
if (timer2) { window.clearTimeout(debounceLatLonChange);
window.clearTimeout(timer2); debounceLatLonChange = window.setTimeout(center, 300);
} });
timer2 = window.setTimeout(center, 300);
});
$inp.change(load); $location.change(load);
$inp.keyup(function () { $location.keyup(function () {
if (timer) { window.clearTimeout(debounceLoad);
window.clearTimeout(timer); debounceLoad = window.setTimeout(load, 1000);
} if ($notifications.attr("data-notify") == "confirm" && lastLocation !== cleanup(this.value)) $notifications.attr("data-notify", "");
timer = window.setTimeout(load, 1000); });
});
// Map $updateButton.click(function() {
var $grp = $sec.find(".geodata-group"); $lat.val(lat);
var tiles = $grp.attr("data-tiles"); $lon.val(lon).trigger("change");// change-event is needed by bulk-edit
var attrib = $grp.attr("data-attrib"); touched = false;
if (tiles) { center(13);
var $map = $("<div>"); $notifications.attr("data-notify", "updated");
$grp.append($("<div>").addClass("col-md-9 col-md-offset-3").append($map)); delayUpdateDismissal = window.setTimeout(function() {
var map = L.map($map.get(0)); if ($notifications.attr("data-notify") == "updated") $notifications.attr("data-notify", "");
L.tileLayer(tiles, { }, 2500);
attribution: attrib, });
maxZoom: 18,
}).addTo(map);
var $lat = $sec.find("input[name$=geo_lat]");
var $lon = $sec.find("input[name$=geo_lon]");
function getpoint() { // Map
if ($lat.val() !== "" && $lon.val() !== "") { var $grp = $(".geodata-group", this);
var p = [parseFloat($lat.val().replace(",", ".")), parseFloat($lon.val().replace(",", "."))]; var tiles = $grp.attr("data-tiles");
// Clip to valid ranges. Very invalid lon/lat values can even lead to browser crashes in leaflet apparently var attrib = $grp.attr("data-attrib");
if (p[0] < -90) p[0] = -90 if (tiles) {
if (p[0] > 90) p[0] = 90 var $map = $("<div>");
if (p[1] < -180) p[1] = -180 $grp.append($("<div>").addClass("col-md-9 col-md-offset-3").append($map));
if (p[1] > 180) p[1] = 180 var map = L.map($map.get(0));
return p L.tileLayer(tiles, {
attribution: attrib,
maxZoom: 18,
}).addTo(map);
function getpoint() {
if ($lat.val() !== "" && $lon.val() !== "") {
var p = [parseFloat($lat.val().replace(",", ".")), parseFloat($lon.val().replace(",", "."))];
// Clip to valid ranges. Very invalid lon/lat values can even lead to browser crashes in leaflet apparently
if (p[0] < -90) p[0] = -90
if (p[0] > 90) p[0] = 90
if (p[1] < -180) p[1] = -180
if (p[1] > 180) p[1] = 180
return p
} else {
return [0.0, 0.0];
}
}
var marker = L.marker(getpoint(), {
draggable: 'true',
icon: L.icon({
iconUrl: $grp.attr("data-icon"),
shadowUrl: $grp.attr("data-shadow"),
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41]
})
});
marker.addTo(map);
marker.on("dragend", function (event) {
var position = marker.getLatLng();
marker.setLatLng(position, {
draggable: 'true'
}).bindPopup(position).update();
$lat.val(position.lat.toFixed(7));
$lon.val(position.lng.toFixed(7));
touched = true;
center(null);
});
function center(zoom) {
if ($lat.val() !== "" && $lon.val() !== "") {
if (zoom) {
map.setView(getpoint(), zoom);
} else {
map.panTo(getpoint());
}
marker.setLatLng(getpoint(), {
draggable: 'true'
}).bindPopup(getpoint()).update();
} else {
map.fitWorld();
}
}
center(13);
} else { } else {
return [0.0, 0.0]; function center(zoom) {
}
} }
}
var marker = L.marker(getpoint(), { });
draggable: 'true',
icon: L.icon({
iconUrl: $grp.attr("data-icon"),
shadowUrl: $grp.attr("data-shadow"),
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41]
})
});
marker.addTo(map);
marker.on("dragend", function (event) {
var position = marker.getLatLng();
marker.setLatLng(position, {
draggable: 'true'
}).bindPopup(position).update();
$lat.val(position.lat.toFixed(7));
$lon.val(position.lng.toFixed(7));
center(null);
});
function center(zoom) {
if ($lat.val() !== "" && $lon.val() !== "") {
if (zoom) {
map.setView(getpoint(), zoom);
} else {
map.panTo(getpoint());
}
marker.setLatLng(getpoint(), {
draggable: 'true'
}).bindPopup(getpoint()).update();
} else {
map.fitWorld();
}
}
center(13);
} else {
function center(zoom) {
}
}
});
}); });

View File

@@ -241,6 +241,23 @@ div.mail-preview {
input[type=number].short { input[type=number].short {
width: 80px !important; width: 80px !important;
} }
.geodata-autoupdate {
display: block;
}
.geodata-autoupdate .btn-link {
padding: 0;
}
.geodata-autoupdate [data-notification] {
display: none;
}
[data-notify=error] [data-notification=error],
[data-notify=loading] [data-notification=loading],
[data-notify=updated] [data-notification=updated],
[data-notify=confirm] [data-notification=confirm] {
display: block;
}
.geodata-group { .geodata-group {
.form-group { .form-group {
margin: 0 0 10px 0; margin: 0 0 10px 0;