Merge branch 'release/1.3'

This commit is contained in:
Mark van Renswoude 2022-01-14 13:02:36 +01:00
commit 2cfb341f47
48 changed files with 759 additions and 150 deletions

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace PettingZoo.Core.Connection namespace PettingZoo.Core.Connection
{ {
public interface ISubscriber : IAsyncDisposable public interface ISubscriber : IDisposable
{ {
string? QueueName { get; } string? QueueName { get; }
string? Exchange {get; } string? Exchange {get; }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
namespace PettingZoo.Core.ExportImport namespace PettingZoo.Core.ExportImport
@ -13,7 +12,10 @@ namespace PettingZoo.Core.ExportImport
public string? QueueName { get; } public string? QueueName { get; }
public string? Exchange => null; public string? Exchange => null;
public string? RoutingKey => null; public string? RoutingKey => null;
#pragma warning disable CS0067 // "The event ... is never used" - it's part of the interface so it's required.
public event EventHandler<MessageReceivedEventArgs>? MessageReceived; public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
#pragma warning restore CS0067
public ImportSubscriber(string filename, IReadOnlyList<ReceivedMessageInfo> messages) public ImportSubscriber(string filename, IReadOnlyList<ReceivedMessageInfo> messages)
@ -23,10 +25,9 @@ namespace PettingZoo.Core.ExportImport
} }
public ValueTask DisposeAsync() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
return default;
} }

View File

@ -58,7 +58,7 @@ namespace PettingZoo.Core.ExportImport
public StreamWrapper(StreamProgressDecorator owner, Stream decoratedStream) public StreamWrapper(StreamProgressDecorator owner, Stream decoratedStream)
{ {
this.owner = owner; this.owner = owner;
this.DecoratedStream = decoratedStream; DecoratedStream = decoratedStream;
} }

View File

@ -17,11 +17,13 @@ namespace PettingZoo.Core.Generator
public interface IClassTypeExample : IExample public interface IClassTypeExample : IExample
{ {
public string AssemblyName { get; } string AssemblyName { get; }
public string? Namespace { get; } string? Namespace { get; }
public string ClassName { get; } string ClassName { get; }
public string FullClassName => (!string.IsNullOrEmpty(Namespace) ? Namespace + "." : "") + ClassName; string FullClassName => (!string.IsNullOrEmpty(Namespace) ? Namespace + "." : "") + ClassName;
bool TryGetPublishDestination(out string exchange, out string routingKey);
} }

View File

@ -0,0 +1,22 @@
namespace PettingZoo.Core.Macros
{
public abstract class BasePayloadMacro : IPayloadMacro
{
public string DisplayName { get; }
public string MacroText { get; }
public string MacroCommand { get; }
protected BasePayloadMacro(string macroCommand, string displayName)
{
MacroCommand = macroCommand;
DisplayName = displayName;
MacroText = "{{" + macroCommand + "}}";
}
public abstract string GetValue();
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace PettingZoo.Core.Macros
{
public class JsonDateTimePayloadMacro : BasePayloadMacro
{
public JsonDateTimePayloadMacro()
: base("JsonUtcNow", "Current date/time (yyyy-mm-ddThh:mm:ss.mmmZ)")
{
}
public override string GetValue()
{
return DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'");
}
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace PettingZoo.Core.Macros
{
public interface IPayloadMacroProcessor
{
string Apply(string payload);
IEnumerable<IPayloadMacro> Macros { get; }
}
public interface IPayloadMacro
{
public string DisplayName { get; }
public string MacroText { get; }
}
}

View File

@ -0,0 +1,18 @@
using System;
namespace PettingZoo.Core.Macros
{
public class NewGuidPayloadMacro : BasePayloadMacro
{
public NewGuidPayloadMacro()
: base("NewGuid", "Generate GUID")
{
}
public override string GetValue()
{
return Guid.NewGuid().ToString();
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace PettingZoo.Core.Macros
{
public class PayloadMacroProcessor : IPayloadMacroProcessor
{
private readonly BasePayloadMacro[] macros;
public IEnumerable<IPayloadMacro> Macros => macros;
public PayloadMacroProcessor()
{
macros = new BasePayloadMacro[]
{
new NewGuidPayloadMacro(),
new JsonDateTimePayloadMacro()
};
}
// For now we only support simple one-keyboard macros, but this could be extended with parameters if required
private static readonly Regex MacroRegex = new("{{(.+?)}}", RegexOptions.Compiled);
public string Apply(string payload)
{
return MacroRegex.Replace(payload, match =>
{
var macroCommand = match.Groups[1].Value.Trim();
var macro = macros.FirstOrDefault(m => string.Equals(m.MacroCommand, macroCommand, StringComparison.CurrentCultureIgnoreCase));
return macro != null
? macro.GetValue()
: match.Groups[0].Value;
});
}
}
}

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
@ -29,14 +28,12 @@ namespace PettingZoo.RabbitMQ
} }
public ValueTask DisposeAsync() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
if (model != null && consumerTag != null && model.IsOpen) if (model != null && consumerTag != null && model.IsOpen)
model.BasicCancelNoWait(consumerTag); model.BasicCancelNoWait(consumerTag);
return default;
} }

View File

@ -7,6 +7,7 @@ using System.Runtime.Loader;
using Newtonsoft.Json; using Newtonsoft.Json;
using PettingZoo.Core.Generator; using PettingZoo.Core.Generator;
using PettingZoo.Core.Validation; using PettingZoo.Core.Validation;
using Tapeti.Default;
namespace PettingZoo.Tapeti.AssemblyParser namespace PettingZoo.Tapeti.AssemblyParser
{ {
@ -83,6 +84,24 @@ namespace PettingZoo.Tapeti.AssemblyParser
} }
public bool TryGetPublishDestination(out string exchange, out string routingKey)
{
try
{
// Assume default strategies are used
exchange = new NamespaceMatchExchangeStrategy().GetExchange(type);
routingKey = new TypeNameRoutingKeyStrategy().GetRoutingKey(type);
return true;
}
catch
{
exchange = "";
routingKey = "";
return false;
}
}
public bool CanValidate() public bool CanValidate()
{ {
return InitializeValidation(); return InitializeValidation();

View File

@ -17,6 +17,7 @@
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" /> <PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />
<PackageReference Include="Tapeti" Version="2.8.2" />
<PackageReference Include="Tapeti.Annotations" Version="3.0.0" /> <PackageReference Include="Tapeti.Annotations" Version="3.0.0" />
<PackageReference Include="Tapeti.DataAnnotations.Extensions" Version="3.0.0" /> <PackageReference Include="Tapeti.DataAnnotations.Extensions" Version="3.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Threading;
using PettingZoo.Core.Generator; using PettingZoo.Core.Generator;
using PettingZoo.Core.Settings; using PettingZoo.Core.Settings;
using PettingZoo.Tapeti.AssemblyLoader; using PettingZoo.Tapeti.AssemblyLoader;

View File

@ -300,6 +300,14 @@ namespace PettingZoo.Tapeti.UI.ClassSelection
{ {
return ""; return "";
} }
public bool TryGetPublishDestination(out string exchange, out string routingKey)
{
exchange = "";
routingKey = "";
return false;
}
} }
} }
} }

View File

