Added reviewing of results in graphic mode
This commit is contained in:
@@ -7,6 +7,9 @@ using System.Windows.Media;
|
||||
|
||||
namespace FieldVisualizer.Entities.ColorMaps.Factories
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for creating of different color maps
|
||||
/// </summary>
|
||||
public static class ColorMapFactory
|
||||
{
|
||||
public static IColorMap GetColorMap(ColorMapsTypes mapsTypes)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FieldVisualizer.Entities.Values.Primitives
|
||||
{
|
||||
public class CirclePrimitive : ICirclePrimitive
|
||||
{
|
||||
public double Diameter { get; set; }
|
||||
public double Value { get; set; }
|
||||
public double CenterX { get; set; }
|
||||
public double CenterY { get; set; }
|
||||
public double Area => Math.PI * Math.Pow(Diameter, 2) / 4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FieldVisualizer.Entities.Values.Primitives
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent circle primitive
|
||||
/// </summary>
|
||||
public interface ICirclePrimitive : IValuePrimitive
|
||||
{
|
||||
double Diameter { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,6 @@ namespace FieldVisualizer.Entities.Values.Primitives
|
||||
double Value { get; }
|
||||
double CenterX { get; }
|
||||
double CenterY { get; }
|
||||
double Area { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ namespace FieldVisualizer.Entities.Values.Primitives
|
||||
public double Value { get; set; }
|
||||
public double CenterX { get; set; }
|
||||
public double CenterY { get; set; }
|
||||
public double Area => Height * Width;
|
||||
}
|
||||
}
|
||||
|
||||
27
FieldVisualizer/InfraStructures/Comands/RelayCommand.cs
Normal file
27
FieldVisualizer/InfraStructures/Comands/RelayCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FieldVisualizer.Infrastructure.Commands
|
||||
{
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private Action<object> execute;
|
||||
private Func<object, bool> canExecute;
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add => CommandManager.RequerySuggested += value;
|
||||
remove => CommandManager.RequerySuggested -= value;
|
||||
}
|
||||
|
||||
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
|
||||
{
|
||||
this.execute = execute;
|
||||
this.canExecute = canExecute;
|
||||
}
|
||||
|
||||
public bool CanExecute(object parameter) => canExecute == null || canExecute(parameter);
|
||||
|
||||
public void Execute(object parameter) => execute(parameter);
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,6 @@ namespace FieldVisualizer.InfraStructures.Strings
|
||||
public static class ErrorStrings
|
||||
{
|
||||
public static string ColorMapTypeIsUnknown => "#0001: ColorMap type is unknown";
|
||||
public static string PrimitiveTypeIsUnknown => "#0002: Type of primitive is unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,19 @@ namespace FieldVisualizer.Services.ColorServices
|
||||
const byte Alpha = 0xff;
|
||||
public static Color GetColorByValue(IValueRange range, IColorMap map, double val)
|
||||
{
|
||||
double minVal = range.BottomValue;
|
||||
double maxVal = range.TopValue;
|
||||
double minVal = range.BottomValue - 1e-15d*(Math.Abs(range.BottomValue));
|
||||
double maxVal = range.TopValue + 1e-15d * (Math.Abs(range.TopValue));
|
||||
if (range.TopValue == minVal || map.Colors.Count == 0) { return map.Colors[0]; }
|
||||
if (val > maxVal || val < minVal) { return Colors.Gray; }
|
||||
if (val == minVal) { return map.Colors[0]; }
|
||||
if (val == maxVal) { return map.Colors[map.Colors.Count - 1]; }
|
||||
|
||||
double valPerc = (val - minVal) / (maxVal - minVal);// value%
|
||||
double colorPerc = 1d / (map.Colors.Count - 1); // % of each block of color. the last is the "100% Color"
|
||||
//if (valPerc == 1d)
|
||||
//{ return map.Colors[map.Colors.Count - 1]; }
|
||||
double colorPerc = 1d / (map.Colors.Count - 1d); // % of each block of color. the last is the "100% Color"
|
||||
double blockOfColor = valPerc / colorPerc;// the integer part repersents how many block to skip
|
||||
int blockIdx = (int)Math.Truncate(blockOfColor);// Idx of
|
||||
int blockIdx = (int)Math.Truncate(blockOfColor);// Idx of
|
||||
double valPercResidual = valPerc - (blockIdx * colorPerc);//remove the part represented of block
|
||||
double percOfColor = valPercResidual / colorPerc;// % of color of this block that will be filled
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using FieldVisualizer.Entities.Values;
|
||||
using FieldVisualizer.Entities.Values.Primitives;
|
||||
using FieldVisualizer.InfraStructures.Exceptions;
|
||||
using FieldVisualizer.InfraStructures.Strings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -11,7 +13,7 @@ namespace FieldVisualizer.Services.PrimitiveServices
|
||||
{
|
||||
public static class PrimitiveOperations
|
||||
{
|
||||
public static IValueRange GetValuRange(IEnumerable<IValuePrimitive> valuePrimitives)
|
||||
public static IValueRange GetValueRange(IEnumerable<IValuePrimitive> valuePrimitives)
|
||||
{
|
||||
double minVal =0d, maxVal = 0d;
|
||||
foreach (var primitive in valuePrimitives)
|
||||
@@ -57,6 +59,13 @@ namespace FieldVisualizer.Services.PrimitiveServices
|
||||
coords.Add(rectanglePrimitive.CenterX + rectanglePrimitive.Width / 2);
|
||||
coords.Add(rectanglePrimitive.CenterX - rectanglePrimitive.Width / 2);
|
||||
}
|
||||
else if (primitive is ICirclePrimitive)
|
||||
{
|
||||
ICirclePrimitive circlePrimitive = primitive as ICirclePrimitive;
|
||||
coords.Add(circlePrimitive.CenterX + circlePrimitive.Diameter / 2);
|
||||
coords.Add(circlePrimitive.CenterX - circlePrimitive.Diameter / 2);
|
||||
}
|
||||
else { throw new FieldVisulizerException(ErrorStrings.PrimitiveTypeIsUnknown);}
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
@@ -72,6 +81,13 @@ namespace FieldVisualizer.Services.PrimitiveServices
|
||||
coords.Add(rectanglePrimitive.CenterY + rectanglePrimitive.Height / 2);
|
||||
coords.Add(rectanglePrimitive.CenterY - rectanglePrimitive.Height / 2);
|
||||
}
|
||||
else if (primitive is ICirclePrimitive)
|
||||
{
|
||||
ICirclePrimitive circlePrimitive = primitive as ICirclePrimitive;
|
||||
coords.Add(circlePrimitive.CenterY + circlePrimitive.Diameter / 2);
|
||||
coords.Add(circlePrimitive.CenterY - circlePrimitive.Diameter / 2);
|
||||
}
|
||||
else { throw new FieldVisulizerException(ErrorStrings.PrimitiveTypeIsUnknown); }
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ namespace FieldVisualizer.Services.ValueRanges
|
||||
else
|
||||
{
|
||||
double dVal = (valueRange.TopValue - valueRange.BottomValue) / divisionNumber;
|
||||
double currentBottom = valueRange.BottomValue;
|
||||
double startBottom = valueRange.BottomValue;
|
||||
for (int i = 0; i < divisionNumber; i++ )
|
||||
{
|
||||
currentBottom = i * dVal;
|
||||
|
||||
double currentBottom = startBottom + i * dVal;
|
||||
var newRange = new ValueRange() { BottomValue = currentBottom, TopValue = currentBottom + dVal };
|
||||
valueRanges.Add(newRange);
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="RebuildButton" Content="Rebuild" Click="RebuildButton_Click"/>
|
||||
<Button x:Name="ZoomInButton" Content="ZoomIn" Click="ZoomInButton_Click"/>
|
||||
<Button x:Name="ZoomOutButton" Content="ZoomOut" Click="ZoomOutButton_Click"/>
|
||||
<Button x:Name="ChangeColorMapButton" Content="ColorMap" Click="ChangeColorMapButton_Click"/>
|
||||
<Button x:Name="RebuildButton" Content="Rebuild" Command="{Binding RebuildCommand}"/>
|
||||
<Button x:Name="ZoomInButton" Content="ZoomIn" Command="{Binding ZoomInCommand}"/>
|
||||
<Button x:Name="ZoomOutButton" Content="ZoomOut" Command="{Binding ZoomOutCommand}"/>
|
||||
<Button x:Name="ChangeColorMapButton" Content="ColorMap" Command="{Binding ChangeColorMapCommand}"/>
|
||||
</StackPanel>
|
||||
<ScrollViewer Name="WorkPlaneViewer" Grid.Row="1" HorizontalScrollBarVisibility="Visible">
|
||||
<ScrollViewer.Background>
|
||||
|
||||
@@ -2,25 +2,20 @@
|
||||
using FieldVisualizer.Entities.ColorMaps.Factories;
|
||||
using FieldVisualizer.Entities.Values;
|
||||
using FieldVisualizer.Entities.Values.Primitives;
|
||||
using FieldVisualizer.Infrastructure.Commands;
|
||||
using FieldVisualizer.InfraStructures.Enums;
|
||||
using FieldVisualizer.InfraStructures.Exceptions;
|
||||
using FieldVisualizer.InfraStructures.Strings;
|
||||
using FieldVisualizer.Services.ColorServices;
|
||||
using FieldVisualizer.Services.PrimitiveServices;
|
||||
using FieldVisualizer.Services.ValueRanges;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace FieldVisualizer.Windows.UserControls
|
||||
@@ -30,7 +25,12 @@ namespace FieldVisualizer.Windows.UserControls
|
||||
/// </summary>
|
||||
public partial class FieldViewer : UserControl
|
||||
{
|
||||
public IPrimitiveSet _PrimitiveSet { get; set; }
|
||||
public ICommand RebuildCommand { get; }
|
||||
public ICommand ZoomInCommand { get; }
|
||||
public ICommand ZoomOutCommand { get; }
|
||||
public ICommand ChangeColorMapCommand { get; }
|
||||
|
||||
public IPrimitiveSet PrimitiveSet { get; set; }
|
||||
private double dX, dY;
|
||||
private ColorMapsTypes _ColorMapType;
|
||||
private IColorMap _ColorMap;
|
||||
@@ -43,34 +43,38 @@ namespace FieldVisualizer.Windows.UserControls
|
||||
{
|
||||
InitializeComponent();
|
||||
_ColorMapType = ColorMapsTypes.FullSpectrum;
|
||||
DataContext = this;
|
||||
RebuildCommand = new RelayCommand(o => ProcessPrimitives(), o => PrimitiveValidation());
|
||||
ZoomInCommand = new RelayCommand(o => Zoom(1.2), o => PrimitiveValidation());
|
||||
ZoomOutCommand = new RelayCommand(o => Zoom(0.8), o => PrimitiveValidation());
|
||||
ChangeColorMapCommand = new RelayCommand(o => ChangeColorMap(), o => PrimitiveValidation());
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
_ValueRange = PrimitiveOperations.GetValuRange(_PrimitiveSet.ValuePrimitives);
|
||||
if (PrimitiveValidation() == false) { return; }
|
||||
_ValueRange = PrimitiveOperations.GetValueRange(PrimitiveSet.ValuePrimitives);
|
||||
_ValueRanges = ValueRangeOperations.DivideValueRange(_ValueRange, RangeNumber);
|
||||
_ColorMap = ColorMapFactory.GetColorMap(_ColorMapType);
|
||||
_ValueColorRanges = ColorOperations.GetValueColorRanges(_ValueRange, _ValueRanges, _ColorMap);
|
||||
if ((_PrimitiveSet is null) == false)
|
||||
if ((PrimitiveSet is null) == false)
|
||||
{
|
||||
ProcessPrimitives();
|
||||
LegendViewer.ValueColorRanges = _ValueColorRanges;
|
||||
LegendViewer.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPrimitives()
|
||||
{
|
||||
WorkPlaneCanvas.Children.Clear();
|
||||
double sizeX = PrimitiveOperations.GetSizeX(_PrimitiveSet.ValuePrimitives);
|
||||
double sizeY = PrimitiveOperations.GetSizeY(_PrimitiveSet.ValuePrimitives);
|
||||
dX = PrimitiveOperations.GetMinMaxX(_PrimitiveSet.ValuePrimitives)[0];
|
||||
dY = PrimitiveOperations.GetMinMaxY(_PrimitiveSet.ValuePrimitives)[0];
|
||||
double sizeX = PrimitiveOperations.GetSizeX(PrimitiveSet.ValuePrimitives);
|
||||
double sizeY = PrimitiveOperations.GetSizeY(PrimitiveSet.ValuePrimitives);
|
||||
dX = PrimitiveOperations.GetMinMaxX(PrimitiveSet.ValuePrimitives)[0];
|
||||
dY = PrimitiveOperations.GetMinMaxY(PrimitiveSet.ValuePrimitives)[0];
|
||||
WorkPlaneCanvas.Width = Math.Abs(sizeX);
|
||||
WorkPlaneCanvas.Height = Math.Abs(sizeY);
|
||||
WorkPlaneBox.Width = WorkPlaneViewer.ActualWidth - 50;
|
||||
WorkPlaneBox.Height = WorkPlaneViewer.ActualHeight - 50;
|
||||
foreach (var primitive in _PrimitiveSet.ValuePrimitives)
|
||||
foreach (var primitive in PrimitiveSet.ValuePrimitives)
|
||||
{
|
||||
if (primitive is IRectanglePrimitive)
|
||||
{
|
||||
@@ -78,58 +82,77 @@ namespace FieldVisualizer.Windows.UserControls
|
||||
Rectangle rectangle = ProcessRectanglePrimitive(rectanglePrimitive);
|
||||
WorkPlaneCanvas.Children.Add(rectangle);
|
||||
}
|
||||
else if (primitive is ICirclePrimitive)
|
||||
{
|
||||
ICirclePrimitive circlePrimitive = primitive as ICirclePrimitive;
|
||||
Ellipse ellipse = ProcessCirclePrimitive(circlePrimitive);
|
||||
WorkPlaneCanvas.Children.Add(ellipse);
|
||||
}
|
||||
else { throw new FieldVisulizerException(ErrorStrings.PrimitiveTypeIsUnknown);}
|
||||
}
|
||||
}
|
||||
|
||||
private void WorkPlaneBox_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
WorkPlaneBox.Height *= WorkPlaneBox.ActualHeight * 0.5;
|
||||
WorkPlaneBox.Width *= WorkPlaneBox.ActualWidth * 0.5;
|
||||
}
|
||||
|
||||
private Rectangle ProcessRectanglePrimitive(IRectanglePrimitive rectanglePrimitive)
|
||||
{
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.Height = rectanglePrimitive.Height;
|
||||
rectangle.Width = rectanglePrimitive.Width;
|
||||
Rectangle rectangle = new Rectangle
|
||||
{
|
||||
Height = rectanglePrimitive.Height,
|
||||
Width = rectanglePrimitive.Width
|
||||
};
|
||||
double addX = rectanglePrimitive.Width / 2;
|
||||
double addY = rectanglePrimitive.Height / 2;
|
||||
ProcessShape(rectangle, rectanglePrimitive, addX, addY);
|
||||
return rectangle;
|
||||
}
|
||||
private Ellipse ProcessCirclePrimitive(ICirclePrimitive circlePrimitive)
|
||||
{
|
||||
Ellipse ellipse = new Ellipse
|
||||
{
|
||||
Height = circlePrimitive.Diameter,
|
||||
Width = circlePrimitive.Diameter
|
||||
};
|
||||
double addX = circlePrimitive.Diameter / 2;
|
||||
double addY = circlePrimitive.Diameter / 2;
|
||||
|
||||
ProcessShape(ellipse, circlePrimitive, addX, addY);
|
||||
return ellipse;
|
||||
}
|
||||
private void ProcessShape(Shape shape, IValuePrimitive valuePrimitive, double addX, double addY)
|
||||
{
|
||||
SolidColorBrush brush = new SolidColorBrush();
|
||||
brush.Color = ColorOperations.GetColorByValue(_ValueRange, _ColorMap, rectanglePrimitive.Value);
|
||||
brush.Color = ColorOperations.GetColorByValue(_ValueRange, _ColorMap, valuePrimitive.Value);
|
||||
foreach (var valueRange in _ValueColorRanges)
|
||||
{
|
||||
if (rectanglePrimitive.Value >= valueRange.BottomValue & rectanglePrimitive.Value <= valueRange.TopValue & (! valueRange.IsActive))
|
||||
if (valuePrimitive.Value >= valueRange.BottomValue & valuePrimitive.Value <= valueRange.TopValue & (!valueRange.IsActive))
|
||||
{
|
||||
brush.Color = Colors.Gray;
|
||||
}
|
||||
}
|
||||
rectangle.ToolTip = rectanglePrimitive.Value;
|
||||
rectangle.Fill = brush;
|
||||
Canvas.SetLeft(rectangle, rectanglePrimitive.CenterX - dX);
|
||||
Canvas.SetTop(rectangle, rectanglePrimitive.CenterY - dY);
|
||||
return rectangle;
|
||||
shape.ToolTip = valuePrimitive.Value;
|
||||
shape.Tag = valuePrimitive;
|
||||
shape.Fill = brush;
|
||||
Canvas.SetLeft(shape, valuePrimitive.CenterX - addX - dX);
|
||||
Canvas.SetTop(shape, valuePrimitive.CenterY - addY - dY);
|
||||
}
|
||||
|
||||
private void RebuildButton_Click(object sender, RoutedEventArgs e)
|
||||
private void Zoom(double coefficient)
|
||||
{
|
||||
ProcessPrimitives();
|
||||
WorkPlaneBox.Width *= coefficient;
|
||||
WorkPlaneBox.Height *= coefficient;
|
||||
}
|
||||
|
||||
private void ZoomInButton_Click(object sender, RoutedEventArgs e)
|
||||
private void ChangeColorMap()
|
||||
{
|
||||
WorkPlaneBox.Width *= 1.2;
|
||||
WorkPlaneBox.Height *= 1.2;
|
||||
}
|
||||
|
||||
private void ChangeColorMapButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_ColorMapType < ColorMapsTypes.BlueToWhite) { _ColorMapType++;}
|
||||
else { _ColorMapType = 0;}
|
||||
//Iterate all available color maps one by one
|
||||
try
|
||||
{
|
||||
_ColorMapType++;
|
||||
IColorMap colorMap = ColorMapFactory.GetColorMap(_ColorMapType);
|
||||
}
|
||||
catch (Exception ex) { _ColorMapType = 0; }
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void ZoomOutButton_Click(object sender, RoutedEventArgs e)
|
||||
private bool PrimitiveValidation()
|
||||
{
|
||||
WorkPlaneBox.Width *= 0.8;
|
||||
WorkPlaneBox.Height *= 0.8;
|
||||
if (PrimitiveSet == null || PrimitiveSet.ValuePrimitives.Count() == 0) { return false; }
|
||||
else return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid x:Name="grid">
|
||||
<StackPanel>
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FF868686"></TextBlock>
|
||||
<ListBox Name="LegendBox" ItemsSource="{Binding}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
@@ -32,6 +33,7 @@
|
||||
</LinearGradientBrush>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FF868686" Text="{Binding AverageValue}"/>
|
||||
<Rectangle Grid.Column="3" Margin="0,2,0,2" ToolTip="{Binding Path=TopValue}">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{Binding Path=TopColor}"/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FieldVisualizer.Windows.UserControls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -21,20 +22,27 @@ namespace FieldVisualizer.Windows
|
||||
/// </summary>
|
||||
public partial class WndFieldViewer : Window
|
||||
{
|
||||
public ObservableCollection<IPrimitiveSet> PrimitiveSets { get; private set; }
|
||||
public WndFieldViewer(IEnumerable<IPrimitiveSet> primitiveSets)
|
||||
{
|
||||
InitializeComponent();
|
||||
this.DataContext = primitiveSets;
|
||||
PrimitiveSets = new ObservableCollection<IPrimitiveSet>();
|
||||
foreach (var primitiveSet in primitiveSets)
|
||||
{
|
||||
FieldViewerControl._PrimitiveSet = primitiveSet;
|
||||
PrimitiveSets.Add(primitiveSet);
|
||||
}
|
||||
this.DataContext = PrimitiveSets;
|
||||
|
||||
}
|
||||
|
||||
private void SetsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
FieldViewerControl.Refresh();
|
||||
ListBox lb = sender as ListBox;
|
||||
if (lb.SelectedItem != null)
|
||||
{
|
||||
FieldViewerControl.PrimitiveSet = lb.SelectedItem as IPrimitiveSet;
|
||||
FieldViewerControl.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user