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
{
public interface ISubscriber : IAsyncDisposable
public interface ISubscriber : IDisposable
{
string? QueueName { get; }
string? Exchange {get; }

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using PettingZoo.Core.Connection;
namespace PettingZoo.Core.ExportImport
@ -13,7 +12,10 @@ namespace PettingZoo.Core.ExportImport
public string? QueueName { get; }
public string? Exchange => 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;
#pragma warning restore CS0067
public ImportSubscriber(string filename, IReadOnlyList<ReceivedMessageInfo> messages)
@ -23,10 +25,9 @@ namespace PettingZoo.Core.ExportImport
}
public ValueTask DisposeAsync()
public void Dispose()
{
GC.SuppressFinalize(this);
return default;
}

View File

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

View File

@ -17,11 +17,13 @@ namespace PettingZoo.Core.Generator
public interface IClassTypeExample : IExample
{
public string AssemblyName { get; }
public string? Namespace { get; }
public string ClassName { get; }
string AssemblyName { get; }
string? Namespace { 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PettingZoo.Core.Connection;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
@ -29,14 +28,12 @@ namespace PettingZoo.RabbitMQ
}
public ValueTask DisposeAsync()
public void Dispose()
{
GC.SuppressFinalize(this);
if (model != null && consumerTag != null && model.IsOpen)
model.BasicCancelNoWait(consumerTag);
return default;
}

View File

@ -7,6 +7,7 @@ using System.Runtime.Loader;
using Newtonsoft.Json;
using PettingZoo.Core.Generator;
using PettingZoo.Core.Validation;
using Tapeti.Default;
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()
{
return InitializeValidation();

View File

@ -17,6 +17,7 @@
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Reactive" Version="5.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.DataAnnotations.Extensions" Version="3.0.0" />
</ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

@ -4,9 +4,6 @@
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

View File

@ -170,7 +170,7 @@ namespace PettingZoo.UI.Main
if (connectionSettings.Subscribe)
{
var subscriber = connection.Subscribe(connectionSettings.Exchange, connectionSettings.RoutingKey);
AddTab(tabFactory.CreateSubscriberTab(connection, subscriber));
tabFactory.CreateSubscriberTab(connection, subscriber);
}
ConnectionChanged();
@ -214,7 +214,7 @@ namespace PettingZoo.UI.Main
subscribeDialogParams = newParams;
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)
return;
AddTab(tabFactory.CreatePublisherTab(connection));
tabFactory.CreatePublisherTab(connection);
}
@ -235,7 +235,8 @@ namespace PettingZoo.UI.Main
private void CloseTabExecute()
{
RemoveActiveTab();
var tab = RemoveActiveTab();
(tab as IDisposable)?.Dispose();
}
@ -300,8 +301,7 @@ namespace PettingZoo.UI.Main
progressWindow.Close();
progressWindow = null;
AddTab(tabFactory.CreateSubscriberTab(connection,
new ImportSubscriber(filename, messages)));
tabFactory.CreateSubscriberTab(connection, new ImportSubscriber(filename, messages));
});
}
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)
{
if (undockedTabs.Remove(tab, out var tabHostWindow))

View File

@ -2,13 +2,10 @@
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
{
ITab CreateSubscriberTab(IConnection? connection, ISubscriber subscriber);
ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null);
void CreateSubscriberTab(IConnection? connection, ISubscriber subscriber);
string CreateReplySubscriberTab(IConnection connection);
void CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null);
}
}

View File

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

View File

