diff --git a/Hyperbar.Windows.Primary/PrimaryCommandConfiguration.cs b/Hyperbar.Windows.Primary/PrimaryCommandConfiguration.cs index 567ef92..cdcbf89 100644 --- a/Hyperbar.Windows.Primary/PrimaryCommandConfiguration.cs +++ b/Hyperbar.Windows.Primary/PrimaryCommandConfiguration.cs @@ -7,6 +7,7 @@ namespace Hyperbar.Windows.Primary; public class PrimaryCommandConfiguration { public List Commands { get; set; } = []; + public required string Icon { get; set; } public required Guid Id { get; set; } diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetConfiguration.cs b/Hyperbar.Windows.Primary/PrimaryWidgetConfiguration.cs index 891f486..9c8abef 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetConfiguration.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetConfiguration.cs @@ -1,10 +1,6 @@ namespace Hyperbar.Windows.Primary; -public class PrimaryWidgetConfiguration : - List +public class PrimaryWidgetConfiguration { - public static PrimaryWidgetConfiguration Defaults => new() - { - new KeyAcceleratorCommandConfiguration { Id = Guid.NewGuid(), Order = 1, Icon = "\uE720", Text = "Test", Key = 91, Modifiers = [] } - }; + public List Commands { get; set; } = []; } \ No newline at end of file diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationHandler.cs b/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationHandler.cs index 3dc87c2..08281fa 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationHandler.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetConfigurationHandler.cs @@ -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 notification, CancellationToken cancellationToken) { - HashSet configurationIds = new(configuration.SelectMany(item => new[] { item } - .Concat(item.Commands).Select(x => x.Id))); + List<(Guid ParentId, Guid Id, PrimaryCommandConfiguration Configuration)> items = []; - foreach (KeyValuePair item in cache.Where(x => !configurationIds.Contains(x.Key))) + void AddToItems(Guid parentId, Guid id, List configurations) + { + if (configurations is null) + { + return; + } + + Stack<(Guid, List)> stack = new(); + stack.Push((parentId, configurations)); + + while (stack.Count > 0) + { + (Guid currentParentId, List 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 item in cache + .Where(x => !items.Any(k => x.Key == k.Id))) { await mediator.PublishAsync(new Removed(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 - .For(item.Order, viewModel), - cancellationToken); - } + await mediator.PublishAsync(new Inserted(item.Configuration.Order, viewModel), + nameof(PrimaryWidgetViewModel), + cancellationToken); } } } diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs b/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs index 21f9d08..d89fdc0 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs @@ -1,5 +1,6 @@ namespace Hyperbar.Windows.Primary; +[NotificationHandler(nameof(PrimaryWidgetViewModel))] public class PrimaryWidgetViewModel(ITemplateFactory templateFactory, IServiceFactory serviceFactory, IMediator mediator, diff --git a/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs b/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs index 392a279..6bf343e 100644 --- a/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs +++ b/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs @@ -6,7 +6,7 @@ public class WidgetComponentViewModelEnumerator(PrimaryWidgetConfiguration confi { public IEnumerable 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); } diff --git a/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs b/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs index 957e5a8..7402328 100644 --- a/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs +++ b/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs @@ -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; diff --git a/Hyperbar.Windows/Views/WidgetSplitButtonView.xaml b/Hyperbar.Windows/Views/WidgetSplitButtonView.xaml index 744e7cb..39bb924 100644 --- a/Hyperbar.Windows/Views/WidgetSplitButtonView.xaml +++ b/Hyperbar.Windows/Views/WidgetSplitButtonView.xaml @@ -22,18 +22,13 @@ FontSize="16"> - - - - - - - - - + + + + + + + diff --git a/Hyperbar/Mediators/IMediator.cs b/Hyperbar/Mediators/IMediator.cs index d33ff90..3370d01 100644 --- a/Hyperbar/Mediators/IMediator.cs +++ b/Hyperbar/Mediators/IMediator.cs @@ -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 notification, + object key, + CancellationToken cancellationToken = default) + where TNotification : + INotification; + + Task PublishAsync(object key, + CancellationToken cancellationToken = default) + where TNotification : + INotification, + new(); + Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : @@ -9,6 +27,7 @@ public interface IMediator Task PublishAsync(TNotification notification, Func, Task> marshal, + object? key = null, CancellationToken cancellationToken = default) where TNotification : INotification; diff --git a/Hyperbar/Mediators/Mediator.cs b/Hyperbar/Mediators/Mediator.cs index 47d91d4..505ef5f 100644 --- a/Hyperbar/Mediators/Mediator.cs +++ b/Hyperbar/Mediators/Mediator.cs @@ -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> subjects = []; + private readonly ConcurrentDictionary> subjects = []; + + public Task PublishAsync(object key, + CancellationToken cancellationToken = default) + where TNotification : + INotification, + new() => PublishAsync(new TNotification(), args => dispatcher.InvokeAsync(async () => await args()), + key, cancellationToken); public Task PublishAsync(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 notification, + object key, + CancellationToken cancellationToken = default) + where TNotification : + INotification + { + return PublishAsync(notification, args => dispatcher.InvokeAsync(async () => await args()), + key, cancellationToken); } public Task PublishAsync(TNotification notification, Func, Task> marshal, + object? key = null, CancellationToken cancellationToken = default) where TNotification : INotification @@ -26,14 +46,11 @@ public class Mediator(IServiceProvider provider, List> handlers = provider.GetServices>().ToList(); - foreach (KeyValuePair> handler in subjects) + foreach (KeyValuePair> 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(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 SendAsync(IRequest 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; + }); + } } } } \ No newline at end of file diff --git a/Hyperbar/Views/Inserted.cs b/Hyperbar/Views/Inserted.cs index 6c0de95..eebb699 100644 --- a/Hyperbar/Views/Inserted.cs +++ b/Hyperbar/Views/Inserted.cs @@ -1,9 +1,3 @@ namespace Hyperbar; -public record Inserted(int Index, TValue Value, object Target) : INotification -{ - public static Inserted For(int index, TValue value) - { - return new Inserted(index, value, typeof(TTarget).Name); - } -} \ No newline at end of file +public record Inserted(int Index, TValue Value) : INotification; \ No newline at end of file diff --git a/Hyperbar/Views/ObservableCollectionViewModel.cs b/Hyperbar/Views/ObservableCollectionViewModel.cs index 11bc477..9e5a483 100644 --- a/Hyperbar/Views/ObservableCollectionViewModel.cs +++ b/Hyperbar/Views/ObservableCollectionViewModel.cs @@ -229,12 +229,9 @@ public partial class ObservableCollectionViewModel : public Task Handle(Created 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 : public Task Handle(Inserted 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; diff --git a/Hyperbar/Views/WidgetButtonViewModel.cs b/Hyperbar/Views/WidgetButtonViewModel.cs index be0fdb3..f96efa7 100644 --- a/Hyperbar/Views/WidgetButtonViewModel.cs +++ b/Hyperbar/Views/WidgetButtonViewModel.cs @@ -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; } \ No newline at end of file diff --git a/Hyperbar/Views/WidgetComponentViewModel.cs b/Hyperbar/Views/WidgetComponentViewModel.cs index 23b7850..1e934fa 100644 --- a/Hyperbar/Views/WidgetComponentViewModel.cs +++ b/Hyperbar/Views/WidgetComponentViewModel.cs @@ -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 items, - Guid id = default) : base(serviceFactory, mediator, disposer, items) + IEnumerable 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; } diff --git a/Hyperbar/Views/WidgetMenuViewModel.cs b/Hyperbar/Views/WidgetMenuViewModel.cs index 9546347..bf08730 100644 --- a/Hyperbar/Views/WidgetMenuViewModel.cs +++ b/Hyperbar/Views/WidgetMenuViewModel.cs @@ -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; } \ No newline at end of file diff --git a/Hyperbar/Views/WidgetSplitButtonViewModel.cs b/Hyperbar/Views/WidgetSplitButtonViewModel.cs index aaa5f1e..9a1dd89 100644 --- a/Hyperbar/Views/WidgetSplitButtonViewModel.cs +++ b/Hyperbar/Views/WidgetSplitButtonViewModel.cs @@ -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 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; } \ No newline at end of file