@ -67,6 +67,12 @@
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<Style x:Key="ButtonIcon" TargetType="{x:Type Image}">
<Setter Property="Margin" Value="0,0,8,0" />
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
</Style>
<Style x:Key="Timestamp" TargetType="{x:Type TextBlock}"> <Style x:Key="Timestamp" TargetType="{x:Type TextBlock}">
<Style.Triggers> <Style.Triggers>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="271px" viewBox="0 0 256 271" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M245.44,108.307692 L160.349538,108.307692 C156.081231,108.307692 152.615385,104.841846 152.615385,100.573538 L152.615385,11.8941538 C152.615385,5.32676923 147.288615,0 140.726154,0 L110.350769,0 C103.783385,0 98.4615385,5.32676923 98.4615385,11.8941538 L98.4615385,100.036923 C98.4615385,104.610462 94.7643077,108.327385 90.1907692,108.347077 L62.3064615,108.48 C57.6935385,108.504615 53.9470769,104.763077 53.9569231,100.155077 L54.1292308,11.9138462 C54.144,5.33661538 48.8172308,0 42.24,0 L11.8892308,0 C5.32184615,0 0,5.32676923 0,11.8941538 L0,260.209231 C0,266.043077 4.72615385,270.769231 10.5550769,270.769231 L245.44,270.769231 C251.273846,270.769231 256,266.043077 256,260.209231 L256,118.867692 C256,113.033846 251.273846,108.307692 245.44,108.307692 L245.44,108.307692 Z M205.538462,201.540923 C205.538462,209.186462 199.340308,215.384615 191.694769,215.384615 L167.689846,215.384615 C160.044308,215.384615 153.846154,209.186462 153.846154,201.540923 L153.846154,177.536 C153.846154,169.890462 160.044308,163.692308 167.689846,163.692308 L191.694769,163.692308 C199.340308,163.692308 205.538462,169.890462 205.538462,177.536 L205.538462,201.540923 L205.538462,201.540923 Z" fill="#FF6600"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -28,6 +28,8 @@
<None Remove="Images\Import.svg" /> <None Remove="Images\Import.svg" />
<None Remove="Images\Ok.svg" /> <None Remove="Images\Ok.svg" />
<None Remove="Images\PublishSend.svg" /> <None Remove="Images\PublishSend.svg" />
<None Remove="Images\RabbitMQ.svg" />
<None Remove="Images\Tapeti.png" />
<None Remove="Images\Undock.svg" /> <None Remove="Images\Undock.svg" />
</ItemGroup> </ItemGroup>
@ -44,6 +46,7 @@
<Resource Include="Images\Ok.svg" /> <Resource Include="Images\Ok.svg" />
<Resource Include="Images\Publish.svg" /> <Resource Include="Images\Publish.svg" />
<Resource Include="Images\PublishSend.svg" /> <Resource Include="Images\PublishSend.svg" />
<Resource Include="Images\RabbitMQ.svg" />
<Resource Include="Images\Subscribe.svg" /> <Resource Include="Images\Subscribe.svg" />
</ItemGroup> </ItemGroup>
@ -66,6 +69,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Images\Tapeti.png" />
<Resource Include="Images\Undock.svg" /> <Resource Include="Images\Undock.svg" />
<Resource Include="Images\Busy.svg" /> <Resource Include="Images\Busy.svg" />
</ItemGroup> </ItemGroup>
@ -144,7 +148,7 @@
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\PayloadEditorStrings.resx"> <EmbeddedResource Update="UI\Tab\Publisher\PayloadEditorStrings.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>PayloadEditorStrings.Designer.cs</LastGenOutput> <LastGenOutput>PayloadEditorStrings.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Update="UI\Tab\Publisher\TapetiPublisherViewStrings.resx"> <EmbeddedResource Update="UI\Tab\Publisher\TapetiPublisherViewStrings.resx">

View File

@ -6,6 +6,7 @@ using System.Windows.Markup;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.ExportImport; using PettingZoo.Core.ExportImport;
using PettingZoo.Core.Generator; using PettingZoo.Core.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.Core.Settings; using PettingZoo.Core.Settings;
using PettingZoo.RabbitMQ; using PettingZoo.RabbitMQ;
using PettingZoo.Settings.LiteDB; using PettingZoo.Settings.LiteDB;
@ -84,6 +85,7 @@ namespace PettingZoo
container.Register<IExampleGenerator, TapetiClassLibraryExampleGenerator>(); container.Register<IExampleGenerator, TapetiClassLibraryExampleGenerator>();
container.RegisterSingleton<ITabHostProvider, TabHostProvider>(); container.RegisterSingleton<ITabHostProvider, TabHostProvider>();
container.Register<ITabFactory, ViewTabFactory>(); container.Register<ITabFactory, ViewTabFactory>();
container.RegisterSingleton<IPayloadMacroProcessor, PayloadMacroProcessor>();
container.RegisterInstance<IExportImportFormatProvider>(new ExportImportFormatProvider( container.RegisterInstance<IExportImportFormatProvider>(new ExportImportFormatProvider(
new TapetiCmdExportFormat(), new TapetiCmdExportFormat(),

View File

@ -4,9 +4,6 @@
Should-have Should-have
----------- -----------
- Single tab for responses, don't create a new subscriber tab for 1 message each time
Use the CorrelationId in the list for such cases instead of the routing key (which is the name of the dynamic queue for the responses).
Set the CorrelationId to the request routing key for example, so the different responses can be somewhat identified.
Nice-to-have Nice-to-have

View File

@ -170,7 +170,7 @@ namespace PettingZoo.UI.Main
if (connectionSettings.Subscribe) if (connectionSettings.Subscribe)
{ {
var subscriber = connection.Subscribe(connectionSettings.Exchange, connectionSettings.RoutingKey); var subscriber = connection.Subscribe(connectionSettings.Exchange, connectionSettings.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber)); tabFactory.CreateSubscriberTab(connection, subscriber);
} }
ConnectionChanged(); ConnectionChanged();
@ -214,7 +214,7 @@ namespace PettingZoo.UI.Main
subscribeDialogParams = newParams; subscribeDialogParams = newParams;
var subscriber = connection.Subscribe(subscribeDialogParams.Exchange, subscribeDialogParams.RoutingKey); var subscriber = connection.Subscribe(subscribeDialogParams.Exchange, subscribeDialogParams.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber)); tabFactory.CreateSubscriberTab(connection, subscriber);
} }
@ -223,7 +223,7 @@ namespace PettingZoo.UI.Main
if (connection == null) if (connection == null)
return; return;
AddTab(tabFactory.CreatePublisherTab(connection)); tabFactory.CreatePublisherTab(connection);
} }
@ -235,7 +235,8 @@ namespace PettingZoo.UI.Main
private void CloseTabExecute() private void CloseTabExecute()
{ {
RemoveActiveTab(); var tab = RemoveActiveTab();
(tab as IDisposable)?.Dispose();
} }
@ -300,8 +301,7 @@ namespace PettingZoo.UI.Main
progressWindow.Close(); progressWindow.Close();
progressWindow = null; progressWindow = null;
AddTab(tabFactory.CreateSubscriberTab(connection, tabFactory.CreateSubscriberTab(connection, new ImportSubscriber(filename, messages));
new ImportSubscriber(filename, messages)));
}); });
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -377,6 +377,15 @@ namespace PettingZoo.UI.Main
} }
public void ActivateTab(ITab tab)
{
if (undockedTabs.TryGetValue(tab, out var window))
window.Activate();
else if (Tabs.Contains(tab))
ActiveTab = tab;
}
public void DockTab(ITab tab) public void DockTab(ITab tab)
{ {
if (undockedTabs.Remove(tab, out var tabHostWindow)) if (undockedTabs.Remove(tab, out var tabHostWindow))

View File

@ -2,13 +2,10 @@
namespace PettingZoo.UI.Tab namespace PettingZoo.UI.Tab
{ {
// Passing the closeTabCommand is necessary because I haven't figured out how to bind the main window's
// context menu items for the tab to the main window's datacontext yet. RelativeSource doesn't seem to work
// because the popup is it's own window. Refactor if a better solution is found.
public interface ITabFactory public interface ITabFactory
{ {
ITab CreateSubscriberTab(IConnection? connection, ISubscriber subscriber); void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber);
ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null); string CreateReplySubscriberTab(IConnection connection);
void CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null);
} }
} }

View File

@ -3,6 +3,7 @@
public interface ITabHost public interface ITabHost
{ {
void AddTab(ITab tab); void AddTab(ITab tab);
void ActivateTab(ITab tab);
void DockTab(ITab tab); void DockTab(ITab tab);
void UndockedTabClosed(ITab tab); void UndockedTabClosed(ITab tab);

View File

@ -5,6 +5,7 @@
string Exchange { get; } string Exchange { get; }
string RoutingKey { get; } string RoutingKey { get; }
string? GetReplyTo(); string? GetReplyTo(ref string? correlationId);
void SetExchangeDestination(string exchange, string routingKey);
} }
} }

View File

