mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-21 14:17:42 +08:00
JSDoc complete type check
This commit is contained in:
25
js/template/ColorHandlerTemplate.js
Executable file
25
js/template/ColorHandlerTemplate.js
Executable file
@@ -0,0 +1,25 @@
|
||||
import IDraggableTemplate from "./IDraggableTemplate"
|
||||
import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
|
||||
|
||||
/** @typedef {import("../element/ColorHandlerElement").default} ColorHandlerElement */
|
||||
|
||||
/** @extends {IDraggableTemplate<ColorHandlerElement>} */
|
||||
export default class ColorHandlerTemplate extends IDraggableTemplate {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
this.window = this.element.closest("ueb-window")
|
||||
}
|
||||
|
||||
createDraggableObject() {
|
||||
new MouseMoveDraggable(this.element, this.element.blueprint, {
|
||||
draggableElement: this.element.parentElement,
|
||||
ignoreTranslateCompensate: true,
|
||||
looseTarget: true,
|
||||
moveEverywhere: true,
|
||||
movementSpace: this.element.parentElement,
|
||||
repositionClickOffset: true,
|
||||
stepSize: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { html } from "lit"
|
||||
import Configuration from "../Configuration"
|
||||
import LinearColorEntity from "../entity/LinearColorEntity"
|
||||
import WindowTemplate from "./WindowTemplate"
|
||||
|
||||
/** @typedef {import("../element/WindowElement").default} WindowElement */
|
||||
@@ -7,31 +9,47 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
|
||||
|
||||
static windowName = html`Color Picker`
|
||||
|
||||
#picker
|
||||
/** @type {LinearColorEntity} */
|
||||
#color
|
||||
get color() {
|
||||
return this.#color
|
||||
}
|
||||
/** @param {LinearColorEntity} value */
|
||||
set color(value) {
|
||||
if (value.num() == this.color.num()) {
|
||||
this.element.requestUpdate("color", this.#color)
|
||||
this.#color = value
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
this.color = this.element.windowOptions.getPinColor()
|
||||
}
|
||||
|
||||
/** @param {Map} changedProperties */
|
||||
firstUpdated(changedProperties) {
|
||||
this.#picker = new iro.ColorPicker(
|
||||
this.element.querySelector(".ueb-color-picker"),
|
||||
{
|
||||
layout: [
|
||||
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const rgba = this.color.rgba()
|
||||
return html`
|
||||
<div class="ueb-color-picker">
|
||||
<div class="ueb-color-picker-theme"></div>
|
||||
<div class="ueb-color-picker-srgb"></div>
|
||||
<div class="ueb-color-picker-wheel"></div>
|
||||
<div class="ueb-color-picker-saturation"></div>
|
||||
<div class="ueb-color-picker-value"></div>
|
||||
<div class="ueb-color-picker-preview">
|
||||
<div class="ueb-color-picker-preview-old"></div>
|
||||
<div class="ueb-color-picker-preview-new"></div>
|
||||
<div class="ueb-color-picker"
|
||||
.style="--ueb-color-r: ${rgba[0]}; --ueb-color-g: ${rgba[1]}; --ueb-color-b: ${rgba[2]}; --ueb-color-a: ${rgba[3]};">
|
||||
<div class="ueb-color-picker-toolbar">
|
||||
<div class="ueb-color-picker-theme"></div>
|
||||
<div class="ueb-color-picker-srgb"></div>
|
||||
</div>
|
||||
<div class="ueb-color-picker-main">
|
||||
<div class="ueb-color-picker-wheel">
|
||||
<ueb-color-handler></ueb-color-handler>
|
||||
</div>
|
||||
<div class="ueb-color-picker-saturation"></div>
|
||||
<div class="ueb-color-picker-value"></div>
|
||||
<div class="ueb-color-picker-preview">
|
||||
<div class="ueb-color-picker-preview-old"></div>
|
||||
<div class="ueb-color-picker-preview-new"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ueb-color-picker-advanced-toggle"></div>
|
||||
<div class="ueb-color-picker-advanced">
|
||||
@@ -50,4 +68,8 @@ export default class ColorPickerWindowTemplate extends WindowTemplate {
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.element.blueprint.removeEventListener(Configuration.colorWindowEventName, this.colorWindowHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import ITemplate from "./ITemplate"
|
||||
import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
|
||||
|
||||
/** @typedef {import("../element/IDraggableElement").default} IDraggableElement */
|
||||
/**
|
||||
* @typedef {import("../entity/IEntity").default} IEntity
|
||||
* @typedef {import("../element/IDraggableElement").default} IDraggableElement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {ISelectableDraggableElement} T
|
||||
* @template {IDraggableElement} T
|
||||
* @extends {ITemplate<T>}
|
||||
*/
|
||||
export default class IDraggableTemplate extends ITemplate {
|
||||
@@ -27,9 +30,7 @@ export default class IDraggableTemplate extends ITemplate {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map} changedProperties
|
||||
*/
|
||||
/** @param {Map} changedProperties */
|
||||
update(changedProperties) {
|
||||
super.update(changedProperties)
|
||||
if (changedProperties.has("locationX")) {
|
||||
|
||||
@@ -2,8 +2,15 @@ import { html } from "lit"
|
||||
import MouseIgnore from "../input/mouse/MouseIgnore"
|
||||
import PinTemplate from "./PinTemplate"
|
||||
|
||||
/** @typedef {import("../element/PinElement").default} PinElement */
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import("../element/PinElement").default<T>} PinElement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @extends PinTemplate<T>
|
||||
*/
|
||||
export default class IInputPinTemplate extends PinTemplate {
|
||||
|
||||
/** @type {HTMLElement[]} */
|
||||
@@ -12,24 +19,24 @@ export default class IInputPinTemplate extends PinTemplate {
|
||||
return this.#inputContentElements
|
||||
}
|
||||
|
||||
/** @param {String} value */
|
||||
static stringFromInputToUE(value) {
|
||||
return value
|
||||
.replace(/(?=\n\s*)\n$/, "") // Remove trailing double newline
|
||||
.replaceAll("\n", "\\r\n") // Replace newline with \r\n (default newline in UE)
|
||||
}
|
||||
|
||||
/** @param {String} value */
|
||||
static stringFromUEToInput(value) {
|
||||
return value
|
||||
.replaceAll(/(?:\r|(?<=(?:^|[^\\])(?:\\\\)*)\\r)(?=\n)/g, "") // Remove \r leftover from \r\n
|
||||
.replace(/(?<=\n\s*)$/, "\n") // Put back trailing double newline
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map} changedProperties
|
||||
*/
|
||||
/** @param {Map} changedProperties */
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties)
|
||||
this.#inputContentElements = [...this.element.querySelectorAll(".ueb-pin-input-content")]
|
||||
this.#inputContentElements = /** @type {HTMLElement[]} */([...this.element.querySelectorAll(".ueb-pin-input-content")])
|
||||
if (this.#inputContentElements.length) {
|
||||
this.setInputs(this.getInputs(), false)
|
||||
let self = this
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { html } from "lit"
|
||||
import MouseOpenWindow from "../input/mouse/MouseOpenWindow"
|
||||
import ColorPickerWindowTemplate from "./ColorPickerWindowTemplate"
|
||||
import IInputPinTemplate from "./IInputPinTemplate"
|
||||
import MouseOpenWindow from "../input/mouse/MouseOpenWindow"
|
||||
|
||||
/**
|
||||
* @typedef {import("../element/PinElement").default} PinElement
|
||||
@@ -24,7 +25,14 @@ export default class LinearColorPinTemplate extends IInputPinTemplate {
|
||||
...super.createInputObjects(),
|
||||
new MouseOpenWindow(this.#input, this.element.blueprint, {
|
||||
moveEverywhere: true,
|
||||
looseTarget: true
|
||||
looseTarget: true,
|
||||
windowType: ColorPickerWindowTemplate,
|
||||
windowOptions: {
|
||||
// The created window will use the following functions to get and set the color
|
||||
getPinColor: () => this.element.defaultValue,
|
||||
/** @param {LinearColorEntity} color */
|
||||
setPinColor: color => this.element.setDefaultValue(color),
|
||||
},
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class NodeTemplate extends SelectableDraggableTemplate {
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="ueb-node-name-text">
|
||||
<span class="ueb-node-name-text ueb-ellipsis-nowrap-text">
|
||||
${this.element.nodeDisplayName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import { css, html } from "lit"
|
||||
import { html } from "lit"
|
||||
import ITemplate from "./ITemplate"
|
||||
import MouseCreateLink from "../input/mouse/MouseCreateLink"
|
||||
import Utility from "../Utility"
|
||||
|
||||
/**
|
||||
* @typedef {import("../element/NodeElement").default} NodeElement
|
||||
* @typedef {import("../element/PinElement").default} PinElement
|
||||
* @typedef {import("../input/IInput").default} IInput
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import("../element/PinElement").default<T>} PinElement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @extends ITemplate<PinElement<T>>
|
||||
*/
|
||||
export default class PinTemplate extends ITemplate {
|
||||
|
||||
static styles = css``
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
this.element.nodeElement = this.element.closest("ueb-node")
|
||||
@@ -23,7 +28,7 @@ export default class PinTemplate extends ITemplate {
|
||||
return [
|
||||
new MouseCreateLink(this.element.clickableElement, this.element.blueprint, {
|
||||
moveEverywhere: true,
|
||||
looseTarget: true
|
||||
looseTarget: true,
|
||||
})
|
||||
]
|
||||
}
|
||||
@@ -36,7 +41,7 @@ export default class PinTemplate extends ITemplate {
|
||||
`
|
||||
const content = html`
|
||||
<div class="ueb-pin-content">
|
||||
<span class="ueb-pin-name">${this.element.getPinDisplayName()}</span>
|
||||
<span class="ueb-pin-name ">${this.element.getPinDisplayName()}</span>
|
||||
${this.renderInput()}
|
||||
</div>
|
||||
`
|
||||
@@ -47,8 +52,7 @@ export default class PinTemplate extends ITemplate {
|
||||
`
|
||||
}
|
||||
|
||||
/** @param {PinElement} pin */
|
||||
renderIcon(pin) {
|
||||
renderIcon() {
|
||||
return html`
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle class="ueb-pin-tofill" cx="16" cy="16" r="14" fill="none" stroke="currentColor" stroke-width="5" />
|
||||
@@ -57,8 +61,7 @@ export default class PinTemplate extends ITemplate {
|
||||
`
|
||||
}
|
||||
|
||||
/** @param {PinElement} pin */
|
||||
renderInput(pin) {
|
||||
renderInput() {
|
||||
return html``
|
||||
}
|
||||
|
||||
@@ -69,13 +72,12 @@ export default class PinTemplate extends ITemplate {
|
||||
this.element.clickableElement = this.element
|
||||
}
|
||||
|
||||
/** @param {PinElement} pin */
|
||||
getLinkLocation(pin) {
|
||||
const rect = pin.querySelector(".ueb-pin-icon").getBoundingClientRect()
|
||||
getLinkLocation() {
|
||||
const rect = this.element.querySelector(".ueb-pin-icon").getBoundingClientRect()
|
||||
const location = Utility.convertLocation(
|
||||
[(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2],
|
||||
pin.blueprint.gridElement
|
||||
this.element.blueprint.gridElement
|
||||
)
|
||||
return pin.blueprint.compensateTranslation(location)
|
||||
return this.element.blueprint.compensateTranslation(location)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,28 @@ import { html } from "lit"
|
||||
import IInputPinTemplate from "./IInputPinTemplate"
|
||||
import Utility from "../Utility"
|
||||
|
||||
/**
|
||||
* @typedef {import("../entity/VectorEntity").default} VectorEntity
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {import("../element/PinElement").default<T>} PinElement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {Number} T
|
||||
* @extends IInputPinTemplate<T>
|
||||
*/
|
||||
export default class RealPinTemplate extends IInputPinTemplate {
|
||||
|
||||
/** @param {String[]} values */
|
||||
setInputs(values = [], updateDefaultValue = false) {
|
||||
if (!values || values.length == 0) {
|
||||
values = this.getInput()
|
||||
values = [this.getInput()]
|
||||
}
|
||||
let parsedValues = []
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
let num = parseFloat(values[i])
|
||||
if (isNaN(num)) {
|
||||
num = parseFloat(this.element.entity.DefaultValue != ""
|
||||
? /** @type {String} */(this.element.entity.DefaultValue)
|
||||
: this.element.entity.AutogeneratedDefaultValue)
|
||||
}
|
||||
if (isNaN(num)) {
|
||||
num = 0
|
||||
updateDefaultValue = false
|
||||
@@ -35,7 +43,9 @@ export default class RealPinTemplate extends IInputPinTemplate {
|
||||
return html`
|
||||
<div class="ueb-pin-input">
|
||||
<span class="ueb-pin-input-content" role="textbox" contenteditable="true"
|
||||
.innerText="${IInputPinTemplate.stringFromUEToInput(Utility.minDecimals(this.element.entity.DefaultValue))}"></span>
|
||||
.innerText="${
|
||||
IInputPinTemplate.stringFromUEToInput(Utility.minDecimals(this.element.entity.DefaultValue))
|
||||
}"></span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -3,7 +3,28 @@ import RotatorEntity from "../entity/RotatorEntity"
|
||||
import IInputPinTemplate from "./IInputPinTemplate"
|
||||
import RealPinTemplate from "./RealPinTemplate"
|
||||
|
||||
export default class RotatorPinTemplate extends RealPinTemplate {
|
||||
/** @typedef {import("../entity/RotatorEntity").default} Rotator */
|
||||
|
||||
/** @extends IInputPinTemplate<Rotator> */
|
||||
export default class RotatorPinTemplate extends IInputPinTemplate {
|
||||
|
||||
/** @param {String[]} values */
|
||||
setInputs(values = [], updateDefaultValue = false) {
|
||||
if (!values || values.length == 0) {
|
||||
values = [this.getInput()]
|
||||
}
|
||||
let parsedValues = []
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
let num = parseFloat(values[i])
|
||||
if (isNaN(num)) {
|
||||
num = 0
|
||||
updateDefaultValue = false
|
||||
}
|
||||
parsedValues.push(num)
|
||||
}
|
||||
super.setInputs(values, false)
|
||||
this.setDefaultValue(parsedValues, values)
|
||||
}
|
||||
|
||||
setDefaultValue(values = [], rawValues = values) {
|
||||
if (!(this.element.entity.DefaultValue instanceof RotatorEntity)) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import IDraggableTemplate from "./IDraggableTemplate"
|
||||
import ITemplate from "./ITemplate"
|
||||
import MouseMoveNodes from "../input/mouse/MouseMoveNodes"
|
||||
|
||||
/** @typedef {import("../element/ISelectableDraggableElement").default} ISelectableDraggableElement */
|
||||
/**
|
||||
* @typedef {import("../element/ISelectableDraggableElement").default} ISelectableDraggableElement
|
||||
* @typedef {import("../input/mouse/MouseMoveDraggable").default} MouseMoveDraggable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {ISelectableDraggableElement} T
|
||||
* @extends {ITemplate<T>}
|
||||
* @extends {IDraggableTemplate<T>}
|
||||
*/
|
||||
export default class SelectableDraggableTemplate extends IDraggableTemplate {
|
||||
|
||||
@@ -15,10 +17,10 @@ export default class SelectableDraggableTemplate extends IDraggableTemplate {
|
||||
}
|
||||
|
||||
createDraggableObject() {
|
||||
return new MouseMoveNodes(this.element, this.element.blueprint, {
|
||||
return /** @type {MouseMoveDraggable} */ (new MouseMoveNodes(this.element, this.element.blueprint, {
|
||||
draggableElement: this.getDraggableElement(),
|
||||
looseTarget: true,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/** @param {Map} changedProperties */
|
||||
|
||||
@@ -2,6 +2,7 @@ import IFromToPositionedTemplate from "./IFromToPositionedTemplate"
|
||||
|
||||
/** @typedef {import("../element/SelectorElement").default} SelectorElement */
|
||||
|
||||
/** @extends IFromToPositionedTemplate<SelectorElement> */
|
||||
export default class SelectorTemplate extends IFromToPositionedTemplate {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
import { html } from "lit"
|
||||
import IInputPinTemplate from "./IInputPinTemplate"
|
||||
import RealPinTemplate from "./RealPinTemplate"
|
||||
import Utility from "../Utility"
|
||||
import VectorEntity from "../entity/VectorEntity"
|
||||
|
||||
/**
|
||||
* @typedef {import("../element/PinElement").default} PinElement
|
||||
* @typedef {import("../entity/LinearColorEntity").default} LinearColorEntity}
|
||||
* @typedef {import("../entity/LinearColorEntity").default} LinearColorEntity
|
||||
*/
|
||||
|
||||
export default class VectorPinTemplate extends RealPinTemplate {
|
||||
/**
|
||||
* @template {VectorEntity} T
|
||||
* @extends IInputPinTemplate<T>
|
||||
*/
|
||||
export default class VectorPinTemplate extends IInputPinTemplate {
|
||||
|
||||
/** @param {String[]} values */
|
||||
setInputs(values = [], updateDefaultValue = false) {
|
||||
if (!values || values.length == 0) {
|
||||
values = [this.getInput()]
|
||||
}
|
||||
let parsedValues = []
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
let num = parseFloat(values[i])
|
||||
if (isNaN(num)) {
|
||||
num = 0
|
||||
updateDefaultValue = false
|
||||
}
|
||||
parsedValues.push(num)
|
||||
}
|
||||
super.setInputs(values, false)
|
||||
this.setDefaultValue(parsedValues, values)
|
||||
}
|
||||
|
||||
setDefaultValue(values = [], rawValues = values) {
|
||||
if (!(this.element.entity.DefaultValue instanceof VectorEntity)) {
|
||||
@@ -27,27 +48,21 @@ export default class VectorPinTemplate extends RealPinTemplate {
|
||||
<div class="ueb-pin-input-wrapper">
|
||||
<span class="ueb-pin-input-label">X</span>
|
||||
<div class="ueb-pin-input">
|
||||
<span class="ueb-pin-input-content ueb-pin-input-x" role="textbox" contenteditable="true" .innerText="${IInputPinTemplate.stringFromUEToInput(
|
||||
Utility.minDecimals(
|
||||
this.element.entity.getDefaultValue().X
|
||||
)
|
||||
)}"></span>
|
||||
<span class="ueb-pin-input-content ueb-pin-input-x" role="textbox" contenteditable="true" .innerText="${IInputPinTemplate
|
||||
.stringFromUEToInput(Utility.minDecimals(this.element.entity.getDefaultValue().X))
|
||||
}"></span>
|
||||
</div>
|
||||
<span class="ueb-pin-input-label">Y</span>
|
||||
<div class="ueb-pin-input">
|
||||
<span class="ueb-pin-input-content ueb-pin-input-y" role="textbox" contenteditable="true" .innerText="${IInputPinTemplate.stringFromUEToInput(
|
||||
Utility.minDecimals(
|
||||
this.element.entity.getDefaultValue().Y
|
||||
)
|
||||
)}"></span>
|
||||
<span class="ueb-pin-input-content ueb-pin-input-y" role="textbox" contenteditable="true" .innerText="${IInputPinTemplate
|
||||
.stringFromUEToInput(Utility.minDecimals(this.element.entity.getDefaultValue().Y))
|
||||
}"></span>
|
||||
</div>
|
||||
<span class="ueb-pin-input-label">Z</span>
|
||||
<div class="ueb-pin-input">
|
||||
<span class="ueb-pin-input-content ueb-pin-input-z" role="textbox" contenteditable="true" .innerText="${IInputPinTemplate.stringFromUEToInput(
|
||||
Utility.minDecimals(
|
||||
this.element.entity.getDefaultValue().Z
|
||||
)
|
||||
)}"></span>
|
||||
<span class="ueb-pin-input-content ueb-pin-input-z" role="textbox" contenteditable="true" .innerText="${IInputPinTemplate
|
||||
.stringFromUEToInput(Utility.minDecimals(this.element.entity.getDefaultValue().Z))
|
||||
}"></span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -4,7 +4,7 @@ import MouseMoveDraggable from "../input/mouse/MouseMoveDraggable"
|
||||
|
||||
/** @typedef {import("../element/WindowElement").default} WindowElement */
|
||||
|
||||
/** @extends {SelectableDraggableTemplate<WindowElement>} */
|
||||
/** @extends {IDraggableTemplate<WindowElement>} */
|
||||
export default class WindowTemplate extends IDraggableTemplate {
|
||||
|
||||
static windowName = html`Window`
|
||||
@@ -12,7 +12,7 @@ export default class WindowTemplate extends IDraggableTemplate {
|
||||
toggleAdvancedDisplayHandler
|
||||
|
||||
getDraggableElement() {
|
||||
return this.element.querySelector(".ueb-window-top")
|
||||
return /** @type {WindowElement} */(this.element.querySelector(".ueb-window-top"))
|
||||
}
|
||||
|
||||
createDraggableObject() {
|
||||
@@ -35,7 +35,9 @@ export default class WindowTemplate extends IDraggableTemplate {
|
||||
return html`
|
||||
<div class="ueb-window">
|
||||
<div class="ueb-window-top">
|
||||
<div class="ueb-window-name">${this.constructor.windowName}</div>
|
||||
<div class="ueb-window-name ueb-ellipsis-nowrap-text">${
|
||||
// @ts-expect-error
|
||||
this.constructor.windowName}</div>
|
||||
<div class="ueb-window-close">
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="2" y1="2" x2="30" y2="30" stroke="currentColor" stroke-width="4" />
|
||||
|
||||
Reference in New Issue
Block a user