Changed map drag position to use a ref value to avoid re-renders
Added a useMapBrush helper
This commit is contained in:
parent
8932ceb1e3
commit
b0c1dcf9dd
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useContext, useState, useCallback } from "react";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
import { Group, Line, Rect, Circle } from "react-konva";
|
import { Group, Line, Rect, Circle } from "react-konva";
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
} from "../../helpers/drawing";
|
} from "../../helpers/drawing";
|
||||||
|
|
||||||
import colors from "../../helpers/colors";
|
import colors from "../../helpers/colors";
|
||||||
|
import useMapBrush from "../../helpers/useMapBrush";
|
||||||
|
|
||||||
function MapDrawing({
|
function MapDrawing({
|
||||||
shapes,
|
shapes,
|
||||||
@ -23,13 +24,7 @@ function MapDrawing({
|
|||||||
selectedToolSettings,
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { stageScale, mapWidth, mapHeight } = useContext(MapInteractionContext);
|
||||||
stageDragState,
|
|
||||||
mapDragPosition,
|
|
||||||
stageScale,
|
|
||||||
mapWidth,
|
|
||||||
mapHeight,
|
|
||||||
} = useContext(MapInteractionContext);
|
|
||||||
const [drawingShape, setDrawingShape] = useState(null);
|
const [drawingShape, setDrawingShape] = useState(null);
|
||||||
|
|
||||||
const shouldHover = selectedToolId === "erase";
|
const shouldHover = selectedToolId === "erase";
|
||||||
@ -38,122 +33,120 @@ function MapDrawing({
|
|||||||
selectedToolId === "shape" ||
|
selectedToolId === "shape" ||
|
||||||
selectedToolId === "erase";
|
selectedToolId === "erase";
|
||||||
|
|
||||||
useEffect(() => {
|
const handleShapeDraw = useCallback(
|
||||||
if (!isEditing) {
|
(brushState, mapBrushPosition) => {
|
||||||
return;
|
function startShape() {
|
||||||
}
|
const brushPosition = getBrushPositionForTool(
|
||||||
|
mapBrushPosition,
|
||||||
function startShape() {
|
selectedToolId,
|
||||||
const brushPosition = getBrushPositionForTool(
|
selectedToolSettings,
|
||||||
mapDragPosition,
|
gridSize,
|
||||||
selectedToolId,
|
shapes
|
||||||
selectedToolSettings,
|
);
|
||||||
gridSize,
|
const commonShapeData = {
|
||||||
shapes
|
color: selectedToolSettings && selectedToolSettings.color,
|
||||||
);
|
blend: selectedToolSettings && selectedToolSettings.useBlending,
|
||||||
const commonShapeData = {
|
id: shortid.generate(),
|
||||||
color: selectedToolSettings && selectedToolSettings.color,
|
};
|
||||||
blend: selectedToolSettings && selectedToolSettings.useBlending,
|
if (selectedToolId === "brush") {
|
||||||
id: shortid.generate(),
|
setDrawingShape({
|
||||||
};
|
type: "path",
|
||||||
if (selectedToolId === "brush") {
|
pathType: selectedToolSettings.type,
|
||||||
setDrawingShape({
|
data: { points: [brushPosition] },
|
||||||
type: "path",
|
strokeWidth: selectedToolSettings.type === "stroke" ? 1 : 0,
|
||||||
pathType: selectedToolSettings.type,
|
...commonShapeData,
|
||||||
data: { points: [brushPosition] },
|
});
|
||||||
strokeWidth: selectedToolSettings.type === "stroke" ? 1 : 0,
|
} else if (selectedToolId === "shape") {
|
||||||
...commonShapeData,
|
setDrawingShape({
|
||||||
});
|
type: "shape",
|
||||||
} else if (selectedToolId === "shape") {
|
shapeType: selectedToolSettings.type,
|
||||||
setDrawingShape({
|
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
|
||||||
type: "shape",
|
strokeWidth: 0,
|
||||||
shapeType: selectedToolSettings.type,
|
...commonShapeData,
|
||||||
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
|
});
|
||||||
strokeWidth: 0,
|
}
|
||||||
...commonShapeData,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function continueShape() {
|
function continueShape() {
|
||||||
const brushPosition = getBrushPositionForTool(
|
const brushPosition = getBrushPositionForTool(
|
||||||
mapDragPosition,
|
mapBrushPosition,
|
||||||
selectedToolId,
|
selectedToolId,
|
||||||
selectedToolSettings,
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
shapes
|
shapes
|
||||||
);
|
);
|
||||||
if (selectedToolId === "brush") {
|
if (selectedToolId === "brush") {
|
||||||
setDrawingShape((prevShape) => {
|
setDrawingShape((prevShape) => {
|
||||||
const prevPoints = prevShape.data.points;
|
const prevPoints = prevShape.data.points;
|
||||||
if (
|
if (
|
||||||
comparePoints(
|
comparePoints(
|
||||||
prevPoints[prevPoints.length - 1],
|
prevPoints[prevPoints.length - 1],
|
||||||
brushPosition,
|
brushPosition,
|
||||||
0.001
|
0.001
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return prevShape;
|
return prevShape;
|
||||||
}
|
}
|
||||||
const simplified = simplifyPoints(
|
const simplified = simplifyPoints(
|
||||||
[...prevPoints, brushPosition],
|
[...prevPoints, brushPosition],
|
||||||
gridSize,
|
gridSize,
|
||||||
stageScale
|
stageScale
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
|
...prevShape,
|
||||||
|
data: { points: simplified },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else if (selectedToolId === "shape") {
|
||||||
|
setDrawingShape((prevShape) => ({
|
||||||
...prevShape,
|
...prevShape,
|
||||||
data: { points: simplified },
|
data: getUpdatedShapeData(
|
||||||
};
|
prevShape.shapeType,
|
||||||
});
|
prevShape.data,
|
||||||
} else if (selectedToolId === "shape") {
|
brushPosition,
|
||||||
setDrawingShape((prevShape) => ({
|
gridSize
|
||||||
...prevShape,
|
),
|
||||||
data: getUpdatedShapeData(
|
}));
|
||||||
prevShape.shapeType,
|
}
|
||||||
prevShape.data,
|
|
||||||
brushPosition,
|
|
||||||
gridSize
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function endShape() {
|
function endShape() {
|
||||||
if (selectedToolId === "brush" && drawingShape) {
|
if (selectedToolId === "brush" && drawingShape) {
|
||||||
if (drawingShape.data.points.length > 1) {
|
if (drawingShape.data.points.length > 1) {
|
||||||
|
onShapeAdd(drawingShape);
|
||||||
|
}
|
||||||
|
} else if (selectedToolId === "shape" && drawingShape) {
|
||||||
onShapeAdd(drawingShape);
|
onShapeAdd(drawingShape);
|
||||||
}
|
}
|
||||||
} else if (selectedToolId === "shape" && drawingShape) {
|
setDrawingShape(null);
|
||||||
onShapeAdd(drawingShape);
|
|
||||||
}
|
}
|
||||||
setDrawingShape(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (stageDragState) {
|
switch (brushState) {
|
||||||
case "first":
|
case "first":
|
||||||
startShape();
|
startShape();
|
||||||
return;
|
return;
|
||||||
case "dragging":
|
case "drawing":
|
||||||
continueShape();
|
continueShape();
|
||||||
return;
|
return;
|
||||||
case "last":
|
case "last":
|
||||||
endShape();
|
endShape();
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [
|
},
|
||||||
stageDragState,
|
[
|
||||||
mapDragPosition,
|
selectedToolId,
|
||||||
selectedToolId,
|
selectedToolSettings,
|
||||||
selectedToolSettings,
|
gridSize,
|
||||||
isEditing,
|
stageScale,
|
||||||
gridSize,
|
onShapeAdd,
|
||||||
stageScale,
|
shapes,
|
||||||
onShapeAdd,
|
drawingShape,
|
||||||
shapes,
|
]
|
||||||
drawingShape,
|
);
|
||||||
]);
|
|
||||||
|
useMapBrush(isEditing, handleShapeDraw);
|
||||||
|
|
||||||
function handleShapeClick(_, shape) {
|
function handleShapeClick(_, shape) {
|
||||||
if (selectedToolId === "erase") {
|
if (selectedToolId === "erase") {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useContext, useState, useCallback } from "react";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
import { Group, Line } from "react-konva";
|
import { Group, Line } from "react-konva";
|
||||||
import useImage from "use-image";
|
import useImage from "use-image";
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from "../../helpers/drawing";
|
} from "../../helpers/drawing";
|
||||||
|
|
||||||
import colors from "../../helpers/colors";
|
import colors from "../../helpers/colors";
|
||||||
|
import useMapBrush from "../../helpers/useMapBrush";
|
||||||
|
|
||||||
function MapFog({
|
function MapFog({
|
||||||
shapes,
|
shapes,
|
||||||
@ -25,13 +26,7 @@ function MapFog({
|
|||||||
selectedToolSettings,
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { stageScale, mapWidth, mapHeight } = useContext(MapInteractionContext);
|
||||||
stageDragState,
|
|
||||||
mapDragPosition,
|
|
||||||
stageScale,
|
|
||||||
mapWidth,
|
|
||||||
mapHeight,
|
|
||||||
} = useContext(MapInteractionContext);
|
|
||||||
const [drawingShape, setDrawingShape] = useState(null);
|
const [drawingShape, setDrawingShape] = useState(null);
|
||||||
|
|
||||||
const isEditing = selectedToolId === "fog";
|
const isEditing = selectedToolId === "fog";
|
||||||
@ -42,105 +37,103 @@ function MapFog({
|
|||||||
|
|
||||||
const [patternImage] = useImage(diagonalPattern);
|
const [patternImage] = useImage(diagonalPattern);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleShapeDraw = useCallback(
|
||||||
if (!isEditing) {
|
(brushState, mapBrushPosition) => {
|
||||||
return;
|
function startShape() {
|
||||||
}
|
const brushPosition = getBrushPositionForTool(
|
||||||
|
mapBrushPosition,
|
||||||
function startShape() {
|
selectedToolId,
|
||||||
const brushPosition = getBrushPositionForTool(
|
selectedToolSettings,
|
||||||
mapDragPosition,
|
gridSize,
|
||||||
selectedToolId,
|
shapes
|
||||||
selectedToolSettings,
|
);
|
||||||
gridSize,
|
if (selectedToolSettings.type === "add") {
|
||||||
shapes
|
setDrawingShape({
|
||||||
);
|
type: "fog",
|
||||||
if (selectedToolSettings.type === "add") {
|
data: { points: [brushPosition] },
|
||||||
setDrawingShape({
|
strokeWidth: 0.5,
|
||||||
type: "fog",
|
color: "black",
|
||||||
data: { points: [brushPosition] },
|
blend: false,
|
||||||
strokeWidth: 0.5,
|
id: shortid.generate(),
|
||||||
color: "black",
|
visible: true,
|
||||||
blend: false,
|
});
|
||||||
id: shortid.generate(),
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function continueShape() {
|
|
||||||
const brushPosition = getBrushPositionForTool(
|
|
||||||
mapDragPosition,
|
|
||||||
selectedToolId,
|
|
||||||
selectedToolSettings,
|
|
||||||
gridSize,
|
|
||||||
shapes
|
|
||||||
);
|
|
||||||
if (selectedToolSettings.type === "add") {
|
|
||||||
setDrawingShape((prevShape) => {
|
|
||||||
const prevPoints = prevShape.data.points;
|
|
||||||
if (
|
|
||||||
comparePoints(
|
|
||||||
prevPoints[prevPoints.length - 1],
|
|
||||||
brushPosition,
|
|
||||||
0.001
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return prevShape;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...prevShape,
|
|
||||||
data: { points: [...prevPoints, brushPosition] },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function endShape() {
|
|
||||||
if (selectedToolSettings.type === "add" && drawingShape) {
|
|
||||||
if (drawingShape.data.points.length > 1) {
|
|
||||||
const shape = {
|
|
||||||
...drawingShape,
|
|
||||||
data: {
|
|
||||||
points: simplifyPoints(
|
|
||||||
drawingShape.data.points,
|
|
||||||
gridSize,
|
|
||||||
// Downscale fog as smoothing doesn't currently work with edge snapping
|
|
||||||
stageScale / 2
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
onShapeAdd(shape);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setDrawingShape(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (stageDragState) {
|
function continueShape() {
|
||||||
case "first":
|
const brushPosition = getBrushPositionForTool(
|
||||||
startShape();
|
mapBrushPosition,
|
||||||
return;
|
selectedToolId,
|
||||||
case "dragging":
|
selectedToolSettings,
|
||||||
continueShape();
|
gridSize,
|
||||||
return;
|
shapes
|
||||||
case "last":
|
);
|
||||||
endShape();
|
if (selectedToolSettings.type === "add") {
|
||||||
return;
|
setDrawingShape((prevShape) => {
|
||||||
default:
|
const prevPoints = prevShape.data.points;
|
||||||
return;
|
if (
|
||||||
}
|
comparePoints(
|
||||||
}, [
|
prevPoints[prevPoints.length - 1],
|
||||||
stageDragState,
|
brushPosition,
|
||||||
mapDragPosition,
|
0.001
|
||||||
selectedToolId,
|
)
|
||||||
selectedToolSettings,
|
) {
|
||||||
isEditing,
|
return prevShape;
|
||||||
gridSize,
|
}
|
||||||
stageScale,
|
return {
|
||||||
onShapeAdd,
|
...prevShape,
|
||||||
shapes,
|
data: { points: [...prevPoints, brushPosition] },
|
||||||
drawingShape,
|
};
|
||||||
]);
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endShape() {
|
||||||
|
if (selectedToolSettings.type === "add" && drawingShape) {
|
||||||
|
if (drawingShape.data.points.length > 1) {
|
||||||
|
const shape = {
|
||||||
|
...drawingShape,
|
||||||
|
data: {
|
||||||
|
points: simplifyPoints(
|
||||||
|
drawingShape.data.points,
|
||||||
|
gridSize,
|
||||||
|
// Downscale fog as smoothing doesn't currently work with edge snapping
|
||||||
|
stageScale / 2
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
onShapeAdd(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDrawingShape(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (brushState) {
|
||||||
|
case "first":
|
||||||
|
startShape();
|
||||||
|
return;
|
||||||
|
case "drawing":
|
||||||
|
continueShape();
|
||||||
|
return;
|
||||||
|
case "last":
|
||||||
|
endShape();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
selectedToolId,
|
||||||
|
selectedToolSettings,
|
||||||
|
gridSize,
|
||||||
|
stageScale,
|
||||||
|
onShapeAdd,
|
||||||
|
shapes,
|
||||||
|
drawingShape,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
useMapBrush(isEditing, handleShapeDraw);
|
||||||
|
|
||||||
function handleShapeClick(_, shape) {
|
function handleShapeClick(_, shape) {
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
|
@ -29,12 +29,12 @@ function MapInteraction({ map, children, controls, selectedToolId }) {
|
|||||||
// "none" | "first" | "dragging" | "last"
|
// "none" | "first" | "dragging" | "last"
|
||||||
const [stageDragState, setStageDragState] = useState("none");
|
const [stageDragState, setStageDragState] = useState("none");
|
||||||
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
const [preventMapInteraction, setPreventMapInteraction] = useState(false);
|
||||||
const [mapDragPosition, setMapDragPosition] = useState({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
const stageWidthRef = useRef(stageWidth);
|
const stageWidthRef = useRef(stageWidth);
|
||||||
const stageHeightRef = useRef(stageHeight);
|
const stageHeightRef = useRef(stageHeight);
|
||||||
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
// Avoid state udpates when panning the map by using a ref and updating the konva element directly
|
||||||
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
const stageTranslateRef = useRef({ x: 0, y: 0 });
|
||||||
|
const mapDragPositionRef = useRef({ x: 0, y: 0 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const layer = mapLayerRef.current;
|
const layer = mapLayerRef.current;
|
||||||
@ -103,9 +103,11 @@ function MapInteraction({ map, children, controls, selectedToolId }) {
|
|||||||
layer.y(newTranslate.y);
|
layer.y(newTranslate.y);
|
||||||
layer.draw();
|
layer.draw();
|
||||||
stageTranslateRef.current = newTranslate;
|
stageTranslateRef.current = newTranslate;
|
||||||
} else {
|
}
|
||||||
setMapDragPosition(getMapDragPosition(xy));
|
mapDragPositionRef.current = getMapDragPosition(xy);
|
||||||
setStageDragState(first ? "first" : last ? "last" : "dragging");
|
const newDragState = first ? "first" : last ? "last" : "dragging";
|
||||||
|
if (stageDragState !== newDragState) {
|
||||||
|
setStageDragState(newDragState);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDragEnd: () => {
|
onDragEnd: () => {
|
||||||
@ -153,7 +155,7 @@ function MapInteraction({ map, children, controls, selectedToolId }) {
|
|||||||
setPreventMapInteraction,
|
setPreventMapInteraction,
|
||||||
mapWidth,
|
mapWidth,
|
||||||
mapHeight,
|
mapHeight,
|
||||||
mapDragPosition,
|
mapDragPositionRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,9 +4,11 @@ const MapInteractionContext = React.createContext({
|
|||||||
stageScale: 1,
|
stageScale: 1,
|
||||||
stageWidth: 1,
|
stageWidth: 1,
|
||||||
stageHeight: 1,
|
stageHeight: 1,
|
||||||
|
stageDragState: "none",
|
||||||
setPreventMapInteraction: () => {},
|
setPreventMapInteraction: () => {},
|
||||||
mapWidth: 1,
|
mapWidth: 1,
|
||||||
mapHeight: 1,
|
mapHeight: 1,
|
||||||
|
mapDragPositionRef: { current: undefined },
|
||||||
});
|
});
|
||||||
export const MapInteractionProvider = MapInteractionContext.Provider;
|
export const MapInteractionProvider = MapInteractionContext.Provider;
|
||||||
|
|
||||||
|
71
src/helpers/useMapBrush.js
Normal file
71
src/helpers/useMapBrush.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useContext, useRef, useEffect } from "react";
|
||||||
|
|
||||||
|
import MapInteractionContext from "../contexts/MapInteractionContext";
|
||||||
|
|
||||||
|
import { compare } from "./vector2";
|
||||||
|
|
||||||
|
import usePrevious from "./usePrevious";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback onBrushUpdate
|
||||||
|
* @param {string} drawState "first" | "drawing" | "last"
|
||||||
|
* @param {Object} brushPosition the normalized x and y coordinates of the brush on the map
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the maps drag position as it changes
|
||||||
|
* @param {boolean} shouldUpdate
|
||||||
|
* @param {onBrushUpdate} onBrushUpdate
|
||||||
|
*/
|
||||||
|
function useMapBrush(shouldUpdate, onBrushUpdate) {
|
||||||
|
const { stageDragState, mapDragPositionRef } = useContext(
|
||||||
|
MapInteractionContext
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestRef = useRef();
|
||||||
|
const previousDragState = usePrevious(stageDragState);
|
||||||
|
const previousBrushPositionRef = useRef(mapDragPositionRef.current);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function updateBrush(forceUpdate) {
|
||||||
|
const drawState =
|
||||||
|
stageDragState === "dragging" ? "drawing" : stageDragState;
|
||||||
|
const brushPosition = mapDragPositionRef.current;
|
||||||
|
const previousBrushPostition = previousBrushPositionRef.current;
|
||||||
|
// Only update brush when it has moved
|
||||||
|
if (
|
||||||
|
!compare(brushPosition, previousBrushPostition, 0.0001) ||
|
||||||
|
forceUpdate
|
||||||
|
) {
|
||||||
|
onBrushUpdate(drawState, brushPosition);
|
||||||
|
previousBrushPositionRef.current = brushPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
if (!shouldUpdate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestRef.current = requestAnimationFrame(animate);
|
||||||
|
updateBrush(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestRef.current = requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
if (stageDragState !== previousDragState && shouldUpdate) {
|
||||||
|
updateBrush(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(requestRef.current);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
shouldUpdate,
|
||||||
|
onBrushUpdate,
|
||||||
|
stageDragState,
|
||||||
|
mapDragPositionRef,
|
||||||
|
previousDragState,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMapBrush;
|
Loading…
Reference in New Issue
Block a user