@ -21,18 +21,36 @@
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Visibility="{Binding ValidationVisibility}" Margin="0,8,0,0"> <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Visibility="{Binding ValidationVisibility}" Margin="0,8,0,0">
<Image Source="{svgc:SvgImage Source=/Images/Ok.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ValidationOk}" /> <Image Source="{svgc:SvgImage Source=/Images/Ok.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="1,4,0,0" Visibility="{Binding ValidationOk}" />
<Image Source="{svgc:SvgImage Source=/Images/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ValidationError}" /> <Image Source="{svgc:SvgImage Source=/Images/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="1,4,0,0" Visibility="{Binding ValidationError}" />
<Image Source="{svgc:SvgImage Source=/Images/Busy.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ValidationValidating}" /> <Image Source="{svgc:SvgImage Source=/Images/Busy.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="1,4,0,0" Visibility="{Binding ValidationValidating}" />
<TextBlock Text="{Binding ValidationMessage}" Margin="4" /> <TextBlock Text="{Binding ValidationMessage}" Margin="4" />
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Margin="0,8,0,0">
<CheckBox Content="{x:Static publisher:PayloadEditorStrings.CheckEnableMacros}" VerticalAlignment="Center" IsChecked="{Binding EnableMacros}" />
</StackPanel>
<Border Style="{StaticResource ControlBorder}" Name="EditorBorder"> <Border Style="{StaticResource ControlBorder}" Name="EditorBorder">
<avalonedit:TextEditor <avalonedit:TextEditor
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="Editor" Name="Editor"
SyntaxHighlighting="{Binding SyntaxHighlighting}" SyntaxHighlighting="{Binding SyntaxHighlighting}"
Style="{StaticResource Payload}" /> Style="{StaticResource Payload}">
<avalonedit:TextEditor.ContextMenu>
<ContextMenu Opened="ContextMenu_OnOpened">
<MenuItem Header="{x:Static publisher:PayloadEditorStrings.ContextMenuUndo}" Name="ContextMenuUndo" Click="Undo_Click" InputGestureText="Ctrl+Z" />
<MenuItem Header="{x:Static publisher:PayloadEditorStrings.ContextMenuRedo}" Name="ContextMenuRedo" Click="Redo_Click" InputGestureText="Ctrl+Y "/>
<Separator/>
<MenuItem Header="{x:Static publisher:PayloadEditorStrings.ContextMenuCut}" Name="ContextMenuCut" Click="Cut_Click" InputGestureText="Ctrl+X "/>
<MenuItem Header="{x:Static publisher:PayloadEditorStrings.ContextMenuCopy}" Name="ContextMenuCopy" Click="Copy_Click" InputGestureText="Ctrl+C "/>
<MenuItem Header="{x:Static publisher:PayloadEditorStrings.ContextMenuPaste}" Name="ContextMenuPaste" Click="Paste_Click" InputGestureText="Ctrl+V "/>
<Separator/>
<MenuItem Header="{x:Static publisher:PayloadEditorStrings.ContextMenuInsertMacro}" Name="ContextMenuInsertMacro" />
</ContextMenu>
</avalonedit:TextEditor.ContextMenu>
</avalonedit:TextEditor>
</Border> </Border>
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View File