@ -5,6 +5,7 @@
string Exchange { 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 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/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" 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/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="1,4,0,0" Visibility="{Binding ValidationError}" />
<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" />
</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">
<avalonedit:TextEditor
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="Editor"
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>
</DockPanel>
</UserControl>

View File

@ -2,7 +2,9 @@
using System.Reactive.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using PettingZoo.Core.Macros;
using PettingZoo.Core.Validation;
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
{
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();
public PayloadEditorControl()
@ -123,6 +166,12 @@ namespace PettingZoo.UI.Tab.Publisher
viewModel.Payload = value;
});
this.OnPropertyChanges<bool>(EnableMacrosProperty)
.ObserveOn(SynchronizationContext.Current!)
.Subscribe(value =>
{
viewModel.EnableMacros = value;
});
viewModel.PropertyChanged += (_, args) =>
{
@ -139,6 +188,10 @@ namespace PettingZoo.UI.Tab.Publisher
case nameof(viewModel.Payload):
SetValue(PayloadProperty, viewModel.Payload);
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...
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.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class PayloadEditorStrings {
public class PayloadEditorStrings {
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.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
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.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
public static global::System.Globalization.CultureInfo Culture {
get {
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>
/// Looks up a localized string similar to JSON.
/// </summary>
internal static string ContentTypeJson {
public static string ContentTypeJson {
get {
return ResourceManager.GetString("ContentTypeJson", resourceCulture);
}
@ -72,7 +81,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary>
/// Looks up a localized string similar to Other.
/// </summary>
internal static string ContentTypeOther {
public static string ContentTypeOther {
get {
return ResourceManager.GetString("ContentTypeOther", resourceCulture);
}
@ -81,16 +90,79 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary>
/// Looks up a localized string similar to Plain text.
/// </summary>
internal static string ContentTypePlain {
public static string ContentTypePlain {
get {
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>
/// Looks up a localized string similar to Invalid: {0}.
/// </summary>
internal static string ValidationError {
public static string ValidationError {
get {
return ResourceManager.GetString("ValidationError", resourceCulture);
}
@ -99,7 +171,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary>
/// Looks up a localized string similar to Valid.
/// </summary>
internal static string ValidationOk {
public static string ValidationOk {
get {
return ResourceManager.GetString("ValidationOk", resourceCulture);
}
@ -108,7 +180,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary>
/// Looks up a localized string similar to Valid syntax.
/// </summary>
internal static string ValidationOkSyntax {
public static string ValidationOkSyntax {
get {
return ResourceManager.GetString("ValidationOkSyntax", resourceCulture);
}
@ -117,7 +189,7 @@ namespace PettingZoo.UI.Tab.Publisher {
/// <summary>
/// Looks up a localized string similar to Validating....
/// </summary>
internal static string ValidationValidating {
public static string ValidationValidating {
get {
return ResourceManager.GetString("ValidationValidating", resourceCulture);
}

View File

@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CheckEnableMacros" xml:space="preserve">
<value>Enable macros (right-click editor to insert)</value>
</data>
<data name="ContentTypeJson" xml:space="preserve">
<value>JSON</value>
</data>
@ -126,6 +129,27 @@
<data name="ContentTypePlain" xml:space="preserve">
<value>Plain text</value>
</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">
<value>Invalid: {0}</value>
</data>

View File

@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Highlighting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -60,10 +61,11 @@ namespace PettingZoo.UI.Tab.Publisher
private string contentType = ContentTypeJson;
private PayloadEditorContentType contentTypeSelection = PayloadEditorContentType.Json;
private bool fixedJson;
private ValidationInfo validationInfo = new(ValidationStatus.OkSyntax);
private string payload = "";
private bool enableMacros;
public string ContentType
@ -136,6 +138,7 @@ namespace PettingZoo.UI.Tab.Publisher
public Visibility ContentTypeVisibility => FixedJson ? Visibility.Collapsed : Visibility.Visible;
public string 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
? HighlightingManager.Instance.GetDefinition(@"Json")
: null;

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:res="clr-namespace:PettingZoo.UI.Tab.Publisher"
xmlns:controls="clr-namespace:PettingZoo.WPF.Controls;assembly=PettingZoo.WPF"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
@ -13,6 +14,8 @@
<ScrollViewer VerticalScrollBarVisibility="Auto">
<controls:GridLayout Style="{StaticResource Form}" Margin="4" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
@ -20,8 +23,7 @@
<RowDefinition Height="16"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="16" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
@ -30,37 +32,47 @@
<ColumnDefinition Width="*" />
</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">
<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}" />
</StackPanel>
</Label>
<Label Grid.Row="1" 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}" />
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelExchange}" 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}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ExchangeVisibility}" />
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelRoutingKey}" 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}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Queue, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding QueueVisibility}" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelQueue}" 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}" />
<StackPanel Orientation="Horizontal" Grid.Row="5" Grid.Column="1">
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static res:PublisherViewStrings.LabelReplyTo}" />
<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.LabelReplyToNewSubscriber}" IsChecked="{Binding ReplyToNewSubscriber}" Style="{StaticResource TypeSelection}" />
</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">
<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="10" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 8 0 0" Content="{Binding MessageTypeControl}" />
<ContentControl Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 8 0 0" Content="{Binding MessageTypeControl}" />
<Button Grid.Row="10" Grid.Column="1" Command="{Binding PublishCommand}" Content="{x:Static res:PublisherViewStrings.CommandPublish}" HorizontalAlignment="Left" />
<Button Grid.Row="11" Grid.Column="1" Command="{Binding PublishCommand}" Content="{x:Static res:PublisherViewStrings.CommandPublish}" HorizontalAlignment="Left" />
</controls:GridLayout>
</ScrollViewer>
</UserControl>

View File

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

View File

@ -112,10 +112,10 @@
<value>2.0</value>
</resheader>
<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 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>
<data name="CommandPublish" xml:space="preserve">
<value>Publish</value>
@ -153,6 +153,9 @@
<data name="OptionMessageTypeTapeti" xml:space="preserve">
<value>Tapeti message</value>
</data>
<data name="ReplyToCorrelationIdPrefix" xml:space="preserve">
<value>Re: </value>
</data>
<data name="TabTitle" xml:space="preserve">
<value>Publish: {0}</value>
</data>

View File

@ -131,6 +131,7 @@
</Button>
<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>
</UserControl>

View File

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

View File

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

View File

@ -46,6 +46,7 @@
</Grid>
<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>
</UserControl>

View File

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

View File

@ -2,9 +2,9 @@
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using PettingZoo.Core.Connection;
using PettingZoo.Core.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.Core.Validation;
using PettingZoo.WPF.ViewModel;
using IConnection = PettingZoo.Core.Connection.IConnection;
@ -21,6 +21,7 @@ namespace PettingZoo.UI.Tab.Publisher
private string correlationId = "";
private string payload = "";
private bool enableMacros;
private string className = "";
private string assemblyName = "";
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 BrowseClassCommand => browseClassCommand;
public IPayloadMacroProcessor PayloadMacroProcessor { get; }
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.publishDestination = publishDestination;
this.exampleGenerator = exampleGenerator;
@ -135,6 +147,9 @@ namespace PettingZoo.UI.Tab.Publisher
AssemblyName = classTypeExample.AssemblyName;
ClassName = classTypeExample.FullClassName;
if (classTypeExample.TryGetPublishDestination(out var exchange, out var routingKey))
publishDestination.SetExchangeDestination(exchange, routingKey);
validatingExample = classTypeExample as IValidatingExample;
break;
}
@ -151,20 +166,30 @@ namespace PettingZoo.UI.Tab.Publisher
{
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(
publishDestination.Exchange,
publishDestination.RoutingKey,
Encoding.UTF8.GetBytes(Payload),
encodedPayload,
new MessageProperties(new Dictionary<string, string>
{
{ @"classType", $"{ClassName}:{AssemblyName}" }
})
{
ContentType = @"application/json",
CorrelationId = NullIfEmpty(CorrelationId),
CorrelationId = publishCorrelationId,
DeliveryMode = MessageDeliveryMode.Persistent,
ReplyTo = publishDestination.GetReplyTo()
ReplyTo = replyTo
}));
}
@ -199,7 +224,7 @@ namespace PettingZoo.UI.Tab.Publisher
public class DesignTimeTapetiPublisherViewModel : TapetiPublisherViewModel
{
public DesignTimeTapetiPublisherViewModel() : base(null!, null!, null!)
public DesignTimeTapetiPublisherViewModel() : base(null!, null!, null!, null!)
{
AssemblyName = "Messaging.Example";
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:DataContext="{d:DesignInstance res:DesignTimeSubscriberViewModel, IsDesignTimeCreatable=True}"
Background="White">
<UserControl.Resources>
<res:SameMessageVisibilityConverter x:Key="SameMessageVisibilityConverter" />
</UserControl.Resources>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -64,7 +61,8 @@
</Grid.ColumnDefinitions>
<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>
<ContextMenu>

View File

@ -21,10 +21,9 @@ using Timer = System.Threading.Timer;
namespace PettingZoo.UI.Tab.Subscriber
{
public class SubscriberViewModel : BaseViewModel, ITabToolbarCommands, ITabActivate
public class SubscriberViewModel : BaseViewModel, IDisposable, ITabToolbarCommands, ITabActivate
{
private readonly ILogger logger;
private readonly ITabHostProvider tabHostProvider;
private readonly ITabFactory tabFactory;
private readonly IConnection? connection;
private readonly ISubscriber subscriber;
@ -76,16 +75,43 @@ namespace PettingZoo.UI.Tab.Subscriber
set => SetField(ref selectedMessageProperties, value);
}
public string Title =>
(subscriber.Exchange != null ? $"{subscriber.Exchange} - {subscriber.RoutingKey}" : $"{subscriber.QueueName}") +
(tabActive || unreadCount == 0 ? "" : $" ({unreadCount})");
public string Title
{
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 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.tabHostProvider = tabHostProvider;
this.tabFactory = tabFactory;
this.connection = connection;
this.subscriber = subscriber;
@ -111,6 +137,15 @@ namespace PettingZoo.UI.Tab.Subscriber
subscriber.Start();
}
public void Dispose()
{
GC.SuppressFinalize(this);
newMessageTimer?.Dispose();
subscriber.Dispose();
}
private void ClearExecute()
{
Messages.Clear();
@ -222,8 +257,7 @@ namespace PettingZoo.UI.Tab.Subscriber
if (connection == null)
return;
var publisherTab = tabFactory.CreatePublisherTab(connection, SelectedMessage);
tabHostProvider.Instance.AddTab(publisherTab);
tabFactory.CreatePublisherTab(connection, SelectedMessage);
}
@ -320,7 +354,7 @@ namespace PettingZoo.UI.Tab.Subscriber
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++)
(i > 2 ? UnreadMessages : Messages).Add(new ReceivedMessageInfo(
@ -340,9 +374,9 @@ namespace PettingZoo.UI.Tab.Subscriber
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);
}
}
/// <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">
<value>Value</value>
</data>
<data name="ReplyTabTitle" xml:space="preserve">
<value>Replies</value>
</data>
</root>

View File

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

View File

@ -8,7 +8,7 @@ using System.Windows.Controls;
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 ContentControl Content { get; }
@ -63,5 +63,12 @@ namespace PettingZoo.UI.Tab
{
(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.Generator;
using PettingZoo.Core.Macros;
using PettingZoo.UI.Tab.Publisher;
using PettingZoo.UI.Tab.Subscriber;
using Serilog;
@ -13,34 +16,119 @@ namespace PettingZoo.UI.Tab
private readonly ITabHostProvider tabHostProvider;
private readonly IExampleGenerator exampleGenerator;
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.tabHostProvider = tabHostProvider;
this.exampleGenerator = exampleGenerator;
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);
return new ViewTab<SubscriberView, SubscriberViewModel>(
new SubscriberView(viewModel),
viewModel,
vm => vm.Title);
InternalCreateSubscriberTab(connection, subscriber, false);
}
public ITab CreatePublisherTab(IConnection connection, ReceivedMessageInfo? fromReceivedMessage = null)
public string CreateReplySubscriberTab(IConnection connection)
{
var viewModel = new PublisherViewModel(tabHostProvider, this, connection, exampleGenerator, fromReceivedMessage);
return new ViewTab<PublisherView, PublisherViewModel>(
if (replySubscriber?.QueueName != null && replySubscriberTab != null)
{
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),
viewModel,
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();
}
}
}
}