mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-05-04 15:01:29 +08:00
连接点可以添加文字
This commit is contained in:
@@ -1103,6 +1103,8 @@ namespace AIStudio.Wpf.DiagramApp.ViewModels
|
|||||||
|
|
||||||
private void SelectedColorExecuted(object para)
|
private void SelectedColorExecuted(object para)
|
||||||
{
|
{
|
||||||
|
if (para == null) return;
|
||||||
|
|
||||||
switch (ColorType)
|
switch (ColorType)
|
||||||
{
|
{
|
||||||
case Models.ColorType.Text: DiagramsViewModel?.SetFont(new FontViewModel() { FontColor = (Color)para }, "FontColor"); break;
|
case Models.ColorType.Text: DiagramsViewModel?.SetFont(new FontViewModel() { FontColor = (Color)para }, "FontColor"); break;
|
||||||
|
|||||||
@@ -998,10 +998,10 @@
|
|||||||
<Fluent:Button Margin="5" Command="{Binding DistributeVerticalCommand}" Size="Small" Icon="/AIStudio.Wpf.DiagramApp;component/Images/DistributeObjectsVertical.png"/>
|
<Fluent:Button Margin="5" Command="{Binding DistributeVerticalCommand}" Size="Small" Icon="/AIStudio.Wpf.DiagramApp;component/Images/DistributeObjectsVertical.png"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Fluent:DropDownButton>
|
</Fluent:DropDownButton>
|
||||||
<Fluent:DropDownButton Header="翻转" Icon="{iconPacks:VaadinIcons Kind=FlipH}" Width="50" VerticalAlignment="Top" IsEnabled="{Binding SelectedItem.ShowRotate}">
|
<Fluent:SplitButton Header="翻转" Icon="{iconPacks:VaadinIcons Kind=FlipH}" Width="50" VerticalAlignment="Top" IsCheckable="True" IsChecked="{Binding SelectedItem.ShowRotate}">
|
||||||
<Fluent:DropDownButton.LargeIcon>
|
<Fluent:SplitButton.LargeIcon>
|
||||||
<iconPacks:PackIconVaadinIcons Kind="FlipH" VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
<iconPacks:PackIconVaadinIcons Kind="FlipH" VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||||||
</Fluent:DropDownButton.LargeIcon>
|
</Fluent:SplitButton.LargeIcon>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock Margin="5" Text="镜像" VerticalAlignment="Center"/>
|
<TextBlock Margin="5" Text="镜像" VerticalAlignment="Center"/>
|
||||||
<Fluent:ToggleButton IsChecked="{Binding SelectedItem.ScaleX,Converter={dd:ConverterValueMapToBool Parameter='1'}, ConverterParameter='-1'}"
|
<Fluent:ToggleButton IsChecked="{Binding SelectedItem.ScaleX,Converter={dd:ConverterValueMapToBool Parameter='1'}, ConverterParameter='-1'}"
|
||||||
@@ -1015,7 +1015,7 @@
|
|||||||
<Fluent:Spinner Margin="5" Width="60" Size="Small" Value="{Binding SelectedItem.Angle,Mode=TwoWay}" Maximum="359" Minimum="0"
|
<Fluent:Spinner Margin="5" Width="60" Size="Small" Value="{Binding SelectedItem.Angle,Mode=TwoWay}" Maximum="359" Minimum="0"
|
||||||
Format="0 deg" />
|
Format="0 deg" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Fluent:DropDownButton>
|
</Fluent:SplitButton>
|
||||||
<Fluent:DropDownButton Header="大小" Icon="{iconPacks:FontAwesome Kind=ArrowsAltSolid}" Width="50" VerticalAlignment="Top">
|
<Fluent:DropDownButton Header="大小" Icon="{iconPacks:FontAwesome Kind=ArrowsAltSolid}" Width="50" VerticalAlignment="Top">
|
||||||
<Fluent:DropDownButton.LargeIcon>
|
<Fluent:DropDownButton.LargeIcon>
|
||||||
<iconPacks:PackIconFontAwesome Kind="ExpandArrowsAltSolid" VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
<iconPacks:PackIconFontAwesome Kind="ExpandArrowsAltSolid" VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" />
|
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
|
<PackageReference Include="SvgPathProperties" Version="1.1.2" />
|
||||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||||
<PackageReference Include="WpfAnimatedGif" Version="2.0.0" />
|
<PackageReference Include="WpfAnimatedGif" Version="2.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -43,33 +43,28 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
|
|
||||||
static void Fe_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
static void Fe_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
SelectableDesignerItemViewModelBase selectableDesignerItemViewModelBase =
|
if (((FrameworkElement)sender).DataContext is ISelectable selectable)
|
||||||
(SelectableDesignerItemViewModelBase)((FrameworkElement)sender).DataContext;
|
|
||||||
|
|
||||||
if(selectableDesignerItemViewModelBase != null && selectableDesignerItemViewModelBase.IsHitTestVisible)
|
|
||||||
{
|
{
|
||||||
if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None)
|
if (selectable.IsHitTestVisible)
|
||||||
{
|
{
|
||||||
if ((Keyboard.Modifiers & (ModifierKeys.Shift)) != ModifierKeys.None)
|
if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None)
|
||||||
{
|
{
|
||||||
selectableDesignerItemViewModelBase.IsSelected = !selectableDesignerItemViewModelBase.IsSelected;
|
if ((Keyboard.Modifiers & (ModifierKeys.Shift)) != ModifierKeys.None)
|
||||||
}
|
{
|
||||||
|
selectable.IsSelected = !selectable.IsSelected;
|
||||||
|
}
|
||||||
|
|
||||||
if ((Keyboard.Modifiers & (ModifierKeys.Control)) != ModifierKeys.None)
|
if ((Keyboard.Modifiers & (ModifierKeys.Control)) != ModifierKeys.None)
|
||||||
|
{
|
||||||
|
selectable.IsSelected = !selectable.IsSelected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!selectable.IsSelected)
|
||||||
{
|
{
|
||||||
selectableDesignerItemViewModelBase.IsSelected = !selectableDesignerItemViewModelBase.IsSelected;
|
selectable.AddToSelection(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!selectableDesignerItemViewModelBase.IsSelected)
|
}
|
||||||
{
|
|
||||||
foreach (SelectableDesignerItemViewModelBase item in selectableDesignerItemViewModelBase.Parent.SelectedItems)
|
|
||||||
item.IsSelected = false;
|
|
||||||
|
|
||||||
selectableDesignerItemViewModelBase.Parent.SelectedItems.Clear();
|
|
||||||
//selectableDesignerItemViewModelBase.IsSelected = true;
|
|
||||||
selectableDesignerItemViewModelBase.Parent.SelectionService.AddToSelection(selectableDesignerItemViewModelBase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,7 +413,9 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
|
|
||||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPreviewKeyDown(e);
|
base.OnPreviewKeyDown(e);
|
||||||
|
|
||||||
|
//FFE6E6FA
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Size MeasureOverride(Size constraint)
|
protected override Size MeasureOverride(Size constraint)
|
||||||
|
|||||||
@@ -116,5 +116,11 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
Vertices = SerializeHelper.DeserializePointList(value);
|
Vertices = SerializeHelper.DeserializePointList(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[XmlArray]
|
||||||
|
public List<ConnectorItem> Connectors
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AIStudio.Wpf.DiagramDesigner.Models.Serializables
|
||||||
|
{
|
||||||
|
public class LinkLabelItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -541,11 +541,6 @@
|
|||||||
<Canvas.InputBindings>
|
<Canvas.InputBindings>
|
||||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding EditCommand}" CommandParameter="{Binding }" />
|
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding EditCommand}" CommandParameter="{Binding }" />
|
||||||
</Canvas.InputBindings>
|
</Canvas.InputBindings>
|
||||||
<!--<Polyline x:Name="poly"
|
|
||||||
Points="{Binding Path=Vertices, Converter={x:Static s:ConnectionPointConverter.Instance}}"
|
|
||||||
Stroke="{Binding ColorViewModel.LineColor,Converter={StaticResource ColorBrushConverter}}"
|
|
||||||
StrokeThickness="{Binding ColorViewModel.LineWidth}"
|
|
||||||
StrokeDashArray="{Binding ColorViewModel.LineDashStyle,Converter={StaticResource LineDashConverter}}" />-->
|
|
||||||
<Path x:Name="poly" Stroke="{Binding ColorViewModel.LineColor,Converter={StaticResource ColorBrushConverter}}"
|
<Path x:Name="poly" Stroke="{Binding ColorViewModel.LineColor,Converter={StaticResource ColorBrushConverter}}"
|
||||||
StrokeThickness="{Binding ColorViewModel.LineWidth}"
|
StrokeThickness="{Binding ColorViewModel.LineWidth}"
|
||||||
StrokeDashArray="{Binding ColorViewModel.LineDashStyle,Converter={StaticResource LineDashConverter}}">
|
StrokeDashArray="{Binding ColorViewModel.LineDashStyle,Converter={StaticResource LineDashConverter}}">
|
||||||
@@ -600,7 +595,7 @@
|
|||||||
</c:DragThumb.InputBindings>
|
</c:DragThumb.InputBindings>
|
||||||
</c:DragThumb>
|
</c:DragThumb>
|
||||||
|
|
||||||
<s:PointContainer x:Name="PART_PointContainer" Style="{StaticResource innerConnectorContainer}" ItemsSource="{Binding Vertices}" Visibility="Collapsed">
|
<s:PointContainer x:Name="PART_VerticesContainer" Style="{StaticResource innerConnectorContainer}" ItemsSource="{Binding Vertices}" Visibility="Collapsed">
|
||||||
<s:PointContainer.ItemTemplate>
|
<s:PointContainer.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -616,7 +611,24 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</s:PointContainer.Resources>
|
</s:PointContainer.Resources>
|
||||||
</s:PointContainer>
|
</s:PointContainer>
|
||||||
|
|
||||||
|
<s:PointContainer x:Name="PART_LabelsContainer" Style="{StaticResource innerConnectorContainer}" ItemsSource="{Binding Labels}" >
|
||||||
|
<s:PointContainer.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid s:SelectionProps.EnabledForSelection="True" Background="{Binding ColorViewModel.FillColor,Converter={StaticResource ColorBrushConverter}}">
|
||||||
|
<c:PointDragThumb Cursor="SizeAll" Opacity="0"/>
|
||||||
|
<s:TextControl Margin="5" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</s:PointContainer.ItemTemplate>
|
||||||
|
<s:PointContainer.Resources>
|
||||||
|
<Style TargetType="{x:Type ContentPresenter}">
|
||||||
|
<Setter Property="Canvas.Left" Value="{Binding Left}" />
|
||||||
|
<Setter Property="Canvas.Top" Value="{Binding Top}" />
|
||||||
|
</Style>
|
||||||
|
</s:PointContainer.Resources>
|
||||||
|
</s:PointContainer>
|
||||||
|
|
||||||
<Rectangle Fill="#7F243859" Opacity="0.5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="{Binding ShouldInsertAnchor, Converter={StaticResource BooleanToVisibilityConverter}}">
|
<Rectangle Fill="#7F243859" Opacity="0.5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Visibility="{Binding ShouldInsertAnchor, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
<i:Interaction.Behaviors>
|
<i:Interaction.Behaviors>
|
||||||
<s:ControlMouseLeftButtonDownCommandBehavior Command="{Binding AddVertexCommand}" />
|
<s:ControlMouseLeftButtonDownCommandBehavior Command="{Binding AddVertexCommand}" />
|
||||||
@@ -635,7 +647,7 @@
|
|||||||
<Setter TargetName="rightarrow"
|
<Setter TargetName="rightarrow"
|
||||||
Property="Fill"
|
Property="Fill"
|
||||||
Value="Black" />
|
Value="Black" />
|
||||||
<Setter TargetName="PART_PointContainer"
|
<Setter TargetName="PART_VerticesContainer"
|
||||||
Property="Visibility"
|
Property="Visibility"
|
||||||
Value="Visible"/>
|
Value="Visible"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
@@ -651,14 +663,14 @@
|
|||||||
<Setter TargetName="rightarrow"
|
<Setter TargetName="rightarrow"
|
||||||
Property="Fill"
|
Property="Fill"
|
||||||
Value="Black" />
|
Value="Black" />
|
||||||
<Setter TargetName="PART_PointContainer"
|
<Setter TargetName="PART_VerticesContainer"
|
||||||
Property="Visibility"
|
Property="Visibility"
|
||||||
Value="Visible"/>
|
Value="Visible"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
|
|
||||||
<DataTrigger Value="True"
|
<DataTrigger Value="True"
|
||||||
Binding="{Binding ShouldInsertAnchor}">
|
Binding="{Binding ShouldInsertAnchor}">
|
||||||
<Setter TargetName="PART_PointContainer"
|
<Setter TargetName="PART_VerticesContainer"
|
||||||
Property="Visibility"
|
Property="Visibility"
|
||||||
Value="Visible"/>
|
Value="Visible"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class TextControl : UserControl
|
public partial class TextControl : UserControl
|
||||||
{
|
{
|
||||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
public static readonly DependencyProperty DoubleEditProperty = DependencyProperty.Register(
|
||||||
"DoubleEdit", typeof(bool), typeof(TextControl), new FrameworkPropertyMetadata(
|
nameof(DoubleEdit), typeof(bool), typeof(TextControl), new FrameworkPropertyMetadata(
|
||||||
true));
|
true));
|
||||||
|
|
||||||
public bool DoubleEdit
|
public bool DoubleEdit
|
||||||
{
|
{
|
||||||
get => (bool)GetValue(TextProperty);
|
get => (bool)GetValue(DoubleEditProperty);
|
||||||
set => SetValue(TextProperty, value);
|
set => SetValue(DoubleEditProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextControl()
|
public TextControl()
|
||||||
@@ -48,7 +48,10 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
PART_ShowText.SelectionStart = PART_ShowText.Text.Length;
|
PART_ShowText.SelectionStart = PART_ShowText.Text.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
(this.DataContext as SelectableDesignerItemViewModelBase).PropertyChanged += TextControl_PropertyChanged;
|
if (this.DataContext is ISelectable selectable)
|
||||||
|
{
|
||||||
|
selectable.PropertyChanged += TextControl_PropertyChanged;
|
||||||
|
}
|
||||||
TextControl_PropertyChanged(this.DataContext, new System.ComponentModel.PropertyChangedEventArgs("IsSelected"));
|
TextControl_PropertyChanged(this.DataContext, new System.ComponentModel.PropertyChangedEventArgs("IsSelected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +59,9 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
{
|
{
|
||||||
if (e.PropertyName == "IsSelected")
|
if (e.PropertyName == "IsSelected")
|
||||||
{
|
{
|
||||||
if (sender is SelectableDesignerItemViewModelBase itemViewModelBase)
|
if (sender is ISelectable selectable)
|
||||||
{
|
{
|
||||||
if (itemViewModelBase.IsSelected == false)
|
if (selectable.IsSelected == false)
|
||||||
{
|
{
|
||||||
PART_ShowText.Visibility = Visibility.Collapsed;
|
PART_ShowText.Visibility = Visibility.Collapsed;
|
||||||
PART_TextBlock.Visibility = Visibility.Visible;
|
PART_TextBlock.Visibility = Visibility.Visible;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
public ColorViewModel()
|
public ColorViewModel()
|
||||||
{
|
{
|
||||||
LineColor = new ColorObject() { Color = Colors.Gray };
|
LineColor = new ColorObject() { Color = Colors.Gray };
|
||||||
FillColor = new ColorObject() { Color = Colors.Transparent };
|
FillColor = new ColorObject() { Color = Colors.White };
|
||||||
}
|
}
|
||||||
private IColorObject _lineColor;
|
private IColorObject _lineColor;
|
||||||
public IColorObject LineColor
|
public IColorObject LineColor
|
||||||
|
|||||||
@@ -125,7 +125,20 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
{
|
{
|
||||||
SetProperty(ref _colorViewModel, value);
|
SetProperty(ref _colorViewModel, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IFontViewModel _fontViewModel;
|
||||||
|
public IFontViewModel FontViewModel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fontViewModel;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _fontViewModel, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static ConnectorPoint operator -(ConnectorPoint a, ConnectorPoint b)
|
public static ConnectorPoint operator -(ConnectorPoint a, ConnectorPoint b)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
//using System.Windows;
|
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
||||||
using AIStudio.Wpf.DiagramDesigner.Helpers;
|
using AIStudio.Wpf.DiagramDesigner.Helpers;
|
||||||
|
using SvgPathProperties;
|
||||||
|
|
||||||
namespace AIStudio.Wpf.DiagramDesigner
|
namespace AIStudio.Wpf.DiagramDesigner
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
protected virtual void Init(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo)
|
protected virtual void Init(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo)
|
||||||
{
|
{
|
||||||
this.Parent = sourceConnectorInfo.DataItem.Parent;
|
this.Parent = sourceConnectorInfo.DataItem.Parent;
|
||||||
|
|
||||||
if (Parent != null && Parent.ColorViewModel != null)
|
if (Parent != null && Parent.ColorViewModel != null)
|
||||||
{
|
{
|
||||||
this.ColorViewModel = CopyHelper.Mapper(Parent.ColorViewModel);
|
this.ColorViewModel = CopyHelper.Mapper(Parent.ColorViewModel);
|
||||||
@@ -60,7 +60,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
this.SourceConnectorInfo = sourceConnectorInfo;
|
this.SourceConnectorInfo = sourceConnectorInfo;
|
||||||
this.SinkConnectorInfo = sinkConnectorInfo;
|
this.SinkConnectorInfo = sinkConnectorInfo;
|
||||||
DeleteConnectionCommand = new SimpleCommand(DeleteConnection);
|
DeleteConnectionCommand = new SimpleCommand(DeleteConnection);
|
||||||
AddVertexCommand = new SimpleCommand(AddVertex);
|
AddVertexCommand = new SimpleCommand(AddVertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void LoadDesignerItemViewModel(SelectableDesignerItemBase designerbase)
|
protected void LoadDesignerItemViewModel(SelectableDesignerItemBase designerbase)
|
||||||
@@ -112,7 +112,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
SetProperty(ref _sourceB, value);
|
SetProperty(ref _sourceB, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
SetProperty(ref _area, value);
|
SetProperty(ref _area, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,18 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<LinkLabelModel> Labels { get; set; } = new List<LinkLabelModel>();
|
private ObservableCollection<LinkLabelModel> _labels = new ObservableCollection<LinkLabelModel>();
|
||||||
|
public ObservableCollection<LinkLabelModel> Labels
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _labels;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
SetProperty(ref _labels, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual Dictionary<string, string> PropertiesSetting
|
public virtual Dictionary<string, string> PropertiesSetting
|
||||||
{
|
{
|
||||||
@@ -277,7 +288,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
SetProperty(ref _sinkConnectorInfo, value);
|
SetProperty(ref _sinkConnectorInfo, value);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +362,12 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
vertice.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
vertice.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case nameof(Labels):
|
||||||
|
foreach (var label in Labels)
|
||||||
|
{
|
||||||
|
label.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case nameof(SourceConnectorInfo):
|
case nameof(SourceConnectorInfo):
|
||||||
SourceA = PointHelper.GetPointForConnector(SourceConnectorInfo);
|
SourceA = PointHelper.GetPointForConnector(SourceConnectorInfo);
|
||||||
(SourceConnectorInfo.DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
(SourceConnectorInfo.DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
||||||
@@ -362,6 +379,12 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
(((FullyCreatedConnectorInfo)SinkConnectorInfo).DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
(((FullyCreatedConnectorInfo)SinkConnectorInfo).DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case nameof(IsSelected):
|
||||||
|
if (IsSelected == false)
|
||||||
|
{
|
||||||
|
Labels.FirstOrDefault()?.AddToSelection(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,13 +418,35 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
{
|
{
|
||||||
switch (e.PropertyName)
|
switch (e.PropertyName)
|
||||||
{
|
{
|
||||||
case nameof(ConnectorPoint.Left):
|
case nameof(ConnectorPoint.X):
|
||||||
case nameof(ConnectorPoint.Top):
|
case nameof(ConnectorPoint.Y):
|
||||||
UpdatePathGeneratorResult();
|
UpdatePathGeneratorResult();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (sender is LinkLabelModel linkLabelModel)
|
||||||
|
{
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case nameof(ConnectorPoint.X):
|
||||||
|
{
|
||||||
|
if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs)
|
||||||
|
{
|
||||||
|
linkLabelModel.UpdateOffsetX((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case nameof(ConnectorPoint.Y):
|
||||||
|
{
|
||||||
|
if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs)
|
||||||
|
{
|
||||||
|
linkLabelModel.UpdateOffsetY((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateArea()
|
private void UpdateArea()
|
||||||
@@ -485,6 +530,12 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
|
|
||||||
StartAngle = PathGeneratorResult.SourceMarkerAngle;
|
StartAngle = PathGeneratorResult.SourceMarkerAngle;
|
||||||
EndAngle = PathGeneratorResult.TargetMarkerAngle;
|
EndAngle = PathGeneratorResult.TargetMarkerAngle;
|
||||||
|
|
||||||
|
var paths = Labels.Count > 0 ? PathGeneratorResult.Paths.Select(p => new SvgPath(p)).ToArray() : Array.Empty<SvgPath>();
|
||||||
|
foreach (var label in Labels)
|
||||||
|
{
|
||||||
|
label.UpdatePosition(paths);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeleteConnection(object args)
|
private void DeleteConnection(object args)
|
||||||
@@ -496,22 +547,6 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddVertex(object parameter)
|
|
||||||
{
|
|
||||||
MouseButtonEventArgs mosueArg = ((EventToCommandArgs)parameter).EventArgs as MouseButtonEventArgs;
|
|
||||||
var position = mosueArg.GetPosition(((EventToCommandArgs)parameter).Sender as IInputElement);
|
|
||||||
|
|
||||||
var vertice = new LinkVertexModel(this, new PointBase(position.X, position.Y));
|
|
||||||
vertice.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
|
||||||
Vertices.Add(vertice);
|
|
||||||
UpdatePathGeneratorResult();
|
|
||||||
|
|
||||||
if (!((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control))
|
|
||||||
{
|
|
||||||
ShouldInsertAnchor = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private (PointBase? source, PointBase? target) FindConnectionPoints(PointBase[] route)
|
private (PointBase? source, PointBase? target) FindConnectionPoints(PointBase[] route)
|
||||||
{
|
{
|
||||||
if (IsPortless) // Portless
|
if (IsPortless) // Portless
|
||||||
@@ -544,7 +579,7 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
if (port == null)
|
if (port == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (marker == null)
|
if (marker == 0)
|
||||||
return port.MiddlePosition;
|
return port.MiddlePosition;
|
||||||
|
|
||||||
var pt = port.Position;
|
var pt = port.Position;
|
||||||
@@ -588,6 +623,22 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 双击添加
|
#region 双击添加
|
||||||
|
private void AddVertex(object parameter)
|
||||||
|
{
|
||||||
|
MouseButtonEventArgs mosueArg = ((EventToCommandArgs)parameter).EventArgs as MouseButtonEventArgs;
|
||||||
|
var position = mosueArg.GetPosition(((EventToCommandArgs)parameter).Sender as IInputElement);
|
||||||
|
|
||||||
|
var vertice = new LinkVertexModel(this, new PointBase(position.X, position.Y));
|
||||||
|
vertice.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
||||||
|
Vertices.Add(vertice);
|
||||||
|
UpdatePathGeneratorResult();
|
||||||
|
|
||||||
|
if (!((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control))
|
||||||
|
{
|
||||||
|
ShouldInsertAnchor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ExecuteEditCommand(object param)
|
protected override void ExecuteEditCommand(object param)
|
||||||
{
|
{
|
||||||
if (this.OutTextItem != null) return;
|
if (this.OutTextItem != null) return;
|
||||||
@@ -596,48 +647,25 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
|
|
||||||
public void AddText(string text)
|
public void AddText(string text)
|
||||||
{
|
{
|
||||||
if (this.Parent is IDiagramViewModel)
|
var label = new LinkLabelModel(this, "");
|
||||||
{
|
label.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
||||||
var diagramVM = this.Parent as IDiagramViewModel;
|
label.IsSelected = true;
|
||||||
|
Labels.Add(label);
|
||||||
|
|
||||||
TextDesignerItemViewModel textitem = new TextDesignerItemViewModel();
|
var paths = Labels.Count > 0 ? PathGeneratorResult.Paths.Select(p => new SvgPath(p)).ToArray() : Array.Empty<SvgPath>();
|
||||||
textitem.ItemWidth = Double.NaN;
|
label.UpdatePosition(paths);
|
||||||
textitem.ItemHeight = double.NaN;
|
|
||||||
//if (this.PathMode == DrawMode.ConnectingLineBoundary.ToString())
|
|
||||||
//{
|
|
||||||
// var mid = (int)(ConnectionPoints.Count / 2);
|
|
||||||
// var p = PathGenerators.SegmentMiddlePoint(ConnectionPoints[mid - 1], ConnectionPoints[mid]);
|
|
||||||
// textitem.Left = this.Area.Left + p.X + 2;
|
|
||||||
// textitem.Top = this.Area.Top + p.Y - 15;
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
{
|
|
||||||
textitem.Left = this.Area.Left + this.Area.Width / 2 - 16;
|
|
||||||
textitem.Top = this.Area.Top + this.Area.Height / 2 - 5;
|
|
||||||
}
|
|
||||||
textitem.Watermark = null;
|
|
||||||
textitem.ZIndex = diagramVM.Items.Count;
|
|
||||||
textitem.ParentId = this.Id;
|
|
||||||
textitem.ParentItem = this;
|
|
||||||
textitem.ColorViewModel.FillColor = new ColorObject() { Color = Colors.White };
|
|
||||||
textitem.Text = text;
|
|
||||||
|
|
||||||
diagramVM.DirectAddItemCommand.Execute(textitem);
|
|
||||||
|
|
||||||
this.OutTextItem = textitem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OutTextItemLocation(RectangleBase oldArea, RectangleBase newArea)
|
public void OutTextItemLocation(RectangleBase oldArea, RectangleBase newArea)
|
||||||
{
|
{
|
||||||
if (this.OutTextItem is TextDesignerItemViewModel text)
|
//if (this.OutTextItem is TextDesignerItemViewModel text)
|
||||||
{
|
//{
|
||||||
var oldpoint = new PointBase(oldArea.Left + oldArea.Width / 2, oldArea.Top + oldArea.Height / 2);
|
// var oldpoint = new PointBase(oldArea.Left + oldArea.Width / 2, oldArea.Top + oldArea.Height / 2);
|
||||||
var newpoint = new PointBase(newArea.Left + newArea.Width / 2, newArea.Top + newArea.Height / 2);
|
// var newpoint = new PointBase(newArea.Left + newArea.Width / 2, newArea.Top + newArea.Height / 2);
|
||||||
|
|
||||||
text.Left = text.Left + newpoint.X - oldpoint.X;
|
// text.Left = text.Left + newpoint.X - oldpoint.X;
|
||||||
text.Top = text.Top + newpoint.Y - oldpoint.Y;
|
// text.Top = text.Top + newpoint.Y - oldpoint.Y;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,20 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowRotate { get; set; } = true;
|
private bool _showRotate = false;
|
||||||
|
[Browsable(true)]
|
||||||
|
public bool ShowRotate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _showRotate;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _showRotate, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowArrow { get; set; } = true;
|
public bool ShowArrow { get; set; } = true;
|
||||||
|
|
||||||
private double _left;
|
private double _left;
|
||||||
|
|||||||
@@ -1,32 +1,171 @@
|
|||||||
namespace AIStudio.Wpf.DiagramDesigner
|
using System.Linq;
|
||||||
|
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
||||||
|
using SvgPathProperties;
|
||||||
|
|
||||||
|
namespace AIStudio.Wpf.DiagramDesigner
|
||||||
{
|
{
|
||||||
public class LinkLabelModel : ConnectorPoint
|
public class LinkLabelModel : ConnectorPoint, ISelectable
|
||||||
{
|
{
|
||||||
public LinkLabelModel(ConnectorViewModel parent, string id, string content, double? distance = null, ConnectorPoint offset = null)
|
public LinkLabelModel(ConnectorViewModel parent, string content, double? distance = null, PointBase? offset = null)
|
||||||
{
|
{
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
Content = content;
|
Text = content;
|
||||||
Distance = distance;
|
Distance = distance;
|
||||||
Offset = offset;
|
Offset = offset ?? new PointBase();
|
||||||
|
FontViewModel = Parent.FontViewModel;
|
||||||
|
ColorViewModel = Parent.ColorViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkLabelModel(ConnectorViewModel parent, string content, double? distance = null, ConnectorPoint offset = null)
|
public ConnectorViewModel Parent
|
||||||
{
|
{
|
||||||
Parent = parent;
|
get;
|
||||||
Content = content;
|
}
|
||||||
Distance = distance;
|
|
||||||
Offset = offset;
|
public bool IsHitTestVisible
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Parent.IsHitTestVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _text;
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _text, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectorViewModel Parent { get; }
|
|
||||||
public string Content { get; set; }
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3 types of values are possible:
|
/// 3 types of values are possible:
|
||||||
/// <para>- A number between 0 and 1: Position relative to the link's length</para>
|
/// <para>- A number between 0 and 1: Position relative to the link's length</para>
|
||||||
/// <para>- A positive number, greater than 1: Position away from the start</para>
|
/// <para>- A positive number, greater than 1: Position away from the start</para>
|
||||||
/// <para>- A negative number, less than 0: Position away from the end</para>
|
/// <para>- A negative number, less than 0: Position away from the end</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double? Distance { get; set; }
|
public double? Distance
|
||||||
public ConnectorPoint Offset { get; set; }
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
public PointBase Offset
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isSelected;
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _isSelected;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _isSelected, value))
|
||||||
|
{
|
||||||
|
//如果没有文字,失去焦点自动清除
|
||||||
|
if (_isSelected == false && string.IsNullOrEmpty(Text))
|
||||||
|
{
|
||||||
|
Parent.Labels.Remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PointBase Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new PointBase(Parent.Area.Left + Left, Parent.Area.Top + Top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PointBase MiddlePosition => new PointBase(Parent.Area.Left + Left + ConnectorWidth / 2, Parent.Area.Top + Top + ConnectorHeight / 2);
|
||||||
|
|
||||||
|
private bool updating = false;
|
||||||
|
|
||||||
|
public void UpdatePosition(SvgPath[] paths)
|
||||||
|
{
|
||||||
|
var position = FindPosition(paths);
|
||||||
|
if (position == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
updating = true;
|
||||||
|
X = position.Value.X;
|
||||||
|
Y = position.Value.Y;
|
||||||
|
updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointBase? FindPosition(SvgPath[] paths)
|
||||||
|
{
|
||||||
|
var totalLength = paths.Sum(p => p.Length);
|
||||||
|
|
||||||
|
double length;
|
||||||
|
|
||||||
|
|
||||||
|
if (Distance >= 0 && Distance <= 1)
|
||||||
|
{
|
||||||
|
length = Distance.Value * totalLength;
|
||||||
|
}
|
||||||
|
else if (Distance > 1)
|
||||||
|
{
|
||||||
|
length = Distance.Value;
|
||||||
|
}
|
||||||
|
else if (Distance < 0)
|
||||||
|
{
|
||||||
|
length = totalLength + Distance.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
length = totalLength * (Parent.Labels.IndexOf(this) + 1) / (Parent.Labels.Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
var pathLength = path.Length;
|
||||||
|
if (length < pathLength)
|
||||||
|
{
|
||||||
|
var pt = path.GetPointAtLength(length);
|
||||||
|
return new PointBase(pt.X + Offset.X, pt.Y + Offset.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
length -= pathLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOffsetX(double oldvalue, double newvalue)
|
||||||
|
{
|
||||||
|
if (updating == true) return;
|
||||||
|
|
||||||
|
Offset += new VectorBase(newvalue - oldvalue, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOffsetY(double oldvalue, double newvalue)
|
||||||
|
{
|
||||||
|
if (updating == true) return;
|
||||||
|
|
||||||
|
Offset += new VectorBase(0, newvalue - oldvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddToSelection(bool selected)
|
||||||
|
{
|
||||||
|
foreach (var item in Parent.Labels)
|
||||||
|
item.IsSelected = false;
|
||||||
|
|
||||||
|
if (selected == true)
|
||||||
|
{
|
||||||
|
IsSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,6 +364,18 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
IsSelected = select;
|
IsSelected = select;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddToSelection(bool selected)
|
||||||
|
{
|
||||||
|
foreach (SelectableDesignerItemViewModelBase item in Parent.SelectedItems)
|
||||||
|
item.IsSelected = false;
|
||||||
|
|
||||||
|
Parent.SelectedItems.Clear();
|
||||||
|
if (selected == true)
|
||||||
|
{
|
||||||
|
Parent.SelectionService.AddToSelection(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void FontViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
private void FontViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName == "FontCase")
|
if (e.PropertyName == "FontCase")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -7,6 +8,18 @@ namespace AIStudio.Wpf.DiagramDesigner
|
|||||||
{
|
{
|
||||||
public interface ISelectable
|
public interface ISelectable
|
||||||
{
|
{
|
||||||
bool IsSelected { get; set; }
|
bool IsSelected
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHitTestVisible
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddToSelection(bool selected);
|
||||||
|
|
||||||
|
event PropertyChangedEventHandler PropertyChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user