mirror of
https://github.com/lxsang/antos-frontend.git
synced 2024-12-27 17:58:22 +01:00
add more tag
This commit is contained in:
parent
ffdb5b5f8a
commit
2a990491d9
40
Makefile
40
Makefile
@ -11,22 +11,25 @@ ifeq ($(UNAME_S),Darwin)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
coffees= src/core/core.coffee\
|
coffees= src/core/core.coffee \
|
||||||
src/core/api.coffee\
|
src/core/api.coffee \
|
||||||
src/core/settings.coffee\
|
src/core/settings.coffee \
|
||||||
src/core/handles/RemoteHandle.coffee\
|
src/core/handles/RemoteHandle.coffee \
|
||||||
src/core/Announcerment.coffee\
|
src/core/Announcerment.coffee \
|
||||||
src/core/vfs.coffee\
|
src/core/vfs.coffee \
|
||||||
src/core/vfs/GoogleDriveHandle.coffee\
|
src/core/vfs/GoogleDriveHandle.coffee \
|
||||||
src/core/db.coffee\
|
src/core/db.coffee \
|
||||||
src/core/gui.coffee\
|
src/core/gui.coffee \
|
||||||
src/core/BaseModel.coffee\
|
src/core/BaseModel.coffee \
|
||||||
src/core/BaseApplication.coffee\
|
src/core/BaseApplication.coffee \
|
||||||
src/core/BaseService.coffee\
|
src/core/BaseService.coffee \
|
||||||
src/core/BaseEvent.coffee\
|
src/core/BaseEvent.coffee \
|
||||||
src/core/BaseDialog.coffee\
|
src/core/BaseDialog.coffee \
|
||||||
src/core/tags/tag.coffee\
|
src/core/tags/tag.coffee \
|
||||||
src/core/tags/WindowTag.coffee\
|
src/core/tags/WindowTag.coffee \
|
||||||
|
src/core/tags/TileLayoutTags.coffee \
|
||||||
|
src/core/tags/ResizerTag.coffee \
|
||||||
|
src/core/tags/LabelTag.coffee \
|
||||||
src/antos.coffee
|
src/antos.coffee
|
||||||
|
|
||||||
|
|
||||||
@ -145,4 +148,7 @@ uglify:
|
|||||||
release: main uglify
|
release: main uglify
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILDDIR)/{resources,scripts,packages,index.html}
|
rm -rf $(BUILDDIR)/resources
|
||||||
|
rm -rf $(BUILDDIR)/scripts
|
||||||
|
rm -rf $(BUILDDIR)/packages
|
||||||
|
rm -rf $(BUILDDIR)/index.html
|
||||||
|
18
src/core/tags/ButtonTag.coffee
Normal file
18
src/core/tags/ButtonTag.coffee
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class ButtonTag extends Ant.OS.GUI.BaseTag
|
||||||
|
constructor: (r, o) ->
|
||||||
|
super r, o
|
||||||
|
@setopt "color", undefined
|
||||||
|
@setopt "icon", undefined
|
||||||
|
@setopt "iconclass", undefined
|
||||||
|
@setopt "text", ""
|
||||||
|
@setopt "enable", true
|
||||||
|
|
||||||
|
layout: () ->
|
||||||
|
{
|
||||||
|
el: "Button", ref: "button", children: [
|
||||||
|
{ el: "afx-label", ref: "label" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Ant.OS.GUI.define "afx-button", ButtonTag
|
47
src/core/tags/LabelTag.coffee
Normal file
47
src/core/tags/LabelTag.coffee
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
class LabelTag extends Ant.OS.GUI.BaseTag
|
||||||
|
constructor: (r, o) ->
|
||||||
|
super r, o
|
||||||
|
@setopt "color", undefined
|
||||||
|
@setopt "icon", undefined
|
||||||
|
@setopt "iconclass", undefined
|
||||||
|
@setopt "text", ""
|
||||||
|
|
||||||
|
on_color_changed: (v) ->
|
||||||
|
return unless v
|
||||||
|
$(@refs.container).css "color", v
|
||||||
|
|
||||||
|
on_icon_changed: (v) ->
|
||||||
|
$(@refs.i).attr "style", ""
|
||||||
|
if v
|
||||||
|
$(@refs.i)
|
||||||
|
.css "background", "url(#{Ant.OS.API.handle.get}/#{v})"
|
||||||
|
.css "background-size", "100% 100%"
|
||||||
|
.css "background-repeat", "no-repeat"
|
||||||
|
$(@refs.i).show()
|
||||||
|
else
|
||||||
|
$(@refs.i).hide()
|
||||||
|
|
||||||
|
on_iconclass_changed: (v) ->
|
||||||
|
$(@refs.iclass).removeClass()
|
||||||
|
if v
|
||||||
|
$(@refs.iclass).addClass v
|
||||||
|
$(@refs.iclass).show()
|
||||||
|
else
|
||||||
|
$(@refs.iclass).hide()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
on_text_changed: (v) ->
|
||||||
|
$(@refs.text).text v.__() if v
|
||||||
|
|
||||||
|
layout: () ->
|
||||||
|
{
|
||||||
|
el: "span", ref: "container", children: [
|
||||||
|
{ el: "i", ref: "iclass" },
|
||||||
|
{ el: "i", ref: "i", class: "icon-style" },
|
||||||
|
{ el: "i", ref: "text" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Ant.OS.GUI.define "afx-label", LabelTag
|
67
src/core/tags/ResizerTag.coffee
Normal file
67
src/core/tags/ResizerTag.coffee
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
class ResizerTag extends Ant.OS.GUI.BaseTag
|
||||||
|
constructor: (r, o) ->
|
||||||
|
super r, o
|
||||||
|
@dir = "hz"
|
||||||
|
@resizable_el = undefined
|
||||||
|
@parent = $(@root).parent().parent()
|
||||||
|
@minsize = 0
|
||||||
|
|
||||||
|
mount: () ->
|
||||||
|
tagname = $(@parent).prop("tagName")
|
||||||
|
@resizable_el = if $(@root).prev().length is 1 then $(@root).prev()[0] else undefined
|
||||||
|
if tagname is "AFX-HBOX"
|
||||||
|
@dir = "hz"
|
||||||
|
$(@root).css "cursor", "col-resize"
|
||||||
|
if @resizable_el
|
||||||
|
att = $(@resizable_el).attr "min-width"
|
||||||
|
@minsize = parseInt(att) if att
|
||||||
|
else if tagname is "AFX-VBOX"
|
||||||
|
@dir = "ve"
|
||||||
|
$(@root).css "cursor", "row-resize"
|
||||||
|
if @resizable_el
|
||||||
|
att = $(@resizable_el).attr "min-height"
|
||||||
|
@minsize = parseInt(att) if att
|
||||||
|
else
|
||||||
|
@dir = "none"
|
||||||
|
@minsize = 10 if @minsize is 0
|
||||||
|
@draggable()
|
||||||
|
|
||||||
|
draggable: () ->
|
||||||
|
me = @
|
||||||
|
$(@root).css "user-select", "none"
|
||||||
|
$(@root).on "mousedown", (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
$(window).on "mousemove", (evt) ->
|
||||||
|
return unless me.resizable_el
|
||||||
|
if me.dir is "hz"
|
||||||
|
me.horizontalResize evt
|
||||||
|
else if me.dir is "ve"
|
||||||
|
me.verticalResize evt
|
||||||
|
|
||||||
|
$(window).on "mouseup", (evt) ->
|
||||||
|
$(window).unbind "mousemove", null
|
||||||
|
$(window).unbind "mouseup", null
|
||||||
|
|
||||||
|
$(window).unbind "mouseup", null
|
||||||
|
|
||||||
|
horizontalResize: (e) ->
|
||||||
|
return unless @resizable_el
|
||||||
|
offset = $(@resizable_el).offset()
|
||||||
|
w = Math.round(e.clientX - offset.left)
|
||||||
|
w = @minsize if w < @minsize
|
||||||
|
$(@resizable_el).attr "data-width", w.toString()
|
||||||
|
@observable.trigger "calibrate", @resizable_el.aid()
|
||||||
|
|
||||||
|
|
||||||
|
verticalResize: (e) ->
|
||||||
|
return unless @resizable_el
|
||||||
|
offset = $(@resizable_el).offset()
|
||||||
|
h = Math.round(e.clientY - offset.top)
|
||||||
|
h = @minsize if h < @minsize
|
||||||
|
$(@resizable_el).attr "data-height", h.toString()
|
||||||
|
@observable.trigger "calibrate", @resizable_el.aid()
|
||||||
|
|
||||||
|
layout: () ->
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
Ant.OS.GUI.define "afx-resizer", ResizerTag
|
@ -2,10 +2,11 @@ class TileLayoutTag extends Ant.OS.GUI.BaseTag
|
|||||||
constructor: (r, o, @conf) ->
|
constructor: (r, o, @conf) ->
|
||||||
super r, o
|
super r, o
|
||||||
@setopt @conf.opt, "grow"
|
@setopt @conf.opt, "grow"
|
||||||
@mount()
|
|
||||||
|
|
||||||
mount: () ->
|
mount: () ->
|
||||||
|
$(@root).css("display", "block")
|
||||||
$(@refs.yield)
|
$(@refs.yield)
|
||||||
|
.addClass("afx-#{@conf.name}-container")
|
||||||
.css("display", "flex")
|
.css("display", "flex")
|
||||||
.css("flex-direction", @conf.dir)
|
.css("flex-direction", @conf.dir)
|
||||||
.css("width", "100%")
|
.css("width", "100%")
|
||||||
@ -19,7 +20,7 @@ class TileLayoutTag extends Ant.OS.GUI.BaseTag
|
|||||||
|
|
||||||
layout: () ->
|
layout: () ->
|
||||||
{
|
{
|
||||||
el: "div", class: "afx-#{@conf.name}-container", ref: "yield"
|
el: "div", ref: "yield"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -31,6 +32,30 @@ class HBoxTag extends TileLayoutTag
|
|||||||
opt: "data-width"
|
opt: "data-width"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calibrate: () ->
|
||||||
|
auto_width = []
|
||||||
|
ocwidth = 0
|
||||||
|
avaiheight = $(@root).height()
|
||||||
|
avaiWidth = $(@root).width()
|
||||||
|
$(@refs.yield).css "height", "#{avaiheight}px"
|
||||||
|
$(@refs.yield)
|
||||||
|
.children()
|
||||||
|
.each (e) ->
|
||||||
|
dw = $(@).attr "data-width"
|
||||||
|
if dw and dw isnt "grow"
|
||||||
|
dw = Number(dw.slice(0, -1)) * avaiWidth / 100 if dw[dw.length - 1] is "%"
|
||||||
|
$(@).css "width", "#{dw}px"
|
||||||
|
ocwidth += Number dw
|
||||||
|
else
|
||||||
|
$(@).css "flex-grow", "1"
|
||||||
|
auto_width.push(@)
|
||||||
|
|
||||||
|
csize = (avaiWidth - ocwidth) / auto_width.length
|
||||||
|
if csize > 0
|
||||||
|
$.each auto_width, (i, v) ->
|
||||||
|
$(v).css "width", "#{csize}px"
|
||||||
|
@observable.trigger "hboxchange", { id: @aid(), w: avaiWidth, h: avaiheight }
|
||||||
|
|
||||||
|
|
||||||
class VBoxTag extends TileLayoutTag
|
class VBoxTag extends TileLayoutTag
|
||||||
constructor: (r, o) ->
|
constructor: (r, o) ->
|
||||||
@ -39,3 +64,31 @@ class VBoxTag extends TileLayoutTag
|
|||||||
dir: "column",
|
dir: "column",
|
||||||
opt: "data-height"
|
opt: "data-height"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calibrate: () ->
|
||||||
|
auto_height = []
|
||||||
|
ocheight = 0
|
||||||
|
avaiheight = $(@root).height()
|
||||||
|
avaiwidth = $(@root).width()
|
||||||
|
$(@refs.yield).css "height", "#{avaiheight}px"
|
||||||
|
$(@refs.yield)
|
||||||
|
.children()
|
||||||
|
.each (e) ->
|
||||||
|
dh = $(@).attr "data-height"
|
||||||
|
if dh and dh isnt "grow"
|
||||||
|
dh = Number(dh.slice(0, -1)) * avaiheight / 100 if dh[dh.length - 1] is "%"
|
||||||
|
$(@).css "height", "#{dh}px"
|
||||||
|
ocheight += Number(dh)
|
||||||
|
else
|
||||||
|
$(@).css "flex-grow", "1"
|
||||||
|
auto_height.push @
|
||||||
|
|
||||||
|
csize = (avaiheight - ocheight) / auto_height.length
|
||||||
|
if csize > 0
|
||||||
|
$.each auto_height, (i, v) ->
|
||||||
|
$(v).css "height", "#{csize}px"
|
||||||
|
|
||||||
|
@observable.trigger "vboxchange", { id: @aid(), w: avaiwidth, h: avaiheight }
|
||||||
|
|
||||||
|
Ant.OS.GUI.define "afx-hbox", HBoxTag
|
||||||
|
Ant.OS.GUI.define "afx-vbox", VBoxTag
|
@ -12,6 +12,13 @@ class WindowTag extends Ant.OS.GUI.BaseTag
|
|||||||
@history = {}
|
@history = {}
|
||||||
@desktop = $(@get "desktop")
|
@desktop = $(@get "desktop")
|
||||||
@desktop_pos = @desktop.offset()
|
@desktop_pos = @desktop.offset()
|
||||||
|
|
||||||
|
resize: () ->
|
||||||
|
ch = $(@refs["yield"]).height() / $(@refs["yield"]).children().length
|
||||||
|
$(@refs["yield"]).children().each (e) ->
|
||||||
|
$(this).css "height", "#{ch}px"
|
||||||
|
|
||||||
|
mount: () ->
|
||||||
me = @
|
me = @
|
||||||
@root.contextmenuHandle = (e) ->
|
@root.contextmenuHandle = (e) ->
|
||||||
$(@refs["minbt"]).click (e) ->
|
$(@refs["minbt"]).click (e) ->
|
||||||
@ -22,15 +29,6 @@ class WindowTag extends Ant.OS.GUI.BaseTag
|
|||||||
|
|
||||||
$(@refs["closebt"]).click (e) ->
|
$(@refs["closebt"]).click (e) ->
|
||||||
me.observable.trigger("exit")
|
me.observable.trigger("exit")
|
||||||
@mount()
|
|
||||||
|
|
||||||
resize: () ->
|
|
||||||
ch = $(@refs["yield"]).height() / $(@refs["yield"]).children().length
|
|
||||||
$(@refs["yield"]).children().each (e) ->
|
|
||||||
$(this).css "height", "#{ch}px"
|
|
||||||
|
|
||||||
mount: () ->
|
|
||||||
me = @
|
|
||||||
left = ($(@desktop).width() - (@get "width")) / 2
|
left = ($(@desktop).width() - (@get "width")) / 2
|
||||||
top = ($(@desktop).height() - (@get "height")) / 2
|
top = ($(@desktop).height() - (@get "height")) / 2
|
||||||
$(@root)
|
$(@root)
|
||||||
@ -146,7 +144,7 @@ class WindowTag extends Ant.OS.GUI.BaseTag
|
|||||||
.css("width", "#{w}px")
|
.css("width", "#{w}px")
|
||||||
.css("height", "#{h}px")
|
.css("height", "#{h}px")
|
||||||
me.isMaxi = false
|
me.isMaxi = false
|
||||||
me.observable.trigger "resize", { id: me.id(), w: w, h: h }
|
me.observable.trigger "resize", { id: me.aid(), w: w, h: h }
|
||||||
|
|
||||||
$(window).on "mouseup", (e) ->
|
$(window).on "mouseup", (e) ->
|
||||||
$(window).unbind "mousemove", null
|
$(window).unbind "mousemove", null
|
||||||
@ -169,7 +167,7 @@ class WindowTag extends Ant.OS.GUI.BaseTag
|
|||||||
.css("height", "#{h}px")
|
.css("height", "#{h}px")
|
||||||
.css("top", "0")
|
.css("top", "0")
|
||||||
.css("left", "0")
|
.css("left", "0")
|
||||||
@observable.trigger 'resize', { id: @id(), w: w, h: h }
|
@observable.trigger 'resize', { id: @aid(), w: w, h: h }
|
||||||
@isMaxi = true
|
@isMaxi = true
|
||||||
else
|
else
|
||||||
@isMaxi = false
|
@isMaxi = false
|
||||||
@ -178,7 +176,7 @@ class WindowTag extends Ant.OS.GUI.BaseTag
|
|||||||
.css("height", @history.height)
|
.css("height", @history.height)
|
||||||
.css("top", @history.top)
|
.css("top", @history.top)
|
||||||
.css("left", @history.left)
|
.css("left", @history.left)
|
||||||
@observable.trigger 'resize', { id: @id(), w: history.width, h: history.height }
|
@observable.trigger 'resize', { id: @aid(), w: history.width, h: history.height }
|
||||||
|
|
||||||
layout: () ->
|
layout: () ->
|
||||||
{
|
{
|
||||||
|
@ -9,16 +9,19 @@ class Ant.OS.GUI.BaseTag
|
|||||||
@root.observable = @observable
|
@root.observable = @observable
|
||||||
@root.set = (k, v) -> me.set k, v
|
@root.set = (k, v) -> me.set k, v
|
||||||
@root.get = (k) -> me.get k
|
@root.get = (k) -> me.get k
|
||||||
|
@root.aid = () -> me.aid()
|
||||||
@refs = {}
|
@refs = {}
|
||||||
@setopt "data-id", Math.floor(Math.random() * 100000) + 1
|
@setopt "data-id", Math.floor(Math.random() * 100000) + 1
|
||||||
@wrapper = @mkui()
|
@children = []
|
||||||
if @refs["yield"]
|
dom = @mkui()
|
||||||
($($(v).detach()[0].uify(@observable)).appendTo(@refs.yield)) for v in $(@root).children()
|
if dom
|
||||||
$(@wrapper).appendTo(@root)
|
if @refs.yield
|
||||||
else
|
@children = $(@root).children()
|
||||||
$(@root).empty()
|
$(v).detach().appendTo @refs.yield for v in @children
|
||||||
$(@wrapper).appendTo(@root)
|
$(dom).appendTo(@root)
|
||||||
|
else
|
||||||
|
$(@root).empty()
|
||||||
|
$(dom).appendTo(@root)
|
||||||
|
|
||||||
setopt: (name, val) ->
|
setopt: (name, val) ->
|
||||||
value = val
|
value = val
|
||||||
@ -34,19 +37,27 @@ class Ant.OS.GUI.BaseTag
|
|||||||
@opts[opt] = value
|
@opts[opt] = value
|
||||||
@["on_#{opt}_changed"](value) if @["on_#{opt}_changed"]
|
@["on_#{opt}_changed"](value) if @["on_#{opt}_changed"]
|
||||||
|
|
||||||
id: () ->
|
aid: () ->
|
||||||
@get "data-id"
|
@get "data-id"
|
||||||
|
|
||||||
|
|
||||||
get: (opt) ->
|
get: (opt) ->
|
||||||
@opts[opt]
|
@opts[opt]
|
||||||
|
|
||||||
|
uify: () ->
|
||||||
|
@mount()
|
||||||
|
v.uify(@observable) for v in @children
|
||||||
|
@root
|
||||||
|
|
||||||
|
mount: () ->
|
||||||
|
|
||||||
layout: () ->
|
layout: () ->
|
||||||
# should be defined by subclasses
|
# should be defined by subclasses
|
||||||
|
|
||||||
mkui: (obj) ->
|
mkui: (obj) ->
|
||||||
tag = obj
|
tag = obj
|
||||||
tag = @layout() unless tag
|
tag = @layout() unless tag
|
||||||
|
return undefined unless tag
|
||||||
dom = $("<#{tag.el}>")
|
dom = $("<#{tag.el}>")
|
||||||
$(dom).addClass tag.class if tag.class
|
$(dom).addClass tag.class if tag.class
|
||||||
if tag.children
|
if tag.children
|
||||||
@ -54,12 +65,13 @@ class Ant.OS.GUI.BaseTag
|
|||||||
if tag.ref
|
if tag.ref
|
||||||
@refs[tag.ref] = dom
|
@refs[tag.ref] = dom
|
||||||
# dom.mount @observable
|
# dom.mount @observable
|
||||||
return dom
|
dom
|
||||||
|
|
||||||
Element.prototype.uify = (observable) ->
|
Element.prototype.uify = (observable) ->
|
||||||
tag = @tagName.toLowerCase()
|
tag = @tagName.toLowerCase()
|
||||||
if RegExp('afx-*', "i" ).test(tag) and Ant.OS.GUI.tag[tag]
|
if RegExp("afx-*", "i" ).test(tag) and Ant.OS.GUI.tag[tag]
|
||||||
return new Ant.OS.GUI.tag[tag](@, observable).root
|
o = new Ant.OS.GUI.tag[tag](@, observable)
|
||||||
|
return o.uify()
|
||||||
return @
|
return @
|
||||||
|
|
||||||
Ant.OS.GUI.define = (name, cls) ->
|
Ant.OS.GUI.define = (name, cls) ->
|
||||||
|
@ -35,19 +35,43 @@ class ShowCase extends this.OS.GUI.BaseApplication
|
|||||||
openwin: () ->
|
openwin: () ->
|
||||||
scheme = $.parseHTML """
|
scheme = $.parseHTML """
|
||||||
<afx-app-window apptitle="Preview" width="650" height="500">
|
<afx-app-window apptitle="Preview" width="650" height="500">
|
||||||
<div>
|
<afx-hbox>
|
||||||
<p>hello</p>
|
<afx-vbox data-width="150">
|
||||||
</div>
|
<div data-height="30%">box 1</div>
|
||||||
|
<div>box 2</div>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-resizer data-width="5" />
|
||||||
|
<afx-vbox data-width="grow">
|
||||||
|
<afx-hbox min-height="50">
|
||||||
|
<afx-label text="__(This is the label)"
|
||||||
|
iconclass="fa fa-camera-retro fa-lg"
|
||||||
|
icon="os://packages/DummyApp/icon.png"/>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-resizer data-height="5" />
|
||||||
|
<afx-hbox>
|
||||||
|
<div>box center 3</div>
|
||||||
|
<div>box center 4</div>
|
||||||
|
</afx-hbox>
|
||||||
|
<afx-hbox data-height="150">
|
||||||
|
<div>box center 3</div>
|
||||||
|
<div>box center 4</div>
|
||||||
|
</afx-hbox>
|
||||||
|
</afx-vbox>
|
||||||
|
<afx-vbox data-width="150">
|
||||||
|
<div data-height="grow">box 3</div>
|
||||||
|
<div data-height="200">box 4</div>
|
||||||
|
</afx-vbox>
|
||||||
|
</afx-hbox>
|
||||||
</afx-app-window>
|
</afx-app-window>
|
||||||
"""
|
"""
|
||||||
|
($ "#desktop").append scheme[0]
|
||||||
obj = scheme[0].uify()
|
obj = scheme[0].uify()
|
||||||
($ "#desktop").append obj
|
obj.set "resizable", true
|
||||||
obj.set "resizable", false
|
|
||||||
obj.set "minimizable", false
|
obj.set "minimizable", false
|
||||||
obj.observable.on "exit", () ->
|
obj.observable.on "exit", () ->
|
||||||
console.log "exit"
|
console.log "exit"
|
||||||
obj.observable.off "*"
|
obj.observable.off "*"
|
||||||
$(obj).remove()
|
$(obj).remove()
|
||||||
|
|
||||||
ShowCase.singleton = false
|
ShowCase.singleton = true
|
||||||
this.OS.register "ShowCase", ShowCase
|
this.OS.register "ShowCase", ShowCase
|
Loading…
Reference in New Issue
Block a user