+97
-3
@@ -120,11 +120,84 @@ class Class(Exportable):
|
|||||||
return '%sExport' % self.name
|
return '%sExport' % self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Function(Exportable):
|
||||||
|
|
||||||
|
def __init__(self, name, object_literal, return_type, objects):
|
||||||
|
Exportable.__init__(self, name)
|
||||||
|
self.object_literal = object_literal
|
||||||
|
self.return_type = return_type
|
||||||
|
self.objects = objects
|
||||||
|
|
||||||
|
__repr__ = simplerepr
|
||||||
|
|
||||||
|
def property_object_literal(self, object_literal, prop):
|
||||||
|
prop_object_literal = None
|
||||||
|
types = object_literal.prop_types[prop].split('|')
|
||||||
|
for t in types:
|
||||||
|
if t in self.objects:
|
||||||
|
o = self.objects[t]
|
||||||
|
if isinstance(o, ObjectLiteral):
|
||||||
|
if prop_object_literal:
|
||||||
|
raise RuntimeError('Multiple "literal" types found for '
|
||||||
|
'option %s.%s: %s, %s.' %
|
||||||
|
(object_literal.name, prop,
|
||||||
|
prop_object_literal.name, o.name))
|
||||||
|
prop_object_literal = o
|
||||||
|
return prop_object_literal
|
||||||
|
|
||||||
|
def recursive_export(self, lines, prop, object_literal, local_var=None, depth=1):
|
||||||
|
indent = ' ' * depth
|
||||||
|
if not local_var:
|
||||||
|
local_var = prop.split('.')[-1]
|
||||||
|
lines.append('%s/** @type {%s} */\n' % (indent, object_literal.name))
|
||||||
|
lines.append('%svar %s =\n' % (indent, local_var))
|
||||||
|
lines.append('%s /** @type {%s} */\n' % (indent, object_literal.name))
|
||||||
|
lines.append('%s (%s);\n' % (indent, prop))
|
||||||
|
lines.append('%sif (goog.isDefAndNotNull(%s)) {\n' % (indent, prop))
|
||||||
|
for key in sorted(object_literal.prop_types.keys()):
|
||||||
|
prop_object_literal = self.property_object_literal(object_literal, key)
|
||||||
|
if prop_object_literal:
|
||||||
|
lv = self.recursive_export(lines, '%s.%s' % (prop, key),
|
||||||
|
prop_object_literal, depth=depth + 1)
|
||||||
|
lines.append('%s %s.%s =\n%s %s;\n' %
|
||||||
|
(indent, local_var, key, indent, lv))
|
||||||
|
else:
|
||||||
|
lines.append('%s %s.%s =\n%s %s.%s;\n' %
|
||||||
|
(indent, local_var, key, indent, prop, key))
|
||||||
|
lines.append('%s}\n' % (indent,))
|
||||||
|
return local_var
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
lines = []
|
||||||
|
local_var = 'arg'
|
||||||
|
lines.append('\n\n')
|
||||||
|
lines.append('/**\n')
|
||||||
|
lines.append(' * @param {%s} options Options.\n' % (self.object_literal.extern_name(),))
|
||||||
|
if self.return_type:
|
||||||
|
lines.append(' * @return {%s} Return value.\n' % (self.return_type,))
|
||||||
|
lines.append(' */\n')
|
||||||
|
lines.append('%s = function(options) {\n' % (self.export_name(),))
|
||||||
|
self.recursive_export(lines, 'options', self.object_literal,
|
||||||
|
local_var=local_var)
|
||||||
|
if self.return_type:
|
||||||
|
lines.append(' return %s(%s);\n' % (self.name, local_var))
|
||||||
|
else:
|
||||||
|
lines.append(' %s(arg);\n' % (self.name,))
|
||||||
|
lines.append('};\n')
|
||||||
|
lines.append('goog.exportSymbol(\n')
|
||||||
|
lines.append(' \'%s\',\n' % (self.name,))
|
||||||
|
lines.append(' %s);\n' % (self.export_name(),))
|
||||||
|
return ''.join(lines)
|
||||||
|
|
||||||
|
def export_name(self):
|
||||||
|
return '%sExport' % self.name
|
||||||
|
|
||||||
class ObjectLiteral(Exportable):
|
class ObjectLiteral(Exportable):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name, objects):
|
||||||
Exportable.__init__(self, name)
|
Exportable.__init__(self, name)
|
||||||
self.prop_types = {}
|
self.prop_types = {}
|
||||||
|
self.objects = objects
|
||||||
|
|
||||||
__repr__ = simplerepr
|
__repr__ = simplerepr
|
||||||
|
|
||||||
@@ -138,7 +211,12 @@ class ObjectLiteral(Exportable):
|
|||||||
for prop in sorted(self.prop_types.keys()):
|
for prop in sorted(self.prop_types.keys()):
|
||||||
lines.append('\n\n')
|
lines.append('\n\n')
|
||||||
lines.append('/**\n')
|
lines.append('/**\n')
|
||||||
lines.append(' * @type {%s}\n' % (self.prop_types[prop],))
|
prop_types = self.prop_types[prop].split('|')
|
||||||
|
for i, t in enumerate(prop_types):
|
||||||
|
if t in self.objects and isinstance(self.objects[t], ObjectLiteral):
|
||||||
|
prop_types[i] = self.objects[t].extern_name()
|
||||||
|
prop_types = '|'.join(prop_types)
|
||||||
|
lines.append(' * @type {%s}\n' % (prop_types,))
|
||||||
lines.append(' */\n')
|
lines.append(' */\n')
|
||||||
lines.append('%s.prototype.%s;\n' % (self.extern_name(), prop))
|
lines.append('%s.prototype.%s;\n' % (self.extern_name(), prop))
|
||||||
return ''.join(lines)
|
return ''.join(lines)
|
||||||
@@ -221,7 +299,7 @@ def main(argv):
|
|||||||
name = m.group('name')
|
name = m.group('name')
|
||||||
if name in objects:
|
if name in objects:
|
||||||
raise RuntimeError(line) # Name already defined
|
raise RuntimeError(line) # Name already defined
|
||||||
object_literal = ObjectLiteral(name)
|
object_literal = ObjectLiteral(name, objects)
|
||||||
objects[name] = object_literal
|
objects[name] = object_literal
|
||||||
continue
|
continue
|
||||||
m = re.match(r'\*\s*@property\s*{(?P<type>.*)}\s*(?P<prop>\S+)', line)
|
m = re.match(r'\*\s*@property\s*{(?P<type>.*)}\s*(?P<prop>\S+)', line)
|
||||||
@@ -262,6 +340,22 @@ def main(argv):
|
|||||||
objects[name] = symbol
|
objects[name] = symbol
|
||||||
symbol.props.add(prop)
|
symbol.props.add(prop)
|
||||||
continue
|
continue
|
||||||
|
m = re.match(r'@exportFunction\s+(?P<name>\S+)(?:\s+(?P<object_literal_name>\S+))?(?:\s+(?P<return_type>\S+))?\Z', line)
|
||||||
|
if m:
|
||||||
|
name = m.group('name')
|
||||||
|
if name in objects:
|
||||||
|
raise RuntimeError(line) # Name already defined
|
||||||
|
object_literal_name = m.group('object_literal_name')
|
||||||
|
if object_literal_name not in objects:
|
||||||
|
raise RuntimeError(line)
|
||||||
|
object_literal = objects[object_literal_name]
|
||||||
|
if not isinstance(object_literal, ObjectLiteral):
|
||||||
|
raise RuntimeError(line)
|
||||||
|
return_type = m.group('return_type')
|
||||||
|
function = Function(name, object_literal, return_type, objects)
|
||||||
|
objects[name] = function
|
||||||
|
requires.add(name)
|
||||||
|
continue
|
||||||
m = re.match(r'@exportSymbol\s+(?P<name>\S+)(?:\s+(?P<export_as>\S+))?\Z', line)
|
m = re.match(r'@exportSymbol\s+(?P<name>\S+)(?:\s+(?P<export_as>\S+))?\Z', line)
|
||||||
if m:
|
if m:
|
||||||
name = m.group('name')
|
name = m.group('name')
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.RendererHints');
|
goog.require('ol.RendererHints');
|
||||||
goog.require('ol.View2D');
|
goog.require('ol.View2D');
|
||||||
goog.require('ol.animation');
|
goog.require('ol.animation.bounce');
|
||||||
|
goog.require('ol.animation.pan');
|
||||||
|
goog.require('ol.animation.rotate');
|
||||||
goog.require('ol.easing');
|
goog.require('ol.easing');
|
||||||
goog.require('ol.layer.TileLayer');
|
goog.require('ol.layer.TileLayer');
|
||||||
goog.require('ol.proj');
|
goog.require('ol.proj');
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ ol.inherits(app.RotateNorthControl, ol.control.Control);
|
|||||||
|
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new app.RotateNorthControl()
|
new app.RotateNorthControl()
|
||||||
]),
|
]),
|
||||||
layers: [
|
layers: [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ goog.require('ol.source.MapQuestOpenAerial');
|
|||||||
|
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
interactions: ol.interaction.defaults({}, [
|
interactions: ol.interaction.defaults().extend([
|
||||||
new ol.interaction.DragRotateAndZoom()
|
new ol.interaction.DragRotateAndZoom()
|
||||||
]),
|
]),
|
||||||
layers: [
|
layers: [
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ var layers = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new ol.control.ScaleLine({
|
new ol.control.ScaleLine({
|
||||||
units: ol.control.ScaleLineUnits.DEGREES
|
units: ol.control.ScaleLineUnits.DEGREES
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ goog.require('ol.source.BingMaps');
|
|||||||
|
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new ol.control.FullScreen()
|
new ol.control.FullScreen()
|
||||||
]),
|
]),
|
||||||
interactions: ol.interaction.defaults({}, [
|
interactions: ol.interaction.defaults().extend([
|
||||||
new ol.interaction.DragRotateAndZoom()
|
new ol.interaction.DragRotateAndZoom()
|
||||||
]),
|
]),
|
||||||
layers: [
|
layers: [
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var view = new ol.View2D({
|
|||||||
});
|
});
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new ol.control.FullScreen()
|
new ol.control.FullScreen()
|
||||||
]),
|
]),
|
||||||
layers: [
|
layers: [
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ var mousePositionControl = new ol.control.MousePosition({
|
|||||||
});
|
});
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [mousePositionControl]),
|
controls: ol.control.defaults().extend([mousePositionControl]),
|
||||||
layers: [
|
layers: [
|
||||||
new ol.layer.TileLayer({
|
new ol.layer.TileLayer({
|
||||||
source: new ol.source.OSM()
|
source: new ol.source.OSM()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ goog.require('ol.source.OSM');
|
|||||||
|
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new ol.control.ZoomToExtent({
|
new ol.control.ZoomToExtent({
|
||||||
extent: [
|
extent: [
|
||||||
813079.7791264898,
|
813079.7791264898,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ goog.require('ol.source.OSM');
|
|||||||
var scaleLineControl = new ol.control.ScaleLine();
|
var scaleLineControl = new ol.control.ScaleLine();
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
scaleLineControl
|
scaleLineControl
|
||||||
]),
|
]),
|
||||||
layers: [
|
layers: [
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ var lineStringCollection = ol.geom2.LineStringCollection.pack([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new ol.control.MousePosition({
|
new ol.control.MousePosition({
|
||||||
undefinedHTML: ' '
|
undefinedHTML: ' '
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ var layers = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
controls: ol.control.defaults({}, [
|
controls: ol.control.defaults().extend([
|
||||||
new ol.control.ScaleLine({
|
new ol.control.ScaleLine({
|
||||||
units: ol.control.ScaleLineUnits.METRIC
|
units: ol.control.ScaleLineUnits.METRIC
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@exportSymbol ol.animation.bounce
|
@exportFunction ol.animation.bounce ol.animation.BounceOptions ol.PreRenderFunction
|
||||||
@exportSymbol ol.animation.pan
|
@exportFunction ol.animation.pan ol.animation.PanOptions ol.PreRenderFunction
|
||||||
@exportSymbol ol.animation.rotate
|
@exportFunction ol.animation.rotate ol.animation.RotateOptions ol.PreRenderFunction
|
||||||
@exportSymbol ol.animation.zoom
|
@exportFunction ol.animation.zoom ol.animation.ZoomOptions ol.PreRenderFunction
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,9 @@
|
|||||||
// FIXME works for View2D only
|
// FIXME works for View2D only
|
||||||
|
|
||||||
goog.provide('ol.animation');
|
goog.provide('ol.animation.bounce');
|
||||||
|
goog.provide('ol.animation.pan');
|
||||||
|
goog.provide('ol.animation.rotate');
|
||||||
|
goog.provide('ol.animation.zoom');
|
||||||
|
|
||||||
goog.require('ol.PreRenderFunction');
|
goog.require('ol.PreRenderFunction');
|
||||||
goog.require('ol.ViewHint');
|
goog.require('ol.ViewHint');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@exportSymbol ol.Collection
|
@exportSymbol ol.Collection
|
||||||
@exportProperty ol.Collection.prototype.clear
|
@exportProperty ol.Collection.prototype.clear
|
||||||
|
@exportProperty ol.Collection.prototype.extend
|
||||||
@exportProperty ol.Collection.prototype.forEach
|
@exportProperty ol.Collection.prototype.forEach
|
||||||
@exportProperty ol.Collection.prototype.getAt
|
@exportProperty ol.Collection.prototype.getAt
|
||||||
@exportProperty ol.Collection.prototype.getLength
|
@exportProperty ol.Collection.prototype.getLength
|
||||||
|
|||||||
@@ -86,12 +86,14 @@ ol.Collection.prototype.clear = function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array} arr Array.
|
* @param {Array} arr Array.
|
||||||
|
* @return {ol.Collection} This collection.
|
||||||
*/
|
*/
|
||||||
ol.Collection.prototype.extend = function(arr) {
|
ol.Collection.prototype.extend = function(arr) {
|
||||||
var i, ii;
|
var i, ii;
|
||||||
for (i = 0, ii = arr.length; i < ii; ++i) {
|
for (i = 0, ii = arr.length; i < ii; ++i) {
|
||||||
this.push(arr[i]);
|
this.push(arr[i]);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
@exportSymbol ol.control.defaults ol.control.defaults
|
@exportFunction ol.control.defaults ol.control.DefaultsOptions ol.Collection
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ goog.require('ol.control.Zoom');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.control.DefaultsOptions=} opt_options Defaults options.
|
* @param {ol.control.DefaultsOptions=} opt_options Defaults options.
|
||||||
* @param {Array.<ol.control.Control>=} opt_controls Additional controls.
|
|
||||||
* @return {ol.Collection} Controls.
|
* @return {ol.Collection} Controls.
|
||||||
*/
|
*/
|
||||||
ol.control.defaults = function(opt_options, opt_controls) {
|
ol.control.defaults = function(opt_options) {
|
||||||
|
|
||||||
var options = goog.isDef(opt_options) ? opt_options : {};
|
var options = goog.isDef(opt_options) ? opt_options : {};
|
||||||
|
|
||||||
@@ -36,15 +35,11 @@ ol.control.defaults = function(opt_options, opt_controls) {
|
|||||||
var zoomControl = goog.isDef(options.zoom) ?
|
var zoomControl = goog.isDef(options.zoom) ?
|
||||||
options.zoom : true;
|
options.zoom : true;
|
||||||
if (zoomControl) {
|
if (zoomControl) {
|
||||||
var zoomControlOptions = goog.isDef(options.zoomControlOptions) ?
|
var zoomControlOptions = goog.isDef(options.zoomOptions) ?
|
||||||
options.zoomControlOptions : undefined;
|
options.zoomOptions : undefined;
|
||||||
controls.push(new ol.control.Zoom(zoomControlOptions));
|
controls.push(new ol.control.Zoom(zoomControlOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (goog.isDef(opt_controls)) {
|
|
||||||
controls.extend(opt_controls);
|
|
||||||
}
|
|
||||||
|
|
||||||
return controls;
|
return controls;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ goog.require('goog.dom');
|
|||||||
goog.require('goog.dom.TagName');
|
goog.require('goog.dom.TagName');
|
||||||
goog.require('goog.events');
|
goog.require('goog.events');
|
||||||
goog.require('goog.events.EventType');
|
goog.require('goog.events.EventType');
|
||||||
goog.require('ol.animation');
|
goog.require('ol.animation.zoom');
|
||||||
goog.require('ol.control.Control');
|
goog.require('ol.control.Control');
|
||||||
goog.require('ol.css');
|
goog.require('ol.css');
|
||||||
goog.require('ol.easing');
|
goog.require('ol.easing');
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ goog.require('goog.fx.Dragger.EventType');
|
|||||||
goog.require('goog.math');
|
goog.require('goog.math');
|
||||||
goog.require('goog.math.Rect');
|
goog.require('goog.math.Rect');
|
||||||
goog.require('goog.style');
|
goog.require('goog.style');
|
||||||
goog.require('ol.animation');
|
goog.require('ol.animation.zoom');
|
||||||
goog.require('ol.control.Control');
|
goog.require('ol.control.Control');
|
||||||
goog.require('ol.css');
|
goog.require('ol.css');
|
||||||
goog.require('ol.easing');
|
goog.require('ol.easing');
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
goog.provide('ol.interaction.Interaction');
|
goog.provide('ol.interaction.Interaction');
|
||||||
|
|
||||||
goog.require('ol.MapBrowserEvent');
|
goog.require('ol.MapBrowserEvent');
|
||||||
goog.require('ol.animation');
|
goog.require('ol.animation.pan');
|
||||||
|
goog.require('ol.animation.rotate');
|
||||||
|
goog.require('ol.animation.zoom');
|
||||||
goog.require('ol.easing');
|
goog.require('ol.easing');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
@exportSymbol ol.interaction.defaults ol.interaction.defaults
|
@exportFunction ol.interaction.defaults ol.interaction.DefaultsOptions ol.Collection
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ goog.require('ol.interaction.DoubleClickZoom');
|
|||||||
goog.require('ol.interaction.DragPan');
|
goog.require('ol.interaction.DragPan');
|
||||||
goog.require('ol.interaction.DragRotate');
|
goog.require('ol.interaction.DragRotate');
|
||||||
goog.require('ol.interaction.DragZoom');
|
goog.require('ol.interaction.DragZoom');
|
||||||
goog.require('ol.interaction.Interaction');
|
|
||||||
goog.require('ol.interaction.KeyboardPan');
|
goog.require('ol.interaction.KeyboardPan');
|
||||||
goog.require('ol.interaction.KeyboardZoom');
|
goog.require('ol.interaction.KeyboardZoom');
|
||||||
goog.require('ol.interaction.MouseWheelZoom');
|
goog.require('ol.interaction.MouseWheelZoom');
|
||||||
@@ -17,11 +16,9 @@ goog.require('ol.interaction.TouchZoom');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.interaction.DefaultsOptions=} opt_options Defaults options.
|
* @param {ol.interaction.DefaultsOptions=} opt_options Defaults options.
|
||||||
* @param {Array.<ol.interaction.Interaction>=} opt_interactions Additional
|
|
||||||
* interactions.
|
|
||||||
* @return {ol.Collection} Interactions.
|
* @return {ol.Collection} Interactions.
|
||||||
*/
|
*/
|
||||||
ol.interaction.defaults = function(opt_options, opt_interactions) {
|
ol.interaction.defaults = function(opt_options) {
|
||||||
|
|
||||||
var options = goog.isDef(opt_options) ? opt_options : {};
|
var options = goog.isDef(opt_options) ? opt_options : {};
|
||||||
|
|
||||||
@@ -92,10 +89,6 @@ ol.interaction.defaults = function(opt_options, opt_interactions) {
|
|||||||
interactions.push(new ol.interaction.DragZoom());
|
interactions.push(new ol.interaction.DragZoom());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (goog.isDef(opt_interactions)) {
|
|
||||||
interactions.extend(opt_interactions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return interactions;
|
return interactions;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ goog.provide('ol.Kinetic');
|
|||||||
|
|
||||||
goog.require('ol.Coordinate');
|
goog.require('ol.Coordinate');
|
||||||
goog.require('ol.PreRenderFunction');
|
goog.require('ol.PreRenderFunction');
|
||||||
goog.require('ol.animation');
|
goog.require('ol.animation.pan');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user