add keyed based publication to mediator

This commit is contained in:
TheXamlGuy
2024-01-18 20:56:14 +00:00
parent a3065b25ee
commit 78cedcdeb8
15 changed files with 151 additions and 87 deletions
@@ -7,6 +7,7 @@ namespace Hyperbar.Windows.Primary;
public class PrimaryCommandConfiguration
{
public List<PrimaryCommandConfiguration> Commands { get; set; } = [];
public required string Icon { get; set; }
public required Guid Id { get; set; }
@@ -1,10 +1,6 @@
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetConfiguration :
List<PrimaryCommandConfiguration>
public class PrimaryWidgetConfiguration
{
public static PrimaryWidgetConfiguration Defaults => new()
{
new KeyAcceleratorCommandConfiguration { Id = Guid.NewGuid(), Order = 1, Icon = "\uE720", Text = "Test", Key = 91, Modifiers = [] }
};
public List<PrimaryCommandConfiguration> Commands { get; set; } = [];
}
@@ -1,4 +1,5 @@
namespace Hyperbar.Windows.Primary;
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetConfigurationHandler(IMediator mediator,
PrimaryWidgetConfiguration configuration,
@@ -9,26 +10,52 @@ public class PrimaryWidgetConfigurationHandler(IMediator mediator,
public async Task Handle(ConfigurationChanged<PrimaryWidgetConfiguration> notification,
CancellationToken cancellationToken)
{
HashSet<Guid> configurationIds = new(configuration.SelectMany(item => new[] { item }
.Concat(item.Commands).Select(x => x.Id)));
List<(Guid ParentId, Guid Id, PrimaryCommandConfiguration Configuration)> items = [];
foreach (KeyValuePair<Guid, IWidgetComponentViewModel> item in cache.Where(x => !configurationIds.Contains(x.Key)))
void AddToItems(Guid parentId, Guid id, List<PrimaryCommandConfiguration> configurations)
{
if (configurations is null)
{
return;
}
Stack<(Guid, List<PrimaryCommandConfiguration>)> stack = new();
stack.Push((parentId, configurations));
while (stack.Count > 0)
{
(Guid currentParentId, List<PrimaryCommandConfiguration> currentConfigurations) = stack.Pop();
foreach (PrimaryCommandConfiguration configuration in currentConfigurations)
{
items.Add((currentParentId, configuration.Id, configuration));
if (configuration.Commands is not null && configuration.Commands.Count > 0)
{
stack.Push((configuration.Id, configuration.Commands));
}
}
}
}
AddToItems(Guid.Empty, Guid.Empty, configuration.Commands);
foreach (KeyValuePair<Guid, IWidgetComponentViewModel> item in cache
.Where(x => !items.Any(k => x.Key == k.Id)))
{
await mediator.PublishAsync(new Removed<IWidgetComponentViewModel>(item.Value),
cancellationToken);
nameof(PrimaryWidgetViewModel),
cancellationToken);
cache.Remove(item.Key);
}
foreach (PrimaryCommandConfiguration item in configuration)
foreach ((Guid ParentId, Guid Id, PrimaryCommandConfiguration Configuration) item in
items.Where(x => !cache.Any(k => x.Id == k.Key)))
{
if (!cache.ContainsKey(item.Id))
if (factory.Create(item.Configuration) is IWidgetComponentViewModel viewModel)
{
if (factory.Create(item) is IWidgetComponentViewModel viewModel)
{
await mediator.PublishAsync(Inserted<IWidgetComponentViewModel>
.For<PrimaryWidgetViewModel>(item.Order, viewModel),
cancellationToken);
}
await mediator.PublishAsync(new Inserted<IWidgetComponentViewModel>(item.Configuration.Order, viewModel),
nameof(PrimaryWidgetViewModel),
cancellationToken);
}
}
}
@@ -1,5 +1,6 @@
namespace Hyperbar.Windows.Primary;
[NotificationHandler(nameof(PrimaryWidgetViewModel))]
public class PrimaryWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
@@ -6,7 +6,7 @@ public class WidgetComponentViewModelEnumerator(PrimaryWidgetConfiguration confi
{
public IEnumerable<IWidgetComponentViewModel?> Next()
{
foreach (PrimaryCommandConfiguration item in configuration.OrderBy(x => x.Order))
foreach (PrimaryCommandConfiguration item in configuration.Commands.OrderBy(x => x.Order))
{
yield return factory.Create(item);
}
@@ -49,7 +49,7 @@ public class WidgetComponentViewModelFactory(IServiceFactory service,
if (childViewModel is not null)
{
childViewModels.Add(childViewModel);
cache.Add(childViewModel.Id, childViewModel);
cache.Add(childCommandConfiguration.Id, childViewModel);
}
}
@@ -68,7 +68,7 @@ public class WidgetComponentViewModelFactory(IServiceFactory service,
if (viewModel is not null)
{
cache.Add(viewModel.Id, viewModel);
cache.Add(configuration.Id, viewModel);
}
return viewModel;
@@ -22,18 +22,13 @@
FontSize="16">
<SplitButton.Flyout>
<Flyout ShouldConstrainToRootBounds="False">
<Border
Width="300"
Height="300"
Background="red">
<ItemsControl Margin="-16,-13,-16,-15" ItemsSource="{Binding Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MenuFlyoutItem Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ItemsControl Margin="-16,-13,-16,-15" ItemsSource="{Binding Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MenuFlyoutItem Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Flyout>
</SplitButton.Flyout>
</SplitButton>
+19
View File
@@ -1,7 +1,25 @@
namespace Hyperbar;
[AttributeUsage(AttributeTargets.Class)]
public class NotificationHandlerAttribute(object key) : Attribute
{
public object Key { get; } = key;
}
public interface IMediator
{
Task PublishAsync<TNotification>(TNotification notification,
object key,
CancellationToken cancellationToken = default)
where TNotification :
INotification;
Task PublishAsync<TNotification>(object key,
CancellationToken cancellationToken = default)
where TNotification :
INotification,
new();
Task PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default)
where TNotification :
@@ -9,6 +27,7 @@ public interface IMediator
Task PublishAsync<TNotification>(TNotification notification,
Func<Func<Task>, Task> marshal,
object? key = null,
CancellationToken cancellationToken = default)
where TNotification :
INotification;
+53 -19
View File
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;
using System.Reflection;
namespace Hyperbar;
@@ -7,18 +8,37 @@ public class Mediator(IServiceProvider provider,
IDispatcher dispatcher) :
IMediator
{
private readonly ConcurrentDictionary<Type, List<dynamic>> subjects = [];
private readonly ConcurrentDictionary<object, List<dynamic>> subjects = [];
public Task PublishAsync<TNotification>(object key,
CancellationToken cancellationToken = default)
where TNotification :
INotification,
new() => PublishAsync(new TNotification(), args => dispatcher.InvokeAsync(async () => await args()),
key, cancellationToken);
public Task PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default)
where TNotification :
INotification
{
return PublishAsync(notification, args => dispatcher.InvokeAsync(async () => await args()), cancellationToken);
return PublishAsync(notification, args => dispatcher.InvokeAsync(async () => await args()),
null, cancellationToken);
}
public Task PublishAsync<TNotification>(TNotification notification,
object key,
CancellationToken cancellationToken = default)
where TNotification :
INotification
{
return PublishAsync(notification, args => dispatcher.InvokeAsync(async () => await args()),
key, cancellationToken);
}
public Task PublishAsync<TNotification>(TNotification notification,
Func<Func<Task>, Task> marshal,
object? key = null,
CancellationToken cancellationToken = default)
where TNotification :
INotification
@@ -26,14 +46,11 @@ public class Mediator(IServiceProvider provider,
List<INotificationHandler<TNotification>> handlers =
provider.GetServices<INotificationHandler<TNotification>>().ToList();
foreach (KeyValuePair<Type, List<dynamic>> handler in subjects)
foreach (KeyValuePair<object, List<dynamic>> handler in subjects)
{
if (handler.Key == typeof(TNotification))
if (key is not null && handler.Key.Equals(key) || handler.Key.Equals(typeof(TNotification)))
{
foreach (dynamic value in handler.Value)
{
handlers.Add(value);
}
handlers.Add(handler.Value[0]);
}
}
@@ -48,7 +65,8 @@ public class Mediator(IServiceProvider provider,
public Task PublishAsync<TNotification>(CancellationToken cancellationToken = default)
where TNotification :
INotification,
new() => PublishAsync(new TNotification(), null, cancellationToken);
new() => PublishAsync(new TNotification(), args => dispatcher.InvokeAsync(async () => await args()),
null, cancellationToken);
public Task<TResponse?> SendAsync<TResponse>(IRequest<TResponse> request,
CancellationToken cancellationToken = default)
@@ -88,21 +106,37 @@ public class Mediator(IServiceProvider provider,
public void Subscribe(object handler)
{
Type[] interfaceTypes = handler.GetType().GetInterfaces();
foreach (Type interfaceType in interfaceTypes.Where(x => x.IsGenericType))
Type handlerType = handler.GetType();
object? key = null;
if (Attribute.GetCustomAttribute(handlerType, typeof(NotificationHandlerAttribute))
is NotificationHandlerAttribute attribute)
{
if (interfaceType.GetGenericTypeDefinition() == typeof(INotificationHandler<>))
if (handlerType.GetProperty($"{attribute.Key}") is PropertyInfo property)
{
if (interfaceType.GetGenericArguments() is { Length: 1 } arguments)
if (property.GetValue(handler, null) is { } value)
{
Type notificationType = arguments[0];
subjects.AddOrUpdate(notificationType, [handler], (value, collection) =>
{
collection.Add(handler);
return collection;
});
key = value;
}
}
else
{
key = attribute.Key;
}
}
foreach (Type interfaceType in handlerType.GetInterfaces().Where(x => x.IsGenericType
&& x.GetGenericTypeDefinition() == typeof(INotificationHandler<>)))
{
if (interfaceType.GetGenericArguments() is { Length: 1 } arguments)
{
key ??= arguments[0];
subjects.AddOrUpdate(key, [handler], (value, collection) =>
{
collection.Add(handler);
return collection;
});
}
}
}
}
+1 -7
View File
@@ -1,9 +1,3 @@
namespace Hyperbar;
public record Inserted<TValue>(int Index, TValue Value, object Target) : INotification
{
public static Inserted<TValue> For<TTarget>(int index, TValue value)
{
return new Inserted<TValue>(index, value, typeof(TTarget).Name);
}
}
public record Inserted<TValue>(int Index, TValue Value) : INotification;
@@ -229,12 +229,9 @@ public partial class ObservableCollectionViewModel<TItem> :
public Task Handle(Created<TItem> notification,
CancellationToken cancellationToken)
{
if (notification.Target.Equals(GetType().Name))
if (notification.Value is TItem item)
{
if (notification.Value is TItem item)
{
Add(item);
}
Add(item);
}
return Task.CompletedTask;
@@ -243,12 +240,9 @@ public partial class ObservableCollectionViewModel<TItem> :
public Task Handle(Inserted<TItem> notification,
CancellationToken cancellationToken)
{
if (notification.Target.Equals(GetType().Name))
if (notification.Value is TItem item)
{
if (notification.Value is TItem item)
{
Insert(notification.Index, item);
}
Insert(notification.Index, item);
}
return Task.CompletedTask;
+6 -2
View File
@@ -3,14 +3,15 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar;
[NotificationHandler(nameof(Id))]
public partial class WidgetButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
Guid guid = default,
Guid id,
string? text = null,
string? icon = null,
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, guid)
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory)
{
[ObservableProperty]
private IRelayCommand? click = command;
@@ -18,6 +19,9 @@ public partial class WidgetButtonViewModel(IServiceFactory serviceFactory,
[ObservableProperty]
private string? icon = icon;
[ObservableProperty]
private Guid id = id;
[ObservableProperty]
private string? text = text;
}
+2 -11
View File
@@ -7,29 +7,20 @@ public partial class WidgetComponentViewModel :
IWidgetComponentViewModel,
ITemplatedViewModel
{
[ObservableProperty]
private Guid id;
public WidgetComponentViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
IEnumerable<IWidgetComponentViewModel> items,
Guid id = default) : base(serviceFactory, mediator, disposer, items)
IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, mediator, disposer, items)
{
this.id = id;
TemplateFactory = templateFactory;
}
public WidgetComponentViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
Guid id = default) : base(serviceFactory, mediator, disposer)
ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer)
{
this.id = id;
TemplateFactory = templateFactory;
}
+6 -2
View File
@@ -3,14 +3,15 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar;
[NotificationHandler(nameof(Id))]
public partial class WidgetMenuViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
Guid guid = default,
Guid id = default,
string? text = null,
string? icon = null,
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, guid)
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory)
{
[ObservableProperty]
private IRelayCommand? click = command;
@@ -18,6 +19,9 @@ public partial class WidgetMenuViewModel(IServiceFactory serviceFactory,
[ObservableProperty]
private string? icon = icon;
[ObservableProperty]
private Guid id = id;
[ObservableProperty]
private string? text = text;
}
+6 -2
View File
@@ -3,15 +3,16 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar;
[NotificationHandler(nameof(Id))]
public partial class WidgetSplitButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
IEnumerable<IWidgetComponentViewModel> items,
Guid guid = default,
Guid id = default,
string? text = null,
string? icon = null,
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, items, guid)
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, items)
{
[ObservableProperty]
private IRelayCommand? click = command;
@@ -19,6 +20,9 @@ public partial class WidgetSplitButtonViewModel(IServiceFactory serviceFactory,
[ObservableProperty]
private string? icon = icon;
[ObservableProperty]
private Guid id = id;
[ObservableProperty]
private string? text = text;
}