Merge pull request #128 from Nitwel/improvements
Add search to edit menu and placeholder option for input
This commit is contained in:
commit
50bc612ebc
|
@ -4,14 +4,24 @@ class_name Input3D
|
||||||
|
|
||||||
var text_handler = preload ("res://content/ui/components/input/text_handler.gd").new()
|
var text_handler = preload ("res://content/ui/components/input/text_handler.gd").new()
|
||||||
|
|
||||||
|
signal on_text_changed(text: String)
|
||||||
|
|
||||||
@onready var caret: MeshInstance3D = $Body/Label/Caret
|
@onready var caret: MeshInstance3D = $Body/Label/Caret
|
||||||
@onready var panel: Panel3D = $Body/Panel3D
|
@onready var panel: Panel3D = $Body/Panel3D
|
||||||
@onready var body: StaticBody3D = $Body
|
@onready var body: StaticBody3D = $Body
|
||||||
@onready var collision: CollisionShape3D = $Body/Collision
|
@onready var collision: CollisionShape3D = $Body/Collision
|
||||||
@onready var animation: AnimationPlayer = $AnimationPlayer
|
@onready var animation: AnimationPlayer = $AnimationPlayer
|
||||||
@onready var label: Label3D = $Body/Label
|
@onready var label: Label3D = $Body/Label
|
||||||
|
@onready var placeholder_label: Label3D = $Body/Placeholder
|
||||||
|
|
||||||
@export var text: String:
|
@export var placeholder: String = "":
|
||||||
|
set(value):
|
||||||
|
placeholder = value
|
||||||
|
if !is_inside_tree(): return
|
||||||
|
|
||||||
|
placeholder_label.text = placeholder
|
||||||
|
|
||||||
|
@export var text: String = "":
|
||||||
set(value):
|
set(value):
|
||||||
text = value
|
text = value
|
||||||
if !is_inside_tree(): return
|
if !is_inside_tree(): return
|
||||||
|
@ -19,6 +29,7 @@ var text_handler = preload ("res://content/ui/components/input/text_handler.gd")
|
||||||
var focused = Engine.is_editor_hint() == false&&EventSystem.is_focused(self) == false
|
var focused = Engine.is_editor_hint() == false&&EventSystem.is_focused(self) == false
|
||||||
text_handler.set_text(text, focused)
|
text_handler.set_text(text, focused)
|
||||||
label.text = text_handler.get_display_text()
|
label.text = text_handler.get_display_text()
|
||||||
|
_update_placeholder()
|
||||||
|
|
||||||
@export var disabled: bool = false:
|
@export var disabled: bool = false:
|
||||||
set(value):
|
set(value):
|
||||||
|
@ -41,7 +52,7 @@ var input_plane = Plane(Vector3.UP, Vector3.ZERO)
|
||||||
func _ready():
|
func _ready():
|
||||||
text_handler.label = label
|
text_handler.label = label
|
||||||
|
|
||||||
Update.props(self, ["text", "disabled", "width"])
|
Update.props(self, ["text", "disabled", "width", "placeholder"])
|
||||||
_update()
|
_update()
|
||||||
|
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
|
@ -54,6 +65,7 @@ func _ready():
|
||||||
text=EventKey.key_to_string(event.key, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length())
|
text=EventKey.key_to_string(event.key, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length())
|
||||||
caret.position.x=text_handler.get_caret_position()
|
caret.position.x=text_handler.get_caret_position()
|
||||||
label.text=text_handler.get_display_text()
|
label.text=text_handler.get_display_text()
|
||||||
|
on_text_changed.emit(text)
|
||||||
)
|
)
|
||||||
|
|
||||||
func _input(event):
|
func _input(event):
|
||||||
|
@ -68,6 +80,7 @@ func _input(event):
|
||||||
if keyboard_input:
|
if keyboard_input:
|
||||||
text = EventKey.key_to_string(event.keycode, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length())
|
text = EventKey.key_to_string(event.keycode, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length())
|
||||||
caret.position.x = text_handler.get_caret_position()
|
caret.position.x = text_handler.get_caret_position()
|
||||||
|
on_text_changed.emit(text)
|
||||||
|
|
||||||
func _process(_delta):
|
func _process(_delta):
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
|
@ -122,6 +135,7 @@ func _on_focus_in(_event):
|
||||||
caret.visible = true
|
caret.visible = true
|
||||||
panel.active = true
|
panel.active = true
|
||||||
animation.play("blink")
|
animation.play("blink")
|
||||||
|
_update_placeholder()
|
||||||
|
|
||||||
func update_caret_position(event):
|
func update_caret_position(event):
|
||||||
var ray_pos = event.ray.global_position
|
var ray_pos = event.ray.global_position
|
||||||
|
@ -147,6 +161,7 @@ func _on_focus_out(_event):
|
||||||
animation.stop()
|
animation.stop()
|
||||||
caret.visible = false
|
caret.visible = false
|
||||||
panel.active = false
|
panel.active = false
|
||||||
|
_update_placeholder()
|
||||||
|
|
||||||
func _draw_debug_text_gaps():
|
func _draw_debug_text_gaps():
|
||||||
if text_handler.gap_offsets == null:
|
if text_handler.gap_offsets == null:
|
||||||
|
@ -160,6 +175,12 @@ func _draw_debug_text_gaps():
|
||||||
Color(1, 0, 0) if i != text_handler.overflow_index else Color(0, 1, 0)
|
Color(1, 0, 0) if i != text_handler.overflow_index else Color(0, 1, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func _update_placeholder():
|
||||||
|
var show_placeholder = text == ""&&panel.active == false
|
||||||
|
|
||||||
|
placeholder_label.visible = show_placeholder
|
||||||
|
label.visible = !show_placeholder
|
||||||
|
|
||||||
func _update():
|
func _update():
|
||||||
text_handler.width = size.x
|
text_handler.width = size.x
|
||||||
panel.size = Vector2(size.x, size.y)
|
panel.size = Vector2(size.x, size.y)
|
||||||
|
@ -167,4 +188,5 @@ func _update():
|
||||||
collision.shape.size = size
|
collision.shape.size = size
|
||||||
label.position = Vector3( - size.x / 2 + 0.002, 0, size.z / 2)
|
label.position = Vector3( - size.x / 2 + 0.002, 0, size.z / 2)
|
||||||
label.text = text_handler.get_display_text()
|
label.text = text_handler.get_display_text()
|
||||||
|
placeholder_label.position = label.position
|
||||||
body.position = Vector3(0, 0, size.z / 2)
|
body.position = Vector3(0, 0, size.z / 2)
|
|
@ -5,7 +5,7 @@
|
||||||
[ext_resource type="FontVariation" uid="uid://d2ofyimg5s65q" path="res://assets/fonts/ui_font_500.tres" id="3_ij5fh"]
|
[ext_resource type="FontVariation" uid="uid://d2ofyimg5s65q" path="res://assets/fonts/ui_font_500.tres" id="3_ij5fh"]
|
||||||
[ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="3_nl02b"]
|
[ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="3_nl02b"]
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_exbug"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_md6kx"]
|
||||||
resource_local_to_scene = true
|
resource_local_to_scene = true
|
||||||
render_priority = 10
|
render_priority = 10
|
||||||
shader = ExtResource("3_nl02b")
|
shader = ExtResource("3_nl02b")
|
||||||
|
@ -20,7 +20,7 @@ shader_parameter/corner_radius = 0.2
|
||||||
shader_parameter/roughness = 0.3
|
shader_parameter/roughness = 0.3
|
||||||
shader_parameter/grain_amount = 0.02
|
shader_parameter/grain_amount = 0.02
|
||||||
|
|
||||||
[sub_resource type="QuadMesh" id="QuadMesh_lemta"]
|
[sub_resource type="QuadMesh" id="QuadMesh_btoa3"]
|
||||||
size = Vector2(0.15, 0.03)
|
size = Vector2(0.15, 0.03)
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_x4yp8"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_x4yp8"]
|
||||||
|
@ -72,6 +72,7 @@ _data = {
|
||||||
|
|
||||||
[node name="Input" type="Node3D" groups=["ui_focus"]]
|
[node name="Input" type="Node3D" groups=["ui_focus"]]
|
||||||
script = ExtResource("1_uml3t")
|
script = ExtResource("1_uml3t")
|
||||||
|
placeholder = "Placeholder"
|
||||||
text = "Example Text"
|
text = "Example Text"
|
||||||
size = Vector3(0.15, 0.03, 0.01)
|
size = Vector3(0.15, 0.03, 0.01)
|
||||||
|
|
||||||
|
@ -82,8 +83,8 @@ collision_mask = 6
|
||||||
|
|
||||||
[node name="Panel3D" type="MeshInstance3D" parent="Body"]
|
[node name="Panel3D" type="MeshInstance3D" parent="Body"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.005)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.005)
|
||||||
material_override = SubResource("ShaderMaterial_exbug")
|
material_override = SubResource("ShaderMaterial_md6kx")
|
||||||
mesh = SubResource("QuadMesh_lemta")
|
mesh = SubResource("QuadMesh_btoa3")
|
||||||
script = ExtResource("3_3bvrj")
|
script = ExtResource("3_3bvrj")
|
||||||
size = Vector2(0.15, 0.03)
|
size = Vector2(0.15, 0.03)
|
||||||
|
|
||||||
|
@ -108,6 +109,20 @@ material_override = SubResource("StandardMaterial3D_x1ra7")
|
||||||
mesh = SubResource("BoxMesh_2736g")
|
mesh = SubResource("BoxMesh_2736g")
|
||||||
skeleton = NodePath("../..")
|
skeleton = NodePath("../..")
|
||||||
|
|
||||||
|
[node name="Placeholder" type="Label3D" parent="Body"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.073, 0, 0.005)
|
||||||
|
visible = false
|
||||||
|
pixel_size = 0.001
|
||||||
|
double_sided = false
|
||||||
|
render_priority = 30
|
||||||
|
outline_render_priority = 29
|
||||||
|
modulate = Color(0.470076, 0.470076, 0.470076, 1)
|
||||||
|
text = "Placeholder"
|
||||||
|
font = ExtResource("3_ij5fh")
|
||||||
|
font_size = 18
|
||||||
|
outline_size = 0
|
||||||
|
horizontal_alignment = 0
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
root_node = NodePath("../Body")
|
root_node = NodePath("../Body")
|
||||||
libraries = {
|
libraries = {
|
||||||
|
|
|
@ -6,26 +6,40 @@ const ButtonScene = preload ("res://content/ui/components/button/button.tscn")
|
||||||
|
|
||||||
@onready var grid_container = $GridContainer3D
|
@onready var grid_container = $GridContainer3D
|
||||||
@onready var pagination = $Pagination3D
|
@onready var pagination = $Pagination3D
|
||||||
|
@onready var search_input: Input3D = $Input
|
||||||
|
|
||||||
var page = R.state(0)
|
var page = R.state(0)
|
||||||
var page_size = 28.0
|
var page_size = 28.0
|
||||||
|
var search = R.state("")
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
var devices = R.computed(func(_arg):
|
||||||
var pages = R.computed(func(_arg):
|
|
||||||
var devices=Store.devices.state.devices
|
var devices=Store.devices.state.devices
|
||||||
|
|
||||||
return ceil(devices.size() / page_size)
|
if search.value != "":
|
||||||
|
return devices.filter(func(device):
|
||||||
|
var info=device.values()[0]
|
||||||
|
return info["name"].to_lower().find(search.value.to_lower()) != - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
return devices
|
||||||
|
)
|
||||||
|
|
||||||
|
var pages = R.computed(func(_arg):
|
||||||
|
return ceil(devices.value.size() / page_size)
|
||||||
)
|
)
|
||||||
|
|
||||||
var visible_devices = R.computed(func(_arg):
|
var visible_devices = R.computed(func(_arg):
|
||||||
var devices=Store.devices.state.devices
|
return devices.value.slice(page.value * page_size, page.value * page_size + page_size)
|
||||||
|
|
||||||
return devices.slice(page.value * page_size, page.value * page_size + page_size)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
R.bind(pagination, "pages", pages)
|
R.bind(pagination, "pages", pages)
|
||||||
R.bind(pagination, "page", page, pagination.on_page_changed)
|
R.bind(pagination, "page", page, pagination.on_page_changed)
|
||||||
|
R.bind(search_input, "text", search, search_input.on_text_changed)
|
||||||
|
|
||||||
|
search_input.on_text_changed.connect(func(_arg):
|
||||||
|
page.value=0
|
||||||
|
)
|
||||||
|
|
||||||
R.effect(func(_arg):
|
R.effect(func(_arg):
|
||||||
for child in grid_container.get_children():
|
for child in grid_container.get_children():
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
[gd_scene load_steps=11 format=3 uid="uid://crrb0l3ekuotj"]
|
[gd_scene load_steps=13 format=3 uid="uid://crrb0l3ekuotj"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://content/ui/menu/edit/edit_menu.gd" id="1_34cbn"]
|
[ext_resource type="Script" path="res://content/ui/menu/edit/edit_menu.gd" id="1_34cbn"]
|
||||||
[ext_resource type="Script" path="res://content/ui/menu/edit/devices.gd" id="2_rkvf4"]
|
[ext_resource type="Script" path="res://content/ui/menu/edit/devices.gd" id="2_rkvf4"]
|
||||||
[ext_resource type="Script" path="res://content/ui/components/grid_container/grid_container.gd" id="3_0xvyw"]
|
[ext_resource type="Script" path="res://content/ui/components/grid_container/grid_container.gd" id="3_0xvyw"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bef3gamrm6at2" path="res://content/ui/components/pagination/pagination.tscn" id="4_4jiu6"]
|
[ext_resource type="PackedScene" uid="uid://bef3gamrm6at2" path="res://content/ui/components/pagination/pagination.tscn" id="4_4jiu6"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="4_lpv7m"]
|
[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="4_lpv7m"]
|
||||||
|
[ext_resource type="FontVariation" uid="uid://sshfnckriqxn" path="res://assets/icons/icons.tres" id="4_t5uud"]
|
||||||
[ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="4_xunmy"]
|
[ext_resource type="Shader" path="res://content/ui/components/panel/glass.gdshader" id="4_xunmy"]
|
||||||
[ext_resource type="Script" path="res://content/ui/menu/edit/entities.gd" id="5_t34xe"]
|
[ext_resource type="Script" path="res://content/ui/menu/edit/entities.gd" id="5_t34xe"]
|
||||||
[ext_resource type="Script" path="res://content/ui/components/flex_container/flex_container.gd" id="6_cr6p6"]
|
[ext_resource type="Script" path="res://content/ui/components/flex_container/flex_container.gd" id="6_cr6p6"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://blrhy2uccrdn4" path="res://content/ui/components/input/input.tscn" id="6_evh58"]
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hstwo"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_hstwo"]
|
||||||
render_priority = -3
|
render_priority = -3
|
||||||
|
@ -55,6 +57,24 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.02, -0.29, 0)
|
||||||
pages = 10
|
pages = 10
|
||||||
size = Vector3(9.92, 0.03, 0.01)
|
size = Vector3(9.92, 0.03, 0.01)
|
||||||
|
|
||||||
|
[node name="LabelSearch" type="Label3D" parent="Devices"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.38, -0.03, 0)
|
||||||
|
pixel_size = 0.001
|
||||||
|
double_sided = false
|
||||||
|
render_priority = 15
|
||||||
|
outline_render_priority = 14
|
||||||
|
text = "search"
|
||||||
|
font = ExtResource("4_t5uud")
|
||||||
|
font_size = 24
|
||||||
|
outline_size = 0
|
||||||
|
horizontal_alignment = 0
|
||||||
|
|
||||||
|
[node name="Input" parent="Devices" instance=ExtResource("6_evh58")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, -0.03, 0)
|
||||||
|
placeholder = "Search..."
|
||||||
|
text = ""
|
||||||
|
size = Vector3(0.14, 0.03, 0.01)
|
||||||
|
|
||||||
[node name="Entities" type="Node3D" parent="."]
|
[node name="Entities" type="Node3D" parent="."]
|
||||||
script = ExtResource("5_t34xe")
|
script = ExtResource("5_t34xe")
|
||||||
|
|
||||||
|
@ -87,9 +107,26 @@ label = "arrow_back"
|
||||||
icon = true
|
icon = true
|
||||||
size = Vector3(0.03, 0.03, 0.01)
|
size = Vector3(0.03, 0.03, 0.01)
|
||||||
|
|
||||||
|
[node name="LabelSearch" type="Label3D" parent="Entities"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.38, -0.03, 0)
|
||||||
|
pixel_size = 0.001
|
||||||
|
double_sided = false
|
||||||
|
render_priority = 15
|
||||||
|
outline_render_priority = 14
|
||||||
|
text = "search"
|
||||||
|
font = ExtResource("4_t5uud")
|
||||||
|
font_size = 24
|
||||||
|
outline_size = 0
|
||||||
|
horizontal_alignment = 0
|
||||||
|
|
||||||
|
[node name="Input" parent="Entities" instance=ExtResource("6_evh58")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, -0.03, 0)
|
||||||
|
placeholder = "Search..."
|
||||||
|
text = ""
|
||||||
|
size = Vector3(0.14, 0.03, 0.01)
|
||||||
|
|
||||||
[node name="Background" type="MeshInstance3D" parent="."]
|
[node name="Background" type="MeshInstance3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 1.06581e-14, 0, -1.06581e-14, 1, 0.21, -0.16, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 1.06581e-14, 0, -1.06581e-14, 1, 0.21, -0.16, 0)
|
||||||
visible = false
|
|
||||||
material_override = SubResource("ShaderMaterial_hstwo")
|
material_override = SubResource("ShaderMaterial_hstwo")
|
||||||
mesh = SubResource("QuadMesh_4pj6f")
|
mesh = SubResource("QuadMesh_4pj6f")
|
||||||
skeleton = NodePath("../..")
|
skeleton = NodePath("../..")
|
||||||
|
|
|
@ -8,20 +8,30 @@ const EntityScene = preload ("entity.tscn")
|
||||||
@onready var entity_container = $FlexContainer3D
|
@onready var entity_container = $FlexContainer3D
|
||||||
@onready var pagination = $Pagination3D
|
@onready var pagination = $Pagination3D
|
||||||
@onready var back_button = $Button
|
@onready var back_button = $Button
|
||||||
|
@onready var search_input: Input3D = $Input
|
||||||
|
|
||||||
var page = R.state(0)
|
var page = R.state(0)
|
||||||
var page_size = 5.0
|
var page_size = 5.0
|
||||||
var selected_device = R.state(null)
|
var selected_device = R.state(null)
|
||||||
|
var search = R.state("")
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
var entities = R.computed(func(_arg):
|
var entities = R.computed(func(_arg):
|
||||||
var devices=Store.devices.state.devices
|
var devices=Store.devices.state.devices
|
||||||
|
|
||||||
|
var entities=[]
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
if device.keys()[0] == selected_device.value:
|
if device.keys()[0] == selected_device.value:
|
||||||
return device.values()[0]["entities"]
|
entities=device.values()[0]["entities"]
|
||||||
|
break
|
||||||
|
|
||||||
return []
|
if search.value != "":
|
||||||
|
return entities.filter(func(entity):
|
||||||
|
return entity.to_lower().find(search.value.to_lower()) != - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
return entities
|
||||||
)
|
)
|
||||||
|
|
||||||
var pages = R.computed(func(_arg):
|
var pages = R.computed(func(_arg):
|
||||||
|
@ -34,6 +44,11 @@ func _ready():
|
||||||
|
|
||||||
R.bind(pagination, "pages", pages)
|
R.bind(pagination, "pages", pages)
|
||||||
R.bind(pagination, "page", page, pagination.on_page_changed)
|
R.bind(pagination, "page", page, pagination.on_page_changed)
|
||||||
|
R.bind(search_input, "text", search, search_input.on_text_changed)
|
||||||
|
|
||||||
|
search_input.on_text_changed.connect(func(_arg):
|
||||||
|
page.value=0
|
||||||
|
)
|
||||||
|
|
||||||
back_button.on_button_up.connect(func():
|
back_button.on_button_up.connect(func():
|
||||||
on_back.emit()
|
on_back.emit()
|
||||||
|
|
|
@ -282,7 +282,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, 0.06, 0)
|
||||||
gizmo_extents = 0.02
|
gizmo_extents = 0.02
|
||||||
|
|
||||||
[node name="KeyboardPlace" type="Marker3D" parent="AnimationContainer"]
|
[node name="KeyboardPlace" type="Marker3D" parent="AnimationContainer"]
|
||||||
transform = Transform3D(0.5, 0, 0, 0, 0.353553, 0.353553, 0, -0.353553, 0.353553, 0.2, -0.38, 0.16)
|
transform = Transform3D(0.5, 0, 0, 0, 0.433012, 0.25, 0, -0.25, 0.433012, 0.2, -0.38, 0.11)
|
||||||
gizmo_extents = 0.02
|
gizmo_extents = 0.02
|
||||||
|
|
||||||
[node name="ImmersiveHomePanels" type="MeshInstance3D" parent="."]
|
[node name="ImmersiveHomePanels" type="MeshInstance3D" parent="."]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user