mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Visual scripting - Added pin disconnect with MMB
Visual scripting - Added min and max node
This commit is contained in:
parent
b6e0c0fb66
commit
dc17253518
@ -0,0 +1,43 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node editor command that can be used to connect two pins.
|
||||||
|
/// </summary>
|
||||||
|
public class DisconnectPins : INodeEditorCommand
|
||||||
|
{
|
||||||
|
private readonly IPin _pin;
|
||||||
|
private readonly List<IPin> _originalConnections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="DisconnectPins" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pin">The pin to disconnect.</param>
|
||||||
|
public DisconnectPins(IPin pin)
|
||||||
|
{
|
||||||
|
_pin = pin;
|
||||||
|
_originalConnections = new List<IPin>(_pin.ConnectedTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of INodeEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => "Disconnect pins";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_pin.DisconnectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
foreach (IPin pin in _originalConnections)
|
||||||
|
_pin.ConnectTo(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -106,8 +106,8 @@ public interface INodeVmFactory : IVmFactory
|
|||||||
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
|
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
|
||||||
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to);
|
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to);
|
||||||
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
|
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
|
||||||
InputPinViewModel InputPinViewModel(IPin inputPin);
|
InputPinViewModel InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel);
|
||||||
OutputPinViewModel OutputPinViewModel(IPin outputPin);
|
OutputPinViewModel OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel);
|
||||||
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||||
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,12 +47,12 @@ public class NodeViewModel : ActivatableViewModelBase
|
|||||||
// Create observable collections split up by direction
|
// Create observable collections split up by direction
|
||||||
nodePins.Connect()
|
nodePins.Connect()
|
||||||
.Filter(n => n.Direction == PinDirection.Input)
|
.Filter(n => n.Direction == PinDirection.Input)
|
||||||
.Transform(p => (PinViewModel) nodeVmFactory.InputPinViewModel(p))
|
.Transform(p => (PinViewModel) nodeVmFactory.InputPinViewModel(p, nodeScriptViewModel))
|
||||||
.Bind(out ReadOnlyObservableCollection<PinViewModel> inputPins)
|
.Bind(out ReadOnlyObservableCollection<PinViewModel> inputPins)
|
||||||
.Subscribe();
|
.Subscribe();
|
||||||
nodePins.Connect()
|
nodePins.Connect()
|
||||||
.Filter(n => n.Direction == PinDirection.Output)
|
.Filter(n => n.Direction == PinDirection.Output)
|
||||||
.Transform(p => (PinViewModel) nodeVmFactory.OutputPinViewModel(p))
|
.Transform(p => (PinViewModel) nodeVmFactory.OutputPinViewModel(p, nodeScriptViewModel))
|
||||||
.Bind(out ReadOnlyObservableCollection<PinViewModel> outputPins)
|
.Bind(out ReadOnlyObservableCollection<PinViewModel> outputPins)
|
||||||
.Subscribe();
|
.Subscribe();
|
||||||
InputPinViewModels = inputPins;
|
InputPinViewModels = inputPins;
|
||||||
|
|||||||
@ -6,19 +6,21 @@ namespace Artemis.UI.Screens.VisualScripting.Pins;
|
|||||||
|
|
||||||
public class InputPinCollectionViewModel : PinCollectionViewModel
|
public class InputPinCollectionViewModel : PinCollectionViewModel
|
||||||
{
|
{
|
||||||
|
private readonly NodeScriptViewModel _nodeScriptViewModel;
|
||||||
private readonly INodeVmFactory _nodeVmFactory;
|
private readonly INodeVmFactory _nodeVmFactory;
|
||||||
public IPinCollection InputPinCollection { get; }
|
public IPinCollection InputPinCollection { get; }
|
||||||
|
|
||||||
public InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
|
public InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
|
||||||
: base(inputPinCollection, nodeScriptViewModel, nodeEditorService)
|
: base(inputPinCollection, nodeScriptViewModel, nodeEditorService)
|
||||||
{
|
{
|
||||||
|
_nodeScriptViewModel = nodeScriptViewModel;
|
||||||
_nodeVmFactory = nodeVmFactory;
|
_nodeVmFactory = nodeVmFactory;
|
||||||
InputPinCollection = inputPinCollection;
|
InputPinCollection = inputPinCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override PinViewModel CreatePinViewModel(IPin pin)
|
protected override PinViewModel CreatePinViewModel(IPin pin)
|
||||||
{
|
{
|
||||||
PinViewModel vm = _nodeVmFactory.InputPinViewModel(pin);
|
PinViewModel vm = _nodeVmFactory.InputPinViewModel(pin, _nodeScriptViewModel);
|
||||||
vm.RemovePin = RemovePin;
|
vm.RemovePin = RemovePin;
|
||||||
return vm;
|
return vm;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<StyleInclude Source="/Screens/VisualScripting/VisualScripting.axaml" />
|
<StyleInclude Source="/Screens/VisualScripting/VisualScripting.axaml" />
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6">
|
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6" PointerReleased="PinContainer_OnPointerReleased">
|
||||||
<Border Name="PinPoint">
|
<Border Name="PinPoint">
|
||||||
<Border Name="VisualPinPoint"
|
<Border Name="VisualPinPoint"
|
||||||
BorderBrush="{CompiledBinding PinColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
BorderBrush="{CompiledBinding PinColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
using System;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
@ -15,4 +17,10 @@ public class InputPinView : PinView
|
|||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PinContainer_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.InitialPressMouseButton == MouseButton.Middle)
|
||||||
|
ViewModel?.DisconnectPin.Execute().Subscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,13 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
public class InputPinViewModel : PinViewModel
|
public class InputPinViewModel : PinViewModel
|
||||||
{
|
{
|
||||||
public InputPinViewModel(IPin inputPin, INodeService nodeService) : base(inputPin, nodeService)
|
public InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||||
|
: base(inputPin, nodeScriptViewModel, nodeService, nodeEditorService)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,19 +6,21 @@ namespace Artemis.UI.Screens.VisualScripting.Pins;
|
|||||||
|
|
||||||
public class OutputPinCollectionViewModel : PinCollectionViewModel
|
public class OutputPinCollectionViewModel : PinCollectionViewModel
|
||||||
{
|
{
|
||||||
|
private readonly NodeScriptViewModel _nodeScriptViewModel;
|
||||||
private readonly INodeVmFactory _nodeVmFactory;
|
private readonly INodeVmFactory _nodeVmFactory;
|
||||||
public IPinCollection OutputPinCollection { get; }
|
public IPinCollection OutputPinCollection { get; }
|
||||||
|
|
||||||
public OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
|
public OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
|
||||||
: base(outputPinCollection, nodeScriptViewModel, nodeEditorService)
|
: base(outputPinCollection, nodeScriptViewModel, nodeEditorService)
|
||||||
{
|
{
|
||||||
|
_nodeScriptViewModel = nodeScriptViewModel;
|
||||||
_nodeVmFactory = nodeVmFactory;
|
_nodeVmFactory = nodeVmFactory;
|
||||||
OutputPinCollection = outputPinCollection;
|
OutputPinCollection = outputPinCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override PinViewModel CreatePinViewModel(IPin pin)
|
protected override PinViewModel CreatePinViewModel(IPin pin)
|
||||||
{
|
{
|
||||||
PinViewModel vm = _nodeVmFactory.OutputPinViewModel(pin);
|
PinViewModel vm = _nodeVmFactory.OutputPinViewModel(pin, _nodeScriptViewModel);
|
||||||
vm.RemovePin = RemovePin;
|
vm.RemovePin = RemovePin;
|
||||||
return vm;
|
return vm;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<StyleInclude Source="/Screens/VisualScripting/VisualScripting.axaml" />
|
<StyleInclude Source="/Screens/VisualScripting/VisualScripting.axaml" />
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6">
|
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6" PointerReleased="PinContainer_OnPointerReleased">
|
||||||
<TextBlock Name="PinName" VerticalAlignment="Center" Text="{CompiledBinding Pin.Name}" />
|
<TextBlock Name="PinName" VerticalAlignment="Center" Text="{CompiledBinding Pin.Name}" />
|
||||||
<Border Name="PinPoint">
|
<Border Name="PinPoint">
|
||||||
<Border Name="VisualPinPoint"
|
<Border Name="VisualPinPoint"
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
using System;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
@ -15,4 +17,10 @@ public class OutputPinView : PinView
|
|||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PinContainer_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.InitialPressMouseButton == MouseButton.Middle)
|
||||||
|
ViewModel?.DisconnectPin.Execute().Subscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,13 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
public class OutputPinViewModel : PinViewModel
|
public class OutputPinViewModel : PinViewModel
|
||||||
{
|
{
|
||||||
public OutputPinViewModel(IPin outputPin, INodeService nodeService) : base(outputPin, nodeService)
|
public OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||||
|
: base(outputPin, nodeScriptViewModel, nodeService, nodeEditorService)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,6 +6,8 @@ using Artemis.Core.Events;
|
|||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Extensions;
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
@ -22,11 +24,13 @@ public abstract class PinViewModel : ActivatableViewModelBase
|
|||||||
private Color _pinColor;
|
private Color _pinColor;
|
||||||
private Color _darkenedPinColor;
|
private Color _darkenedPinColor;
|
||||||
|
|
||||||
protected PinViewModel(IPin pin, INodeService nodeService)
|
protected PinViewModel(IPin pin, NodeScriptViewModel nodeScriptViewModel, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||||
{
|
{
|
||||||
_nodeService = nodeService;
|
_nodeService = nodeService;
|
||||||
|
|
||||||
Pin = pin;
|
Pin = pin;
|
||||||
|
DisconnectPin = ReactiveCommand.Create(() => nodeEditorService.ExecuteCommand(nodeScriptViewModel.NodeScript, new DisconnectPins(Pin)));
|
||||||
|
|
||||||
SourceList<IPin> connectedPins = new();
|
SourceList<IPin> connectedPins = new();
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
@ -78,6 +82,8 @@ public abstract class PinViewModel : ActivatableViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _removePin, value);
|
set => RaiseAndSetIfChanged(ref _removePin, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> DisconnectPin { get; }
|
||||||
|
|
||||||
public bool IsCompatibleWith(PinViewModel pinViewModel)
|
public bool IsCompatibleWith(PinViewModel pinViewModel)
|
||||||
{
|
{
|
||||||
if (pinViewModel.Pin.Direction == Pin.Direction || pinViewModel.Pin.Node == Pin.Node)
|
if (pinViewModel.Pin.Direction == Pin.Direction || pinViewModel.Pin.Node == Pin.Node)
|
||||||
|
|||||||
@ -8,19 +8,19 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.15"/>
|
<PackageReference Include="Avalonia" Version="0.10.15" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15"/>
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
|
||||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14"/>
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
|
||||||
<PackageReference Include="Ninject" Version="3.3.4"/>
|
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||||
<PackageReference Include="NoStringEvaluating" Version="2.2.2"/>
|
<PackageReference Include="NoStringEvaluating" Version="2.2.2" />
|
||||||
<PackageReference Include="ReactiveUI" Version="17.1.50"/>
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1"/>
|
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1"/>
|
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj"/>
|
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj"/>
|
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
35
src/Artemis.VisualScripting/Nodes/Mathematics/MaxNode.cs
Normal file
35
src/Artemis.VisualScripting/Nodes/Mathematics/MaxNode.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Mathematics;
|
||||||
|
|
||||||
|
[Node("Max", "Outputs the largest of the connected numeric values.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
|
||||||
|
public class MaxNumericsNode : Node
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public MaxNumericsNode()
|
||||||
|
: base("Max", "Outputs the largest of the connected numeric values.")
|
||||||
|
{
|
||||||
|
Values = CreateInputPinCollection<Numeric>("Values", 2);
|
||||||
|
Max = CreateOutputPin<Numeric>("Max");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Max.Value = Values.Values.Max();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPinCollection<Numeric> Values { get; }
|
||||||
|
|
||||||
|
public OutputPin<Numeric> Max { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
35
src/Artemis.VisualScripting/Nodes/Mathematics/MinNode.cs
Normal file
35
src/Artemis.VisualScripting/Nodes/Mathematics/MinNode.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Mathematics;
|
||||||
|
|
||||||
|
[Node("Min", "Outputs the smallest of the connected numeric values.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
|
||||||
|
public class MinNumericsNode : Node
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public MinNumericsNode()
|
||||||
|
: base("Min", "Outputs the smallest of the connected numeric values.")
|
||||||
|
{
|
||||||
|
Values = CreateInputPinCollection<Numeric>("Values", 2);
|
||||||
|
Min = CreateOutputPin<Numeric>("Min");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
Min.Value = Values.Values.Min();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties & Fields
|
||||||
|
|
||||||
|
public InputPinCollection<Numeric> Values { get; }
|
||||||
|
|
||||||
|
public OutputPin<Numeric> Min { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user