@ -2,7 +2,9 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Data; using System.Windows.Data;
using PettingZoo.Core.Macros;
using PettingZoo.Core.Validation; using PettingZoo.Core.Validation;
namespace PettingZoo.UI.Tab.Publisher namespace PettingZoo.UI.Tab.Publisher
@ -88,6 +90,32 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public static readonly DependencyProperty EnableMacrosProperty
= DependencyProperty.Register(
"EnableMacros",
typeof(bool),
typeof(PayloadEditorControl),
new FrameworkPropertyMetadata(false)
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
}
);
public bool EnableMacros
{
get => viewModel.EnableMacros;
set
{
if (value == viewModel.EnableMacros)
return;
SetValue(EnableMacrosProperty, value);
viewModel.EnableMacros = value;
}
}
public IPayloadValidator? Validator public IPayloadValidator? Validator
{ {
get => viewModel.Validator; get => viewModel.Validator;
@ -95,6 +123,21 @@ namespace PettingZoo.UI.Tab.Publisher
} }
private IPayloadMacroProcessor? macroProcessor;
public IPayloadMacroProcessor? MacroProcessor
{
get => macroProcessor;
set
{
if (value == macroProcessor)
return;
macroProcessor = value;
UpdateMacroContextMenu();
}
}
private readonly ErrorHighlightingTransformer errorHighlightingTransformer = new(); private readonly ErrorHighlightingTransformer errorHighlightingTransformer = new();
public PayloadEditorControl() public PayloadEditorControl()
@ -123,6 +166,12 @@ namespace PettingZoo.UI.Tab.Publisher
viewModel.Payload = value; viewModel.Payload = value;
}); });
this.OnPropertyChanges<bool>(EnableMacrosProperty)
.ObserveOn(SynchronizationContext.Current!)
.Subscribe(value =>
{
viewModel.EnableMacros = value;
});
viewModel.PropertyChanged += (_, args) => viewModel.PropertyChanged += (_, args) =>
{ {
@ -139,6 +188,10 @@ namespace PettingZoo.UI.Tab.Publisher
case nameof(viewModel.Payload): case nameof(viewModel.Payload):
SetValue(PayloadProperty, viewModel.Payload); SetValue(PayloadProperty, viewModel.Payload);
break; break;
case nameof(viewModel.EnableMacros):
SetValue(EnableMacrosProperty, viewModel.EnableMacros);
break;
} }
}; };
@ -207,5 +260,70 @@ namespace PettingZoo.UI.Tab.Publisher
// so I've moved the ViewModel one level down to get the best of both worlds... // so I've moved the ViewModel one level down to get the best of both worlds...
DataContextContainer.DataContext = viewModel; DataContextContainer.DataContext = viewModel;
} }
private void UpdateMacroContextMenu()
{
ContextMenuInsertMacro.Items.Clear();
if (macroProcessor == null)
return;
foreach (var macro in macroProcessor.Macros)
{
var macroMenuItem = new MenuItem
{
Header = macro.DisplayName
};
macroMenuItem.Click += (_, _) =>
{
Editor.SelectedText = macro.MacroText;
var length = Editor.SelectionLength;
Editor.SelectionLength = 0;
Editor.SelectionStart += length;
viewModel.EnableMacros = true;
};
ContextMenuInsertMacro.Items.Add(macroMenuItem);
}
}
private void Undo_Click(object sender, RoutedEventArgs e)
{
Editor.Undo();
}
private void Redo_Click(object sender, RoutedEventArgs e)
{
Editor.Redo();
}
private void Cut_Click(object sender, RoutedEventArgs e)
{
Editor.Cut();
}
private void Copy_Click(object sender, RoutedEventArgs e)
{
Editor.Copy();
}
private void Paste_Click(object sender, RoutedEventArgs e)
{
Editor.Paste();
}
private void ContextMenu_OnOpened(object sender, RoutedEventArgs e)
{
ContextMenuUndo.IsEnabled = Editor.CanUndo;
ContextMenuRedo.IsEnabled = Editor.CanRedo;
ContextMenuCut.IsEnabled = Editor.SelectionLength > 0;
ContextMenuCopy.IsEnabled = Editor.SelectionLength > 0;
ContextMenuPaste.IsEnabled = Clipboard.ContainsText();
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace PettingZoo.UI.Tab.Publisher {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class PayloadEditorStrings { public class PayloadEditorStrings {
private static global::System.Resources.ResourceManager resourceMan; private static global::System.Resources.ResourceManager resourceMan;
@ -36,7 +36,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// Returns the cached ResourceManager instance used by this class. /// Returns the cached ResourceManager instance used by this class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager { public static global::System.Resources.ResourceManager ResourceManager {
get { get {
if (object.ReferenceEquals(resourceMan, null)) { if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Tab.Publisher.PayloadEditorStrings", typeof(PayloadEditorStrings).Assembly); global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PettingZoo.UI.Tab.Publisher.PayloadEditorStrings", typeof(PayloadEditorStrings).Assembly);
@ -51,7 +51,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// resource lookups using this strongly typed resource class. /// resource lookups using this strongly typed resource class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture { public static global::System.Globalization.CultureInfo Culture {
get { get {
return resourceCulture; return resourceCulture;
} }
@ -60,10 +60,19 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Enable macros (right-click editor to insert).
/// </summary>
public static string CheckEnableMacros {
get {
return ResourceManager.GetString("CheckEnableMacros", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to JSON. /// Looks up a localized string similar to JSON.
/// </summary> /// </summary>
internal static string ContentTypeJson { public static string ContentTypeJson {
get { get {
return ResourceManager.GetString("ContentTypeJson", resourceCulture); return ResourceManager.GetString("ContentTypeJson", resourceCulture);
} }
@ -72,7 +81,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary> /// <summary>
/// Looks up a localized string similar to Other. /// Looks up a localized string similar to Other.
/// </summary> /// </summary>
internal static string ContentTypeOther { public static string ContentTypeOther {
get { get {
return ResourceManager.GetString("ContentTypeOther", resourceCulture); return ResourceManager.GetString("ContentTypeOther", resourceCulture);
} }
@ -81,16 +90,79 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary> /// <summary>
/// Looks up a localized string similar to Plain text. /// Looks up a localized string similar to Plain text.
/// </summary> /// </summary>
internal static string ContentTypePlain { public static string ContentTypePlain {
get { get {
return ResourceManager.GetString("ContentTypePlain", resourceCulture); return ResourceManager.GetString("ContentTypePlain", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to Copy.
/// </summary>
public static string ContextMenuCopy {
get {
return ResourceManager.GetString("ContextMenuCopy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cut.
/// </summary>
public static string ContextMenuCut {
get {
return ResourceManager.GetString("ContextMenuCut", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Insert macro.
/// </summary>
public static string ContextMenuInsertMacro {
get {
return ResourceManager.GetString("ContextMenuInsertMacro", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Paste.
/// </summary>
public static string ContextMenuPaste {
get {
return ResourceManager.GetString("ContextMenuPaste", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Redo.
/// </summary>
public static string ContextMenuRedo {
get {
return ResourceManager.GetString("ContextMenuRedo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Undo.
/// </summary>
public static string ContextMenuUndo {
get {
return ResourceManager.GetString("ContextMenuUndo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show available macros.
/// </summary>
public static string ShowMacrosHint {
get {
return ResourceManager.GetString("ShowMacrosHint", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Invalid: {0}. /// Looks up a localized string similar to Invalid: {0}.
/// </summary> /// </summary>
internal static string ValidationError { public static string ValidationError {
get { get {
return ResourceManager.GetString("ValidationError", resourceCulture); return ResourceManager.GetString("ValidationError", resourceCulture);
} }
@ -99,7 +171,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary> /// <summary>
/// Looks up a localized string similar to Valid. /// Looks up a localized string similar to Valid.
/// </summary> /// </summary>
internal static string ValidationOk { public static string ValidationOk {
get { get {
return ResourceManager.GetString("ValidationOk", resourceCulture); return ResourceManager.GetString("ValidationOk", resourceCulture);
} }
@ -108,7 +180,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary> /// <summary>
/// Looks up a localized string similar to Valid syntax. /// Looks up a localized string similar to Valid syntax.
/// </summary> /// </summary>
internal static string ValidationOkSyntax { public static string ValidationOkSyntax {
get { get {
return ResourceManager.GetString("ValidationOkSyntax", resourceCulture); return ResourceManager.GetString("ValidationOkSyntax", resourceCulture);
} }
@ -117,7 +189,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary> /// <summary>
/// Looks up a localized string similar to Validating.... /// Looks up a localized string similar to Validating....
/// </summary> /// </summary>
internal static string ValidationValidating { public static string ValidationValidating {
get { get {
return ResourceManager.GetString("ValidationValidating", resourceCulture); return ResourceManager.GetString("ValidationValidating", resourceCulture);
} }

View File

@ -117,6 +117,9 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="CheckEnableMacros" xml:space="preserve">
<value>Enable macros (right-click editor to insert)</value>
</data>
<data name="ContentTypeJson" xml:space="preserve"> <data name="ContentTypeJson" xml:space="preserve">
<value>JSON</value> <value>JSON</value>
</data> </data>
@ -126,6 +129,27 @@
<data name="ContentTypePlain" xml:space="preserve"> <data name="ContentTypePlain" xml:space="preserve">
<value>Plain text</value> <value>Plain text</value>
</data> </data>
<data name="ContextMenuCopy" xml:space="preserve">
<value>Copy</value>
</data>
<data name="ContextMenuCut" xml:space="preserve">
<value>Cut</value>
</data>
<data name="ContextMenuInsertMacro" xml:space="preserve">
<value>Insert macro</value>
</data>
<data name="ContextMenuPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="ContextMenuRedo" xml:space="preserve">
<value>Redo</value>
</data>
<data name="ContextMenuUndo" xml:space="preserve">
<value>Undo</value>
</data>
<data name="ShowMacrosHint" xml:space="preserve">
<value>Show available macros</value>
</data>
<data name="ValidationError" xml:space="preserve"> <data name="ValidationError" xml:space="preserve">
<value>Invalid: {0}</value> <value>Invalid: {0}</value>
</data> </data>

View File

@ -2,6 +2,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -60,10 +61,11 @@ namespace PettingZoo.UI.Tab.Publisher
private string contentType = ContentTypeJson; private string contentType = ContentTypeJson;
private PayloadEditorContentType contentTypeSelection = PayloadEditorContentType.Json; private PayloadEditorContentType contentTypeSelection = PayloadEditorContentType.Json;
private bool fixedJson; private bool fixedJson;
private ValidationInfo validationInfo = new(ValidationStatus.OkSyntax); private ValidationInfo validationInfo = new(ValidationStatus.OkSyntax);
private string payload = ""; private string payload = "";
private bool enableMacros;
public string ContentType public string ContentType
@ -136,6 +138,7 @@ namespace PettingZoo.UI.Tab.Publisher
public Visibility ContentTypeVisibility => FixedJson ? Visibility.Collapsed : Visibility.Visible; public Visibility ContentTypeVisibility => FixedJson ? Visibility.Collapsed : Visibility.Visible;
public string Payload public string Payload
{ {
get => payload; get => payload;
@ -143,6 +146,13 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public bool EnableMacros
{
get => enableMacros;
set => SetField(ref enableMacros, value);
}
public IHighlightingDefinition? SyntaxHighlighting => ContentTypeSelection == PayloadEditorContentType.Json public IHighlightingDefinition? SyntaxHighlighting => ContentTypeSelection == PayloadEditorContentType.Json
? HighlightingManager.Instance.GetDefinition(@"Json") ? HighlightingManager.Instance.GetDefinition(@"Json")
: null; : null;

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher" xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher"
xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF" xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
@ -13,6 +14,8 @@
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<controls:GridLayout Style="{StaticResource Form}" Margin="4" Grid.IsSharedSizeScope="True"> <controls:GridLayout Style="{StaticResource Form}" Margin="4" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@ -20,8 +23,7 @@
<RowDefinition Height="16"/> <RowDefinition Height="16"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="16"/> <RowDefinition Height="16" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -30,37 +32,47 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="1"> <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="1">
<ToggleButton Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeRaw}">
<StackPanel Orientation="Horizontal">
<Image Source="{svgc:SvgImage Source=/Images/RabbitMQ.svg, AppName=PettingZoo}" Style="{StaticResource ButtonIcon}" />
<TextBlock Text="{x:Static res:PublisherViewStrings.OptionMessageTypeRaw}" />
</StackPanel>
</ToggleButton>
<ToggleButton Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/Tapeti.png" Style="{StaticResource ButtonIcon}" />
<TextBlock Text="{x:Static res:PublisherViewStrings.OptionMessageTypeTapeti}" />
</StackPanel>
</ToggleButton>
</StackPanel>
<Label Grid.Row="2" Grid.Column="1">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelSendToExchange}" IsChecked="{Binding SendToExchange}" Style="{StaticResource TypeSelection}" /> <RadioButton Content="{x:Static res:PublisherViewStrings.LabelSendToExchange}" IsChecked="{Binding SendToExchange}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelSendToQueue}" IsChecked="{Binding SendToQueue}" Style="{StaticResource TypeSelection}" /> <RadioButton Content="{x:Static res:PublisherViewStrings.LabelSendToQueue}" IsChecked="{Binding SendToQueue}" Style="{StaticResource TypeSelection}" />
</StackPanel> </StackPanel>
</Label> </Label>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" /> <Label Grid.Row="3" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelExchange}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Exchange, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ExchangeVisibility}" /> <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Exchange, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" /> <Label Grid.Row="4" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelRoutingKey}" Visibility="{Binding ExchangeVisibility}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ExchangeVisibility}" /> <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelQueue}" Visibility="{Binding QueueVisibility}" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelQueue}" Visibility="{Binding QueueVisibility}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding QueueVisibility}" /> <TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Queue, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding QueueVisibility}" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelReplyTo}" /> <Label Grid.Row="7" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelReplyTo}" />
<StackPanel Orientation="Horizontal" Grid.Row="5" Grid.Column="1"> <StackPanel Orientation="Horizontal" Grid.Row="7" Grid.Column="1">
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelReplyToSpecified}" IsChecked="{Binding ReplyToSpecified}" Style="{StaticResource TypeSelection}" /> <RadioButton Content="{x:Static res:PublisherViewStrings.LabelReplyToSpecified}" IsChecked="{Binding ReplyToSpecified}" Style="{StaticResource TypeSelection}" />
<RadioButton Content="{x:Static res:PublisherViewStrings.LabelReplyToNewSubscriber}" IsChecked="{Binding ReplyToNewSubscriber}" Style="{StaticResource TypeSelection}" /> <RadioButton Content="{x:Static res:PublisherViewStrings.LabelReplyToNewSubscriber}" IsChecked="{Binding ReplyToNewSubscriber}" Style="{StaticResource TypeSelection}" />
</StackPanel> </StackPanel>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding ReplyTo}" IsEnabled="{Binding ReplyToSpecified}" /> <TextBox Grid.Row="8" Grid.Column="1" Text="{Binding ReplyTo}" IsEnabled="{Binding ReplyToSpecified}" />
<StackPanel Orientation="Horizontal" Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center"> <ContentControl Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 8 0 0" Content="{Binding MessageTypeControl}" />
<ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeRaw}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeRaw}" />
<ToggleButton Content="{x:Static res:PublisherViewStrings.OptionMessageTypeTapeti}" Style="{StaticResource TypeSelection}" IsChecked="{Binding MessageTypeTapeti}" />
</StackPanel>
<ContentControl Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 8 0 0" Content="{Binding MessageTypeControl}" /> <Button Grid.Row="11" Grid.Column="1" Command="{Binding PublishCommand}" Content="{x:Static res:PublisherViewStrings.CommandPublish}" HorizontalAlignment="Left" />
<Button Grid.Row="10" Grid.Column="1" Command="{Binding PublishCommand}" Content="{x:Static res:PublisherViewStrings.CommandPublish}" HorizontalAlignment="Left" />
</controls:GridLayout> </controls:GridLayout>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View File

@ -5,6 +5,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.Generator; using PettingZoo.Core.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.WPF.ViewModel; using PettingZoo.WPF.ViewModel;
namespace PettingZoo.UI.Tab.Publisher namespace PettingZoo.UI.Tab.Publisher
@ -20,8 +21,8 @@ namespace PettingZoo.UI.Tab.Publisher
{ {
private readonly IConnection connection; private readonly IConnection connection;
private readonly IExampleGenerator exampleGenerator; private readonly IExampleGenerator exampleGenerator;
private readonly IPayloadMacroProcessor payloadMacroProcessor;
private readonly ITabFactory tabFactory; private readonly ITabFactory tabFactory;
private readonly ITabHostProvider tabHostProvider;
private bool sendToExchange = true; private bool sendToExchange = true;
private string exchange = ""; private string exchange = "";
@ -156,12 +157,12 @@ namespace PettingZoo.UI.Tab.Publisher
string IPublishDestination.RoutingKey => SendToExchange ? RoutingKey : Queue; string IPublishDestination.RoutingKey => SendToExchange ? RoutingKey : Queue;
public PublisherViewModel(ITabHostProvider tabHostProvider, ITabFactory tabFactory, IConnection connection, IExampleGenerator exampleGenerator, ReceivedMessageInfo? fromReceivedMessage = null) public PublisherViewModel(ITabFactory tabFactory, IConnection connection, IExampleGenerator exampleGenerator, IPayloadMacroProcessor payloadMacroProcessor, ReceivedMessageInfo? fromReceivedMessage = null)
{ {
this.connection = connection; this.connection = connection;
this.exampleGenerator = exampleGenerator; this.exampleGenerator = exampleGenerator;
this.payloadMacroProcessor = payloadMacroProcessor;
this.tabFactory = tabFactory; this.tabFactory = tabFactory;
this.tabHostProvider = tabHostProvider;
publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute); publishCommand = new DelegateCommand(PublishExecute, PublishCanExecute);
@ -209,7 +210,7 @@ namespace PettingZoo.UI.Tab.Publisher
if (rawPublisherView == null) if (rawPublisherView == null)
{ {
rawPublisherViewModel = new RawPublisherViewModel(connection, this); rawPublisherViewModel = new RawPublisherViewModel(connection, this, payloadMacroProcessor);
rawPublisherViewModel.PublishCommand.CanExecuteChanged += (_, _) => rawPublisherViewModel.PublishCommand.CanExecuteChanged += (_, _) =>
{ {
publishCommand.RaiseCanExecuteChanged(); publishCommand.RaiseCanExecuteChanged();
@ -230,7 +231,7 @@ namespace PettingZoo.UI.Tab.Publisher
if (tapetiPublisherView == null) if (tapetiPublisherView == null)
{ {
tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator); tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator, payloadMacroProcessor);
tapetiPublisherViewModel.PublishCommand.CanExecuteChanged += (_, _) => tapetiPublisherViewModel.PublishCommand.CanExecuteChanged += (_, _) =>
{ {
publishCommand.RaiseCanExecuteChanged(); publishCommand.RaiseCanExecuteChanged();
@ -265,14 +266,14 @@ namespace PettingZoo.UI.Tab.Publisher
if (TapetiPublisherViewModel.IsTapetiMessage(fromReceivedMessage)) if (TapetiPublisherViewModel.IsTapetiMessage(fromReceivedMessage))
{ {
var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator, fromReceivedMessage); var tapetiPublisherViewModel = new TapetiPublisherViewModel(connection, this, exampleGenerator, payloadMacroProcessor, fromReceivedMessage);
tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel); tapetiPublisherView = new TapetiPublisherView(tapetiPublisherViewModel);
MessageType = MessageType.Tapeti; MessageType = MessageType.Tapeti;
} }
else else
{ {
var rawPublisherViewModel = new RawPublisherViewModel(connection, this, fromReceivedMessage); var rawPublisherViewModel = new RawPublisherViewModel(connection, this, payloadMacroProcessor, fromReceivedMessage);
rawPublisherView = new RawPublisherView(rawPublisherViewModel); rawPublisherView = new RawPublisherView(rawPublisherViewModel);
MessageType = MessageType.Raw; MessageType = MessageType.Raw;
@ -280,17 +281,20 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public string? GetReplyTo() public string? GetReplyTo(ref string? correlationId)
{ {
if (ReplyToSpecified) if (ReplyToSpecified)
return string.IsNullOrEmpty(ReplyTo) ? null : ReplyTo; return string.IsNullOrEmpty(ReplyTo) ? null : ReplyTo;
var subscriber = connection.Subscribe(); correlationId = PublisherViewStrings.ReplyToCorrelationIdPrefix + (SendToExchange ? RoutingKey : Queue);
var tab = tabFactory.CreateSubscriberTab(connection, subscriber); return tabFactory.CreateReplySubscriberTab(connection);
tabHostProvider.Instance.AddTab(tab); }
subscriber.Start();
return subscriber.QueueName; public void SetExchangeDestination(string newExchange, string newRoutingKey)
{
Exchange = newExchange;
RoutingKey = newRoutingKey;
} }

View File

@ -168,6 +168,15 @@ namespace PettingZoo.UI.Tab.Publisher {
} }
} }
/// <summary>
/// Looks up a localized string similar to Re: .
/// </summary>
public static string ReplyToCorrelationIdPrefix {
get {
return ResourceManager.GetString("ReplyToCorrelationIdPrefix", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Publish: {0}. /// Looks up a localized string similar to Publish: {0}.
/// </summary> /// </summary>

View File

@ -112,10 +112,10 @@
<value>2.0</value> <value>2.0</value>
</resheader> </resheader>
<resheader name="reader"> <resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="CommandPublish" xml:space="preserve"> <data name="CommandPublish" xml:space="preserve">
<value>Publish</value> <value>Publish</value>
@ -153,6 +153,9 @@
<data name="OptionMessageTypeTapeti" xml:space="preserve"> <data name="OptionMessageTypeTapeti" xml:space="preserve">
<value>Tapeti message</value> <value>Tapeti message</value>
</data> </data>
<data name="ReplyToCorrelationIdPrefix" xml:space="preserve">
<value>Re: </value>
</data>
<data name="TabTitle" xml:space="preserve"> <data name="TabTitle" xml:space="preserve">
<value>Publish: {0}</value> <value>Publish: {0}</value>
</data> </data>

View File

@ -131,6 +131,7 @@
</Button> </Button>
<Label Grid.Row="14" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPayload}" /> <Label Grid.Row="14" Grid.Column="0" Content="{x:Static publisher:RawPublisherViewStrings.LabelPayload}" />
<publisher:PayloadEditorControl Grid.Row="14" Grid.Column="1" Payload="{Binding Payload}" ContentType="{Binding ContentType}" Height="350"/> <publisher:PayloadEditorControl Grid.Row="14" Grid.Column="1" Payload="{Binding Payload}" ContentType="{Binding ContentType}" Height="350" x:Name="PayloadEditor"
EnableMacros="{Binding EnableMacros}" />
</controls:GridLayout> </controls:GridLayout>
</UserControl> </UserControl>

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading; using System.Windows.Threading;
using PettingZoo.Core.Macros;
namespace PettingZoo.UI.Tab.Publisher namespace PettingZoo.UI.Tab.Publisher
{ {
@ -22,6 +23,8 @@ namespace PettingZoo.UI.Tab.Publisher
DataContext = viewModel; DataContext = viewModel;
InitializeComponent(); InitializeComponent();
PayloadEditor.MacroProcessor = viewModel.PayloadMacroProcessor;
checkEmptyHeaderTimer = new DispatcherTimer(); checkEmptyHeaderTimer = new DispatcherTimer();
checkEmptyHeaderTimer.Tick += CheckEmptyHeaderTimerOnTick; checkEmptyHeaderTimer.Tick += CheckEmptyHeaderTimerOnTick;
checkEmptyHeaderTimer.Interval = TimeSpan.FromMilliseconds(50); checkEmptyHeaderTimer.Interval = TimeSpan.FromMilliseconds(50);

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.Macros;
using PettingZoo.WPF.ViewModel; using PettingZoo.WPF.ViewModel;
namespace PettingZoo.UI.Tab.Publisher namespace PettingZoo.UI.Tab.Publisher
@ -32,6 +33,7 @@ namespace PettingZoo.UI.Tab.Publisher
private string typeProperty = ""; private string typeProperty = "";
private string userId = ""; private string userId = "";
private string payload = ""; private string payload = "";
private bool enableMacros;
@ -118,6 +120,12 @@ namespace PettingZoo.UI.Tab.Publisher
set => SetField(ref payload, value, delegateCommandsChanged: new [] { publishCommand }); set => SetField(ref payload, value, delegateCommandsChanged: new [] { publishCommand });
} }
public bool EnableMacros
{
get => enableMacros;
set => SetField(ref enableMacros, value);
}
public ObservableCollection<Header> Headers { get; } = new(); public ObservableCollection<Header> Headers { get; } = new();
@ -125,6 +133,8 @@ namespace PettingZoo.UI.Tab.Publisher
public ICommand PublishCommand => publishCommand; public ICommand PublishCommand => publishCommand;
public ICommand PropertiesExpandCollapseCommand => propertiesExpandCollapseCommand; public ICommand PropertiesExpandCollapseCommand => propertiesExpandCollapseCommand;
public IPayloadMacroProcessor PayloadMacroProcessor { get; }
public bool PropertiesExpanded public bool PropertiesExpanded
{ {
@ -145,8 +155,10 @@ namespace PettingZoo.UI.Tab.Publisher
protected Header LastHeader; protected Header LastHeader;
public RawPublisherViewModel(IConnection connection, IPublishDestination publishDestination, BaseMessageInfo? receivedMessage = null) public RawPublisherViewModel(IConnection connection, IPublishDestination publishDestination, IPayloadMacroProcessor payloadMacroProcessor, BaseMessageInfo? receivedMessage = null)
{ {
PayloadMacroProcessor = payloadMacroProcessor;
this.connection = connection; this.connection = connection;
this.publishDestination = publishDestination; this.publishDestination = publishDestination;
@ -242,24 +254,31 @@ namespace PettingZoo.UI.Tab.Publisher
} }
} }
var encodedPayload = Encoding.UTF8.GetBytes(
EnableMacros
? PayloadMacroProcessor.Apply(Payload)
: Payload
);
var headers = Headers.Where(h => h.IsValid()).ToDictionary(h => h.Key, h => h.Value); var headers = Headers.Where(h => h.IsValid()).ToDictionary(h => h.Key, h => h.Value);
var publishCorrelationId = NullIfEmpty(CorrelationId);
var replyTo = publishDestination.GetReplyTo(ref publishCorrelationId);
connection.Publish(new PublishMessageInfo( connection.Publish(new PublishMessageInfo(
publishDestination.Exchange, publishDestination.Exchange,
publishDestination.RoutingKey, publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload), encodedPayload,
new MessageProperties(headers) new MessageProperties(headers)
{ {
AppId = NullIfEmpty(AppId), AppId = NullIfEmpty(AppId),
ContentEncoding = NullIfEmpty(ContentEncoding), ContentEncoding = NullIfEmpty(ContentEncoding),
ContentType = NullIfEmpty(ContentType), ContentType = NullIfEmpty(ContentType),
CorrelationId = NullIfEmpty(CorrelationId), CorrelationId = publishCorrelationId,
DeliveryMode = deliveryMode, DeliveryMode = deliveryMode,
Expiration = NullIfEmpty(Expiration), Expiration = NullIfEmpty(Expiration),
MessageId = NullIfEmpty(MessageId), MessageId = NullIfEmpty(MessageId),
Priority = priorityValue, Priority = priorityValue,
ReplyTo = publishDestination.GetReplyTo(), ReplyTo = replyTo,
Timestamp = timestampValue, Timestamp = timestampValue,
Type = NullIfEmpty(TypeProperty), Type = NullIfEmpty(TypeProperty),
UserId = NullIfEmpty(UserId) UserId = NullIfEmpty(UserId)
@ -309,7 +328,7 @@ namespace PettingZoo.UI.Tab.Publisher
public class DesignTimeRawPublisherViewModel : RawPublisherViewModel public class DesignTimeRawPublisherViewModel : RawPublisherViewModel
{ {
public DesignTimeRawPublisherViewModel() : base(null!, null!) public DesignTimeRawPublisherViewModel() : base(null!, null!, null!)
{ {
PropertiesExpanded = true; PropertiesExpanded = true;

View File

@ -46,6 +46,7 @@
</Grid> </Grid>
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelPayload}" /> <Label Grid.Row="6" Grid.Column="0" Content="{x:Static publisher:TapetiPublisherViewStrings.LabelPayload}" />
<publisher:PayloadEditorControl Grid.Row="6" Grid.Column="1" Payload="{Binding Payload}" FixedJson="True" Height="350" x:Name="PayloadEditor"/> <publisher:PayloadEditorControl Grid.Row="6" Grid.Column="1" Payload="{Binding Payload}" FixedJson="True" Height="350" x:Name="PayloadEditor"
EnableMacros="{Binding EnableMacros}" />
</controls:GridLayout> </controls:GridLayout>
</UserControl> </UserControl>

View File

@ -1,5 +1,6 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using PettingZoo.Core.Macros;
namespace PettingZoo.UI.Tab.Publisher namespace PettingZoo.UI.Tab.Publisher
{ {
@ -15,6 +16,7 @@ namespace PettingZoo.UI.Tab.Publisher
InitializeComponent(); InitializeComponent();
PayloadEditor.Validator = viewModel; PayloadEditor.Validator = viewModel;
PayloadEditor.MacroProcessor = viewModel.PayloadMacroProcessor;
} }

View File

@ -2,9 +2,9 @@
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading;
using PettingZoo.Core.Connection; using PettingZoo.Core.Connection;
using PettingZoo.Core.Generator; using PettingZoo.Core.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.Core.Validation; using PettingZoo.Core.Validation;
using PettingZoo.WPF.ViewModel; using PettingZoo.WPF.ViewModel;
using IConnection = PettingZoo.Core.Connection.IConnection; using IConnection = PettingZoo.Core.Connection.IConnection;
@ -21,6 +21,7 @@ namespace PettingZoo.UI.Tab.Publisher
private string correlationId = ""; private string correlationId = "";
private string payload = ""; private string payload = "";
private bool enableMacros;
private string className = ""; private string className = "";
private string assemblyName = ""; private string assemblyName = "";
private Window? tabHostWindow; private Window? tabHostWindow;
@ -68,9 +69,17 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public bool EnableMacros
{
get => enableMacros;
set => SetField(ref enableMacros, value);
}
public ICommand PublishCommand => publishCommand; public ICommand PublishCommand => publishCommand;
public ICommand BrowseClassCommand => browseClassCommand; public ICommand BrowseClassCommand => browseClassCommand;
public IPayloadMacroProcessor PayloadMacroProcessor { get; }
public static bool IsTapetiMessage(ReceivedMessageInfo receivedMessage) public static bool IsTapetiMessage(ReceivedMessageInfo receivedMessage)
@ -100,8 +109,11 @@ namespace PettingZoo.UI.Tab.Publisher
} }
public TapetiPublisherViewModel(IConnection connection, IPublishDestination publishDestination, IExampleGenerator exampleGenerator, ReceivedMessageInfo? receivedMessage = null) public TapetiPublisherViewModel(IConnection connection, IPublishDestination publishDestination, IExampleGenerator exampleGenerator,
IPayloadMacroProcessor payloadMacroProcessor, ReceivedMessageInfo? receivedMessage = null)
{ {
PayloadMacroProcessor = payloadMacroProcessor;
this.connection = connection; this.connection = connection;
this.publishDestination = publishDestination; this.publishDestination = publishDestination;
this.exampleGenerator = exampleGenerator; this.exampleGenerator = exampleGenerator;
@ -135,6 +147,9 @@ namespace PettingZoo.UI.Tab.Publisher
AssemblyName = classTypeExample.AssemblyName; AssemblyName = classTypeExample.AssemblyName;
ClassName = classTypeExample.FullClassName; ClassName = classTypeExample.FullClassName;
if (classTypeExample.TryGetPublishDestination(out var exchange, out var routingKey))
publishDestination.SetExchangeDestination(exchange, routingKey);
validatingExample = classTypeExample as IValidatingExample; validatingExample = classTypeExample as IValidatingExample;
break; break;
} }
@ -151,20 +166,30 @@ namespace PettingZoo.UI.Tab.Publisher
{ {
return string.IsNullOrEmpty(value) ? null : value; return string.IsNullOrEmpty(value) ? null : value;
} }
var encodedPayload = Encoding.UTF8.GetBytes(
EnableMacros
? PayloadMacroProcessor.Apply(Payload)
: Payload
);
var publishCorrelationId = NullIfEmpty(CorrelationId);
var replyTo = publishDestination.GetReplyTo(ref publishCorrelationId);
connection.Publish(new PublishMessageInfo( connection.Publish(new PublishMessageInfo(
publishDestination.Exchange, publishDestination.Exchange,
publishDestination.RoutingKey, publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload), encodedPayload,
new MessageProperties(new Dictionary<string, string> new MessageProperties(new Dictionary<string, string>
{ {
{ @"classType", $"{ClassName}:{AssemblyName}" } { @"classType", $"{ClassName}:{AssemblyName}" }
}) })
{ {
ContentType = @"application/json", ContentType = @"application/json",
CorrelationId = NullIfEmpty(CorrelationId), CorrelationId = publishCorrelationId,
DeliveryMode = MessageDeliveryMode.Persistent, DeliveryMode = MessageDeliveryMode.Persistent,
ReplyTo = publishDestination.GetReplyTo() ReplyTo = replyTo
})); }));
} }
@ -199,7 +224,7 @@ namespace PettingZoo.UI.Tab.Publisher
public class DesignTimeTapetiPublisherViewModel : TapetiPublisherViewModel public class DesignTimeTapetiPublisherViewModel : TapetiPublisherViewModel
{ {
public DesignTimeTapetiPublisherViewModel() : base(null!, null!, null!) public DesignTimeTapetiPublisherViewModel() : base(null!, null!, null!, null!)
{ {
AssemblyName = "Messaging.Example"; AssemblyName = "Messaging.Example";
ClassName = "Messaging.Example.ExampleMessage"; ClassName = "Messaging.Example.ExampleMessage";

View File

@ -1,23 +0,0 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace PettingZoo.UI.Tab.Subscriber
{
public class SameMessageVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ReferenceEquals(values[0], values[1])
? Visibility.Visible
: Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@ -13,9 +13,6 @@
d:DesignWidth="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance res:DesignTimeSubscriberViewModel, IsDesignTimeCreatable=True}" d:DataContext="{d:DesignInstance res:DesignTimeSubscriberViewModel, IsDesignTimeCreatable=True}"
Background="White"> Background="White">
<UserControl.Resources>
<res:SameMessageVisibilityConverter x:Key="SameMessageVisibilityConverter" />
</UserControl.Resources>
<Grid Margin="4"> <Grid Margin="4">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@ -64,7 +61,8 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}" /> <TextBlock Grid.Column="0" Text="{Binding ReceivedTimestamp, StringFormat=g}" Style="{StaticResource Timestamp}" />
<TextBlock Grid.Column="1" Text="{Binding RoutingKey}" Style="{StaticResource RoutingKey}" /> <TextBlock Grid.Column="1" Text="{Binding RoutingKey}" Style="{StaticResource RoutingKey}" Visibility="{Binding DataContext.StandardTabVisibility, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" />
<TextBlock Grid.Column="1" Text="{Binding Properties.CorrelationId}" Style="{StaticResource RoutingKey}" Visibility="{Binding DataContext.ReplyTabVisibility, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" />
<Grid.ContextMenu> <Grid.ContextMenu>
<ContextMenu> <ContextMenu>

View File

@ -21,10 +21,9 @@ using Timer = System.Threading.Timer;
namespace PettingZoo.UI.Tab.Subscriber namespace PettingZoo.UI.Tab.Subscriber
{ {
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands, ITabActivate public class SubscriberViewModel : BaseViewModel, IDisposable, ITabToolbarCommands, ITabActivate
{ {
private readonly ILogger logger; private readonly ILogger logger;
private readonly ITabHostProvider tabHostProvider;
private readonly ITabFactory tabFactory; private readonly ITabFactory tabFactory;
private readonly IConnection? connection; private readonly IConnection? connection;
private readonly ISubscriber subscriber; private readonly ISubscriber subscriber;
@ -76,16 +75,43 @@ namespace PettingZoo.UI.Tab.Subscriber
set => SetField(ref selectedMessageProperties, value); set => SetField(ref selectedMessageProperties, value);
} }
public string Title => public string Title
(subscriber.Exchange != null ? $"{subscriber.Exchange} - {subscriber.RoutingKey}" : $"{subscriber.QueueName}") + {
(tabActive || unreadCount == 0 ? "" : $" ({unreadCount})"); get
{
var title = new StringBuilder();
if (IsReplyTab)
title.Append(SubscriberViewStrings.ReplyTabTitle);
else if (subscriber.Exchange != null)
title.Append(subscriber.Exchange).Append(" - ").Append(subscriber.RoutingKey);
else
title.Append(subscriber.QueueName);
if (!tabActive && unreadCount > 0)
title.Append(" (").Append(unreadCount).Append(')');
return title.ToString();
}
}
public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands; public IEnumerable<TabToolbarCommand> ToolbarCommands => toolbarCommands;
public SubscriberViewModel(ILogger logger, ITabHostProvider tabHostProvider, ITabFactory tabFactory, IConnection? connection, ISubscriber subscriber, IExportImportFormatProvider exportImportFormatProvider) public bool IsReplyTab { get; }
// ReSharper disable UnusedMember.Global - used via BindingProxy
public Visibility StandardTabVisibility => !IsReplyTab ? Visibility.Visible : Visibility.Collapsed;
public Visibility ReplyTabVisibility => IsReplyTab ? Visibility.Visible : Visibility.Collapsed;
// ReSharper restore UnusedMember.Global
public SubscriberViewModel(ILogger logger, ITabFactory tabFactory, IConnection? connection, ISubscriber subscriber, IExportImportFormatProvider exportImportFormatProvider, bool isReplyTab)
{ {
IsReplyTab = isReplyTab;
this.logger = logger; this.logger = logger;
this.tabHostProvider = tabHostProvider;
this.tabFactory = tabFactory; this.tabFactory = tabFactory;
this.connection = connection; this.connection = connection;
this.subscriber = subscriber; this.subscriber = subscriber;
@ -111,6 +137,15 @@ namespace PettingZoo.UI.Tab.Subscriber
subscriber.Start(); subscriber.Start();
} }
public void Dispose()
{
GC.SuppressFinalize(this);
newMessageTimer?.Dispose();
subscriber.Dispose();
}
private void ClearExecute() private void ClearExecute()
{ {
Messages.Clear(); Messages.Clear();
@ -222,8 +257,7 @@ namespace PettingZoo.UI.Tab.Subscriber
if (connection == null) if (connection == null)
return; return;
var publisherTab = tabFactory.CreatePublisherTab(connection, SelectedMessage); tabFactory.CreatePublisherTab(connection, SelectedMessage);
tabHostProvider.Instance.AddTab(publisherTab);
} }
@ -320,7 +354,7 @@ namespace PettingZoo.UI.Tab.Subscriber
public class DesignTimeSubscriberViewModel : SubscriberViewModel public class DesignTimeSubscriberViewModel : SubscriberViewModel
{ {
public DesignTimeSubscriberViewModel() : base(null!, null!, null!, null!, new DesignTimeSubscriber(), null!) public DesignTimeSubscriberViewModel() : base(null!, null!, null!, new DesignTimeSubscriber(), null!, false)
{ {
for (var i = 1; i <= 5; i++) for (var i = 1; i <= 5; i++)
(i > 2 ? UnreadMessages : Messages).Add(new ReceivedMessageInfo( (i > 2 ? UnreadMessages : Messages).Add(new ReceivedMessageInfo(
@ -340,9 +374,9 @@ namespace PettingZoo.UI.Tab.Subscriber
private class DesignTimeSubscriber : ISubscriber private class DesignTimeSubscriber : ISubscriber
{ {
public ValueTask DisposeAsync() public void Dispose()
{ {
return default; GC.SuppressFinalize(this);
} }

View File

@ -185,5 +185,14 @@ namespace PettingZoo.UI.Tab.Subscriber {
return ResourceManager.GetString("PropertyValue", resourceCulture); return ResourceManager.GetString("PropertyValue", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to Replies.
/// </summary>
public static string ReplyTabTitle {
get {
return ResourceManager.GetString("ReplyTabTitle", resourceCulture);
}
}
} }
} }

View File

@ -159,4 +159,7 @@
<data name="PropertyValue" xml:space="preserve"> <data name="PropertyValue" xml:space="preserve">
<value>Value</value> <value>Value</value>
</data> </data>
<data name="ReplyTabTitle" xml:space="preserve">
<value>Replies</value>
</data>
</root> </root>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
@ -13,6 +14,7 @@ namespace PettingZoo.UI.Tab.Undocked
private readonly ITabHostProvider tabHostProvider; private readonly ITabHostProvider tabHostProvider;
private readonly ITab tab; private readonly ITab tab;
private readonly DelegateCommand dockCommand; private readonly DelegateCommand dockCommand;
private bool docked;
public string Title => tab.Title; public string Title => tab.Title;
@ -43,13 +45,18 @@ namespace PettingZoo.UI.Tab.Undocked
private void DockCommandExecute() private void DockCommandExecute()
{ {
docked = true;
tabHostProvider.Instance.DockTab(tab); tabHostProvider.Instance.DockTab(tab);
} }
public void WindowClosed() public void WindowClosed()
{ {
if (docked)
return;
tabHostProvider.Instance.UndockedTabClosed(tab); tabHostProvider.Instance.UndockedTabClosed(tab);
(tab as IDisposable)?.Dispose();
} }

View File

@ -1,7 +1,5 @@
using System; using System.Windows;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Threading;
namespace PettingZoo.UI.Tab.Undocked namespace PettingZoo.UI.Tab.Undocked
{ {

View File

@ -8,7 +8,7 @@ using System.Windows.Controls;
namespace PettingZoo.UI.Tab namespace PettingZoo.UI.Tab
{ {
public class ViewTab<TView, TViewModel> : ITab, ITabToolbarCommands, ITabActivate, ITabHostWindowNotify where TView : ContentControl where TViewModel : INotifyPropertyChanged public class ViewTab<TView, TViewModel> : IDisposable, ITab, ITabToolbarCommands, ITabActivate, ITabHostWindowNotify where TView : ContentControl where TViewModel : INotifyPropertyChanged
{ {
public string Title => getTitle(viewModel); public string Title => getTitle(viewModel);
public ContentControl Content { get; } public ContentControl Content { get; }
@ -63,5 +63,12 @@ namespace PettingZoo.UI.Tab
{ {
(viewModel as ITabHostWindowNotify)?.HostWindowChanged(hostWindow); (viewModel as ITabHostWindowNotify)?.HostWindowChanged(hostWindow);
} }
public void Dispose()
{
GC.SuppressFinalize(this);
(viewModel as IDisposable)?.Dispose();
}
} }
} }

View File

@ -1,6 +1,9 @@
using PettingZoo.Core.Connection; using System;
using System.Collections.Generic;
using PettingZoo.Core.Connection;
using PettingZoo.Core.ExportImport; using PettingZoo.Core.ExportImport;
using PettingZoo.Core.Generator; using PettingZoo.Core.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.UI.Tab.Publisher; using PettingZoo.UI.Tab.Publisher;
using PettingZoo.UI.Tab.Subscriber; using PettingZoo.UI.Tab.Subscriber;
using Serilog; using Serilog;
@ -13,34 +16,119 @@ namespace PettingZoo.UI.Tab
private readonly ITabHostProvider tabHostProvider; private readonly ITabHostProvider tabHostProvider;
private readonly IExampleGenerator exampleGenerator; private readonly IExampleGenerator exampleGenerator;
private readonly IExportImportFormatProvider exportImportFormatProvider; private readonly IExportImportFormatProvider exportImportFormatProvider;
private readonly IPayloadMacroProcessor payloadMacroProcessor;
// Not the cleanest way, but this factory itself can't be singleton without (justifyable) upsetting SimpleInjector
private static ISubscriber? replySubscriber;
private static ITab? replySubscriberTab;
public ViewTabFactory(ILogger logger, ITabHostProvider tabHostProvider, IExampleGenerator exampleGenerator, IExportImportFormatProvider exportImportFormatProvider) public ViewTabFactory(ILogger logger, ITabHostProvider tabHostProvider, IExampleGenerator exampleGenerator, IExportImportFormatProvider exportImportFormatProvider,
IPayloadMacroProcessor payloadMacroProcessor)
{ {
this.logger = logger; this.logger = logger;
this.tabHostProvider = tabHostProvider; this.tabHostProvider = tabHostProvider;
this.exampleGenerator = exampleGenerator; this.exampleGenerator = exampleGenerator;
this.exportImportFormatProvider = exportImportFormatProvider; this.exportImportFormatProvider = exportImportFormatProvider;
this.payloadMacroProcessor = payloadMacroProcessor;
} }
public ITab CreateSubscriberTab(IConnection? connection, ISubscriber subscriber) public void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber)
{ {
var viewModel = new SubscriberViewModel(logger, tabHostProvider, this, connection, subscriber, exportImportFormatProvider); InternalCreateSubscriberTab(connection, subscriber, false);
return new ViewTab<SubscriberView, SubscriberViewModel>(
new SubscriberView(viewModel),
viewModel,
vm => vm.Title);
} }
public ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null) public string CreateReplySubscriberTab(IConnection connection)
{ {
var viewModel = new PublisherViewModel(tabHostProvider, this, connection, exampleGenerator, fromReceivedMessage); if (replySubscriber?.QueueName != null && replySubscriberTab != null)
return new ViewTab<PublisherView, PublisherViewModel>( {
tabHostProvider.Instance.ActivateTab(replySubscriberTab);
return replySubscriber.QueueName;
}
replySubscriber = new SubscriberDecorator(connection.Subscribe(), () =>
{
replySubscriber = null;
replySubscriberTab = null;
});
replySubscriber.Start();
replySubscriberTab = InternalCreateSubscriberTab(connection, replySubscriber, true);
return replySubscriber.QueueName!;
}
public void CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
{
var viewModel = new PublisherViewModel(this, connection, exampleGenerator, payloadMacroProcessor, fromReceivedMessage);
var tab = new ViewTab<PublisherView, PublisherViewModel>(
new PublisherView(viewModel), new PublisherView(viewModel),
viewModel, viewModel,
vm => vm.Title); vm => vm.Title);
tabHostProvider.Instance.AddTab(tab);
}
private ITab InternalCreateSubscriberTab(IConnection? connection, ISubscriber subscriber, bool isReplyTab)
{
var viewModel = new SubscriberViewModel(logger, this, connection, subscriber, exportImportFormatProvider, isReplyTab);
var tab = new ViewTab<SubscriberView, SubscriberViewModel>(
new SubscriberView(viewModel),
viewModel,
vm => vm.Title);
tabHostProvider.Instance.AddTab(tab);
return tab;
}
private class SubscriberDecorator : ISubscriber
{
private readonly ISubscriber decoratedSubscriber;
private readonly Action onDispose;
public string? QueueName => decoratedSubscriber.QueueName;
public string? Exchange => decoratedSubscriber.Exchange;
public string? RoutingKey => decoratedSubscriber.RoutingKey;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public SubscriberDecorator(ISubscriber decoratedSubscriber, Action onDispose)
{
this.decoratedSubscriber = decoratedSubscriber;
this.onDispose = onDispose;
decoratedSubscriber.MessageReceived += (sender, args) =>
{
MessageReceived?.Invoke(sender, args);
};
}
public void Dispose()
{
GC.SuppressFinalize(this);
decoratedSubscriber.Dispose();
onDispose();
}
public IEnumerable<ReceivedMessageInfo> GetInitialMessages()
{
return decoratedSubscriber.GetInitialMessages();
}
public void Start()
{
decoratedSubscriber.Start();
}
} }
} }
} }