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 class PrimaryCommandConfiguration
{ {
public List<PrimaryCommandConfiguration> Commands { get; set; } = []; public List<PrimaryCommandConfiguration> Commands { get; set; } = [];
public required string Icon { get; set; } public required string Icon { get; set; }
public required Guid Id { get; set; } public required Guid Id { get; set; }
@@ -1,10 +1,6 @@
namespace Hyperbar.Windows.Primary; namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetConfiguration : public class PrimaryWidgetConfiguration
List<PrimaryCommandConfiguration>
{ {
public static PrimaryWidgetConfiguration Defaults => new() public List<PrimaryCommandConfiguration> Commands { get; set; } = [];
{
new KeyAcceleratorCommandConfiguration { Id = Guid.NewGuid(), Order = 1, Icon = "\uE720", Text = "Test", Key = 91, Modifiers = [] }
};
} }
@@ -1,4 +1,5 @@
namespace Hyperbar.Windows.Primary;
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetConfigurationHandler(IMediator mediator, public class PrimaryWidgetConfigurationHandler(IMediator mediator,
PrimaryWidgetConfiguration configuration, PrimaryWidgetConfiguration configuration,
@@ -9,27 +10,53 @@ public class PrimaryWidgetConfigurationHandler(IMediator mediator,
public async Task Handle(ConfigurationChanged<PrimaryWidgetConfiguration> notification, public async Task Handle(ConfigurationChanged<PrimaryWidgetConfiguration> notification,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
HashSet<Guid> configurationIds = new(configuration.SelectMany(item => new[] { item } List<(Guid ParentId, Guid Id, PrimaryCommandConfiguration Configuration)> items = [];
.Concat(item.Commands).Select(x => x.Id)));
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), await mediator.PublishAsync(new Removed<IWidgetComponentViewModel>(item.Value),
nameof(PrimaryWidgetViewModel),
cancellationToken); cancellationToken);
cache.Remove(item.Key); 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(new Inserted<IWidgetComponentViewModel>(item.Configuration.Order, viewModel),
{ nameof(PrimaryWidgetViewModel),
await mediator.PublishAsync(Inserted<IWidgetComponentViewModel>
.For<PrimaryWidgetViewModel>(item.Order, viewModel),
cancellationToken); cancellationToken);
} }
} }
} }
}
} }
@@ -1,5 +1,6 @@
namespace Hyperbar.Windows.Primary; namespace Hyperbar.Windows.Primary;
[NotificationHandler(nameof(PrimaryWidgetViewModel))]
public class PrimaryWidgetViewModel(ITemplateFactory templateFactory, public class PrimaryWidgetViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory, IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
@@ -6,7 +6,7 @@ public class WidgetComponentViewModelEnumerator(PrimaryWidgetConfiguration confi
{ {
public IEnumerable<IWidgetComponentViewModel?> Next() 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); yield return factory.Create(item);
} }
@@ -49,7 +49,7 @@ public class WidgetComponentViewModelFactory(IServiceFactory service,
if (childViewModel is not null) if (childViewModel is not null)
{ {
childViewModels.Add(childViewModel); 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) if (viewModel is not null)
{ {
cache.Add(viewModel.Id, viewModel); cache.Add(configuration.Id, viewModel);
} }
return viewModel; return viewModel;
@@ -22,10 +22,6 @@
FontSize="16"> FontSize="16">
<SplitButton.Flyout> <SplitButton.Flyout>
<Flyout ShouldConstrainToRootBounds="False"> <Flyout ShouldConstrainToRootBounds="False">
<Border
Width="300"
Height="300"
Background="red">
<ItemsControl Margin="-16,-13,-16,-15" ItemsSource="{Binding Mode=TwoWay}"> <ItemsControl Margin="-16,-13,-16,-15" ItemsSource="{Binding Mode=TwoWay}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -33,7 +29,6 @@
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</Border>
</Flyout> </Flyout>
</SplitButton.Flyout> </SplitButton.Flyout>
</SplitButton> </SplitButton>
+19
View File
@@ -1,7 +1,25 @@
namespace Hyperbar; namespace Hyperbar;
[AttributeUsage(AttributeTargets.Class)]
public class NotificationHandlerAttribute(object key) : Attribute
{
public object Key { get; } = key;
}
public interface IMediator 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, Task PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TNotification : where TNotification :
@@ -9,6 +27,7 @@ public interface IMediator
Task PublishAsync<TNotification>(TNotification notification, Task PublishAsync<TNotification>(TNotification notification,
Func<Func<Task>, Task> marshal, Func<Func<Task>, Task> marshal,
object? key = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TNotification : where TNotification :
INotification; INotification;
+49 -15
View File
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection;
namespace Hyperbar; namespace Hyperbar;
@@ -7,18 +8,37 @@ public class Mediator(IServiceProvider provider,
IDispatcher dispatcher) : IDispatcher dispatcher) :
IMediator 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, public Task PublishAsync<TNotification>(TNotification notification,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TNotification : where TNotification :
INotification 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, public Task PublishAsync<TNotification>(TNotification notification,
Func<Func<Task>, Task> marshal, Func<Func<Task>, Task> marshal,
object? key = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TNotification : where TNotification :
INotification INotification
@@ -26,14 +46,11 @@ public class Mediator(IServiceProvider provider,
List<INotificationHandler<TNotification>> handlers = List<INotificationHandler<TNotification>> handlers =
provider.GetServices<INotificationHandler<TNotification>>().ToList(); 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(handler.Value[0]);
{
handlers.Add(value);
}
} }
} }
@@ -48,7 +65,8 @@ public class Mediator(IServiceProvider provider,
public Task PublishAsync<TNotification>(CancellationToken cancellationToken = default) public Task PublishAsync<TNotification>(CancellationToken cancellationToken = default)
where TNotification : where TNotification :
INotification, 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, public Task<TResponse?> SendAsync<TResponse>(IRequest<TResponse> request,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
@@ -88,15 +106,32 @@ public class Mediator(IServiceProvider provider,
public void Subscribe(object handler) public void Subscribe(object handler)
{ {
Type[] interfaceTypes = handler.GetType().GetInterfaces(); Type handlerType = handler.GetType();
foreach (Type interfaceType in interfaceTypes.Where(x => x.IsGenericType)) 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 (property.GetValue(handler, null) is { } value)
{
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) if (interfaceType.GetGenericArguments() is { Length: 1 } arguments)
{ {
Type notificationType = arguments[0]; key ??= arguments[0];
subjects.AddOrUpdate(notificationType, [handler], (value, collection) => subjects.AddOrUpdate(key, [handler], (value, collection) =>
{ {
collection.Add(handler); collection.Add(handler);
return collection; return collection;
@@ -104,5 +139,4 @@ public class Mediator(IServiceProvider provider,
} }
} }
} }
}
} }
+1 -7
View File
@@ -1,9 +1,3 @@
namespace Hyperbar; namespace Hyperbar;
public record Inserted<TValue>(int Index, TValue Value, object Target) : INotification public record Inserted<TValue>(int Index, TValue Value) : INotification;
{
public static Inserted<TValue> For<TTarget>(int index, TValue value)
{
return new Inserted<TValue>(index, value, typeof(TTarget).Name);
}
}
@@ -228,28 +228,22 @@ public partial class ObservableCollectionViewModel<TItem> :
public Task Handle(Created<TItem> notification, public Task Handle(Created<TItem> notification,
CancellationToken cancellationToken) 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; return Task.CompletedTask;
} }
public Task Handle(Inserted<TItem> notification, public Task Handle(Inserted<TItem> notification,
CancellationToken cancellationToken) 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; return Task.CompletedTask;
} }
+6 -2
View File
@@ -3,14 +3,15 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar; namespace Hyperbar;
[NotificationHandler(nameof(Id))]
public partial class WidgetButtonViewModel(IServiceFactory serviceFactory, public partial class WidgetButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
ITemplateFactory templateFactory, ITemplateFactory templateFactory,
Guid guid = default, Guid id,
string? text = null, string? text = null,
string? icon = null, string? icon = null,
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, guid) RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory)
{ {
[ObservableProperty] [ObservableProperty]
private IRelayCommand? click = command; private IRelayCommand? click = command;
@@ -18,6 +19,9 @@ public partial class WidgetButtonViewModel(IServiceFactory serviceFactory,
[ObservableProperty] [ObservableProperty]
private string? icon = icon; private string? icon = icon;
[ObservableProperty]
private Guid id = id;
[ObservableProperty] [ObservableProperty]
private string? text = text; private string? text = text;
} }
+2 -11
View File
@@ -7,29 +7,20 @@ public partial class WidgetComponentViewModel :
IWidgetComponentViewModel, IWidgetComponentViewModel,
ITemplatedViewModel ITemplatedViewModel
{ {
[ObservableProperty]
private Guid id;
public WidgetComponentViewModel(IServiceFactory serviceFactory, public WidgetComponentViewModel(IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
ITemplateFactory templateFactory, ITemplateFactory templateFactory,
IEnumerable<IWidgetComponentViewModel> items, IEnumerable<IWidgetComponentViewModel> items) : base(serviceFactory, mediator, disposer, items)
Guid id = default) : base(serviceFactory, mediator, disposer, items)
{ {
this.id = id;
TemplateFactory = templateFactory; TemplateFactory = templateFactory;
} }
public WidgetComponentViewModel(IServiceFactory serviceFactory, public WidgetComponentViewModel(IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
ITemplateFactory templateFactory, ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer)
Guid id = default) : base(serviceFactory, mediator, disposer)
{ {
this.id = id;
TemplateFactory = templateFactory; TemplateFactory = templateFactory;
} }
+6 -2
View File
@@ -3,14 +3,15 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar; namespace Hyperbar;
[NotificationHandler(nameof(Id))]
public partial class WidgetMenuViewModel(IServiceFactory serviceFactory, public partial class WidgetMenuViewModel(IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
ITemplateFactory templateFactory, ITemplateFactory templateFactory,
Guid guid = default, Guid id = default,
string? text = null, string? text = null,
string? icon = null, string? icon = null,
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, guid) RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory)
{ {
[ObservableProperty] [ObservableProperty]
private IRelayCommand? click = command; private IRelayCommand? click = command;
@@ -18,6 +19,9 @@ public partial class WidgetMenuViewModel(IServiceFactory serviceFactory,
[ObservableProperty] [ObservableProperty]
private string? icon = icon; private string? icon = icon;
[ObservableProperty]
private Guid id = id;
[ObservableProperty] [ObservableProperty]
private string? text = text; private string? text = text;
} }
+6 -2
View File
@@ -3,15 +3,16 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar; namespace Hyperbar;
[NotificationHandler(nameof(Id))]
public partial class WidgetSplitButtonViewModel(IServiceFactory serviceFactory, public partial class WidgetSplitButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
ITemplateFactory templateFactory, ITemplateFactory templateFactory,
IEnumerable<IWidgetComponentViewModel> items, IEnumerable<IWidgetComponentViewModel> items,
Guid guid = default, Guid id = default,
string? text = null, string? text = null,
string? icon = null, string? icon = null,
RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, items, guid) RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, items)
{ {
[ObservableProperty] [ObservableProperty]
private IRelayCommand? click = command; private IRelayCommand? click = command;
@@ -19,6 +20,9 @@ public partial class WidgetSplitButtonViewModel(IServiceFactory serviceFactory,
[ObservableProperty] [ObservableProperty]
private string? icon = icon; private string? icon = icon;
[ObservableProperty]
private Guid id = id;
[ObservableProperty] [ObservableProperty]
private string? text = text; private string? text = text;
} }