Upgrade all the dependencies, use external links for all 3rd party plugins
This commit is contained in:
parent
94d56354d6
commit
3143bb8119
143
mapmakr/static/3rdparty/L.Terminator.js
vendored
143
mapmakr/static/3rdparty/L.Terminator.js
vendored
@ -1,143 +0,0 @@
|
|||||||
/* Terminator.js -- Overlay day/night region on a Leaflet map */
|
|
||||||
|
|
||||||
Date.prototype.getJulian = function() {
|
|
||||||
/* Calculate the present UTC Julian Date. Function is valid after
|
|
||||||
* the beginning of the UNIX epoch 1970-01-01 and ignores leap
|
|
||||||
* seconds. */
|
|
||||||
return (this / 86400000) + 2440587.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
Date.prototype.getGMST = function() {
|
|
||||||
/* Calculate Greenwich Mean Sidereal Time according to
|
|
||||||
http://aa.usno.navy.mil/faq/docs/GAST.php */
|
|
||||||
var julianDay = this.getJulian();
|
|
||||||
var d = julianDay - 2451545.0;
|
|
||||||
// Low precision equation is good enough for our purposes.
|
|
||||||
return (18.697374558 + 24.06570982441908 * d) % 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
L.Terminator = L.Polygon.extend({
|
|
||||||
options: {
|
|
||||||
color: '#00',
|
|
||||||
opacity: 0.5,
|
|
||||||
fillColor: '#00',
|
|
||||||
fillOpacity: 0.5,
|
|
||||||
resolution: 2
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
this.version = '0.1.0';
|
|
||||||
this._R2D = 180 / Math.PI;
|
|
||||||
this._D2R = Math.PI / 180;
|
|
||||||
L.Util.setOptions(this, options);
|
|
||||||
var latLng = this._compute(this.options.time || null)
|
|
||||||
this.setLatLngs(latLng);
|
|
||||||
},
|
|
||||||
|
|
||||||
setTime: function(date) {
|
|
||||||
this.options.time = date;
|
|
||||||
var latLng = this._compute(date || null)
|
|
||||||
this.setLatLngs(latLng);
|
|
||||||
},
|
|
||||||
|
|
||||||
_sunEclipticPosition: function(julianDay) {
|
|
||||||
/* Compute the position of the Sun in ecliptic coordinates at
|
|
||||||
julianDay. Following
|
|
||||||
http://en.wikipedia.org/wiki/Position_of_the_Sun */
|
|
||||||
// Days since start of J2000.0
|
|
||||||
var n = julianDay - 2451545.0;
|
|
||||||
// mean longitude of the Sun
|
|
||||||
var L = 280.460 + 0.9856474 * n;
|
|
||||||
L %= 360;
|
|
||||||
// mean anomaly of the Sun
|
|
||||||
var g = 357.528 + 0.9856003 * n;
|
|
||||||
g %= 360;
|
|
||||||
// ecliptic longitude of Sun
|
|
||||||
var lambda = L + 1.915 * Math.sin(g * this._D2R) +
|
|
||||||
0.02 * Math.sin(2 * g * this._D2R);
|
|
||||||
// distance from Sun in AU
|
|
||||||
var R = 1.00014 - 0.01671 * Math.cos(g * this._D2R) -
|
|
||||||
0.0014 * Math.cos(2 * g * this._D2R);
|
|
||||||
return {"lambda": lambda, "R": R};
|
|
||||||
},
|
|
||||||
|
|
||||||
_eclipticObliquity: function(julianDay) {
|
|
||||||
// Following the short term expression in
|
|
||||||
// http://en.wikipedia.org/wiki/Axial_tilt#Obliquity_of_the_ecliptic_.28Earth.27s_axial_tilt.29
|
|
||||||
var n = julianDay - 2451545.0;
|
|
||||||
// Julian centuries since J2000.0
|
|
||||||
var T = n / 36525;
|
|
||||||
var epsilon = 23.43929111 -
|
|
||||||
T * (46.836769 / 3600
|
|
||||||
- T * (0.0001831 / 3600
|
|
||||||
+ T * (0.00200340 / 3600
|
|
||||||
- T * (0.576e-6 / 3600
|
|
||||||
- T * 4.34e-8 / 3600))));
|
|
||||||
return epsilon;
|
|
||||||
},
|
|
||||||
|
|
||||||
_sunEquatorialPosition: function(sunEclLng, eclObliq) {
|
|
||||||
/* Compute the Sun's equatorial position from its ecliptic
|
|
||||||
* position. Inputs are expected in degrees. Outputs are in
|
|
||||||
* degrees as well. */
|
|
||||||
var alpha = Math.atan(Math.cos(eclObliq * this._D2R)
|
|
||||||
* Math.tan(sunEclLng * this._D2R)) * this._R2D;
|
|
||||||
var delta = Math.asin(Math.sin(eclObliq * this._D2R)
|
|
||||||
* Math.sin(sunEclLng * this._D2R)) * this._R2D;
|
|
||||||
|
|
||||||
var lQuadrant = Math.floor(sunEclLng / 90) * 90;
|
|
||||||
var raQuadrant = Math.floor(alpha / 90) * 90;
|
|
||||||
alpha = alpha + (lQuadrant - raQuadrant);
|
|
||||||
|
|
||||||
return {"alpha": alpha, "delta": delta};
|
|
||||||
},
|
|
||||||
|
|
||||||
_hourAngle: function(lng, sunPos, gst) {
|
|
||||||
/* Compute the hour angle of the sun for a longitude on
|
|
||||||
* Earth. Return the hour angle in degrees. */
|
|
||||||
var lst = gst + lng / 15;
|
|
||||||
return lst * 15 - sunPos.alpha;
|
|
||||||
},
|
|
||||||
|
|
||||||
_latitude: function(ha, sunPos) {
|
|
||||||
/* For a given hour angle and sun position, compute the
|
|
||||||
* latitude of the terminator in degrees. */
|
|
||||||
var lat = Math.atan(-Math.cos(ha * this._D2R) /
|
|
||||||
Math.tan(sunPos.delta * this._D2R)) * this._R2D;
|
|
||||||
return lat;
|
|
||||||
},
|
|
||||||
|
|
||||||
_compute: function(time) {
|
|
||||||
if (time == null)
|
|
||||||
var today = new Date();
|
|
||||||
else
|
|
||||||
var today = new Date(time);
|
|
||||||
var julianDay = today.getJulian();
|
|
||||||
var gst = today.getGMST();
|
|
||||||
var latLng = [];
|
|
||||||
var ha, lat;
|
|
||||||
|
|
||||||
var sunEclPos = this._sunEclipticPosition(julianDay);
|
|
||||||
var eclObliq = this._eclipticObliquity(julianDay);
|
|
||||||
var sunEqPos = this._sunEquatorialPosition(sunEclPos.lambda, eclObliq);
|
|
||||||
for (var i = 0; i <= 720 * this.options.resolution; i++) {
|
|
||||||
lng = -360 + i / this.options.resolution;
|
|
||||||
ha = this._hourAngle(lng, sunEqPos, gst);
|
|
||||||
lat = this._latitude(ha, sunEqPos);
|
|
||||||
latLng[i+1] = [lat, lng];
|
|
||||||
}
|
|
||||||
if (sunEqPos.delta < 0) {
|
|
||||||
latLng[0] = [90, -360];
|
|
||||||
latLng[latLng.length] = [90, 360];
|
|
||||||
} else {
|
|
||||||
latLng[0] = [-90, -360];
|
|
||||||
latLng[latLng.length] = [-90, 360];
|
|
||||||
}
|
|
||||||
return latLng;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
L.terminator = function(options) {
|
|
||||||
return new L.Terminator(options);
|
|
||||||
};
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
|||||||
.marker-cluster-small {
|
|
||||||
background-color: rgba(181, 226, 140, 0.6);
|
|
||||||
}
|
|
||||||
.marker-cluster-small div {
|
|
||||||
background-color: rgba(110, 204, 57, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.marker-cluster-medium {
|
|
||||||
background-color: rgba(241, 211, 87, 0.6);
|
|
||||||
}
|
|
||||||
.marker-cluster-medium div {
|
|
||||||
background-color: rgba(240, 194, 12, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.marker-cluster-large {
|
|
||||||
background-color: rgba(253, 156, 115, 0.6);
|
|
||||||
}
|
|
||||||
.marker-cluster-large div {
|
|
||||||
background-color: rgba(241, 128, 23, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* IE 6-8 fallback colors */
|
|
||||||
.leaflet-oldie .marker-cluster-small {
|
|
||||||
background-color: rgb(181, 226, 140);
|
|
||||||
}
|
|
||||||
.leaflet-oldie .marker-cluster-small div {
|
|
||||||
background-color: rgb(110, 204, 57);
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .marker-cluster-medium {
|
|
||||||
background-color: rgb(241, 211, 87);
|
|
||||||
}
|
|
||||||
.leaflet-oldie .marker-cluster-medium div {
|
|
||||||
background-color: rgb(240, 194, 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .marker-cluster-large {
|
|
||||||
background-color: rgb(253, 156, 115);
|
|
||||||
}
|
|
||||||
.leaflet-oldie .marker-cluster-large div {
|
|
||||||
background-color: rgb(241, 128, 23);
|
|
||||||
}
|
|
||||||
|
|
||||||
.marker-cluster {
|
|
||||||
background-clip: padding-box;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
.marker-cluster div {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 15px;
|
|
||||||
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
.marker-cluster span {
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
14
mapmakr/static/3rdparty/MarkerCluster.css
vendored
14
mapmakr/static/3rdparty/MarkerCluster.css
vendored
@ -1,14 +0,0 @@
|
|||||||
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
|
||||||
-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
|
|
||||||
-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
|
|
||||||
-o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
|
|
||||||
transition: transform 0.3s ease-out, opacity 0.3s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-cluster-spider-leg {
|
|
||||||
/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
|
|
||||||
-webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
|
|
||||||
-moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
|
|
||||||
-o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
|
|
||||||
transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
|
|
||||||
}
|
|
56
mapmakr/static/3rdparty/leaflet-easybutton.css
vendored
56
mapmakr/static/3rdparty/leaflet-easybutton.css
vendored
@ -1,56 +0,0 @@
|
|||||||
.leaflet-bar button,
|
|
||||||
.leaflet-bar button:hover {
|
|
||||||
background-color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 26px;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-bar button {
|
|
||||||
background-position: 50% 50%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
overflow: hidden;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-bar button:hover {
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-bar button:first-of-type {
|
|
||||||
border-top-left-radius: 4px;
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-bar button:last-of-type {
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-bar.disabled,
|
|
||||||
.leaflet-bar button.disabled {
|
|
||||||
cursor: default;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: .4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.easy-button-button .button-state{
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
heigth: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-bar button {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
366
mapmakr/static/3rdparty/leaflet-easybutton.js
vendored
366
mapmakr/static/3rdparty/leaflet-easybutton.js
vendored
@ -1,366 +0,0 @@
|
|||||||
(function(){
|
|
||||||
|
|
||||||
// This is for grouping buttons into a bar
|
|
||||||
// takes an array of `L.easyButton`s and
|
|
||||||
// then the usual `.addTo(map)`
|
|
||||||
L.Control.EasyBar = L.Control.extend({
|
|
||||||
|
|
||||||
options: {
|
|
||||||
position: 'topleft', // part of leaflet's defaults
|
|
||||||
id: null, // an id to tag the Bar with
|
|
||||||
leafletClasses: true // use leaflet classes?
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
initialize: function(buttons, options){
|
|
||||||
|
|
||||||
if(options){
|
|
||||||
L.Util.setOptions( this, options );
|
|
||||||
}
|
|
||||||
|
|
||||||
this._buildContainer();
|
|
||||||
this._buttons = [];
|
|
||||||
|
|
||||||
for(var i = 0; i < buttons.length; i++){
|
|
||||||
buttons[i]._bar = this;
|
|
||||||
buttons[i]._container = buttons[i].button;
|
|
||||||
this._buttons.push(buttons[i]);
|
|
||||||
this.container.appendChild(buttons[i].button);
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
_buildContainer: function(){
|
|
||||||
this._container = this.container = L.DomUtil.create('div', '');
|
|
||||||
this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control');
|
|
||||||
this.options.id && (this.container.id = this.options.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
enable: function(){
|
|
||||||
L.DomUtil.addClass(this.container, 'enabled');
|
|
||||||
L.DomUtil.removeClass(this.container, 'disabled');
|
|
||||||
this.container.setAttribute('aria-hidden', 'false');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
disable: function(){
|
|
||||||
L.DomUtil.addClass(this.container, 'disabled');
|
|
||||||
L.DomUtil.removeClass(this.container, 'enabled');
|
|
||||||
this.container.setAttribute('aria-hidden', 'true');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
onAdd: function () {
|
|
||||||
return this.container;
|
|
||||||
},
|
|
||||||
|
|
||||||
addTo: function (map) {
|
|
||||||
this._map = map;
|
|
||||||
|
|
||||||
for(var i = 0; i < this._buttons.length; i++){
|
|
||||||
this._buttons[i]._map = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
var container = this._container = this.onAdd(map),
|
|
||||||
pos = this.getPosition(),
|
|
||||||
corner = map._controlCorners[pos];
|
|
||||||
|
|
||||||
L.DomUtil.addClass(container, 'leaflet-control');
|
|
||||||
|
|
||||||
if (pos.indexOf('bottom') !== -1) {
|
|
||||||
corner.insertBefore(container, corner.firstChild);
|
|
||||||
} else {
|
|
||||||
corner.appendChild(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
L.easyBar = function(){
|
|
||||||
var args = [L.Control.EasyBar];
|
|
||||||
for(var i = 0; i < arguments.length; i++){
|
|
||||||
args.push( arguments[i] );
|
|
||||||
}
|
|
||||||
return new (Function.prototype.bind.apply(L.Control.EasyBar, args));
|
|
||||||
};
|
|
||||||
|
|
||||||
// L.EasyButton is the actual buttons
|
|
||||||
// can be called without being grouped into a bar
|
|
||||||
L.Control.EasyButton = L.Control.extend({
|
|
||||||
|
|
||||||
options: {
|
|
||||||
position: 'topleft', // part of leaflet's defaults
|
|
||||||
|
|
||||||
id: null, // an id to tag the button with
|
|
||||||
|
|
||||||
type: 'replace', // [(replace|animate)]
|
|
||||||
// replace swaps out elements
|
|
||||||
// animate changes classes with all elements inserted
|
|
||||||
|
|
||||||
states: [], // state names look like this
|
|
||||||
// {
|
|
||||||
// stateName: 'untracked',
|
|
||||||
// onClick: function(){ handle_nav_manually(); };
|
|
||||||
// title: 'click to make inactive',
|
|
||||||
// icon: 'fa-circle', // wrapped with <a>
|
|
||||||
// }
|
|
||||||
|
|
||||||
leafletClasses: true // use leaflet styles for the button
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
initialize: function(icon, onClick, title){
|
|
||||||
|
|
||||||
// clear the states manually
|
|
||||||
this.options.states = [];
|
|
||||||
|
|
||||||
// storage between state functions
|
|
||||||
this.storage = {};
|
|
||||||
|
|
||||||
// is the last item an object?
|
|
||||||
if( typeof arguments[arguments.length-1] === 'object' ){
|
|
||||||
|
|
||||||
// if so, it should be the options
|
|
||||||
L.Util.setOptions( this, arguments[arguments.length-1] );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there aren't any states in options
|
|
||||||
// use the early params
|
|
||||||
if( this.options.states.length === 0 &&
|
|
||||||
typeof icon === 'string' &&
|
|
||||||
typeof onClick === 'function'){
|
|
||||||
|
|
||||||
// turn the options object into a state
|
|
||||||
this.options.states.push({
|
|
||||||
icon: icon,
|
|
||||||
onClick: onClick,
|
|
||||||
title: typeof title === 'string' ? title : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// curate and move user's states into
|
|
||||||
// the _states for internal use
|
|
||||||
this._states = [];
|
|
||||||
|
|
||||||
for(var i = 0; i < this.options.states.length; i++){
|
|
||||||
this._states.push( new State(this.options.states[i], this) );
|
|
||||||
}
|
|
||||||
|
|
||||||
this._buildButton();
|
|
||||||
|
|
||||||
this._activateState(this._states[0]);
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
_buildButton: function(){
|
|
||||||
|
|
||||||
this.button = L.DomUtil.create('button', '');
|
|
||||||
|
|
||||||
if (this.options.id ){
|
|
||||||
this.button.id = this.options.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.leafletClasses){
|
|
||||||
L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part');
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't let double clicks get to the map
|
|
||||||
L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop);
|
|
||||||
|
|
||||||
// take care of normal clicks
|
|
||||||
L.DomEvent.addListener(this.button,'click', function(e){
|
|
||||||
L.DomEvent.stop(e);
|
|
||||||
this._currentState.onClick(this, this._map ? this._map : null );
|
|
||||||
this._map.getContainer().focus();
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
// prep the contents of the control
|
|
||||||
if(this.options.type == 'replace'){
|
|
||||||
this.button.appendChild(this._currentState.icon);
|
|
||||||
} else {
|
|
||||||
for(var i=0;i<this._states.length;i++){
|
|
||||||
this.button.appendChild(this._states[i].icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
_currentState: {
|
|
||||||
// placeholder content
|
|
||||||
stateName: 'unnamed',
|
|
||||||
icon: (function(){ return document.createElement('span'); })()
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_states: null, // populated on init
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
state: function(newState){
|
|
||||||
|
|
||||||
// activate by name
|
|
||||||
if(typeof newState == 'string'){
|
|
||||||
|
|
||||||
this._activateStateNamed(newState);
|
|
||||||
|
|
||||||
// activate by index
|
|
||||||
} else if (typeof newState == 'number'){
|
|
||||||
|
|
||||||
this._activateState(this._states[newState]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
_activateStateNamed: function(stateName){
|
|
||||||
for(var i = 0; i < this._states.length; i++){
|
|
||||||
if( this._states[i].stateName == stateName ){
|
|
||||||
this._activateState( this._states[i] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_activateState: function(newState){
|
|
||||||
|
|
||||||
if( newState === this._currentState ){
|
|
||||||
|
|
||||||
// don't touch the dom if it'll just be the same after
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// swap out elements... if you're into that kind of thing
|
|
||||||
if( this.options.type == 'replace' ){
|
|
||||||
this.button.appendChild(newState.icon);
|
|
||||||
this.button.removeChild(this._currentState.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( newState.title ){
|
|
||||||
this.button.title = newState.title;
|
|
||||||
} else {
|
|
||||||
this.button.removeAttribute('title');
|
|
||||||
}
|
|
||||||
|
|
||||||
// update classes for animations
|
|
||||||
for(var i=0;i<this._states.length;i++){
|
|
||||||
L.DomUtil.removeClass(this._states[i].icon, this._currentState.stateName + '-active');
|
|
||||||
L.DomUtil.addClass(this._states[i].icon, newState.stateName + '-active');
|
|
||||||
}
|
|
||||||
|
|
||||||
// update classes for animations
|
|
||||||
L.DomUtil.removeClass(this.button, this._currentState.stateName + '-active');
|
|
||||||
L.DomUtil.addClass(this.button, newState.stateName + '-active');
|
|
||||||
|
|
||||||
// update the record
|
|
||||||
this._currentState = newState;
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enable: function(){
|
|
||||||
L.DomUtil.addClass(this.button, 'enabled');
|
|
||||||
L.DomUtil.removeClass(this.button, 'disabled');
|
|
||||||
this.button.setAttribute('aria-hidden', 'false');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
disable: function(){
|
|
||||||
L.DomUtil.addClass(this.button, 'disabled');
|
|
||||||
L.DomUtil.removeClass(this.button, 'enabled');
|
|
||||||
this.button.setAttribute('aria-hidden', 'true');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
removeFrom: function (map) {
|
|
||||||
|
|
||||||
this._container.parentNode.removeChild(this._container);
|
|
||||||
this._map = null;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
onAdd: function(){
|
|
||||||
var containerObj = L.easyBar([this], {
|
|
||||||
position: this.options.position,
|
|
||||||
leafletClasses: this.options.leafletClasses
|
|
||||||
});
|
|
||||||
this._container = containerObj.container;
|
|
||||||
return this._container;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
L.easyButton = function(/* args will pass automatically */){
|
|
||||||
var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments)
|
|
||||||
return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
|
|
||||||
};
|
|
||||||
|
|
||||||
/*************************
|
|
||||||
*
|
|
||||||
* util functions
|
|
||||||
*
|
|
||||||
*************************/
|
|
||||||
|
|
||||||
// constructor for states so only curated
|
|
||||||
// states end up getting called
|
|
||||||
function State(template, easyButton){
|
|
||||||
|
|
||||||
this.title = template.title;
|
|
||||||
this.stateName = template.stateName ? template.stateName : 'unnamed-state';
|
|
||||||
|
|
||||||
// build the wrapper
|
|
||||||
this.icon = L.DomUtil.create('span', '');
|
|
||||||
|
|
||||||
L.DomUtil.addClass(this.icon, 'button-state state-' + this.stateName.replace(/(^\s*|\s*$)/g,''));
|
|
||||||
this.icon.innerHTML = buildIcon(template.icon);
|
|
||||||
this.onClick = L.Util.bind(template.onClick?template.onClick:function(){}, easyButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildIcon(ambiguousIconString) {
|
|
||||||
|
|
||||||
var tmpIcon;
|
|
||||||
|
|
||||||
// does this look like html? (i.e. not a class)
|
|
||||||
if( ambiguousIconString.match(/[&;=<>"']/) ){
|
|
||||||
|
|
||||||
// if so, the user should have put in html
|
|
||||||
// so move forward as such
|
|
||||||
tmpIcon = ambiguousIconString;
|
|
||||||
|
|
||||||
// then it wasn't html, so
|
|
||||||
// it's a class list, figure out what kind
|
|
||||||
} else {
|
|
||||||
ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,'');
|
|
||||||
tmpIcon = L.DomUtil.create('span', '');
|
|
||||||
|
|
||||||
if( ambiguousIconString.indexOf('fa-') === 0 ){
|
|
||||||
L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString)
|
|
||||||
} else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) {
|
|
||||||
L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString)
|
|
||||||
} else {
|
|
||||||
L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make this a string so that it's easy to set innerHTML below
|
|
||||||
tmpIcon = tmpIcon.outerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmpIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
52
mapmakr/static/3rdparty/leaflet-label.css
vendored
52
mapmakr/static/3rdparty/leaflet-label.css
vendored
@ -1,52 +0,0 @@
|
|||||||
.leaflet-label {
|
|
||||||
background: rgb(235, 235, 235);
|
|
||||||
background: rgba(235, 235, 235, 0.8);
|
|
||||||
background-clip: padding-box;
|
|
||||||
border-color: #777;
|
|
||||||
border-color: rgba(0,0,0,0.25);
|
|
||||||
border-radius: 8px;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
color: #111;
|
|
||||||
display: block;
|
|
||||||
font: 9px/12x "Helvetica Neue", Arial, Helvetica, sans-serif;
|
|
||||||
padding: 1px 6px;
|
|
||||||
position: absolute;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
z-index: 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-label.leaflet-clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-label:before,
|
|
||||||
.leaflet-label:after {
|
|
||||||
border-top: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid transparent;
|
|
||||||
content: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-label:before {
|
|
||||||
border-right: 6px solid black;
|
|
||||||
border-right-color: inherit;
|
|
||||||
left: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-label:after {
|
|
||||||
border-left: 6px solid black;
|
|
||||||
border-left-color: inherit;
|
|
||||||
right: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-label-right:before,
|
|
||||||
.leaflet-label-left:after {
|
|
||||||
content: "";
|
|
||||||
}
|
|
9
mapmakr/static/3rdparty/leaflet-label.js
vendored
9
mapmakr/static/3rdparty/leaflet-label.js
vendored
File diff suppressed because one or more lines are too long
BIN
mapmakr/static/3rdparty/leaflet-search-icon.png
vendored
BIN
mapmakr/static/3rdparty/leaflet-search-icon.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB |
BIN
mapmakr/static/3rdparty/leaflet-search-loader.gif
vendored
BIN
mapmakr/static/3rdparty/leaflet-search-loader.gif
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB |
113
mapmakr/static/3rdparty/leaflet-search.css
vendored
113
mapmakr/static/3rdparty/leaflet-search.css
vendored
@ -1,113 +0,0 @@
|
|||||||
|
|
||||||
.leaflet-container .leaflet-control-search {
|
|
||||||
position:relative;
|
|
||||||
float:left;
|
|
||||||
background:#fff;
|
|
||||||
color:#1978cf;
|
|
||||||
-moz-border-radius: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
z-index:1000;
|
|
||||||
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-control-search.search-exp {/*expanded*/
|
|
||||||
box-shadow: 0 1px 7px #999;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-input {
|
|
||||||
display:block;
|
|
||||||
float:left;
|
|
||||||
background: #fff;
|
|
||||||
border:1px solid #666;
|
|
||||||
border-radius:2px;
|
|
||||||
height:18px;
|
|
||||||
padding:0 18px 0 2px;
|
|
||||||
margin:3px 0 3px 3px;
|
|
||||||
}
|
|
||||||
.leaflet-control-search.search-load .search-input {
|
|
||||||
background: url('leaflet-search-loader.gif') no-repeat center right #fff;
|
|
||||||
}
|
|
||||||
.leaflet-control-search.search-load .search-cancel {
|
|
||||||
visibility:hidden;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-cancel {
|
|
||||||
display:block;
|
|
||||||
width:22px;
|
|
||||||
height:18px;
|
|
||||||
position:absolute;
|
|
||||||
right:22px;
|
|
||||||
margin:3px 0;
|
|
||||||
background: url('leaflet-search-icon.png') no-repeat 0 -46px;
|
|
||||||
text-decoration:none;
|
|
||||||
filter: alpha(opacity=80);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-cancel:hover {
|
|
||||||
filter: alpha(opacity=100);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-cancel span {
|
|
||||||
display:none;/* comment for cancel button imageless */
|
|
||||||
font-size:18px;
|
|
||||||
line-height:20px;
|
|
||||||
color:#ccc;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-cancel:hover span {
|
|
||||||
color:#aaa;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-button {
|
|
||||||
display:block;
|
|
||||||
float:left;
|
|
||||||
width:26px;
|
|
||||||
height:26px;
|
|
||||||
background: url('leaflet-search-icon.png') no-repeat 2px 2px;
|
|
||||||
border-radius:4px;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-button:hover {
|
|
||||||
background: url('leaflet-search-icon.png') no-repeat 2px -22px;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-tooltip {
|
|
||||||
position:absolute;
|
|
||||||
top:100%;
|
|
||||||
left:0;
|
|
||||||
float:left;
|
|
||||||
min-width:120px;
|
|
||||||
max-height:122px;
|
|
||||||
box-shadow: 1px 1px 6px rgba(0,0,0,0.4);
|
|
||||||
background-color: rgba(0, 0, 0, 0.25);
|
|
||||||
z-index:1010;
|
|
||||||
overflow-y:auto;
|
|
||||||
overflow-x:hidden;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-tip {
|
|
||||||
margin:2px;
|
|
||||||
padding:2px 4px;
|
|
||||||
display:block;
|
|
||||||
color:black;
|
|
||||||
background: #eee;
|
|
||||||
border-radius:.25em;
|
|
||||||
text-decoration:none;
|
|
||||||
white-space:nowrap;
|
|
||||||
vertical-align:center;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-tip-select,
|
|
||||||
.leaflet-control-search .search-tip:hover,
|
|
||||||
.leaflet-control-search .search-button:hover {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-control-search .search-alert {
|
|
||||||
cursor:pointer;
|
|
||||||
clear:both;
|
|
||||||
font-size:.75em;
|
|
||||||
margin-bottom:5px;
|
|
||||||
padding:0 .25em;
|
|
||||||
color:#e00;
|
|
||||||
font-weight:bold;
|
|
||||||
border-radius:.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
934
mapmakr/static/3rdparty/leaflet-search.js
vendored
934
mapmakr/static/3rdparty/leaflet-search.js
vendored
@ -1,934 +0,0 @@
|
|||||||
(function() {
|
|
||||||
|
|
||||||
L.Control.Search = L.Control.extend({
|
|
||||||
includes: L.Mixin.Events,
|
|
||||||
//
|
|
||||||
// Name Data passed Description
|
|
||||||
//
|
|
||||||
//Managed Events:
|
|
||||||
// search_locationfound {latlng, title, layer} fired after moved and show markerLocation
|
|
||||||
// search_expanded {} fired after control was expanded
|
|
||||||
// search_collapsed {} fired after control was collapsed
|
|
||||||
//
|
|
||||||
//Public methods:
|
|
||||||
// setLayer() L.LayerGroup() set layer search at runtime
|
|
||||||
// showAlert() 'Text message' show alert message
|
|
||||||
// searchText() 'Text searched' search text by external code
|
|
||||||
//
|
|
||||||
options: {
|
|
||||||
url: '', //url for search by ajax request, ex: "search.php?q={s}". Can be function that returns string for dynamic parameter setting
|
|
||||||
layer: null, //layer where search markers(is a L.LayerGroup)
|
|
||||||
sourceData: null, //function that fill _recordsCache, passed searching text by first param and callback in second
|
|
||||||
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
|
|
||||||
propertyLoc: 'loc', //field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) support dotted format: 'prop.subprop.title'
|
|
||||||
propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer,
|
|
||||||
formatData: null, //callback for reformat all data from source to indexed data object
|
|
||||||
filterData: null, //callback for filtering data from text searched, params: textSearch, allRecords
|
|
||||||
buildTip: null, //function that return row tip html node(or html string), receive text tooltip in first param
|
|
||||||
container: '', //container id to insert Search Control
|
|
||||||
minLength: 1, //minimal text length for autocomplete
|
|
||||||
initial: true, //search elements only by initial text
|
|
||||||
casesesitive: false, //search elements in case sensitive text
|
|
||||||
autoType: true, //complete input with first suggested result and select this filled-in text.
|
|
||||||
delayType: 400, //delay while typing for show tooltip
|
|
||||||
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
|
|
||||||
tipAutoSubmit: true, //auto map panTo when click on tooltip
|
|
||||||
autoResize: true, //autoresize on input change
|
|
||||||
collapsed: true, //collapse search control at startup
|
|
||||||
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
|
|
||||||
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
|
|
||||||
zoom: null, //zoom after pan to location found, default: map.getZoom()
|
|
||||||
position: 'topleft',
|
|
||||||
textErr: 'Location not found', //error message
|
|
||||||
textCancel: 'Cancel', //title in cancel button
|
|
||||||
textPlaceholder: 'Search...',//placeholder value
|
|
||||||
animateLocation: true, //animate a circle over location found
|
|
||||||
circleLocation: true, //draw a circle in location found
|
|
||||||
markerLocation: false, //draw a marker in location found
|
|
||||||
markerIcon: new L.Icon.Default()//custom icon for maker location
|
|
||||||
//TODO add option for persist markerLoc after collapse!
|
|
||||||
//TODO implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
|
|
||||||
//TODO implement can do research on multiple sources layers and remote
|
|
||||||
//TODO history: false, //show latest searches in tooltip
|
|
||||||
},
|
|
||||||
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
|
|
||||||
//FIXME option condition problem {autoCollapse: false }
|
|
||||||
//
|
|
||||||
//TODO important optimization!!! always append data in this._recordsCache
|
|
||||||
// now _recordsCache content is emptied and replaced with new data founded
|
|
||||||
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
|
|
||||||
//
|
|
||||||
//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
|
|
||||||
// run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip
|
|
||||||
//
|
|
||||||
//TODO change structure of _recordsCache
|
|
||||||
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
|
|
||||||
// in this mode every record can have a free structure of attributes, only 'loc' is required
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
L.Util.setOptions(this, options || {});
|
|
||||||
this._inputMinSize = this.options.textPlaceholder ? this.options.textPlaceholder.length : 10;
|
|
||||||
this._layer = this.options.layer || new L.LayerGroup();
|
|
||||||
this._filterData = this.options.filterData || this._defaultFilterData;
|
|
||||||
this._formatData = this.options.formatData || this._defaultFormatData;
|
|
||||||
this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown
|
|
||||||
this._countertips = 0; //number of tips items
|
|
||||||
this._recordsCache = {}; //key,value table! that store locations! format: key,latlng
|
|
||||||
this._curReq = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onAdd: function (map) {
|
|
||||||
this._map = map;
|
|
||||||
this._container = L.DomUtil.create('div', 'leaflet-control-search');
|
|
||||||
this._input = this._createInput(this.options.textPlaceholder, 'search-input');
|
|
||||||
this._tooltip = this._createTooltip('search-tooltip');
|
|
||||||
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
|
|
||||||
this._button = this._createButton(this.options.textPlaceholder, 'search-button');
|
|
||||||
this._alert = this._createAlert('search-alert');
|
|
||||||
|
|
||||||
if(this.options.collapsed===false)
|
|
||||||
this.expand(this.options.collapsed);
|
|
||||||
|
|
||||||
if(this.options.circleLocation || this.options.markerLocation || this.options.markerIcon)
|
|
||||||
this._markerLoc = new L.Control.Search.Marker([0,0], {
|
|
||||||
showCircle: this.options.circleLocation,
|
|
||||||
showMarker: this.options.markerLocation,
|
|
||||||
icon: this.options.markerIcon
|
|
||||||
});//see below
|
|
||||||
|
|
||||||
this.setLayer( this._layer );
|
|
||||||
map.on({
|
|
||||||
// 'layeradd': this._onLayerAddRemove,
|
|
||||||
// 'layerremove': this._onLayerAddRemove
|
|
||||||
'resize': this._handleAutoresize
|
|
||||||
}, this);
|
|
||||||
return this._container;
|
|
||||||
},
|
|
||||||
addTo: function (map) {
|
|
||||||
|
|
||||||
if(this.options.container) {
|
|
||||||
this._container = this.onAdd(map);
|
|
||||||
this._wrapper = L.DomUtil.get(this.options.container);
|
|
||||||
this._wrapper.style.position = 'relative';
|
|
||||||
this._wrapper.appendChild(this._container);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
L.Control.prototype.addTo.call(this, map);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemove: function(map) {
|
|
||||||
this._recordsCache = {};
|
|
||||||
// map.off({
|
|
||||||
// 'layeradd': this._onLayerAddRemove,
|
|
||||||
// 'layerremove': this._onLayerAddRemove
|
|
||||||
// }, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
// _onLayerAddRemove: function(e) {
|
|
||||||
// //console.info('_onLayerAddRemove');
|
|
||||||
// //without this, run setLayer also for each Markers!! to optimize!
|
|
||||||
// if(e.layer instanceof L.LayerGroup)
|
|
||||||
// if( L.stamp(e.layer) != L.stamp(this._layer) )
|
|
||||||
// this.setLayer(e.layer);
|
|
||||||
// },
|
|
||||||
|
|
||||||
_getPath: function(obj, prop) {
|
|
||||||
var parts = prop.split('.'),
|
|
||||||
last = parts.pop(),
|
|
||||||
len = parts.length,
|
|
||||||
cur = parts[0],
|
|
||||||
i = 1;
|
|
||||||
|
|
||||||
if(len > 0)
|
|
||||||
while((obj = obj[cur]) && i < len)
|
|
||||||
cur = parts[i++];
|
|
||||||
|
|
||||||
if(obj)
|
|
||||||
return obj[last];
|
|
||||||
},
|
|
||||||
|
|
||||||
setLayer: function(layer) { //set search layer at runtime
|
|
||||||
//this.options.layer = layer; //setting this, run only this._recordsFromLayer()
|
|
||||||
this._layer = layer;
|
|
||||||
this._layer.addTo(this._map);
|
|
||||||
if(this._markerLoc)
|
|
||||||
this._layer.addLayer(this._markerLoc);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
showAlert: function(text) {
|
|
||||||
text = text || this.options.textErr;
|
|
||||||
this._alert.style.display = 'block';
|
|
||||||
this._alert.innerHTML = text;
|
|
||||||
clearTimeout(this.timerAlert);
|
|
||||||
var that = this;
|
|
||||||
this.timerAlert = setTimeout(function() {
|
|
||||||
that.hideAlert();
|
|
||||||
},this.options.autoCollapseTime);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
hideAlert: function() {
|
|
||||||
this._alert.style.display = 'none';
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
cancel: function() {
|
|
||||||
this._input.value = '';
|
|
||||||
this._handleKeypress({keyCode:8});//simulate backspace keypress
|
|
||||||
this._input.size = this._inputMinSize;
|
|
||||||
this._input.focus();
|
|
||||||
this._cancel.style.display = 'none';
|
|
||||||
this._hideTooltip();
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
expand: function(toggle) {
|
|
||||||
toggle = typeof toggle === 'boolean' ? toggle : true;
|
|
||||||
this._input.style.display = 'block';
|
|
||||||
L.DomUtil.addClass(this._container, 'search-exp');
|
|
||||||
if ( toggle !== false ) {
|
|
||||||
this._input.focus();
|
|
||||||
this._map.on('dragstart click', this.collapse, this);
|
|
||||||
}
|
|
||||||
this.fire('search_expanded');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
collapse: function() {
|
|
||||||
this._hideTooltip();
|
|
||||||
this.cancel();
|
|
||||||
this._alert.style.display = 'none';
|
|
||||||
this._input.blur();
|
|
||||||
if(this.options.collapsed)
|
|
||||||
{
|
|
||||||
this._input.style.display = 'none';
|
|
||||||
this._cancel.style.display = 'none';
|
|
||||||
L.DomUtil.removeClass(this._container, 'search-exp');
|
|
||||||
//this._markerLoc.hide();//maybe unuseful
|
|
||||||
this._map.off('dragstart click', this.collapse, this);
|
|
||||||
}
|
|
||||||
this.fire('search_collapsed');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
collapseDelayed: function() { //collapse after delay, used on_input blur
|
|
||||||
if (!this.options.autoCollapse) return this;
|
|
||||||
var that = this;
|
|
||||||
clearTimeout(this.timerCollapse);
|
|
||||||
this.timerCollapse = setTimeout(function() {
|
|
||||||
that.collapse();
|
|
||||||
}, this.options.autoCollapseTime);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
collapseDelayedStop: function() {
|
|
||||||
clearTimeout(this.timerCollapse);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
////start DOM creations
|
|
||||||
_createAlert: function(className) {
|
|
||||||
var alert = L.DomUtil.create('div', className, this._container);
|
|
||||||
alert.style.display = 'none';
|
|
||||||
|
|
||||||
L.DomEvent
|
|
||||||
.on(alert, 'click', L.DomEvent.stop, this)
|
|
||||||
.on(alert, 'click', this.hideAlert, this);
|
|
||||||
|
|
||||||
return alert;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createInput: function (text, className) {
|
|
||||||
var label = L.DomUtil.create('label', className, this._container);
|
|
||||||
var input = L.DomUtil.create('input', className, this._container);
|
|
||||||
input.type = 'text';
|
|
||||||
input.size = this._inputMinSize;
|
|
||||||
input.value = '';
|
|
||||||
input.autocomplete = 'off';
|
|
||||||
input.autocorrect = 'off';
|
|
||||||
input.autocapitalize = 'off';
|
|
||||||
input.placeholder = text;
|
|
||||||
input.style.display = 'none';
|
|
||||||
input.role = 'search';
|
|
||||||
input.id = input.role + input.type + input.size;
|
|
||||||
|
|
||||||
label.htmlFor = input.id;
|
|
||||||
label.style.display = 'none';
|
|
||||||
label.value = text;
|
|
||||||
|
|
||||||
L.DomEvent
|
|
||||||
.disableClickPropagation(input)
|
|
||||||
.on(input, 'keyup', this._handleKeypress, this)
|
|
||||||
.on(input, 'keydown', this._handleAutoresize, this)
|
|
||||||
.on(input, 'blur', this.collapseDelayed, this)
|
|
||||||
.on(input, 'focus', this.collapseDelayedStop, this);
|
|
||||||
|
|
||||||
return input;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createCancel: function (title, className) {
|
|
||||||
var cancel = L.DomUtil.create('a', className, this._container);
|
|
||||||
cancel.href = '#';
|
|
||||||
cancel.title = title;
|
|
||||||
cancel.style.display = 'none';
|
|
||||||
cancel.innerHTML = "<span>⊗</span>";//imageless(see css)
|
|
||||||
|
|
||||||
L.DomEvent
|
|
||||||
.on(cancel, 'click', L.DomEvent.stop, this)
|
|
||||||
.on(cancel, 'click', this.cancel, this);
|
|
||||||
|
|
||||||
return cancel;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createButton: function (title, className) {
|
|
||||||
var button = L.DomUtil.create('a', className, this._container);
|
|
||||||
button.href = '#';
|
|
||||||
button.title = title;
|
|
||||||
|
|
||||||
L.DomEvent
|
|
||||||
.on(button, 'click', L.DomEvent.stop, this)
|
|
||||||
.on(button, 'click', this._handleSubmit, this)
|
|
||||||
.on(button, 'focus', this.collapseDelayedStop, this)
|
|
||||||
.on(button, 'blur', this.collapseDelayed, this);
|
|
||||||
|
|
||||||
return button;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createTooltip: function(className) {
|
|
||||||
var tool = L.DomUtil.create('ul', className, this._container);
|
|
||||||
tool.style.display = 'none';
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
L.DomEvent
|
|
||||||
.disableClickPropagation(tool)
|
|
||||||
.on(tool, 'blur', this.collapseDelayed, this)
|
|
||||||
.on(tool, 'mousewheel', function(e) {
|
|
||||||
that.collapseDelayedStop();
|
|
||||||
L.DomEvent.stopPropagation(e);//disable zoom map
|
|
||||||
}, this)
|
|
||||||
.on(tool, 'mouseover', function(e) {
|
|
||||||
that.collapseDelayedStop();
|
|
||||||
}, this);
|
|
||||||
return tool;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createTip: function(text, val) {//val is object in recordCache, usually is Latlng
|
|
||||||
var tip;
|
|
||||||
|
|
||||||
if(this.options.buildTip)
|
|
||||||
{
|
|
||||||
tip = this.options.buildTip.call(this, text, val); //custom tip node or html string
|
|
||||||
if(typeof tip === 'string')
|
|
||||||
{
|
|
||||||
var tmpNode = L.DomUtil.create('div');
|
|
||||||
tmpNode.innerHTML = tip;
|
|
||||||
tip = tmpNode.firstChild;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tip = L.DomUtil.create('li', '');
|
|
||||||
tip.innerHTML = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
L.DomUtil.addClass(tip, 'search-tip');
|
|
||||||
tip._text = text; //value replaced in this._input and used by _autoType
|
|
||||||
|
|
||||||
if(this.options.tipAutoSubmit)
|
|
||||||
L.DomEvent
|
|
||||||
.disableClickPropagation(tip)
|
|
||||||
.on(tip, 'click', L.DomEvent.stop, this)
|
|
||||||
.on(tip, 'click', function(e) {
|
|
||||||
this._input.value = text;
|
|
||||||
this._handleAutoresize();
|
|
||||||
this._input.focus();
|
|
||||||
this._hideTooltip();
|
|
||||||
this._handleSubmit();
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
return tip;
|
|
||||||
},
|
|
||||||
|
|
||||||
//////end DOM creations
|
|
||||||
|
|
||||||
_getUrl: function(text) {
|
|
||||||
return (typeof this.options.url === 'function') ? this.options.url(text) : this.options.url;
|
|
||||||
},
|
|
||||||
|
|
||||||
_defaultFilterData: function(text, records) {
|
|
||||||
|
|
||||||
var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'), //remove . * | ( ) ] [
|
|
||||||
I, regSearch,
|
|
||||||
frecords = {};
|
|
||||||
|
|
||||||
text = text.replace(regFilter,''); //sanitize text
|
|
||||||
I = this.options.initial ? '^' : ''; //search only initial text
|
|
||||||
|
|
||||||
regSearch = new RegExp(I + text, !this.options.casesesitive ? 'i' : undefined);
|
|
||||||
|
|
||||||
//TODO use .filter or .map
|
|
||||||
for(var key in records)
|
|
||||||
if( regSearch.test(key) )
|
|
||||||
frecords[key]= records[key];
|
|
||||||
|
|
||||||
return frecords;
|
|
||||||
},
|
|
||||||
|
|
||||||
showTooltip: function(records) {
|
|
||||||
var tip;
|
|
||||||
|
|
||||||
this._countertips = 0;
|
|
||||||
|
|
||||||
this._tooltip.innerHTML = '';
|
|
||||||
this._tooltip.currentSelection = -1; //inizialized for _handleArrowSelect()
|
|
||||||
|
|
||||||
for(var key in records)//fill tooltip
|
|
||||||
{
|
|
||||||
if(++this._countertips == this.options.tooltipLimit) break;
|
|
||||||
|
|
||||||
tip = this._createTip(key, records[key] );
|
|
||||||
|
|
||||||
this._tooltip.appendChild(tip);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this._countertips > 0)
|
|
||||||
{
|
|
||||||
this._tooltip.style.display = 'block';
|
|
||||||
if(this._autoTypeTmp)
|
|
||||||
this._autoType();
|
|
||||||
this._autoTypeTmp = this.options.autoType;//reset default value
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this._hideTooltip();
|
|
||||||
|
|
||||||
this._tooltip.scrollTop = 0;
|
|
||||||
return this._countertips;
|
|
||||||
},
|
|
||||||
|
|
||||||
_hideTooltip: function() {
|
|
||||||
this._tooltip.style.display = 'none';
|
|
||||||
this._tooltip.innerHTML = '';
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
_defaultFormatData: function(json) { //default callback for format data to indexed data
|
|
||||||
var propName = this.options.propertyName,
|
|
||||||
propLoc = this.options.propertyLoc,
|
|
||||||
i, jsonret = {};
|
|
||||||
|
|
||||||
if( L.Util.isArray(propLoc) )
|
|
||||||
for(i in json)
|
|
||||||
jsonret[ this._getPath(json[i],propName) ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
|
|
||||||
else
|
|
||||||
for(i in json)
|
|
||||||
jsonret[ this._getPath(json[i],propName) ]= L.latLng( this._getPath(json[i],propLoc) );
|
|
||||||
//TODO throw new Error("propertyName '"+propName+"' not found in JSON data");
|
|
||||||
return jsonret;
|
|
||||||
},
|
|
||||||
|
|
||||||
_recordsFromJsonp: function(text, callAfter) { //extract searched records from remote jsonp service
|
|
||||||
L.Control.Search.callJsonp = callAfter;
|
|
||||||
var script = L.DomUtil.create('script','leaflet-search-jsonp', document.getElementsByTagName('body')[0] ),
|
|
||||||
url = L.Util.template(this._getUrl(text)+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url
|
|
||||||
//rnd = '&_='+Math.floor(Math.random()*10000);
|
|
||||||
//TODO add rnd param or randomize callback name! in recordsFromJsonp
|
|
||||||
script.type = 'text/javascript';
|
|
||||||
script.src = url;
|
|
||||||
return { abort: function() { script.parentNode.removeChild(script); } };
|
|
||||||
},
|
|
||||||
|
|
||||||
_recordsFromAjax: function(text, callAfter) { //Ajax request
|
|
||||||
if (window.XMLHttpRequest === undefined) {
|
|
||||||
window.XMLHttpRequest = function() {
|
|
||||||
try { return new ActiveXObject("Microsoft.XMLHTTP.6.0"); }
|
|
||||||
catch (e1) {
|
|
||||||
try { return new ActiveXObject("Microsoft.XMLHTTP.3.0"); }
|
|
||||||
catch (e2) { throw new Error("XMLHttpRequest is not supported"); }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var IE8or9 = ( L.Browser.ie && !window.atob && document.querySelector ),
|
|
||||||
request = IE8or9 ? new XDomainRequest() : new XMLHttpRequest(),
|
|
||||||
url = L.Util.template(this._getUrl(text), {s: text});
|
|
||||||
|
|
||||||
//rnd = '&_='+Math.floor(Math.random()*10000);
|
|
||||||
//TODO add rnd param or randomize callback name! in recordsFromAjax
|
|
||||||
|
|
||||||
request.open("GET", url);
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
request.onload = function() {
|
|
||||||
callAfter( JSON.parse(request.responseText) );
|
|
||||||
};
|
|
||||||
request.onreadystatechange = function() {
|
|
||||||
if(request.readyState === 4 && request.status === 200) {
|
|
||||||
this.onload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
request.send();
|
|
||||||
return request;
|
|
||||||
},
|
|
||||||
|
|
||||||
_recordsFromLayer: function() { //return table: key,value from layer
|
|
||||||
var that = this,
|
|
||||||
retRecords = {},
|
|
||||||
propName = this.options.propertyName,
|
|
||||||
loc;
|
|
||||||
|
|
||||||
this._layer.eachLayer(function(layer) {
|
|
||||||
|
|
||||||
if(layer instanceof L.Control.Search.Marker) return;
|
|
||||||
|
|
||||||
if(layer instanceof L.Marker || layer instanceof L.CircleMarker)
|
|
||||||
{
|
|
||||||
if(that._getPath(layer.options,propName))
|
|
||||||
{
|
|
||||||
loc = layer.getLatLng();
|
|
||||||
loc.layer = layer;
|
|
||||||
retRecords[ that._getPath(layer.options,propName) ] = loc;
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(that._getPath(layer.feature.properties,propName)){
|
|
||||||
|
|
||||||
loc = layer.getLatLng();
|
|
||||||
loc.layer = layer;
|
|
||||||
retRecords[ that._getPath(layer.feature.properties,propName) ] = loc;
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Error("propertyName '"+propName+"' not found in marker");
|
|
||||||
}
|
|
||||||
else if(layer.hasOwnProperty('feature'))//GeoJSON
|
|
||||||
{
|
|
||||||
if(layer.feature.properties.hasOwnProperty(propName))
|
|
||||||
{
|
|
||||||
loc = layer.getBounds().getCenter();
|
|
||||||
loc.layer = layer;
|
|
||||||
retRecords[ layer.feature.properties[propName] ] = loc;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Error("propertyName '"+propName+"' not found in feature");
|
|
||||||
}
|
|
||||||
else if(layer instanceof L.LayerGroup)
|
|
||||||
{
|
|
||||||
//TODO: Optimize
|
|
||||||
layer.eachLayer(function(m) {
|
|
||||||
loc = m.getLatLng();
|
|
||||||
loc.layer = m;
|
|
||||||
retRecords[ m.feature.properties[propName] ] = loc;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
},this);
|
|
||||||
|
|
||||||
return retRecords;
|
|
||||||
},
|
|
||||||
|
|
||||||
_autoType: function() {
|
|
||||||
|
|
||||||
//TODO implements autype without selection(useful for mobile device)
|
|
||||||
|
|
||||||
var start = this._input.value.length,
|
|
||||||
firstRecord = this._tooltip.firstChild._text,
|
|
||||||
end = firstRecord.length;
|
|
||||||
|
|
||||||
if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match
|
|
||||||
this._input.value = firstRecord;
|
|
||||||
this._handleAutoresize();
|
|
||||||
|
|
||||||
if (this._input.createTextRange) {
|
|
||||||
var selRange = this._input.createTextRange();
|
|
||||||
selRange.collapse(true);
|
|
||||||
selRange.moveStart('character', start);
|
|
||||||
selRange.moveEnd('character', end);
|
|
||||||
selRange.select();
|
|
||||||
}
|
|
||||||
else if(this._input.setSelectionRange) {
|
|
||||||
this._input.setSelectionRange(start, end);
|
|
||||||
}
|
|
||||||
else if(this._input.selectionStart) {
|
|
||||||
this._input.selectionStart = start;
|
|
||||||
this._input.selectionEnd = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_hideAutoType: function() { // deselect text:
|
|
||||||
|
|
||||||
var sel;
|
|
||||||
if ((sel = this._input.selection) && sel.empty) {
|
|
||||||
sel.empty();
|
|
||||||
}
|
|
||||||
else if (this._input.createTextRange) {
|
|
||||||
sel = this._input.createTextRange();
|
|
||||||
sel.collapse(true);
|
|
||||||
var end = this._input.value.length;
|
|
||||||
sel.moveStart('character', end);
|
|
||||||
sel.moveEnd('character', end);
|
|
||||||
sel.select();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this._input.getSelection) {
|
|
||||||
this._input.getSelection().removeAllRanges();
|
|
||||||
}
|
|
||||||
this._input.selectionStart = this._input.selectionEnd;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleKeypress: function (e) { //run _input keyup event
|
|
||||||
|
|
||||||
switch(e.keyCode)
|
|
||||||
{
|
|
||||||
case 27: //Esc
|
|
||||||
this.collapse();
|
|
||||||
break;
|
|
||||||
case 13: //Enter
|
|
||||||
if(this._countertips == 1)
|
|
||||||
this._handleArrowSelect(1);
|
|
||||||
this._handleSubmit(); //do search
|
|
||||||
break;
|
|
||||||
case 38://Up
|
|
||||||
this._handleArrowSelect(-1);
|
|
||||||
break;
|
|
||||||
case 40://Down
|
|
||||||
this._handleArrowSelect(1);
|
|
||||||
break;
|
|
||||||
case 37://Left
|
|
||||||
case 39://Right
|
|
||||||
case 16://Shift
|
|
||||||
case 17://Ctrl
|
|
||||||
//case 32://Space
|
|
||||||
break;
|
|
||||||
case 8://backspace
|
|
||||||
case 46://delete
|
|
||||||
this._autoTypeTmp = false;//disable temporarily autoType
|
|
||||||
break;
|
|
||||||
default://All keys
|
|
||||||
|
|
||||||
if(this._input.value.length)
|
|
||||||
this._cancel.style.display = 'block';
|
|
||||||
else
|
|
||||||
this._cancel.style.display = 'none';
|
|
||||||
|
|
||||||
if(this._input.value.length >= this.options.minLength)
|
|
||||||
{
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
clearTimeout(this.timerKeypress); //cancel last search request while type in
|
|
||||||
this.timerKeypress = setTimeout(function() { //delay before request, for limit jsonp/ajax request
|
|
||||||
|
|
||||||
that._fillRecordsCache();
|
|
||||||
|
|
||||||
}, this.options.delayType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this._hideTooltip();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
searchText: function(text) {
|
|
||||||
var code = text.charCodeAt(text.length);
|
|
||||||
|
|
||||||
this._input.value = text;
|
|
||||||
|
|
||||||
this._input.style.display = 'block';
|
|
||||||
L.DomUtil.addClass(this._container, 'search-exp');
|
|
||||||
|
|
||||||
this._autoTypeTmp = false;
|
|
||||||
|
|
||||||
this._handleKeypress({keyCode: code});
|
|
||||||
},
|
|
||||||
|
|
||||||
_fillRecordsCache: function() {
|
|
||||||
//TODO important optimization!!! always append data in this._recordsCache
|
|
||||||
// now _recordsCache content is emptied and replaced with new data founded
|
|
||||||
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
|
|
||||||
//
|
|
||||||
//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
|
|
||||||
// run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip
|
|
||||||
//
|
|
||||||
//TODO change structure of _recordsCache
|
|
||||||
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
|
|
||||||
// in this way every record can have a free structure of attributes, only 'loc' is required
|
|
||||||
|
|
||||||
var inputText = this._input.value,
|
|
||||||
that = this, records;
|
|
||||||
|
|
||||||
if(this._curReq && this._curReq.abort)
|
|
||||||
this._curReq.abort();
|
|
||||||
//abort previous requests
|
|
||||||
|
|
||||||
L.DomUtil.addClass(this._container, 'search-load');
|
|
||||||
|
|
||||||
if(this.options.layer)
|
|
||||||
{
|
|
||||||
//TODO _recordsFromLayer must return array of objects, formatted from _formatData
|
|
||||||
this._recordsCache = this._recordsFromLayer();
|
|
||||||
|
|
||||||
records = this._filterData( this._input.value, this._recordsCache );
|
|
||||||
|
|
||||||
this.showTooltip( records );
|
|
||||||
|
|
||||||
L.DomUtil.removeClass(this._container, 'search-load');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(this.options.sourceData)
|
|
||||||
this._retrieveData = this.options.sourceData;
|
|
||||||
|
|
||||||
else if(this.options.url) //jsonp or ajax
|
|
||||||
this._retrieveData = this.options.jsonpParam ? this._recordsFromJsonp : this._recordsFromAjax;
|
|
||||||
|
|
||||||
this._curReq = this._retrieveData.call(this, inputText, function(data) {
|
|
||||||
|
|
||||||
that._recordsCache = that._formatData(data);
|
|
||||||
|
|
||||||
//TODO refact!
|
|
||||||
if(that.options.sourceData)
|
|
||||||
records = that._filterData( that._input.value, that._recordsCache );
|
|
||||||
else
|
|
||||||
records = that._recordsCache;
|
|
||||||
|
|
||||||
that.showTooltip( records );
|
|
||||||
|
|
||||||
L.DomUtil.removeClass(that._container, 'search-load');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleAutoresize: function() { //autoresize this._input
|
|
||||||
//TODO refact _handleAutoresize now is not accurate
|
|
||||||
if (this._input.style.maxWidth != this._map._container.offsetWidth) //If maxWidth isn't the same as when first set, reset to current Map width
|
|
||||||
this._input.style.maxWidth = L.DomUtil.getStyle(this._map._container, 'width');
|
|
||||||
|
|
||||||
if(this.options.autoResize && (this._container.offsetWidth + 45 < this._map._container.offsetWidth))
|
|
||||||
this._input.size = this._input.value.length<this._inputMinSize ? this._inputMinSize : this._input.value.length;
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleArrowSelect: function(velocity) {
|
|
||||||
|
|
||||||
var searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : [];
|
|
||||||
|
|
||||||
for (i=0; i<searchTips.length; i++)
|
|
||||||
L.DomUtil.removeClass(searchTips[i], 'search-tip-select');
|
|
||||||
|
|
||||||
if ((velocity == 1 ) && (this._tooltip.currentSelection >= (searchTips.length - 1))) {// If at end of list.
|
|
||||||
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
|
|
||||||
}
|
|
||||||
else if ((velocity == -1 ) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box.
|
|
||||||
this._tooltip.currentSelection = -1;
|
|
||||||
}
|
|
||||||
else if (this._tooltip.style.display != 'none') {
|
|
||||||
this._tooltip.currentSelection += velocity;
|
|
||||||
|
|
||||||
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
|
|
||||||
|
|
||||||
this._input.value = searchTips[this._tooltip.currentSelection]._text;
|
|
||||||
|
|
||||||
// scroll:
|
|
||||||
var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop;
|
|
||||||
|
|
||||||
if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) {
|
|
||||||
this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight;
|
|
||||||
}
|
|
||||||
else if (tipOffsetTop <= this._tooltip.scrollTop) {
|
|
||||||
this._tooltip.scrollTop = tipOffsetTop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleSubmit: function() { //button and tooltip click and enter submit
|
|
||||||
|
|
||||||
this._hideAutoType();
|
|
||||||
|
|
||||||
this.hideAlert();
|
|
||||||
this._hideTooltip();
|
|
||||||
|
|
||||||
if(this._input.style.display == 'none') //on first click show _input only
|
|
||||||
this.expand();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(this._input.value === '') //hide _input only
|
|
||||||
this.collapse();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var loc = this._getLocation(this._input.value);
|
|
||||||
|
|
||||||
if(loc===false)
|
|
||||||
this.showAlert();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.showLocation(loc, this._input.value);
|
|
||||||
this.fire('search_locationfound', {
|
|
||||||
latlng: loc,
|
|
||||||
text: this._input.value,
|
|
||||||
layer: loc.layer ? loc.layer : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//this.collapse();
|
|
||||||
//FIXME if collapse in _handleSubmit hide _markerLoc!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getLocation: function(key) { //extract latlng from _recordsCache
|
|
||||||
|
|
||||||
if( this._recordsCache.hasOwnProperty(key) )
|
|
||||||
return this._recordsCache[key];//then after use .loc attribute
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
showLocation: function(latlng, title) { //set location on map from _recordsCache
|
|
||||||
|
|
||||||
if(this.options.zoom)
|
|
||||||
this._map.setView(latlng, this.options.zoom);
|
|
||||||
else
|
|
||||||
this._map.panTo(latlng);
|
|
||||||
|
|
||||||
if(this._markerLoc)
|
|
||||||
{
|
|
||||||
this._markerLoc.setLatLng(latlng); //show circle/marker in location found
|
|
||||||
this._markerLoc.setTitle(title);
|
|
||||||
this._markerLoc.show();
|
|
||||||
if(this.options.animateLocation)
|
|
||||||
this._markerLoc.animate();
|
|
||||||
//TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
|
|
||||||
}
|
|
||||||
|
|
||||||
//FIXME autoCollapse option hide this._markerLoc before that visualized!!
|
|
||||||
if(this.options.autoCollapse)
|
|
||||||
this.collapse();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
L.Control.Search.Marker = L.Marker.extend({
|
|
||||||
|
|
||||||
includes: L.Mixin.Events,
|
|
||||||
|
|
||||||
options: {
|
|
||||||
radius: 10,
|
|
||||||
weight: 3,
|
|
||||||
color: '#e03',
|
|
||||||
stroke: true,
|
|
||||||
fill: false,
|
|
||||||
title: '',
|
|
||||||
icon: new L.Icon.Default(),
|
|
||||||
showCircle: true,
|
|
||||||
showMarker: false //show icon optional, show only circleLoc
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function (latlng, options) {
|
|
||||||
L.setOptions(this, options);
|
|
||||||
L.Marker.prototype.initialize.call(this, latlng, options);
|
|
||||||
if(this.options.showCircle)
|
|
||||||
this._circleLoc = new L.CircleMarker(latlng, this.options);
|
|
||||||
},
|
|
||||||
|
|
||||||
onAdd: function (map) {
|
|
||||||
L.Marker.prototype.onAdd.call(this, map);
|
|
||||||
if(this._circleLoc)
|
|
||||||
map.addLayer(this._circleLoc);
|
|
||||||
this.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemove: function (map) {
|
|
||||||
L.Marker.prototype.onRemove.call(this, map);
|
|
||||||
if(this._circleLoc)
|
|
||||||
map.removeLayer(this._circleLoc);
|
|
||||||
},
|
|
||||||
|
|
||||||
setLatLng: function (latlng) {
|
|
||||||
L.Marker.prototype.setLatLng.call(this, latlng);
|
|
||||||
if(this._circleLoc)
|
|
||||||
this._circleLoc.setLatLng(latlng);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
setTitle: function(title) {
|
|
||||||
title = title || '';
|
|
||||||
this.options.title = title;
|
|
||||||
if(this._icon)
|
|
||||||
this._icon.title = title;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function() {
|
|
||||||
if(this.options.showMarker)
|
|
||||||
{
|
|
||||||
if(this._icon)
|
|
||||||
this._icon.style.display = 'block';
|
|
||||||
if(this._shadow)
|
|
||||||
this._shadow.style.display = 'block';
|
|
||||||
//this._bringToFront();
|
|
||||||
}
|
|
||||||
if(this._circleLoc)
|
|
||||||
{
|
|
||||||
this._circleLoc.setStyle({fill: this.options.fill, stroke: this.options.stroke});
|
|
||||||
//this._circleLoc.bringToFront();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
hide: function() {
|
|
||||||
if(this._icon)
|
|
||||||
this._icon.style.display = 'none';
|
|
||||||
if(this._shadow)
|
|
||||||
this._shadow.style.display = 'none';
|
|
||||||
if(this._circleLoc)
|
|
||||||
this._circleLoc.setStyle({fill: false, stroke: false});
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
animate: function() {
|
|
||||||
//TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
|
|
||||||
if(this._circleLoc)
|
|
||||||
{
|
|
||||||
var circle = this._circleLoc,
|
|
||||||
tInt = 200, //time interval
|
|
||||||
ss = 10, //frames
|
|
||||||
mr = parseInt(circle._radius/ss),
|
|
||||||
oldrad = this.options.radius,
|
|
||||||
newrad = circle._radius * 2.5,
|
|
||||||
acc = 0;
|
|
||||||
|
|
||||||
circle._timerAnimLoc = setInterval(function() {
|
|
||||||
acc += 0.5;
|
|
||||||
mr += acc; //adding acceleration
|
|
||||||
newrad -= mr;
|
|
||||||
|
|
||||||
circle.setRadius(newrad);
|
|
||||||
|
|
||||||
if(newrad<oldrad)
|
|
||||||
{
|
|
||||||
clearInterval(circle._timerAnimLoc);
|
|
||||||
circle.setRadius(oldrad);//reset radius
|
|
||||||
//if(typeof afterAnimCall == 'function')
|
|
||||||
//afterAnimCall();
|
|
||||||
//TODO use create event 'animateEnd' in L.Control.Search.Marker
|
|
||||||
}
|
|
||||||
}, tInt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
L.Map.addInitHook(function () {
|
|
||||||
if (this.options.searchControl) {
|
|
||||||
this.searchControl = L.control.search(this.options.searchControl);
|
|
||||||
this.addControl(this.searchControl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
L.control.search = function (options) {
|
|
||||||
return new L.Control.Search(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
@ -1,9 +1,9 @@
|
|||||||
let map, drawnItems, drawControl, currentLayer;
|
let map, drawnItems, drawControl, currentLayer, editModal, editForm;
|
||||||
|
|
||||||
|
|
||||||
function reload_map() {
|
function reloadMap() {
|
||||||
fetch("/marker").then(response => response.json()).then(markers => {
|
fetch("/marker").then(response => response.json()).then(markers => {
|
||||||
// const map = window.map;
|
let markerBounds = new L.LatLngBounds();
|
||||||
drawnItems.clearLayers();
|
drawnItems.clearLayers();
|
||||||
markers.forEach(marker => {
|
markers.forEach(marker => {
|
||||||
let mapMarker = L.marker({"lat": marker.latitude, "lng": marker.longitude});
|
let mapMarker = L.marker({"lat": marker.latitude, "lng": marker.longitude});
|
||||||
@ -12,9 +12,10 @@ function reload_map() {
|
|||||||
for (const opt in marker.options) {
|
for (const opt in marker.options) {
|
||||||
mapMarker.options[opt] = marker.options[opt];
|
mapMarker.options[opt] = marker.options[opt];
|
||||||
}
|
}
|
||||||
mapMarker.bindLabel(marker.name, {noHide: true});
|
// mapMarker.bindTooltip(marker.name, {noHide: true});
|
||||||
mapMarker.bindPopup(marker.name);
|
mapMarker.bindPopup(marker.name);
|
||||||
drawnItems.addLayer(mapMarker);
|
drawnItems.addLayer(mapMarker);
|
||||||
|
markerBounds.extend(mapMarker.getLatLng());
|
||||||
});
|
});
|
||||||
if (!drawControl) {
|
if (!drawControl) {
|
||||||
// add drawControl only after some drawnItems exist, so
|
// add drawControl only after some drawnItems exist, so
|
||||||
@ -31,25 +32,31 @@ function reload_map() {
|
|||||||
featureGroup: drawnItems
|
featureGroup: drawnItems
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(map);
|
|
||||||
console.log(drawControl);
|
|
||||||
map.addControl(drawControl);
|
map.addControl(drawControl);
|
||||||
}
|
}
|
||||||
|
map.fitBounds(markerBounds);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup_map() {
|
function setupMap() {
|
||||||
|
L.AwesomeMarkers.Icon.prototype.options.prefix = 'bs';
|
||||||
|
|
||||||
|
editForm = document.getElementById('edit-form');
|
||||||
|
let editModalDiv = document.getElementById('edit-modal');
|
||||||
|
editModal = new bootstrap.Modal(editModalDiv);
|
||||||
|
editModalDiv.addEventListener('hidden.bs.modal', () => editForm.reset());
|
||||||
|
|
||||||
let osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
let osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
||||||
|
|
||||||
map = new L.Map('mapmakr', {layers: [osm], attributionControl: false, center: new L.LatLng(0, 0), zoom: 2 });
|
map = new L.Map('mapmakr', {layers: [osm], attributionControl: false, center: new L.LatLng(0, 0), zoom: 5});
|
||||||
|
|
||||||
drawnItems = new L.MarkerClusterGroup({maxClusterRadius: 45});
|
drawnItems = new L.MarkerClusterGroup({maxClusterRadius: 45});
|
||||||
map.addLayer(drawnItems);
|
map.addLayer(drawnItems);
|
||||||
reload_map();
|
reloadMap();
|
||||||
|
|
||||||
const refreshButton = new L.easyButton('icon ion-refresh', function() {reload_map();});
|
const refreshButton = new L.easyButton('bi bi-arrow-clockwise', function() {reload_map();});
|
||||||
refreshButton.button.style.fontSize = '18px';
|
refreshButton.button.style.fontSize = '18px';
|
||||||
map.addControl(refreshButton);
|
map.addControl(refreshButton);
|
||||||
|
|
||||||
@ -86,7 +93,7 @@ function setup_map() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentLayer = layer;
|
currentLayer = layer;
|
||||||
$("#edit-form").modal();
|
editModal.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on('draw:edited', function (e) {
|
map.on('draw:edited', function (e) {
|
||||||
@ -100,13 +107,13 @@ function setup_map() {
|
|||||||
map.on('draw:deleted', function (e) {
|
map.on('draw:deleted', function (e) {
|
||||||
e.layers.eachLayer(layer => {
|
e.layers.eachLayer(layer => {
|
||||||
let type = layer.layerType, id = layer.options.id;
|
let type = layer.layerType, id = layer.options.id;
|
||||||
fetch("/marker/" + id, {method: "DELETE"}).then(() => reload_map());
|
fetch("/marker/" + id, {method: "DELETE"}).then(() => reloadMap());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#marker-save").on("click", (event) => {
|
document.getElementById("marker-save").addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let name = $("#marker-name").val();
|
let name = document.getElementById("marker-name").value;
|
||||||
const markerAttributes = {
|
const markerAttributes = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"latitude": currentLayer.getLatLng()["lat"],
|
"latitude": currentLayer.getLatLng()["lat"],
|
||||||
@ -124,17 +131,16 @@ function setup_map() {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(markerAttributes)
|
body: JSON.stringify(markerAttributes)
|
||||||
};
|
};
|
||||||
console.log(currentLayer);
|
|
||||||
if (currentLayer.options.id) {
|
if (currentLayer.options.id) {
|
||||||
requestOptions["method"] = "PATCH";
|
requestOptions["method"] = "PATCH";
|
||||||
fetch("/marker/" + currentLayer.options.id, requestOptions).then(() => reload_map());
|
fetch("/marker/" + currentLayer.options.id, requestOptions).then(() => reloadMap());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
requestOptions["method"] = "POST";
|
requestOptions["method"] = "POST";
|
||||||
fetch("/marker", requestOptions).then(() => reload_map());
|
fetch("/marker", requestOptions).then(() => reloadMap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$.modal.close();
|
editModal.hide();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,59 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{{site_title}}</title>
|
<title>{{site_title}}</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.css" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"/>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
|
||||||
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"/>
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.14/leaflet.draw.css" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.css">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-search.css') }}" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-easybutton.css') }}" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-label.css') }}" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-search@3.0.9/dist/leaflet-search.min.css"></script>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/MarkerCluster.css') }}"/>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/MarkerCluster.Default.css') }}"/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mapmakr" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
<div id="mapmakr" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
||||||
<form id="edit-form" class="modal">
|
<div class="modal" id="edit-modal">
|
||||||
<div class="form-control">
|
<div class="modal-dialog">
|
||||||
<label for="marker-name">Name:</label>
|
<div class="modal-content">
|
||||||
<input id="marker-name" name="marker-name" />
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Edit Marker</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="edit-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="marker-name" class="form-label">Name:</label>
|
||||||
|
<input id="marker-name" name="marker-name" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="marker-description" class="form-label">Description:</label>
|
||||||
|
<textarea id="marker-description" name="marker-description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="marker-website" class="form-label">Website:</label>
|
||||||
|
<input id="marker-website" name="marker-website" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" id="marker-save" class="btn btn-primary">Save changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" id="marker-save">Save</button>
|
</div>
|
||||||
</form>
|
<!-- script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.14/leaflet.draw.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.js"></script>
|
||||||
<script src="{{ url_for('static', path='/3rdparty/leaflet-search.js') }}"></script>
|
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
||||||
<script src="{{ url_for('static', path='/3rdparty/leaflet-easybutton.js') }}"></script>
|
<script src="https://unpkg.com/leaflet-search@3.0.9/dist/leaflet-search.min.js"></script>
|
||||||
<script src="{{ url_for('static', path='/3rdparty/leaflet-label.js') }}"></script>
|
<script src="https://unpkg.com/@joergdietrich/leaflet.terminator"></script>
|
||||||
<script src="{{ url_for('static', path='/3rdparty/L.Terminator.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', path='/3rdparty/leaflet.markercluster.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', path='/js/mapmakr.js') }}"></script>
|
<script src="{{ url_for('static', path='/js/mapmakr.js') }}"></script>
|
||||||
<script>setup_map();</script>
|
<script>setupMap();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user