Merge branch 'master' into 68-sl-formatting

This commit is contained in:
Norbert Renner 2021-04-10 12:48:10 +02:00
commit 1330317f1d
112 changed files with 3368 additions and 741 deletions

View file

@ -1,4 +1,5 @@
BR.LayersConfig = L.Class.extend({
overpassFrontend: new OverpassFrontend(BR.conf.overpassBaseUrl || '//overpass-api.de/api/interpreter'),
defaultBaseLayers: BR.confLayers.defaultBaseLayers,
defaultOverlays: BR.confLayers.defaultOverlays,
legacyNameToIdMap: BR.confLayers.legacyNameToIdMap,
@ -7,6 +8,8 @@ BR.LayersConfig = L.Class.extend({
initialize: function (map) {
this._map = map;
this._overpassLoadingIndicator = new BR.Message('overpass_loading_indicator', { alert: false });
this._overpassActiveRequestCount = 0;
this._addLeafletProvidersLayers();
this._customizeLayers();
@ -169,6 +172,70 @@ BR.LayersConfig = L.Class.extend({
return result;
},
_showOverpassLoadingIndicator: function () {
this._overpassActiveRequestCount++;
this._overpassLoadingIndicator.showLoading(i18next.t('layers.overpass-loading-indicator'));
},
_hideOverpassLoadingIndicator: function () {
if (--this._overpassActiveRequestCount === 0) {
this._overpassLoadingIndicator.hide();
}
},
getOverpassIconUrl: function (icon) {
const iconPrefix = /^(maki|temaki|fas)-/;
let iconUrl = null;
if (icon && iconPrefix.test(icon)) {
const iconName = icon.replace(iconPrefix, '');
const postfix = icon.startsWith('maki-') ? '-11' : '';
iconUrl = `dist/images/${iconName}${postfix}.svg`;
}
return iconUrl;
},
createOverpassLayer: function (query, icon) {
let markerSign = '<i class="fa fa-search icon-white" style="width: 25px;"></i>';
const iconUrl = this.getOverpassIconUrl(icon);
if (iconUrl) {
markerSign = `<img class="icon-invert" src="${iconUrl}" width="11" />`;
}
return Object.assign(
new OverpassLayer({
overpassFrontend: this.overpassFrontend,
query: query,
minZoom: 12,
feature: {
title: '{{ tags.name }}',
body:
'<table class="overpass-tags">{% for k, v in tags %}{% if k[:5] != "addr:" %}<tr><th>{{ k }}</th><td>{% if k matches "/email/" %}<a href="mailto:{{ v }}">{{ v }}</a>{% elseif v matches "/^http/" %}<a href="{{ v }}">{{ v }}</a>{% elseif v matches "/^www/" %}<a href="http://{{ v }}">{{ v }}</a>{% else %}{{ v }}{% endif %}</td></tr>{% endif %}{% endfor %}</table>',
markerSymbol:
'<svg width="25px" height="41px" anchorX="12" anchorY="41" viewBox="0 0 32 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M16,1 C7.7146,1 1,7.65636364 1,15.8648485 C1,24.0760606 16,51 16,51 C16,51 31,24.0760606 31,15.8648485 C31,7.65636364 24.2815,1 16,1 L16,1 Z" fill="#436978"></path></svg>',
markerSign,
style: function (overpassObject) {
return {
// nodeFeature: 'Marker' isn't currently working well, hence use transparent circle color for nodes
color:
overpassObject.type === 'node'
? '#00000000'
: this.defaultBaseLayers?.[0] === 'cyclosm'
? 'darkorange'
: '#3388ff',
};
}.bind(this),
},
}),
{
onLoadStart: this._showOverpassLoadingIndicator.bind(this),
onLoadEnd: this._hideOverpassLoadingIndicator.bind(this),
}
);
},
createLayer: function (layerData) {
var props = layerData.properties;
var url = props.url;
@ -251,6 +318,8 @@ BR.LayersConfig = L.Class.extend({
if (props.subdomains) {
layer.subdomains = props.subdomains;
}
} else if (props.dataSource === 'OverpassAPI') {
layer = this.createOverpassLayer(props.query, props.icon);
} else {
// JOSM
var josmUrl = url;

View file

@ -56,6 +56,10 @@ BR.Map = {
$('#credits').on('show.bs.modal', function (event) {
BR.Map._renderLayerCredits(layersControl._layers);
const overpassUrl = new URL(BR.conf.overpassBaseUrl || 'https://overpass-api.de').origin;
for (const link of document.getElementsByClassName('overpass-url')) {
link.href = overpassUrl;
}
});
new L.Control.PermalinkAttribution().addTo(map);

View file

@ -1,18 +1,30 @@
BR.WhatsNew = {
newOnly: undefined,
init: function () {
var self = this;
self.prepare(self.hasNewVersions());
self.dismissableMessage = new BR.Message('whats_new_message', {
onClosed: function () {
document.getElementsByClassName('version')[0].classList.remove('version-new');
if (BR.Util.localStorageAvailable()) {
localStorage.setItem('changelogVersion', self.getLatestVersion());
}
},
});
$('#whatsnew').on('shown.bs.modal', function () {
self.dismissableMessage.hide();
});
$('#whatsnew').on('hidden.bs.modal', function () {
localStorage.setItem('changelogVersion', self.getLatestVersion());
// next time popup is open, by default we will see everything
self.prepare(false);
});
$('#whatsnew').on('shown.bs.modal', function () {
BR.message.hide();
document.getElementsByClassName('version')[0].classList.remove('version-new');
});
if (!self.getCurrentVersion() && BR.Util.localStorageAvailable()) {
localStorage.setItem('changelogVersion', self.getLatestVersion());
}
self.prepare(self.hasNewVersions());
if (self.hasNewVersions()) {
BR.message.showInfo(i18next.t('whatsnew.new-version'));
self.dismissableMessage.showInfo(i18next.t('whatsnew.new-version'));
document.getElementsByClassName('version')[0].classList.add('version-new');
}
},
@ -21,21 +33,28 @@ BR.WhatsNew = {
return BR.changelog.match('<h2 id="(.*)">')[1];
},
getCurrentVersion: function () {
if (!BR.Util.localStorageAvailable()) return null;
return localStorage.getItem('changelogVersion');
},
hasNewVersions: function () {
if (!BR.Util.localStorageAvailable()) return false;
var currentVersion = localStorage.getItem('changelogVersion');
return !currentVersion || currentVersion < this.getLatestVersion();
return this.getCurrentVersion() && this.getCurrentVersion() !== this.getLatestVersion();
},
prepare: function (newOnly) {
var currentVersion = localStorage.getItem('changelogVersion');
if (newOnly === this.newOnly) {
// do not rebuild modal content unnecessarily
return;
}
this.newOnly = newOnly;
var container = document.querySelector('#whatsnew .modal-body');
var cl = BR.changelog;
if (newOnly && currentVersion) {
var head = '<h2 id="' + currentVersion + '">';
cl = cl.substring(0, cl.indexOf(head));
if (newOnly && this.getCurrentVersion()) {
var head = '<h2 id="' + this.getCurrentVersion() + '">';
var idx = cl.indexOf(head);
if (idx >= 0) cl = cl.substring(0, idx);
}
container.innerHTML = cl;
},

View file

@ -148,6 +148,10 @@ BR.Export = L.Class.extend({
}
},
_selectTrackname: function () {
trackname.setSelectionRange(0, trackname.value.lastIndexOf(BR.Browser.download ? ' (' : ' - '));
},
_generateTrackname: function () {
var trackname = this.trackname;
this._getCityAtPosition(
@ -180,6 +184,8 @@ BR.Export = L.Class.extend({
trackname.value = trackname.value.replace(/[>)]/g, '').replace(/ \(/g, ' - ');
this._validationMessage();
}
this._selectTrackname();
}, this)
);
}, this)

View file

@ -5,7 +5,7 @@ BR.Layers = L.Class.extend({
if (BR.Util.localStorageAvailable()) {
var layers = JSON.parse(localStorage.getItem('map/customLayers'));
for (a in layers) {
this._addLayer(a, layers[a].layer, layers[a].isOverlay);
this._addLayer(a, layers[a].layer, layers[a].isOverlay, layers[a].dataSource);
}
}
},
@ -13,14 +13,22 @@ BR.Layers = L.Class.extend({
_loadTable: function () {
var layersData = [];
for (layer in this._customLayers) {
var isOverlay = this._customLayers[layer].isOverlay;
layersData.push([
layer,
this._customLayers[layer].layer._url,
isOverlay
? i18next.t('sidebar.layers.table.type_overlay')
: i18next.t('sidebar.layers.table.type_layer'),
]);
if (this._customLayers[layer].dataSource === 'OverpassAPI') {
layersData.push([
layer,
this._customLayers[layer].layer.options.query,
i18next.t('sidebar.layers.table.type_overpass_query'),
]);
} else {
var isOverlay = this._customLayers[layer].isOverlay;
layersData.push([
layer,
this._customLayers[layer].layer._url,
isOverlay
? i18next.t('sidebar.layers.table.type_overlay')
: i18next.t('sidebar.layers.table.type_layer'),
]);
}
}
if (this._layersTable != null) {
this._layersTable.destroy();
@ -51,6 +59,7 @@ BR.Layers = L.Class.extend({
L.DomUtil.get('custom_layers_add_base').onclick = L.bind(this._addBaseLayer, this);
L.DomUtil.get('custom_layers_add_overlay').onclick = L.bind(this._addOverlay, this);
L.DomUtil.get('custom_layers_add_overpass').onclick = L.bind(this._addOverpassQuery, this);
L.DomUtil.get('custom_layers_remove').onclick = L.bind(this._remove, this);
this._loadLayers();
@ -83,10 +92,10 @@ BR.Layers = L.Class.extend({
}
},
_addFromInput: function (isOverlay) {
_addFromInput: function (isOverlay, dataSource) {
var layer_name = L.DomUtil.get('layer_name').value;
var layer_url = L.DomUtil.get('layer_url').value;
if (layer_name.length > 0 && layer_url.length > 0) this._addLayer(layer_name, layer_url, isOverlay);
if (layer_name.length > 0 && layer_url.length > 0) this._addLayer(layer_name, layer_url, isOverlay, dataSource);
},
_addBaseLayer: function (evt) {
@ -95,18 +104,28 @@ BR.Layers = L.Class.extend({
_addOverlay: function (evt) {
this._addFromInput(true);
},
_addOverpassQuery: function (evt) {
this._addFromInput(true, 'OverpassAPI');
},
_addLayer: function (layerName, layerUrl, isOverlay) {
_addLayer: function (layerName, layerUrl, isOverlay, dataSource) {
if (layerName in this._layers) return;
if (layerName in this._customLayers) return;
try {
var layer = L.tileLayer(layerUrl);
var layer;
if (dataSource === 'OverpassAPI') {
layer = this._layersControl.layersConfig.createOverpassLayer(layerUrl);
} else {
layer = L.tileLayer(layerUrl);
}
this._customLayers[layerName] = {
layer: layer,
isOverlay: isOverlay,
dataSource: dataSource,
};
if (isOverlay) {
@ -128,6 +147,18 @@ BR.Layers = L.Class.extend({
localStorage.setItem(
'map/customLayers',
JSON.stringify(this._customLayers, function (k, v) {
if (v === undefined) {
return undefined;
}
if (v.dataSource === 'OverpassAPI') {
return {
dataSource: 'OverpassAPI',
isOverlay: true,
layer: v.layer.options.query,
};
}
// dont write Leaflet.Layer in localStorage; simply keep the URL
return v._url || v;
})

View file

@ -170,7 +170,7 @@ BR.LayersTab = BR.ControlLayers.extend({
core: {
multiple: false,
themes: {
icons: false,
icons: true,
dots: false,
},
data: treeData,
@ -188,6 +188,7 @@ BR.LayersTab = BR.ControlLayers.extend({
function createRootNode(name) {
var rootNode = {
text: i18next.t('sidebar.layers.category.' + name, name),
icon: false,
state: {
disabled: true,
},
@ -218,6 +219,7 @@ BR.LayersTab = BR.ControlLayers.extend({
childNode = {
id: id,
text: getText(props, parent),
icon: self.layersConfig.getOverpassIconUrl(props.icon) || false,
state: {
checked: self.layersConfig.isDefaultLayer(id, props.overlay),
},

View file

@ -3,6 +3,7 @@ BR.Message = L.Class.extend({
// true to manually attach click event to close button,
// Bootstrap data-api's auto-initialization doesn't work in Controls because of stopPropagation
alert: false,
onClosed: null,
},
initialize: function (id, options) {
@ -18,12 +19,20 @@ BR.Message = L.Class.extend({
case 'error':
iconClass = 'fa-times-circle';
alertClass = 'alert-danger';
break;
case 'warning':
iconClass = 'fa-exclamation-triangle';
alertClass = 'alert-warning';
break;
case 'loading':
iconClass = 'fa-spinner fa-pulse';
alertClass = 'alert-secondary';
break;
default:
case 'info':
iconClass = 'fa-info-circle';
alertClass = 'alert-info';
break;
}
L.DomEvent.disableClickPropagation(ele);
@ -41,6 +50,10 @@ BR.Message = L.Class.extend({
msg +
'</div>';
if (this.options.onClosed) {
$('#' + this.id + ' .alert').on('closed.bs.alert', this.options.onClosed);
}
if (this.options.alert) {
$('#' + this.id + ' .alert').alert();
}
@ -74,6 +87,10 @@ BR.Message = L.Class.extend({
showInfo: function (msg) {
this._show(msg, 'info');
},
showLoading: function (msg) {
this._show(msg, 'loading');
},
});
// static instance as global control

View file

@ -153,6 +153,9 @@
},
],
value: ['route'],
onShown: function () {
$('button.bootbox-accept', $(this)).focus();
},
callback: function (result) {
if (result !== null) {
if (result.indexOf('route') !== -1) {
@ -454,6 +457,10 @@
);
BR.WhatsNew.init();
$('.modal').on('shown.bs.modal', function (e) {
$('input:visible:enabled:first', e.target).focus();
});
}
i18next.on('languageChanged', function (detectedLanguage) {

View file

@ -11,7 +11,7 @@ BR.CircleGoArea = L.Control.extend({
options: {
countriesUrl: BR.conf.countriesUrl || 'dist/boundaries/countries.topo.json',
statesUrl: BR.conf.statesUrl || 'dist/boundaries/germany-states.topo.json',
overpassBaseUrl: BR.conf.overpassBaseUrl || 'https://overpass-api.de/api/interpreter?data=',
overpassDataUrl: (BR.conf.overpassBaseUrl || 'https://overpass-api.de/api/interpreter') + '?data=',
shortcut: {
draw: {
enable: 73, // char code for 'i'
@ -137,7 +137,7 @@ BR.CircleGoArea = L.Control.extend({
query += '(area.a[admin_level="' + adminLevelFallback + '"];)->.p; relation(pivot.p); out geom;';
}
var url = this.options.overpassBaseUrl + encodeURIComponent(query);
var url = this.options.overpassDataUrl + encodeURIComponent(query);
this.marker.setIcon(this.iconSpinner);
BR.Util.getJson(
@ -281,7 +281,7 @@ BR.CircleGoArea = L.Control.extend({
this._applyStateRules(center);
}
} else if (name === 'Metropolitan France') {
this.radius = 20000;
this.radius = 10000;
this._setNogoCircle(center);
} else {
console.error('unhandled country: ' + name);

View file

@ -5,6 +5,7 @@ BR.NogoAreas = L.Control.extend({
enable: 78, // char code for 'n'
disable: 27, // char code for 'ESC'
},
import: 78, // char code for 'n'; used in conjunction with 'shift'
},
},
@ -97,6 +98,8 @@ BR.NogoAreas = L.Control.extend({
L.DomEvent.addListener(document, 'keydown', this._keydownListener, this);
L.DomUtil.get('nogoFile').onchange = L.bind(this.onFileChanged, this);
this.editTools.on(
'editable:drawing:end',
function (e) {
@ -147,6 +150,13 @@ BR.NogoAreas = L.Control.extend({
if (!BR.Util.keyboardShortcutsAllowed(e)) {
return;
}
if (true === e.shiftKey && e.keyCode === this.options.shortcut.import) {
$('#loadNogos').modal('show');
return;
}
if (e.keyCode === this.options.shortcut.draw.disable && this.button.state() === BR.NogoAreas.STATE_CANCEL) {
this.stopDrawing(this.button);
} else if (
@ -162,6 +172,11 @@ BR.NogoAreas = L.Control.extend({
$('#nogoError').css('display', message ? 'block' : 'none');
},
onFileChanged: function (e) {
if (!e.target.files[0]) return;
$(e.target).next('label').text(e.target.files[0].name);
},
uploadNogos: function () {
var self = this;

View file

@ -15,6 +15,9 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
simplifyTolerance: -1,
isTestMode: false,
simplifyLastKnownGood: 0.001,
shortcut: {
open: 79, // char code for 'O'; used in conjunction with 'shift'
},
},
setDialogDraggable: function (jqDlgHeader) {
@ -84,12 +87,14 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
(this._bounds = undefined), (this._trackPoints = []);
this._currentGeoJSON = {};
this._options = {
ext: 'gpx',
showTrackLayer: true,
showPointAsPoi: true,
simplifyTolerance: -1,
isTestMode: false,
simplifyLastKnownGood: 0.001,
shortcut: {
open: 79, // char code for 'O'; used in conjunction with 'shift'
},
};
},
@ -156,13 +161,13 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
if (typeof isBusy === undefined) {
isBusy = false;
}
if (isBusy === true) $('#loadedittrackdlg #msg_busy').removeClass('invisible');
else $('#loadedittrackdlg #msg_busy').addClass('invisible');
if (isBusy === true) $('#loadedittrack #msg_busy').removeClass('invisible');
else $('#loadedittrack #msg_busy').addClass('invisible');
},
onManualCollapse: function (e) {
//workaround for starting with closed collapse
if ($('#loadedittrackdlg').is(':hidden')) return;
if ($('#loadedittrack').is(':hidden')) return;
this._options.isTestMode = $(e.target).hasClass('show');
if (this._options.isTestMode) {
@ -172,13 +177,13 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
},
onAdd: function (map) {
$('#loadedittrackdlg').on(
$('#loadedittrack').on(
'hidden.bs.modal',
function (e) {
this.cleanup();
}.bind(this)
);
$('#loadedittrackdlg').on(
$('#loadedittrack').on(
'show.bs.modal',
function (e) {
$('#manual_collapse').collapse('hide');
@ -186,7 +191,8 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
}.bind(this)
);
L.DomUtil.get('submitLoadEditTrack').onclick = L.bind(function () {
L.DomUtil.get('submitLoadEditTrack').onclick = L.bind(function (e) {
e.preventDefault(); // prevent page reload on form submission
this._closeCanceled = false;
this.onBusyChanged(true);
if (this._testLayer.getLayers().length > 0) {
@ -195,7 +201,7 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
function () {
this.addRoutingPoints();
this.onBusyChanged(false);
$('#loadedittrackdlg').modal('hide');
$('#loadedittrack').modal('hide');
}.bind(this),
0
);
@ -203,7 +209,7 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
setTimeout(
function () {
this.convertTrackLocal();
$('#loadedittrackdlg').modal('hide');
$('#loadedittrack').modal('hide');
}.bind(this),
0
);
@ -214,7 +220,7 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
L.DomUtil.get('loadedittrackFile').onchange = L.bind(this.onFileChanged, this);
this.onFileChanged({ target: L.DomUtil.get('loadedittrackFile') });
this.setDialogDraggable($('#loadedittrackdlg .modal-header'));
this.setDialogDraggable($('#loadedittrack .modal-header'));
$('#manual_collapse').collapse('hide');
$('#manual_collapse').on(
@ -224,6 +230,8 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
}.bind(this)
);
L.DomEvent.addListener(document, 'keydown', this.keydownListener, this);
// dummy, no own representation, delegating to EasyButton
var dummy = L.DomUtil.create('div');
dummy.hidden = true;
@ -382,6 +390,16 @@ BR.routeLoader = function (map, layersControl, routing, pois) {
this.onBusyChanged(false);
},
keydownListener: function (e) {
if (
BR.Util.keyboardShortcutsAllowed(e) &&
e.keyCode === this._options.shortcut.open &&
true === e.shiftKey
) {
$('#navbarLoadEditTracks').click();
}
},
});
RouteLoader.include(L.Evented.prototype);

View file

@ -1,29 +1,29 @@
BR.Search = L.Control.Geocoder.extend({
options: {
geocoder: new L.Control.Geocoder.LatLng({
next: new L.Control.Geocoder.Nominatim({
serviceUrl: 'https://nominatim.openstreetmap.org/',
}),
sizeInMeters: 800,
}),
position: 'topleft',
shortcut: {
search: 70, // char code for 'f'
},
},
initialize: function (options) {
L.Control.Geocoder.prototype.initialize.call(this, options);
L.setOptions(this, {
// i18next.t will only return 'undefined' if it is called in a static context
// (e.g. when added directly to "options:" above), so we have to call it here
placeholder: i18next.t('map.geocoder-placeholder'),
});
BR.Search = class extends L.Control.Geocoder {
constructor(options) {
super(
Object.assign(
{
geocoder: new L.Control.Geocoder.LatLng({
next: new L.Control.Geocoder.Nominatim({
serviceUrl: 'https://nominatim.openstreetmap.org/',
}),
sizeInMeters: 800,
}),
position: 'topleft',
expand: 'click',
shortcut: {
search: 70, // char code for 'f'
},
placeholder: i18next.t('map.geocoder-placeholder'),
},
options
)
);
L.DomEvent.addListener(document, 'keydown', this._keydownListener, this);
},
}
markGeocode: function (result) {
markGeocode(result) {
this._map.fitBounds(result.geocode.bbox, {
maxZoom: 17,
});
@ -37,18 +37,18 @@ BR.Search = L.Control.Geocoder.extend({
}).addTo(this._map);
return this;
},
}
clear: function () {
clear() {
if (this._geocodeMarker) {
this._map.removeLayer(this._geocodeMarker);
}
},
}
_keydownListener: function (e) {
_keydownListener(e) {
if (BR.Util.keyboardShortcutsAllowed(e) && e.keyCode === this.options.shortcut.search) {
$('#map .leaflet-control-geocoder')[0].dispatchEvent(new MouseEvent('mousedown'));
$('#map .leaflet-control-geocoder')[0].dispatchEvent(new MouseEvent('click'));
e.preventDefault();
}
},
});
}
};

View file

@ -13,7 +13,7 @@ BR.tracksLoader = function (map, layersControl, routing, pois) {
layerOptions: BR.Track.getGeoJsonOptions(layersControl),
addToMap: false,
// File size limit in kb (default: 1024) ?
fileSizeLimit: 1024,
fileSizeLimit: BR.conf.trackSizeLimit || 1024 * 10,
shortcut: {
open: 79, // char code for 'o'
},
@ -61,12 +61,12 @@ BR.tracksLoader = function (map, layersControl, routing, pois) {
},
_keydownListener: function (e) {
if (BR.Util.keyboardShortcutsAllowed(e) && e.keyCode === this.options.shortcut.open) {
if (e.shiftKey) {
$('#loadNogos').modal('show');
} else {
$('#navbarLoadTracks')[0].click();
}
if (
BR.Util.keyboardShortcutsAllowed(e) &&
e.keyCode === this.options.shortcut.open &&
false === e.shiftKey
) {
$('#navbarLoadTracks')[0].click();
}
},
});

View file

@ -129,6 +129,11 @@
L.bind(function (obj, index, array) {
if (obj) {
layersControl.activateLayer(obj);
if (obj.layer instanceof OverpassLayer) {
// hack to select overlay (mark checked) in the layers control
// (OverpassLayer._layerAdd does not fire 'add' event)
layersControl._update();
}
if (obj && !obj.overlay) {
added = true;
}