Compare commits
8 Commits
9ad3f239dd
...
629690d29e
| Author | SHA1 | Date | |
|---|---|---|---|
| 629690d29e | |||
| 12e1e8a90b | |||
| 0c784b3855 | |||
| 0ab8cf026e | |||
| dd3312b7f5 | |||
| 1b4e6193e8 | |||
| 5759618904 | |||
| 77b3a68b76 |
12
src/App.jsx
12
src/App.jsx
@@ -1,18 +1,18 @@
|
||||
import { useState } from 'react'
|
||||
import { useState } from "react";
|
||||
|
||||
import Barra from './components/Barra';
|
||||
import Keeper from './components/coordkeeper';
|
||||
import Barra from "./components/Barra";
|
||||
import Keeper from "./components/coordkeeper";
|
||||
|
||||
function App() {
|
||||
const [type, setType] = useState("overworld");
|
||||
const [type, setType] = useState("overworld");
|
||||
return (
|
||||
<div>
|
||||
<Barra active={type} setType={setType} />
|
||||
<div class="container border text-white vw-100 mt-2 rounded p-2">
|
||||
<Keeper tipo={type}/>
|
||||
<Keeper tipo={type} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
15
src/components/EditarIcon.jsx
Normal file
15
src/components/EditarIcon.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export default function EditarIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m21.561 5.318l-2.879-2.879A1.5 1.5 0 0 0 17.621 2c-.385 0-.768.146-1.061.439L13 6H4a1 1 0 0 0-1 1v13a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1v-9l3.561-3.561c.293-.293.439-.677.439-1.061s-.146-.767-.439-1.06M11.5 14.672L9.328 12.5l6.293-6.293l2.172 2.172zm-2.561-1.339l1.756 1.728L9 15zM16 19H5V8h6l-3.18 3.18c-.293.293-.478.812-.629 1.289c-.16.5-.191 1.056-.191 1.47V17h3.061c.414 0 1.108-.1 1.571-.29c.464-.19.896-.347 1.188-.64L16 13zm2.5-11.328L16.328 5.5l1.293-1.293l2.171 2.172z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
124
src/components/EditarModal.jsx
Normal file
124
src/components/EditarModal.jsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useState } from "react";
|
||||
export default function EditarModal({ tipo, coord, show, close, setCoords }) {
|
||||
if (coord == null) return null;
|
||||
const [hasY, setHasY] = useState(coord.y !== null);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const x = formData.get("x");
|
||||
const y = formData.get("y");
|
||||
const z = formData.get("z");
|
||||
const descripcion = formData.get("descripcion");
|
||||
|
||||
let storedTipo = localStorage.getItem(tipo);
|
||||
if (storedTipo) {
|
||||
let arr = JSON.parse(storedTipo);
|
||||
const index = arr.findIndex((item) => item.num === coord.num);
|
||||
if (index !== -1) {
|
||||
arr[index] = {
|
||||
...coord,
|
||||
x: Number(x),
|
||||
y: y ? Number(y) : null,
|
||||
z: Number(z),
|
||||
descripcion: descripcion,
|
||||
};
|
||||
localStorage.setItem(tipo, JSON.stringify(arr));
|
||||
setCoords(arr);
|
||||
}
|
||||
}
|
||||
close();
|
||||
};
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal"
|
||||
id="formModal"
|
||||
tabIndex="-1"
|
||||
style={{ display: "block" }}
|
||||
>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Update Coord</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={close}
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input me-2"
|
||||
checked={hasY}
|
||||
onChange={(e) => {
|
||||
coord.y = e.target.checked ? "0" : null;
|
||||
setHasY(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<label className="form-label">Lleva coordenada Y?</label>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Coordenada X</label>
|
||||
<input
|
||||
type="number"
|
||||
name="x"
|
||||
className="form-control"
|
||||
defaultValue={coord.x}
|
||||
/>
|
||||
</div>
|
||||
{hasY && (
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Coordenada Y</label>
|
||||
<input
|
||||
type="number"
|
||||
name="y"
|
||||
className="form-control"
|
||||
defaultValue={coord.y}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Coordenada Z</label>
|
||||
<input
|
||||
type="number"
|
||||
name="z"
|
||||
className="form-control"
|
||||
defaultValue={coord.z}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Descripción</label>
|
||||
<input
|
||||
type="text"
|
||||
name="descripcion"
|
||||
className="form-control"
|
||||
defaultValue={coord.descripcion}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<button type="submit" className="btn btn-primary m-2">
|
||||
Actualizar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={close}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export default function NuevaCoord({coord, setcoord, colapsar, tipo}) {
|
||||
export default function NuevaCoord({ coord, setcoord, colapsar, tipo }) {
|
||||
const [llevaY, setLlevaY] = useState(false);
|
||||
const [x, setX] = useState("");
|
||||
const [y, setY] = useState("");
|
||||
const [z, setZ] = useState("");
|
||||
const [descripcion, setDescripcion] = useState("");
|
||||
|
||||
const handleAdd = () => {
|
||||
const newCoord = {
|
||||
@@ -12,17 +13,19 @@ export default function NuevaCoord({coord, setcoord, colapsar, tipo}) {
|
||||
x: x,
|
||||
y: llevaY ? y : null,
|
||||
z: z,
|
||||
descripcion: descripcion,
|
||||
};
|
||||
|
||||
setcoord([...coord, newCoord]);
|
||||
localStorage.setItem(tipo, JSON.stringify([...coord, newCoord]));
|
||||
if (colapsar) colapsar();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input id="1"
|
||||
type="checkbox" className="form-check-input me-2"
|
||||
<input
|
||||
id="1"
|
||||
type="checkbox"
|
||||
className="form-check-input me-2"
|
||||
onChange={(e) => setLlevaY(e.target.checked)}
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseY"
|
||||
@@ -30,26 +33,48 @@ export default function NuevaCoord({coord, setcoord, colapsar, tipo}) {
|
||||
<label className="form-check-label" htmlFor="1">
|
||||
Lleva coordenada y?
|
||||
</label>
|
||||
<hr/>
|
||||
<hr />
|
||||
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<div className="input-group mb-3">
|
||||
<span class="input-group-text">x:</span>
|
||||
<input type="text" className="form-control" onChange={(e) =>setX(e.target.value)}/>
|
||||
<span class="input-group-text">X:</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
onChange={(e) => setX(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="input-group mb-3 collapse" id="collapseY">
|
||||
<span class="input-group-text">y:</span>
|
||||
<input type="text" className="form-control" onChange={(e) =>setY(e.target.value)}/>
|
||||
<span class="input-group-text">Y:</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
onChange={(e) => setY(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="input-group mb-3">
|
||||
<span class="input-group-text">z:</span>
|
||||
<input type="text" className="form-control" onChange={(e) =>setZ(e.target.value)}/>
|
||||
<span class="input-group-text">Z:</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
onChange={(e) => setZ(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<button className="btn btn-primary" onClick={handleAdd}>Añadir</button>
|
||||
<div className="input-group mb-3">
|
||||
<span class="input-group-text">Descripción:</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
onChange={(e) => setDescripcion(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<button className="btn btn-primary" onClick={handleAdd}>
|
||||
Añadir
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
15
src/components/RemoveIcon.jsx
Normal file
15
src/components/RemoveIcon.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export default function RemoveIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
15
src/components/ShareIcon.jsx
Normal file
15
src/components/ShareIcon.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export default function ShareIcon() {
|
||||
return(
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M17 22q-1.25 0-2.125-.875T14 19q0-.15.075-.7L7.05 14.2q-.4.375-.925.588T5 15q-1.25 0-2.125-.875T2 12t.875-2.125T5 9q.6 0 1.125.213t.925.587l7.025-4.1q-.05-.175-.062-.337T14 5q0-1.25.875-2.125T17 2t2.125.875T20 5t-.875 2.125T17 8q-.6 0-1.125-.213T14.95 7.2l-7.025 4.1q.05.175.063.338T8 12t-.012.363t-.063.337l7.025 4.1q.4-.375.925-.587T17 16q1.25 0 2.125.875T20 19t-.875 2.125T17 22"
|
||||
></path>
|
||||
</svg>,
|
||||
);
|
||||
}
|
||||
49
src/components/StaticModal.jsx
Normal file
49
src/components/StaticModal.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
export default function Modal({ CloseModal, Coord, EliminarCoord }) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="modal fade show"
|
||||
id="staticBackdrop"
|
||||
data-bs-backdrop="static"
|
||||
style={{ display: "block" }}
|
||||
>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5">Eliminar Coordenada</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={CloseModal}
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{console.log(Coord)}
|
||||
Desea eliminar la Coordenada: X: {Coord.x},
|
||||
{Coord.y != null && `Y: ${Coord.y}, `}Z: {Coord.z}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger"
|
||||
onClick={() => {
|
||||
EliminarCoord();
|
||||
CloseModal();
|
||||
}}
|
||||
>
|
||||
Eliminar
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={CloseModal}
|
||||
>
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
35
src/components/TimedToast.jsx
Normal file
35
src/components/TimedToast.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function TimedToast({ message, durationMs, show, setShow }) {
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setShow(false);
|
||||
}, durationMs);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [durationMs]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div className="position-fixed bottom-0 end-0 p-3" style={{ zIndex: 11 }}>
|
||||
<div
|
||||
className="toast show"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<div className="toast-header">
|
||||
<strong className="me-auto">Notification</strong>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={() => setShow(false)}
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div className="toast-body">{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,42 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import NuevaCoord from "./NuevaCoord";
|
||||
import EditarIcon from "./EditarIcon";
|
||||
import ShareIcon from "./ShareIcon";
|
||||
import RemoveIcon from "./RemoveIcon";
|
||||
import StaticModal from "./StaticModal";
|
||||
import EditarModal from "./EditarModal";
|
||||
import TimedToast from "./TimedToast";
|
||||
|
||||
function Modall({ show, CloseModal, Coord, Coords, EliminarCoord, tipo }) {
|
||||
if (show === true && Coord !== null) {
|
||||
return (
|
||||
<StaticModal
|
||||
CloseModal={CloseModal}
|
||||
Coord={Coord}
|
||||
EliminarCoord={() => {
|
||||
const nuevasCoords = Coords.filter((c) => c !== Coord);
|
||||
localStorage.setItem(tipo, JSON.stringify(nuevasCoords));
|
||||
EliminarCoord(nuevasCoords);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Keeper({ tipo }) {
|
||||
const [coords, setCoords] = useState(
|
||||
JSON.parse(localStorage.getItem(tipo) || "[]"),
|
||||
);
|
||||
|
||||
export default function Keeper({tipo}){
|
||||
const [coords,setCoords] = useState(JSON.parse(localStorage.getItem(tipo)||"[]"));
|
||||
const collapseRef = useRef(null);
|
||||
const [showModal, setModal] = useState(false);
|
||||
const [showEditar, setEditar] = useState(false);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [selCoord, setCoord] = useState(null);
|
||||
|
||||
console.log(coords);
|
||||
useEffect(() => {
|
||||
const storedCoords = JSON.parse(localStorage.getItem(tipo)||"[]");
|
||||
const storedCoords = JSON.parse(localStorage.getItem(tipo) || "[]");
|
||||
|
||||
console.log(storedCoords);
|
||||
if (storedCoords) {
|
||||
@@ -15,6 +44,56 @@ export default function Keeper({tipo}){
|
||||
}
|
||||
}, [tipo]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePaste = async (e) => {
|
||||
const text = await navigator.clipboard.readText();
|
||||
const regex = /- (.*), X: (-?\d+)(?:\s*Y:\s*(-?\d+))?\s*Z:\s*(-?\d+)/;
|
||||
const match = text.match(regex);
|
||||
|
||||
if (match) {
|
||||
const [_, descripcion, x, y, z] = match;
|
||||
const newCoord = {
|
||||
descripcion,
|
||||
x: parseInt(x),
|
||||
y: y ? parseInt(y) : null,
|
||||
z: parseInt(z),
|
||||
num: coords.length + 1,
|
||||
};
|
||||
|
||||
const newCoords = [...coords, newCoord];
|
||||
setCoords(newCoords);
|
||||
localStorage.setItem(tipo, JSON.stringify(newCoords));
|
||||
setShowToast(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.ctrlKey && e.key === "v") {
|
||||
handlePaste(e);
|
||||
}
|
||||
});
|
||||
|
||||
/*return () => {
|
||||
document.removeEventListener("keydown", handlePaste);
|
||||
};*/
|
||||
}, [coords, tipo]);
|
||||
|
||||
const Editar = (coord) => {
|
||||
setCoord(coord);
|
||||
setEditar(true);
|
||||
};
|
||||
|
||||
const Compartir = (coord) => {
|
||||
setCoord(coord);
|
||||
const textToCopy = `- ${coord.descripcion}, X: ${coord.x}${coord.y ? ` Y: ${coord.y}` : ""} Z: ${coord.z}`;
|
||||
navigator.clipboard.writeText(textToCopy);
|
||||
setShowToast(true);
|
||||
};
|
||||
|
||||
const Remover = (coord) => {
|
||||
setCoord(coord);
|
||||
setModal(true);
|
||||
};
|
||||
const collapseAccordion = () => {
|
||||
if (collapseRef.current) {
|
||||
const bsCollapse = new window.bootstrap.Collapse(collapseRef.current, {
|
||||
@@ -25,6 +104,30 @@ export default function Keeper({tipo}){
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Modall
|
||||
show={showModal}
|
||||
CloseModal={() => setModal(false)}
|
||||
Coord={selCoord}
|
||||
Coords={coords}
|
||||
EliminarCoord={setCoords}
|
||||
tipo={tipo}
|
||||
/>
|
||||
|
||||
<EditarModal
|
||||
show={showEditar}
|
||||
close={() => setEditar(false)}
|
||||
tipo={tipo}
|
||||
coord={selCoord}
|
||||
setCoords={setCoords}
|
||||
/>
|
||||
|
||||
<TimedToast
|
||||
durationMs={5000}
|
||||
setShow={setShowToast}
|
||||
show={showToast}
|
||||
message={`Copiada Coordenada [${selCoord?.descripcion}]`}
|
||||
/>
|
||||
|
||||
<div className="accordion" id="accordionNuevaCoord">
|
||||
<div className="accordion-item">
|
||||
<h2 className="accordion-header" id="headingNuevaCoord">
|
||||
@@ -48,20 +151,71 @@ export default function Keeper({tipo}){
|
||||
ref={collapseRef}
|
||||
>
|
||||
<div className="accordion-body">
|
||||
<NuevaCoord coord={coords} setcoord={setCoords} colapsar={collapseAccordion} tipo={tipo} />
|
||||
<NuevaCoord
|
||||
coord={coords}
|
||||
setcoord={setCoords}
|
||||
colapsar={collapseAccordion}
|
||||
tipo={tipo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
{coords.length == 0 &&
|
||||
<h3 className="text-center">No hay Coordenadas que Mostrar.</h3>
|
||||
}
|
||||
{coords.length !=0 && coords.map(x=>
|
||||
<div className="m-2"><b>#{x.num}:</b> x:{x.x} {x.y !== null && `y: ${x.y}`} z:{x.z} <button class="btn btn-outline-warning btn-sm">Editar</button><button className="btn btn-sm btn-outline-info ms-2">Compartir</button></div>
|
||||
<hr />
|
||||
|
||||
{coords.length == 0 && (
|
||||
<h2 className="list-group-item text-center">
|
||||
No hay Coordenadas que Mostrar.
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<table class="table table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>X:</th>
|
||||
<th>Y:</th>
|
||||
<th>Z:</th>
|
||||
<th>Descripcion</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{coords.length != 0 &&
|
||||
coords.map((x) => (
|
||||
<tr>
|
||||
<td>
|
||||
<b>{x.num}</b>
|
||||
</td>
|
||||
|
||||
<td>{x.x}</td>
|
||||
<td>{x.y !== null && `Y: ${x.y}`}</td>
|
||||
<td>{x.z}</td>
|
||||
<td>{x.descripcion}</td>
|
||||
<td>
|
||||
<button
|
||||
class="ms-2 btn btn-outline-warning btn-sm"
|
||||
onClick={() => Editar(x)}
|
||||
>
|
||||
<EditarIcon />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-info ms-2"
|
||||
onClick={() => Compartir(x)}
|
||||
>
|
||||
<ShareIcon />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger ms-2"
|
||||
onClick={() => Remover(x)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
@@ -6,7 +5,6 @@
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
@@ -14,7 +12,6 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
|
||||
Reference in New Issue
Block a user