Making sketch handlers work over the dateline by using layer.getLonLatFromViewPortPx instead of map.getLonLatFromPixel. Thanks bartvde for the unit and acceptance tests. r=bartvde (closes #2787)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@12346 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
@@ -24,6 +24,13 @@
|
|||||||
OpenLayers.Control.EditingToolbar = OpenLayers.Class(
|
OpenLayers.Control.EditingToolbar = OpenLayers.Class(
|
||||||
OpenLayers.Control.Panel, {
|
OpenLayers.Control.Panel, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIProperty: citeCompliant
|
||||||
|
* {Boolean} If set to true, coordinates of features drawn in a map extent
|
||||||
|
* crossing the date line won't exceed the world bounds. Default is false.
|
||||||
|
*/
|
||||||
|
citeCompliant: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor: OpenLayers.Control.EditingToolbar
|
* Constructor: OpenLayers.Control.EditingToolbar
|
||||||
* Create an editing toolbar for a given layer.
|
* Create an editing toolbar for a given layer.
|
||||||
@@ -39,9 +46,18 @@ OpenLayers.Control.EditingToolbar = OpenLayers.Class(
|
|||||||
[ new OpenLayers.Control.Navigation() ]
|
[ new OpenLayers.Control.Navigation() ]
|
||||||
);
|
);
|
||||||
var controls = [
|
var controls = [
|
||||||
new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}),
|
new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {
|
||||||
new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}),
|
displayClass: 'olControlDrawFeaturePoint',
|
||||||
new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'})
|
handlerOptions: {citeCompliant: this.citeCompliant}
|
||||||
|
}),
|
||||||
|
new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {
|
||||||
|
displayClass: 'olControlDrawFeaturePath',
|
||||||
|
handlerOptions: {citeCompliant: this.citeCompliant}
|
||||||
|
}),
|
||||||
|
new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {
|
||||||
|
displayClass: 'olControlDrawFeaturePolygon',
|
||||||
|
handlerOptions: {citeCompliant: this.citeCompliant}
|
||||||
|
})
|
||||||
];
|
];
|
||||||
this.addControls(controls);
|
this.addControls(controls);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
|
|||||||
* feature.
|
* feature.
|
||||||
*/
|
*/
|
||||||
createFeature: function(pixel) {
|
createFeature: function(pixel) {
|
||||||
var lonlat = this.map.getLonLatFromPixel(pixel);
|
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
|
||||||
var geometry = new OpenLayers.Geometry.Point(
|
var geometry = new OpenLayers.Geometry.Point(
|
||||||
lonlat.lon, lonlat.lat
|
lonlat.lon, lonlat.lat
|
||||||
);
|
);
|
||||||
@@ -165,7 +165,7 @@ OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
|
|||||||
*/
|
*/
|
||||||
addPoint: function(pixel) {
|
addPoint: function(pixel) {
|
||||||
this.layer.removeFeatures([this.point]);
|
this.layer.removeFeatures([this.point]);
|
||||||
var lonlat = this.control.map.getLonLatFromPixel(pixel);
|
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
|
||||||
this.point = new OpenLayers.Feature.Vector(
|
this.point = new OpenLayers.Feature.Vector(
|
||||||
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
|
new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
|
||||||
);
|
);
|
||||||
@@ -326,7 +326,7 @@ OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
|
|||||||
if(!this.line) {
|
if(!this.line) {
|
||||||
this.createFeature(pixel);
|
this.createFeature(pixel);
|
||||||
}
|
}
|
||||||
var lonlat = this.control.map.getLonLatFromPixel(pixel);
|
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
|
||||||
this.point.geometry.x = lonlat.lon;
|
this.point.geometry.x = lonlat.lon;
|
||||||
this.point.geometry.y = lonlat.lat;
|
this.point.geometry.y = lonlat.lat;
|
||||||
this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
|
this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
|
|||||||
*/
|
*/
|
||||||
multi: false,
|
multi: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIProperty: citeCompliant
|
||||||
|
* {Boolean} If set to true, coordinates of features drawn in a map extent
|
||||||
|
* crossing the date line won't exceed the world bounds. Default is false.
|
||||||
|
*/
|
||||||
|
citeCompliant: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property: mouseDown
|
* Property: mouseDown
|
||||||
* {Boolean} The mouse is down
|
* {Boolean} The mouse is down
|
||||||
@@ -164,7 +171,8 @@ OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
|
|||||||
// without this, resolution properties must be specified at the
|
// without this, resolution properties must be specified at the
|
||||||
// map-level for this temporary layer to init its resolutions
|
// map-level for this temporary layer to init its resolutions
|
||||||
// correctly
|
// correctly
|
||||||
calculateInRange: OpenLayers.Function.True
|
calculateInRange: OpenLayers.Function.True,
|
||||||
|
wrapDateLine: this.citeCompliant
|
||||||
}, this.layerOptions);
|
}, this.layerOptions);
|
||||||
this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
|
this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
|
||||||
this.map.addLayer(this.layer);
|
this.map.addLayer(this.layer);
|
||||||
@@ -179,7 +187,7 @@ OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
|
|||||||
* pixel - {<OpenLayers.Pixel>} A pixel location on the map.
|
* pixel - {<OpenLayers.Pixel>} A pixel location on the map.
|
||||||
*/
|
*/
|
||||||
createFeature: function(pixel) {
|
createFeature: function(pixel) {
|
||||||
var lonlat = this.map.getLonLatFromPixel(pixel);
|
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
|
||||||
var geometry = new OpenLayers.Geometry.Point(
|
var geometry = new OpenLayers.Geometry.Point(
|
||||||
lonlat.lon, lonlat.lat
|
lonlat.lon, lonlat.lat
|
||||||
);
|
);
|
||||||
@@ -306,7 +314,7 @@ OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
|
|||||||
if(!this.point) {
|
if(!this.point) {
|
||||||
this.createFeature(pixel);
|
this.createFeature(pixel);
|
||||||
}
|
}
|
||||||
var lonlat = this.map.getLonLatFromPixel(pixel);
|
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
|
||||||
this.point.geometry.x = lonlat.lon;
|
this.point.geometry.x = lonlat.lon;
|
||||||
this.point.geometry.y = lonlat.lat;
|
this.point.geometry.y = lonlat.lat;
|
||||||
this.callback("modify", [this.point.geometry, this.point, false]);
|
this.callback("modify", [this.point.geometry, this.point, false]);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
|
|||||||
* feature.
|
* feature.
|
||||||
*/
|
*/
|
||||||
createFeature: function(pixel) {
|
createFeature: function(pixel) {
|
||||||
var lonlat = this.map.getLonLatFromPixel(pixel);
|
var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
|
||||||
var geometry = new OpenLayers.Geometry.Point(
|
var geometry = new OpenLayers.Geometry.Point(
|
||||||
lonlat.lon, lonlat.lat
|
lonlat.lon, lonlat.lat
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
|
|||||||
*/
|
*/
|
||||||
irregular: false,
|
irregular: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIProperty: citeCompliant
|
||||||
|
* {Boolean} If set to true, coordinates of features drawn in a map extent
|
||||||
|
* crossing the date line won't exceed the world bounds. Default is false.
|
||||||
|
*/
|
||||||
|
citeCompliant: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property: angle
|
* Property: angle
|
||||||
* {Float} The angle from the origin (mouse down) to the current mouse
|
* {Float} The angle from the origin (mouse down) to the current mouse
|
||||||
@@ -174,7 +181,8 @@ OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
|
|||||||
// without this, resolution properties must be specified at the
|
// without this, resolution properties must be specified at the
|
||||||
// map-level for this temporary layer to init its resolutions
|
// map-level for this temporary layer to init its resolutions
|
||||||
// correctly
|
// correctly
|
||||||
calculateInRange: OpenLayers.Function.True
|
calculateInRange: OpenLayers.Function.True,
|
||||||
|
wrapDateLine: this.citeCompliant
|
||||||
}, this.layerOptions);
|
}, this.layerOptions);
|
||||||
this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
|
this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
|
||||||
this.map.addLayer(this.layer);
|
this.map.addLayer(this.layer);
|
||||||
@@ -224,7 +232,7 @@ OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
|
|||||||
*/
|
*/
|
||||||
down: function(evt) {
|
down: function(evt) {
|
||||||
this.fixedRadius = !!(this.radius);
|
this.fixedRadius = !!(this.radius);
|
||||||
var maploc = this.map.getLonLatFromPixel(evt.xy);
|
var maploc = this.layer.getLonLatFromViewPortPx(evt.xy);
|
||||||
this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
|
this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
|
||||||
// create the new polygon
|
// create the new polygon
|
||||||
if(!this.fixedRadius || this.irregular) {
|
if(!this.fixedRadius || this.irregular) {
|
||||||
@@ -250,7 +258,7 @@ OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
|
|||||||
* evt - {Evt} The move event
|
* evt - {Evt} The move event
|
||||||
*/
|
*/
|
||||||
move: function(evt) {
|
move: function(evt) {
|
||||||
var maploc = this.map.getLonLatFromPixel(evt.xy);
|
var maploc = this.layer.getLonLatFromViewPortPx(evt.xy);
|
||||||
var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
|
var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
|
||||||
if(this.irregular) {
|
if(this.irregular) {
|
||||||
var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
|
var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
<script src="../OLLoader.js"></script>
|
<script src="../OLLoader.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function test_ctor_draw(t) {
|
function test_ctor_draw(t) {
|
||||||
t.plan(4);
|
t.plan(5);
|
||||||
var map = new OpenLayers.Map('map');
|
var map = new OpenLayers.Map('map');
|
||||||
var vLayer = new OpenLayers.Layer.Vector();
|
var vLayer = new OpenLayers.Layer.Vector();
|
||||||
map.addLayer(vLayer);
|
map.addLayer(vLayer);
|
||||||
|
|
||||||
var editingToolbar = new OpenLayers.Control.EditingToolbar(vLayer);
|
var editingToolbar = new OpenLayers.Control.EditingToolbar(vLayer, {
|
||||||
|
citeCompliant: "foo"
|
||||||
|
});
|
||||||
map.addControl(editingToolbar);
|
map.addControl(editingToolbar);
|
||||||
|
|
||||||
t.ok(editingToolbar instanceof OpenLayers.Control.EditingToolbar,
|
t.ok(editingToolbar instanceof OpenLayers.Control.EditingToolbar,
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
"First control is active" );
|
"First control is active" );
|
||||||
t.eq(editingToolbar.controls.length, 4,
|
t.eq(editingToolbar.controls.length, 4,
|
||||||
"EditingToolbar contains 4 Controls" );
|
"EditingToolbar contains 4 Controls" );
|
||||||
|
t.eq(editingToolbar.controls[1].handler.citeCompliant, "foo", "citeCompliant option passed to handler correctly")
|
||||||
|
|
||||||
map.destroy();
|
map.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1376,6 +1376,32 @@
|
|||||||
map.destroy();
|
map.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_citeComplaint(t) {
|
||||||
|
t.plan(2);
|
||||||
|
var map = new OpenLayers.Map('map');
|
||||||
|
map.addLayer(new OpenLayers.Layer.OSM());
|
||||||
|
var layer = new OpenLayers.Layer.Vector();
|
||||||
|
map.addLayer(layer);
|
||||||
|
var control = new OpenLayers.Control({});
|
||||||
|
var handler = new OpenLayers.Handler.Path(control, {});
|
||||||
|
control.handler = handler;
|
||||||
|
map.addControl(control);
|
||||||
|
map.zoomToExtent(new OpenLayers.Bounds(-24225034.496992, -11368938.517442, -14206280.326992, -1350184.3474418));
|
||||||
|
handler.activate();
|
||||||
|
handler.createFeature(new OpenLayers.Pixel(100, 50));
|
||||||
|
t.ok(handler.point.geometry.x < 0, "Geometry started correctly when wrapping the dateline using citeCompliant false");
|
||||||
|
control.deactivate();
|
||||||
|
|
||||||
|
handler = new OpenLayers.Handler.Path(control, {}, {citeCompliant: true});
|
||||||
|
control.handler = handler;
|
||||||
|
control.activate();
|
||||||
|
handler.createFeature(new OpenLayers.Pixel(100, 50));
|
||||||
|
t.ok(handler.point.geometry.x > 0, "Geometry started correctly when wrapping the dateline using citeCompliant true");
|
||||||
|
|
||||||
|
map.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1128,6 +1128,31 @@
|
|||||||
map.destroy();
|
map.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_citeComplaint(t) {
|
||||||
|
t.plan(2);
|
||||||
|
var map = new OpenLayers.Map('map');
|
||||||
|
map.addLayer(new OpenLayers.Layer.OSM());
|
||||||
|
var layer = new OpenLayers.Layer.Vector();
|
||||||
|
map.addLayer(layer);
|
||||||
|
var control = new OpenLayers.Control({});
|
||||||
|
var handler = new OpenLayers.Handler.Polygon(control, {});
|
||||||
|
control.handler = handler;
|
||||||
|
map.addControl(control);
|
||||||
|
map.zoomToExtent(new OpenLayers.Bounds(-24225034.496992, -11368938.517442, -14206280.326992, -1350184.3474418));
|
||||||
|
control.activate();
|
||||||
|
handler.createFeature(new OpenLayers.Pixel(100, 50));
|
||||||
|
t.ok(handler.point.geometry.x < 0, "Geometry started correctly when wrapping the dateline using citeCompliant false");
|
||||||
|
control.deactivate();
|
||||||
|
|
||||||
|
var handler = new OpenLayers.Handler.Polygon(control, {}, {citeCompliant: true});
|
||||||
|
control.handler = handler;
|
||||||
|
control.activate();
|
||||||
|
handler.createFeature(new OpenLayers.Pixel(100, 50));
|
||||||
|
t.ok(handler.point.geometry.x > 0, "Geometry started correctly when wrapping the dateline using citeCompliant true");
|
||||||
|
|
||||||
|
map.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -149,9 +149,6 @@
|
|||||||
function test_Handler_RegularPolygon_irregular(t) {
|
function test_Handler_RegularPolygon_irregular(t) {
|
||||||
t.plan(4);
|
t.plan(4);
|
||||||
var map = {
|
var map = {
|
||||||
getLonLatFromPixel: function(px) {
|
|
||||||
return {lon: px.x, lat: px.y};
|
|
||||||
},
|
|
||||||
getResolution: function() {
|
getResolution: function() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -164,6 +161,9 @@
|
|||||||
t.eq(ring.components[0].y, 10, "correct bottom");
|
t.eq(ring.components[0].y, 10, "correct bottom");
|
||||||
t.eq(ring.components[2].x, 10, "correct left");
|
t.eq(ring.components[2].x, 10, "correct left");
|
||||||
t.eq(ring.components[2].y, 15, "correct top");
|
t.eq(ring.components[2].y, 15, "correct top");
|
||||||
|
},
|
||||||
|
getLonLatFromViewPortPx: function(px) {
|
||||||
|
return {lon: px.x, lat: px.y};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var control = {};
|
var control = {};
|
||||||
@@ -190,11 +190,7 @@
|
|||||||
t.plan(1);
|
t.plan(1);
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
var map = new OpenLayers.Map("map", {
|
var map = new OpenLayers.Map("map");
|
||||||
getLonLatFromPixel: function(px) {
|
|
||||||
return {lon: px.x, lat: px.y};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var control = {"map": map};
|
var control = {"map": map};
|
||||||
|
|
||||||
@@ -211,6 +207,17 @@
|
|||||||
|
|
||||||
var isLeftClick = OpenLayers.Event.isLeftClick;
|
var isLeftClick = OpenLayers.Event.isLeftClick;
|
||||||
OpenLayers.Event.isLeftClick = function() { return true; };
|
OpenLayers.Event.isLeftClick = function() { return true; };
|
||||||
|
handler.layer = {
|
||||||
|
renderer: {
|
||||||
|
clear: OpenLayers.Function.Void
|
||||||
|
},
|
||||||
|
addFeatures: OpenLayers.Function.Void,
|
||||||
|
drawFeature: OpenLayers.Function.Void,
|
||||||
|
destroyFeatures: OpenLayers.Function.Void,
|
||||||
|
getLonLatFromViewPortPx: function() {
|
||||||
|
return xy;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// test
|
// test
|
||||||
map.events.triggerEvent("mousedown", {"xy": xy});
|
map.events.triggerEvent("mousedown", {"xy": xy});
|
||||||
|
|||||||
59
tests/manual/dateline-sketch.html
Normal file
59
tests/manual/dateline-sketch.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<title>OpenLayers: Sketch handlers crossing the dateline</title>
|
||||||
|
<link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="../../examples/style.css" type="text/css">
|
||||||
|
<style type="text/css">
|
||||||
|
#map {
|
||||||
|
height: 512px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="http://maps.google.com/maps/api/js?v=3.5&sensor=false"></script>
|
||||||
|
|
||||||
|
<script src="../../lib/OpenLayers.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
// make map available for easy debugging
|
||||||
|
var map;
|
||||||
|
|
||||||
|
function init(){
|
||||||
|
map = new OpenLayers.Map('map');
|
||||||
|
|
||||||
|
var gmap = new OpenLayers.Layer.Google(
|
||||||
|
"Google Streets",
|
||||||
|
{sphericalMercator: true}
|
||||||
|
);
|
||||||
|
var vector = new OpenLayers.Layer.Vector("Editable Vectors");
|
||||||
|
|
||||||
|
map.addLayers([gmap, vector]);
|
||||||
|
map.addControl(new OpenLayers.Control.EditingToolbar(vector));
|
||||||
|
|
||||||
|
var extent = new OpenLayers.Bounds(-24225034.496992, -11368938.517442, -14206280.326992, -1350184.3474418);
|
||||||
|
map.zoomToExtent(extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="init()">
|
||||||
|
<h1 id="title">OpenLayers sketch handlers crossing the dateline example</h1>
|
||||||
|
|
||||||
|
<div id="tags">
|
||||||
|
international date line, dateline, sketch
|
||||||
|
</div>
|
||||||
|
<p id="shortdesc">
|
||||||
|
Start digitizing a polygon or line
|
||||||
|
on one side of the international dateline, and then cross the dateline
|
||||||
|
whilst digitizing. The feature should behave like digitizing on any
|
||||||
|
other location.
|
||||||
|
</p>
|
||||||
|
<div id="map" class="smallmap"></div>
|
||||||
|
|
||||||
|
<div id="docs">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user