Initial navigaiton work; opening Windows

This commit is contained in:
TheXamlGuy
2024-02-04 22:06:55 +00:00
parent 110ac407f5
commit 43f96fd4f0
19 changed files with 228 additions and 94 deletions
+18
View File
@@ -0,0 +1,18 @@
using Microsoft.UI.Xaml;
namespace Hyperbar.UI.Windows;
public class WindowHandler :
INavigationHandler<Window>
{
public Task Handle(Navigate<Window> args,
CancellationToken cancellationToken)
{
if (args.Template is Window window)
{
window.Activate();
}
return Task.CompletedTask;
}
}
+2 -2
View File
@@ -9,8 +9,8 @@ public class WidgetExtensionEnumerator(IFactory<Type, IWidget> factory,
IMediator mediator) : IMediator mediator) :
INotificationHandler<Enumerate<WidgetExtension>> INotificationHandler<Enumerate<WidgetExtension>>
{ {
public Task Handle(Enumerate<WidgetExtension> notification, public Task Handle(Enumerate<WidgetExtension> args,
CancellationToken cancellationToken) CancellationToken cancellationToken = default)
{ {
string extensionsDirectory = Path.Combine(hostEnvironment.ContentRootPath, "Extensions"); string extensionsDirectory = Path.Combine(hostEnvironment.ContentRootPath, "Extensions");
if (Directory.Exists(extensionsDirectory)) if (Directory.Exists(extensionsDirectory))
+2 -1
View File
@@ -8,7 +8,6 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System.Reflection; using System.Reflection;
using Hyperbar.Widget.Windows; using Hyperbar.Widget.Windows;
using Microsoft.UI.Xaml.Markup;
namespace Hyperbar.Windows; namespace Hyperbar.Windows;
@@ -46,6 +45,8 @@ public partial class App :
args.Placement = DesktopApplicationBarPlacemenet.Top; args.Placement = DesktopApplicationBarPlacemenet.Top;
}); });
services.AddNavigationHandler<WindowHandler>();
services.AddSingleton<DesktopApplicationBar>(); services.AddSingleton<DesktopApplicationBar>();
services.AddContentTemplate<ApplicationBarViewModel, ApplicationBarView>(); services.AddContentTemplate<ApplicationBarViewModel, ApplicationBarView>();
services.AddContentTemplate<PrimaryViewModel, PrimaryView>(); services.AddContentTemplate<PrimaryViewModel, PrimaryView>();
+2 -2
View File
@@ -15,9 +15,9 @@ public partial class SecondaryViewModel :
IMediator mediator, IMediator mediator,
IDisposer disposer, IDisposer disposer,
int index) : base(serviceFactory, mediator, disposer) int index) : base(serviceFactory, mediator, disposer)
{ {
TemplateFactory = templateFactory;
this.index = index; this.index = index;
this.TemplateFactory = templateFactory;
Add<SettingsButtonViewModel>(); Add<SettingsButtonViewModel>();
} }
+1
View File
@@ -18,6 +18,7 @@
Width="{ThemeResource ButtonWidth}" Width="{ThemeResource ButtonWidth}"
Height="{ThemeResource ButtonHeight}" Height="{ThemeResource ButtonHeight}"
Padding="{ThemeResource ButtonPadding}" Padding="{ThemeResource ButtonPadding}"
Command="{x:Bind ViewModel.InvokeCommand}"
Content="&#xE713;" Content="&#xE713;"
FontFamily="{ThemeResource SymbolThemeFontFamily}" FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="16" /> FontSize="16" />
+5 -2
View File
@@ -2,9 +2,12 @@ using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows; namespace Hyperbar.Windows;
public sealed partial class SettingsButtonView : public partial class SettingsButtonView :
UserControl UserControl
{ {
public SettingsButtonView() => public SettingsButtonView() =>
this.InitializeComponent(); InitializeComponent();
protected SettingsButtonViewModel ViewModel =>
(SettingsButtonViewModel)DataContext;
} }
+20 -7
View File
@@ -1,11 +1,24 @@
namespace Hyperbar.Windows; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public class SettingsButtonViewModel(ITemplateFactory templateFactory, namespace Hyperbar.Windows;
IServiceFactory serviceFactory,
IMediator mediator, public partial class SettingsButtonViewModel :
IDisposer disposer) : ObservableViewModel,
ObservableViewModel(serviceFactory, mediator, disposer),
ITemplatedViewModel ITemplatedViewModel
{ {
public ITemplateFactory TemplateFactory => templateFactory; [ObservableProperty]
private IRelayCommand? invokeCommand;
public SettingsButtonViewModel(ITemplateFactory templateFactory,
IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer) : base(serviceFactory, mediator, disposer)
{
TemplateFactory = templateFactory;
InvokeCommand = new AsyncRelayCommand(async () =>
await mediator.PublishAsync(new Navigate("Settings")));
}
public ITemplateFactory TemplateFactory { get; }
} }
+3 -3
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<UserControl <Window
x:Class="Hyperbar.Windows.SettingsView" x:Class="Hyperbar.Windows.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid /> <Button>afsdfdg</Button>
</UserControl> </Window>
+3 -2
View File
@@ -1,9 +1,10 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows namespace Hyperbar.Windows
{ {
public sealed partial class SettingsView : public sealed partial class SettingsView :
UserControl Window
{ {
public SettingsView() => public SettingsView() =>
InitializeComponent(); InitializeComponent();
@@ -3,6 +3,8 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using System.Diagnostics.CodeAnalysis;
using System.Net.Mime;
using System.Text.Json; using System.Text.Json;
namespace Hyperbar; namespace Hyperbar;
@@ -128,6 +130,26 @@ public static class IServiceCollectionExtensions
return services; return services;
} }
public static IServiceCollection AddNavigationHandler<THandler>(this IServiceCollection services)
where THandler :
INavigationHandler,
IHandler
{
Type? contract = typeof(THandler).GetInterfaces()
.FirstOrDefault(t => t.Name == typeof(INavigationHandler<>).Name);
if (contract?.GetGenericArguments() is { Length: 1 } arguments)
{
services.AddTransient<INavigationDescriptor>(provider => new NavigationDescriptor
{
Type = arguments[0]
});
}
services.AddHandler<THandler>();
return services;
}
public static IServiceCollection AddContentTemplate<TContent, TTemplate>(this IServiceCollection services, public static IServiceCollection AddContentTemplate<TContent, TTemplate>(this IServiceCollection services,
object? key = null) object? key = null)
{ {
@@ -163,52 +185,59 @@ public static class IServiceCollectionExtensions
services.AddSingleton<IDisposer, Disposer>(); services.AddSingleton<IDisposer, Disposer>();
services.AddHandler<NavigateHandler>();
return services; return services;
} }
public static IServiceCollection AddHandler<THandler>(this IServiceCollection services, public static IServiceCollection AddHandler<THandler>(
this IServiceCollection services,
ServiceLifetime lifetime = ServiceLifetime.Transient) ServiceLifetime lifetime = ServiceLifetime.Transient)
where THandler : where THandler : IHandler
IHandler
{ {
if (typeof(THandler).GetInterfaces() is { } contracts) if (typeof(THandler).GetInterfaces() is { } contracts)
{ {
foreach (Type contract in contracts) foreach (Type contract in contracts)
{ {
if (contract.Name == typeof(INotificationHandler<>).Name) if (contract.Name == typeof(INotificationHandler<>).Name &&
contract.GetGenericArguments() is { Length: 1 } notificationArguments)
{ {
if (contract.GetGenericArguments() is { Length: 1 } arguments) Type notificationType = notificationArguments[0];
{ services.Add(new ServiceDescriptor(
Type notificationType = arguments[0]; typeof(INotificationHandler<>).MakeGenericType(notificationType),
services.Add(new ServiceDescriptor(typeof(INotificationHandler<>).MakeGenericType(notificationType), typeof(THandler), lifetime)); typeof(THandler),
} lifetime));
} }
if (contract.Name == typeof(IHandler<,>).Name) if (contract.Name == typeof(IHandler<,>).Name &&
contract.GetGenericArguments() is { Length: 2 } handlerArguments)
{ {
if (contract.GetGenericArguments() is { Length: 2 } arguments) Type requestType = handlerArguments[0];
{ Type responseType = handlerArguments[1];
Type requestType = arguments[0];
Type responseType = arguments[1];
Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType); Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType);
services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime)); services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime));
services.Add(new ServiceDescriptor(wrapperType, services.Add(new ServiceDescriptor(
provider => provider.GetService<IServiceFactory>()?.Create(wrapperType, wrapperType,
provider =>
provider.GetService<IServiceFactory>()?.Create(
wrapperType,
provider.GetRequiredService<THandler>(), provider.GetRequiredService<THandler>(),
provider.GetServices(typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType)))!, provider.GetServices(typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType))
lifetime )!,
)); lifetime
} ));
} }
} }
return services;
} }
return services; return services;
} }
public static IServiceCollection AddNotificationRelay<TFromNotification, public static IServiceCollection AddNotificationRelay<TFromNotification,
TToNotification>(this IServiceCollection services) TToNotification>(this IServiceCollection services)
where TFromNotification : where TFromNotification :
@@ -0,0 +1,7 @@
namespace Hyperbar;
public interface INavigationDescriptor
{
Type Type { get; set; }
}
@@ -0,0 +1,7 @@
namespace Hyperbar;
public interface INavigationHandler;
public interface INavigationHandler<TNavigation> :
INotificationHandler<Navigate<TNavigation>>,
INavigationHandler;
+8
View File
@@ -0,0 +1,8 @@
namespace Hyperbar;
public record Navigate(object Key) :
INotification;
public record Navigate<TTemplate>(TTemplate Template, object Content) :
INotification;
+53
View File
@@ -0,0 +1,53 @@
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar;
public class NavigateHandler :
INotificationHandler<Navigate>
{
private readonly IEnumerable<IContentTemplateDescriptor> contentTemplateDescriptors;
private readonly IServiceProvider provider;
private readonly IMediator mediator;
private readonly IEnumerable<INavigationDescriptor> navigationDescriptors;
public NavigateHandler(IServiceProvider provider,
IMediator mediator,
IEnumerable<INavigationDescriptor> navigationDescriptors,
IEnumerable<IContentTemplateDescriptor> contentTemplateDescriptors)
{
this.provider = provider;
this.mediator = mediator;
this.navigationDescriptors = navigationDescriptors;
this.contentTemplateDescriptors = contentTemplateDescriptors;
}
public async Task Handle(Navigate args,
CancellationToken cancellationToken)
{
if (contentTemplateDescriptors.FirstOrDefault(x => x.Key == args.Key)
is IContentTemplateDescriptor contentTemplateDescriptor)
{
if (navigationDescriptors.FirstOrDefault(x => contentTemplateDescriptor.TemplateType == x.Type ||
contentTemplateDescriptor.TemplateType.BaseType == x.Type) is { } navigationDescriptor)
{
if (contentTemplateDescriptor.TemplateType == navigationDescriptor.Type ||
contentTemplateDescriptor.TemplateType.BaseType == navigationDescriptor.Type)
{
if (provider.GetRequiredKeyedService(contentTemplateDescriptor.TemplateType,
contentTemplateDescriptor.Key) is { } template)
{
Type navigateType = typeof(Navigate<>)
.MakeGenericType(navigationDescriptor.Type);
if (Activator.CreateInstance(navigateType,
new object[] { template, args.Key }) is object navigate)
{
await mediator.PublishAsync(navigate, cancellationToken);
}
}
}
}
}
}
}
@@ -0,0 +1,8 @@
namespace Hyperbar;
public record NavigationDescriptor :
INavigationDescriptor
{
public required Type Type { get; set; }
}
+2 -33
View File
@@ -1,36 +1,5 @@
namespace Hyperbar; namespace Hyperbar;
public record Remove<TValue>(TValue Value) : INotification; public record Remove<TValue>(TValue Value) :
INotification;
public record Navigate(object Key) :
INotification;
public class NavigateHandler :
INotificationHandler<Navigate>
{
private readonly IEnumerable<IContentTemplateDescriptor> descriptors;
public NavigateHandler(IEnumerable<IContentTemplateDescriptor> descriptors,
IServiceProvider provider)
{
this.descriptors = descriptors;
}
public Task Handle(Navigate args,
CancellationToken cancellationToken)
{
if (descriptors.FirstOrDefault(x => x.Key == args.Key)
is IContentTemplateDescriptor descriptor)
{
//if (provider.GetRequiredKeyedService(descriptor.TemplateType,
// descriptor.Key) is { } template)
//{
// return template;
//}
}
throw new NotImplementedException();
}
}
+5 -4
View File
@@ -19,12 +19,13 @@ public interface IMediator
where TNotification : where TNotification :
INotification; INotification;
Task PublishAsync<TNotification>(TNotification notification, Task PublishAsync(object notification,
CancellationToken cancellationToken = default);
Task PublishAsync(object notification,
Func<Func<Task>, Task> marshal, Func<Func<Task>, Task> marshal,
object? key = null, object? key = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default);
where TNotification :
INotification;
Task PublishAsync<TNotification>(CancellationToken cancellationToken = default) Task PublishAsync<TNotification>(CancellationToken cancellationToken = default)
where TNotification : where TNotification :
+1 -1
View File
@@ -6,5 +6,5 @@ public interface INotificationHandler<in TNotification> :
INotification INotification
{ {
Task Handle(TNotification args, Task Handle(TNotification args,
CancellationToken cancellationToken); CancellationToken cancellationToken = default);
} }
+30 -15
View File
@@ -8,7 +8,7 @@ public class Mediator(IServiceProvider provider,
IDispatcher dispatcher) : IDispatcher dispatcher) :
IMediator IMediator
{ {
private readonly ConcurrentDictionary<object, List<dynamic>> subscriptions = []; private readonly ConcurrentDictionary<object, List<object>> handlers = [];
public Task PublishAsync<TNotification>(object key, public Task PublishAsync<TNotification>(object key,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
@@ -36,30 +36,38 @@ public class Mediator(IServiceProvider provider,
key, cancellationToken); key, cancellationToken);
} }
public Task PublishAsync<TNotification>(TNotification notification, public Task PublishAsync(object notification,
Func<Func<Task>, Task> marshal, Func<Func<Task>, Task> marshal,
object? key = null, object? key = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TNotification :
INotification
{ {
List<INotificationHandler<TNotification>> handlers = Type notificationType = notification.GetType();
provider.GetServices<INotificationHandler<TNotification>>().ToList();
foreach (KeyValuePair<object, List<dynamic>> subscriber in subscriptions) List<object?> handlers = provider.GetServices(typeof(INotificationHandler<>)
.MakeGenericType(notificationType)).ToList();
foreach (KeyValuePair<object, List<object>> subscriber in this.handlers)
{ {
if (subscriber.Key.Equals($"{(key is not null ? $"{key}:" : "")}{typeof(TNotification)}")) if (subscriber.Key.Equals($"{key?.ToString()}:{notificationType}"))
{ {
foreach (dynamic handler in subscriber.Value) handlers.AddRange(subscriber.Value);
{
handlers.Add(handler);
}
} }
} }
foreach (INotificationHandler<TNotification> handler in handlers) foreach (object? handler in handlers)
{ {
marshal(() => handler.Handle(notification, cancellationToken)); if (handler is not null)
{
Type? handlerType = handler.GetType();
MethodInfo? handleMethod = handlerType.GetMethod("Handle",
[notificationType, typeof(CancellationToken)]);
if (handleMethod is not null)
{
marshal(() => (Task)handleMethod.Invoke(handler, new object[] { notification,
cancellationToken })!);
}
}
} }
return Task.CompletedTask; return Task.CompletedTask;
@@ -71,6 +79,13 @@ public class Mediator(IServiceProvider provider,
new() => PublishAsync(new TNotification(), args => dispatcher.InvokeAsync(async () => await args()), new() => PublishAsync(new TNotification(), args => dispatcher.InvokeAsync(async () => await args()),
null, cancellationToken); null, cancellationToken);
public Task PublishAsync(object notification,
CancellationToken cancellationToken = default)
{
return PublishAsync(notification, 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)
{ {
@@ -116,7 +131,7 @@ public class Mediator(IServiceProvider provider,
{ {
if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType) if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType)
{ {
subscriptions.AddOrUpdate($"{(key is not null ? $"{key}:" : "")}{argumentType}", new List<object> { handler }, (value, collection) => handlers.AddOrUpdate($"{(key is not null ? $"{key}:" : "")}{argumentType}", new List<object> { handler }, (value, collection) =>
{ {
collection.Add(handler); collection.Add(handler);
return collection; return collection;