Merged
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using Avalonia.Threading;
|
||||
using IDispatcher = Toolkit.Foundation.IDispatcher;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class AvaloniaDispatcher :
|
||||
IDispatcher
|
||||
{
|
||||
public async Task Invoke(Action action) => await Dispatcher.UIThread.InvokeAsync(action);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ClassicDesktopStyleApplicationHandler :
|
||||
INotificationHandler<NavigateEventArgs<IClassicDesktopStyleApplicationLifetime>>
|
||||
{
|
||||
public Task Handle(NavigateEventArgs<IClassicDesktopStyleApplicationLifetime> args)
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is
|
||||
IClassicDesktopStyleApplicationLifetime lifeTime)
|
||||
{
|
||||
if (args.Template is Window window)
|
||||
{
|
||||
lifeTime.MainWindow = window;
|
||||
window.DataContext = args.Content;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input.Platform;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ClipboardWriter(ITopLevelProvider topLevelProvider) :
|
||||
IClipboardWriter
|
||||
{
|
||||
public async Task Write<TContent>(TContent content)
|
||||
{
|
||||
if (topLevelProvider.Get() is TopLevel topLevel)
|
||||
{
|
||||
if (topLevel.Clipboard is IClipboard clipboard)
|
||||
{
|
||||
if (content is string stringContent)
|
||||
{
|
||||
await clipboard.SetTextAsync(stringContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ContentControlHandler :
|
||||
INotificationHandler<NavigateEventArgs<ContentControl>>
|
||||
{
|
||||
public async Task Handle(NavigateEventArgs<ContentControl> args)
|
||||
{
|
||||
if (args.Region is ContentControl contentControl)
|
||||
{
|
||||
if (args.Template is Control control)
|
||||
{
|
||||
TaskCompletionSource taskCompletionSource = new();
|
||||
async void HandleLoaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Loaded -= HandleLoaded;
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
taskCompletionSource.SetResult();
|
||||
}
|
||||
|
||||
async void HandleUnloaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Unloaded -= HandleLoaded;
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IDeactivated deactivated)
|
||||
{
|
||||
await deactivated.OnDeactivated();
|
||||
}
|
||||
|
||||
if (content is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
control.Loaded += HandleLoaded;
|
||||
control.Unloaded += HandleUnloaded;
|
||||
|
||||
control.DataContext = args.Content;
|
||||
|
||||
contentControl.Content = null;
|
||||
contentControl.Content = control;
|
||||
|
||||
await taskCompletionSource.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using FluentAvalonia.Core;
|
||||
using Toolkit.Foundation;
|
||||
using Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ContentDialogHandler :
|
||||
INotificationHandler<NavigateEventArgs<ContentDialog>>
|
||||
{
|
||||
public async Task Handle(NavigateEventArgs<ContentDialog> args)
|
||||
{
|
||||
if (args.Template is ContentDialog dialog)
|
||||
{
|
||||
dialog.DataContext = args.Content;
|
||||
|
||||
async void HandlePrimaryButtonClick(FluentAvalonia.UI.Controls.ContentDialog sender,
|
||||
FluentAvalonia.UI.Controls.ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
dialog.PrimaryButtonClick -= HandlePrimaryButtonClick;
|
||||
if (dialog.DataContext is object content)
|
||||
{
|
||||
if (content is IPrimaryConfirmation primaryConfirmation)
|
||||
{
|
||||
Deferral deferral = args.GetDeferral();
|
||||
if (!await primaryConfirmation.ConfirmPrimary())
|
||||
{
|
||||
args.Cancel = true;
|
||||
dialog.PrimaryButtonClick += HandlePrimaryButtonClick;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleSecondaryButtonClick(FluentAvalonia.UI.Controls.ContentDialog sender,
|
||||
FluentAvalonia.UI.Controls.ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
dialog.SecondaryButtonClick -= HandleSecondaryButtonClick;
|
||||
if (dialog.DataContext is object content)
|
||||
{
|
||||
if (content is ISecondaryConfirmation secondaryConfirmation)
|
||||
{
|
||||
Deferral deferral = args.GetDeferral();
|
||||
if (!await secondaryConfirmation.ConfirmSecondary())
|
||||
{
|
||||
args.Cancel = true;
|
||||
dialog.SecondaryButtonClick += HandleSecondaryButtonClick;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleClosing(FluentAvalonia.UI.Controls.ContentDialog sender,
|
||||
FluentAvalonia.UI.Controls.ContentDialogClosingEventArgs args)
|
||||
{
|
||||
if (args.Result is FluentAvalonia.UI.Controls.ContentDialogResult.Primary ||
|
||||
args.Result is FluentAvalonia.UI.Controls.ContentDialogResult.Secondary)
|
||||
{
|
||||
dialog.Closing -= HandleClosing;
|
||||
if (dialog.DataContext is object content)
|
||||
{
|
||||
bool cancelled = false;
|
||||
if (content is IConfirmation confirmation)
|
||||
{
|
||||
Deferral deferral = args.GetDeferral();
|
||||
if (!await confirmation.Confirm())
|
||||
{
|
||||
args.Cancel = true;
|
||||
cancelled = true;
|
||||
|
||||
dialog.Closing += HandleClosing;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
|
||||
if (!cancelled)
|
||||
{
|
||||
if (content is IDeactivating deactivating)
|
||||
{
|
||||
await deactivating.OnDeactivating();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleOpened(FluentAvalonia.UI.Controls.ContentDialog sender,
|
||||
EventArgs args)
|
||||
{
|
||||
dialog.Opened -= HandleOpened;
|
||||
if (dialog.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleClosed(FluentAvalonia.UI.Controls.ContentDialog sender,
|
||||
FluentAvalonia.UI.Controls.ContentDialogClosedEventArgs args)
|
||||
{
|
||||
dialog.Closed -= HandleClosed;
|
||||
if (dialog.DataContext is object content)
|
||||
{
|
||||
if (content is IDeactivated deactivated)
|
||||
{
|
||||
await deactivated.OnDeactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.Opened += HandleOpened;
|
||||
dialog.Closing += HandleClosing;
|
||||
dialog.Closed += HandleClosed;
|
||||
|
||||
dialog.PrimaryButtonClick += HandlePrimaryButtonClick;
|
||||
dialog.SecondaryButtonClick += HandleSecondaryButtonClick;
|
||||
|
||||
await dialog.ShowAsync();
|
||||
|
||||
dialog.PrimaryButtonClick += HandlePrimaryButtonClick;
|
||||
dialog.SecondaryButtonClick += HandleSecondaryButtonClick;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Toolkit.Foundation;
|
||||
using Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ContentTemplate :
|
||||
IContentTemplate,
|
||||
IDataTemplate,
|
||||
IItemContainerTemplateSelector
|
||||
{
|
||||
public Control? Build(object? item)
|
||||
{
|
||||
if (item is IObservableViewModel observableViewModel)
|
||||
{
|
||||
if (observableViewModel.Provider is IServiceProvider provider)
|
||||
{
|
||||
Type itemType = item.GetType();
|
||||
if (provider.GetRequiredKeyedService<IContentTemplateDescriptor>(itemType.Name.Replace("ViewModel", ""))
|
||||
is IContentTemplateDescriptor descriptor)
|
||||
{
|
||||
if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) is Control control)
|
||||
{
|
||||
async void HandleLoaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Loaded -= HandleLoaded;
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleDataContextChanged(object? sender, EventArgs args)
|
||||
{
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleUnloaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Unloaded -= HandleUnloaded;
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IDeactivated deactivated)
|
||||
{
|
||||
await deactivated.OnDeactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
control.Loaded += HandleLoaded;
|
||||
control.Unloaded += HandleUnloaded;
|
||||
control.DataContextChanged += HandleDataContextChanged; ;
|
||||
|
||||
return control;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Match(object? data) => true;
|
||||
|
||||
public IDataTemplate? SelectTemplate(object? item, ItemsControl itemsControl)
|
||||
{
|
||||
if (item is IObservableViewModel observableViewModel)
|
||||
{
|
||||
if (observableViewModel.Provider is IServiceProvider provider)
|
||||
{
|
||||
Type itemType = item.GetType();
|
||||
if (provider.GetRequiredKeyedService<IContentTemplateDescriptor>(itemType.Name.Replace("ViewModel", ""))
|
||||
is IContentTemplateDescriptor descriptor)
|
||||
{
|
||||
if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) is Control control)
|
||||
{
|
||||
async void HandleLoaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Loaded -= HandleLoaded;
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleDataContextChanged(object? sender, EventArgs args)
|
||||
{
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleUnloaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Unloaded -= HandleUnloaded;
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IDeactivated deactivated)
|
||||
{
|
||||
await deactivated.OnDeactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
control.Loaded += HandleLoaded;
|
||||
control.Unloaded += HandleUnloaded;
|
||||
control.DataContextChanged += HandleDataContextChanged; ;
|
||||
|
||||
return new FuncDataTemplate(item.GetType(), (_, _) => control);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class FileProvider(ITopLevelProvider topLevelProvider) :
|
||||
IFileProvider
|
||||
{
|
||||
public async Task<IReadOnlyCollection<string>> SelectFiles(FileFilter filter)
|
||||
{
|
||||
if (topLevelProvider.Get() is TopLevel topLevel)
|
||||
{
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
|
||||
{
|
||||
AllowMultiple = filter.AllowMultiple,
|
||||
FileTypeFilter =
|
||||
[
|
||||
new(filter.Name)
|
||||
{
|
||||
Patterns = filter.Extensions is { Count: > 0 } ? filter.Extensions.Select(x => $"*.{x}").ToList() : ["*.*"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return files.Select(x => x.Path.LocalPath).ToList();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class FolderProvider(ITopLevelProvider topLevelProvider) :
|
||||
IFolderProvider
|
||||
{
|
||||
public async Task<IReadOnlyCollection<string>> SelectFolders(FolderFilter filter)
|
||||
{
|
||||
if (topLevelProvider.Get() is TopLevel topLevel)
|
||||
{
|
||||
IReadOnlyList<IStorageFolder> folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
|
||||
{
|
||||
AllowMultiple = filter.AllowMultiple
|
||||
});
|
||||
|
||||
|
||||
return folders.Select(x => x.Path.LocalPath).ToList();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.UI.Media.Animation;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Toolkit.Foundation;
|
||||
using Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class FrameHandler(ITransientNavigationStore<Frame> navigationStore) :
|
||||
INotificationHandler<NavigateEventArgs<Frame>>,
|
||||
INotificationHandler<NavigateBackEventArgs<Frame>>
|
||||
{
|
||||
public Task Handle(NavigateEventArgs<Frame> args)
|
||||
{
|
||||
if (args.Region is Frame frame)
|
||||
{
|
||||
frame.NavigationPageFactory ??= new NavigationPageFactory();
|
||||
if (args.Template is Control control)
|
||||
{
|
||||
void Navigated(Control sender)
|
||||
{
|
||||
async void HandleNavigatedTo(object? _, NavigationEventArgs __)
|
||||
{
|
||||
async void HandleNavigatingFrom(object? _, NavigatingCancelEventArgs args)
|
||||
{
|
||||
sender.RemoveHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom);
|
||||
control.Unloaded -= HandleUnloaded;
|
||||
|
||||
async void HandleNavigatedFrom(object? _, NavigationEventArgs args)
|
||||
{
|
||||
sender.RemoveHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom);
|
||||
if (sender.DataContext is object content)
|
||||
{
|
||||
if (content is IDeactivated deactivated)
|
||||
{
|
||||
await deactivated.OnDeactivated();
|
||||
}
|
||||
|
||||
if (content is IDisposable disposable)
|
||||
{
|
||||
FrameNavigationOptions? options = navigationStore.Get<FrameNavigationOptions>(frame);
|
||||
if (options is not FrameNavigationOptions frameOptions ||
|
||||
!frameOptions.IsNavigationStackEnabled)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.AddHandler(Frame.NavigatedFromEvent, HandleNavigatedFrom);
|
||||
|
||||
if (sender.DataContext is object content)
|
||||
{
|
||||
if (content is IConfirmation confirmation &&
|
||||
!await confirmation.Confirm())
|
||||
{
|
||||
args.Cancel = true;
|
||||
}
|
||||
|
||||
if (!args.Cancel)
|
||||
{
|
||||
if (content is IDeactivating deactivating)
|
||||
{
|
||||
await deactivating.OnDeactivating();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.AddHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom);
|
||||
if (sender.DataContext is object content)
|
||||
{
|
||||
if (content is IActivated activated)
|
||||
{
|
||||
await activated.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
async void HandleUnloaded(object? _, RoutedEventArgs __)
|
||||
{
|
||||
sender.RemoveHandler(Frame.NavigatedToEvent, HandleNavigatedTo);
|
||||
sender.RemoveHandler(Frame.NavigatingFromEvent, HandleNavigatingFrom);
|
||||
|
||||
control.Unloaded -= HandleUnloaded;
|
||||
|
||||
if (control.DataContext is object content)
|
||||
{
|
||||
if (content is IDeactivated deactivated)
|
||||
{
|
||||
await deactivated.OnDeactivated();
|
||||
}
|
||||
|
||||
if (content is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.Unloaded += HandleUnloaded;
|
||||
}
|
||||
|
||||
sender.AddHandler(Frame.NavigatedToEvent, HandleNavigatedTo);
|
||||
}
|
||||
|
||||
control.DataContext = args.Content;
|
||||
|
||||
FrameNavigationOptions navigationOptions = new();
|
||||
List<Action> postNavigateActions = [];
|
||||
|
||||
void CleanUp(int start, int end)
|
||||
{
|
||||
int startIndex = Math.Max(0, start - 1);
|
||||
int endIndex = Math.Min(frame.BackStack.Count - 1, end - 1);
|
||||
|
||||
for (int i = endIndex; i >= startIndex; i--)
|
||||
{
|
||||
PageStackEntry? entry = frame.BackStack[i];
|
||||
if (entry.Context is Control control)
|
||||
{
|
||||
if (control.DataContext is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
frame.BackStack.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Parameters is not null)
|
||||
{
|
||||
if (args.Parameters.TryGetValue("Transition", out object? transition))
|
||||
{
|
||||
switch ($"{transition}")
|
||||
{
|
||||
case "Suppress":
|
||||
navigationOptions.TransitionInfoOverride = new SuppressNavigationTransitionInfo();
|
||||
break;
|
||||
|
||||
case "FromLeft":
|
||||
case "FromRight":
|
||||
case "FromTop":
|
||||
case "FromBottom":
|
||||
navigationOptions.TransitionInfoOverride = new SlideNavigationTransitionInfo
|
||||
{
|
||||
Effect = Enum.Parse<SlideNavigationTransitionEffect>($"{transition}")
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Parameters.TryGetValue("IsBackStackEnabled", out object? isBackStackEnabled))
|
||||
{
|
||||
if (isBackStackEnabled is bool value)
|
||||
{
|
||||
navigationOptions.IsNavigationStackEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Parameters.TryGetValue("ClearBackStack", out object? clearBackStack))
|
||||
{
|
||||
if (clearBackStack is bool clearBool)
|
||||
{
|
||||
if (clearBool)
|
||||
{
|
||||
postNavigateActions.Add(() => CleanUp(1, frame.BackStack.Count));
|
||||
}
|
||||
}
|
||||
|
||||
if (clearBackStack is string clearString)
|
||||
{
|
||||
if (clearString.StartsWith('[') && clearString.EndsWith(']') && clearString.Contains('-'))
|
||||
{
|
||||
string range = clearString.Trim('[', ']');
|
||||
string[] parts = range.Split('-');
|
||||
|
||||
if (parts.Length == 2 && int.TryParse(parts[0], out int start) &&
|
||||
int.TryParse(parts[1], out int end))
|
||||
{
|
||||
postNavigateActions.Add(() => CleanUp(start, end));
|
||||
}
|
||||
else
|
||||
{
|
||||
postNavigateActions.Add(() => CleanUp(1, frame.BackStack.Count));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
postNavigateActions.Add(() => CleanUp(1, frame.BackStack.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Navigated(control);
|
||||
|
||||
navigationStore.Set(frame, navigationOptions);
|
||||
frame.NavigateFromObject(control, navigationOptions);
|
||||
|
||||
foreach (Action postAction in postNavigateActions)
|
||||
{
|
||||
postAction.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(NavigateBackEventArgs<Frame> args)
|
||||
{
|
||||
if (args.Context is Frame frame)
|
||||
{
|
||||
frame.GoBack();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public interface IImageResizer
|
||||
{
|
||||
public Bitmap Resize(Stream stream,
|
||||
double width,
|
||||
double height,
|
||||
bool maintainAspectRatio);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Toolkit.Foundation;
|
||||
using Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public static class IServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddAvalonia(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<ITopLevelProvider, TopLevelProvider>();
|
||||
services.AddTransient<IFileProvider, FileProvider>();
|
||||
services.AddTransient<IFolderProvider, FolderProvider>();
|
||||
|
||||
services.AddTransient<IClipboardWriter, ClipboardWriter>();
|
||||
|
||||
services.AddTransient<IImageReader, ImageReader>();
|
||||
services.AddTransient<IImageWriter, ImageWriter>();
|
||||
services.AddTransient<IImageResizer, ImageResizer>();
|
||||
|
||||
services.AddTransient<IDispatcher, AvaloniaDispatcher>();
|
||||
|
||||
services.AddTransient<IContentTemplate, ContentTemplate>();
|
||||
services.AddTransient<INavigationRegion, NavigationRegion>();
|
||||
|
||||
services.AddHandler<WriteClipboardHandler>();
|
||||
services.AddHandler<SelectFoldersHandler>();
|
||||
services.AddHandler<SelectFilesHandler>();
|
||||
|
||||
services.AddHandler<ClassicDesktopStyleApplicationHandler>(nameof(IClassicDesktopStyleApplicationLifetime));
|
||||
services.AddHandler<SingleViewApplicationHandler>(nameof(ISingleViewApplicationLifetime));
|
||||
services.AddHandler<ContentControlHandler>(nameof(ContentControl));
|
||||
|
||||
services.AddHandler<FrameHandler>(nameof(Frame));
|
||||
services.TryAddSingleton<ITransientNavigationStore<Frame>, TransientNavigationStore<Frame>>();
|
||||
|
||||
services.AddHandler<ContentDialogHandler>(nameof(ContentDialog));
|
||||
services.AddHandler<TaskDialogHandler>(nameof(TaskDialog));
|
||||
|
||||
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>(provider => new NavigationRegionCollection
|
||||
{
|
||||
{ typeof(IClassicDesktopStyleApplicationLifetime), typeof(IClassicDesktopStyleApplicationLifetime) },
|
||||
{ typeof(ISingleViewApplicationLifetime), typeof(ISingleViewApplicationLifetime) }
|
||||
});
|
||||
|
||||
services.AddTransient((Func<IServiceProvider, IProxyServiceCollection<IComponentBuilder>>)(provider =>
|
||||
new ProxyServiceCollection<IComponentBuilder>(services =>
|
||||
{
|
||||
services.AddTransient<ITopLevelProvider, TopLevelProvider>();
|
||||
services.AddTransient<IFileProvider, FileProvider>();
|
||||
services.AddTransient<IFolderProvider, FolderProvider>();
|
||||
|
||||
services.AddTransient<IClipboardWriter, ClipboardWriter>();
|
||||
|
||||
services.AddTransient<IImageReader, ImageReader>();
|
||||
services.AddTransient<IImageWriter, ImageWriter>();
|
||||
services.AddTransient<IImageResizer, ImageResizer>();
|
||||
|
||||
services.AddSingleton(provider.GetRequiredService<IDispatcher>());
|
||||
services.AddTransient<IContentTemplate, ContentTemplate>();
|
||||
|
||||
services.AddTransient<INavigationRegion, NavigationRegion>();
|
||||
|
||||
services.AddHandler<WriteClipboardHandler>();
|
||||
services.AddHandler<SelectFoldersHandler>();
|
||||
services.AddHandler<SelectFilesHandler>();
|
||||
|
||||
services.AddHandler<ContentControlHandler>(nameof(ContentControl));
|
||||
|
||||
services.AddHandler<FrameHandler>(nameof(Frame));
|
||||
|
||||
services.TryAddSingleton(provider.GetRequiredService<ITransientNavigationStore<Frame>>());
|
||||
|
||||
services.AddHandler<ContentDialogHandler>(nameof(ContentDialog));
|
||||
services.AddHandler<TaskDialogHandler>(nameof(TaskDialog));
|
||||
})));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public interface ITopLevelProvider
|
||||
{
|
||||
TopLevel? Get();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public interface ITransientNavigationStore<TControl>
|
||||
where TControl : class
|
||||
{
|
||||
void Clear();
|
||||
|
||||
T? Get<T>(TControl control)
|
||||
where T : class;
|
||||
|
||||
void Remove(TControl control);
|
||||
|
||||
void Set(TControl control,
|
||||
object parameters);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ImageReader(IImageResizer imageResizer) :
|
||||
IImageReader
|
||||
{
|
||||
public IImageDescriptor Get(Stream stream,
|
||||
double width,
|
||||
double height,
|
||||
bool maintainAspectRatio)
|
||||
{
|
||||
Bitmap resizedImage = imageResizer.Resize(stream,
|
||||
width,
|
||||
height,
|
||||
maintainAspectRatio);
|
||||
|
||||
return new ImageDescriptor(resizedImage, width, height);
|
||||
}
|
||||
|
||||
public IImageDescriptor Get(Stream stream)
|
||||
{
|
||||
Bitmap image = new(stream);
|
||||
return new ImageDescriptor(image,
|
||||
image.Size.Width,
|
||||
image.Size.Height);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ImageResizer :
|
||||
IImageResizer
|
||||
{
|
||||
public Bitmap Resize(Stream stream,
|
||||
double width,
|
||||
double height,
|
||||
bool maintainAspectRatio)
|
||||
{
|
||||
Bitmap bitmap = new(stream);
|
||||
|
||||
if (bitmap.Size.Width != width || bitmap.Size.Height != height)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
using SKBitmap sKBitmap = SKBitmap.Decode(stream);
|
||||
|
||||
if (height == 0 && maintainAspectRatio)
|
||||
{
|
||||
height = (int)((float)width / sKBitmap.Width * sKBitmap.Height);
|
||||
}
|
||||
|
||||
if (width == 0 && maintainAspectRatio)
|
||||
{
|
||||
width = (int)((float)height / sKBitmap.Height * sKBitmap.Width);
|
||||
}
|
||||
|
||||
float widthRatio = (float)width / sKBitmap.Width;
|
||||
float heightRatio = (float)height / sKBitmap.Height;
|
||||
float scale = maintainAspectRatio ? Math.Max(widthRatio, heightRatio) : Math.Min(widthRatio, heightRatio);
|
||||
|
||||
int newWidth = (int)(sKBitmap.Width * scale);
|
||||
int newHeight = (int)(sKBitmap.Height * scale);
|
||||
|
||||
using SKBitmap resized = new(newWidth, newHeight);
|
||||
using SKCanvas canvas = new(resized);
|
||||
|
||||
using SKPaint paint = new()
|
||||
{
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
canvas.DrawBitmap(sKBitmap, new SKRect(0, 0, newWidth, newHeight), paint);
|
||||
|
||||
SKBitmap cropped;
|
||||
if (maintainAspectRatio)
|
||||
{
|
||||
int cropX = (int)(newWidth - width) / 2;
|
||||
int cropY = (int)(newHeight - height) / 2;
|
||||
|
||||
cropped = new SKBitmap((int)width, (int)height);
|
||||
using SKCanvas croppedCanvas = new(cropped);
|
||||
SKRect cropRect = new(cropX, cropY, (int)(cropX + width), cropY + (int)height);
|
||||
croppedCanvas.Clear(SKColors.Transparent);
|
||||
croppedCanvas.DrawBitmap(resized, cropRect, new SKRect(0, 0, (int)width, (int)height));
|
||||
}
|
||||
else
|
||||
{
|
||||
cropped = resized;
|
||||
}
|
||||
|
||||
using SKImage image = SKImage.FromBitmap(cropped);
|
||||
using MemoryStream outputStream = new();
|
||||
image.Encode(SKEncodedImageFormat.Png, 100).SaveTo(outputStream);
|
||||
outputStream.Seek(0, SeekOrigin.Begin);
|
||||
bitmap.Dispose();
|
||||
|
||||
return new Bitmap(outputStream);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class ImageWriter :
|
||||
IImageWriter
|
||||
{
|
||||
public void Write(IImageDescriptor imageDescriptor,
|
||||
Stream stream)
|
||||
{
|
||||
if (imageDescriptor.Image is Bitmap bitmap)
|
||||
{
|
||||
bitmap.Save(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class NavigationPageFactory :
|
||||
INavigationPageFactory
|
||||
{
|
||||
public Control? GetPage(Type srcType) => default;
|
||||
|
||||
public Control GetPageFromObject(object target) => (Control)target;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class NavigationRegion(INavigationRegionCollection collection) :
|
||||
INavigationRegion
|
||||
{
|
||||
public void Register(string name,
|
||||
object target)
|
||||
{
|
||||
if (target is Control control)
|
||||
{
|
||||
if (!collection.ContainsKey(name))
|
||||
{
|
||||
collection.Add(name, control);
|
||||
void HandleUnloaded(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
control.Unloaded -= HandleUnloaded;
|
||||
collection.Remove(name);
|
||||
}
|
||||
|
||||
control.Unloaded += HandleUnloaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class SingleViewApplicationHandler :
|
||||
INotificationHandler<NavigateEventArgs<ISingleViewApplicationLifetime>>
|
||||
{
|
||||
public Task Handle(NavigateEventArgs<ISingleViewApplicationLifetime> args)
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is
|
||||
ISingleViewApplicationLifetime lifeTime)
|
||||
{
|
||||
if (args.Template is Control control)
|
||||
{
|
||||
lifeTime.MainView = control;
|
||||
control.DataContext = args.Content;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.Core;
|
||||
using Toolkit.Foundation;
|
||||
using Toolkit.UI.Controls.Avalonia;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class TaskDialogHandler(ITopLevelProvider topLevelProvider) :
|
||||
INotificationHandler<NavigateEventArgs<TaskDialog>>
|
||||
{
|
||||
public async Task Handle(NavigateEventArgs<TaskDialog> args)
|
||||
{
|
||||
if (args.Template is TaskDialog dialog)
|
||||
{
|
||||
if (topLevelProvider.Get() is TopLevel topLevel)
|
||||
{
|
||||
dialog.XamlRoot = topLevel;
|
||||
dialog.DataContext = args.Content;
|
||||
|
||||
async void HandleClosing(FluentAvalonia.UI.Controls.TaskDialog sender,
|
||||
FluentAvalonia.UI.Controls.TaskDialogClosingEventArgs args)
|
||||
{
|
||||
dialog.Closing -= HandleClosing;
|
||||
if (dialog.DataContext is object content)
|
||||
{
|
||||
bool cancelled = false;
|
||||
|
||||
if (args.Result is TaskDialogResult result)
|
||||
{
|
||||
if (result is TaskDialogResult.OK && content is
|
||||
IPrimaryConfirmation primaryConfirmation)
|
||||
{
|
||||
Deferral deferral = args.GetDeferral();
|
||||
if (!await primaryConfirmation.ConfirmPrimary())
|
||||
{
|
||||
args.Cancel = true;
|
||||
cancelled = true;
|
||||
|
||||
dialog.Closing += HandleClosing;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancelled)
|
||||
{
|
||||
if (content is IDeactivating deactivating)
|
||||
{
|
||||
await deactivating.OnDeactivating();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.Closing += HandleClosing;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.0-rc1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Toolkit.Foundation\Toolkit.Foundation.csproj" />
|
||||
<ProjectReference Include="..\Toolkit.UI.Avalonia\Toolkit.UI.Avalonia.csproj" />
|
||||
<ProjectReference Include="..\Toolkit.UI.Controls.Avalonia\Toolkit.UI.Controls.Avalonia.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class TopLevelProvider :
|
||||
ITopLevelProvider
|
||||
{
|
||||
public TopLevel? Get()
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime classicDesktopStyleApplication)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(classicDesktopStyleApplication.MainWindow) is TopLevel topLevel)
|
||||
{
|
||||
return topLevel;
|
||||
}
|
||||
}
|
||||
|
||||
if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewApplication)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(singleViewApplication.MainView) is TopLevel topLevel)
|
||||
{
|
||||
return topLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class TransientNavigationStore<TControl> : ITransientNavigationStore<TControl>
|
||||
where TControl : class
|
||||
{
|
||||
private readonly Dictionary<TControl, Dictionary<Type, object>> controlDataMap = [];
|
||||
|
||||
public void Set(TControl control,
|
||||
object parameters)
|
||||
{
|
||||
if (!controlDataMap.TryGetValue(control, out var typeMap))
|
||||
{
|
||||
typeMap = new Dictionary<Type, object>();
|
||||
controlDataMap[control] = typeMap;
|
||||
}
|
||||
|
||||
typeMap[parameters.GetType()] = parameters;
|
||||
}
|
||||
|
||||
public T? Get<T>(TControl control)
|
||||
where T : class
|
||||
{
|
||||
if (controlDataMap.TryGetValue(control, out var typeMap) &&
|
||||
typeMap.TryGetValue(typeof(T), out var parameters))
|
||||
{
|
||||
typeMap.Remove(typeof(T));
|
||||
return parameters as T;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Remove(TControl control) => controlDataMap.Remove(control);
|
||||
|
||||
public void Clear() => controlDataMap.Clear();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Avalonia;
|
||||
|
||||
public class UserInteraction(ITopLevelProvider topLevelProvider) :
|
||||
IUserInteraction
|
||||
{
|
||||
public event EventHandler<UserInteractedEventArgs>? UserInteracted;
|
||||
|
||||
private void OnPointerMoved(object? sender,
|
||||
PointerEventArgs args) => UserInteracted?.Invoke(this, new UserInteractedEventArgs());
|
||||
|
||||
private void OnKeyDown(object? sender,
|
||||
KeyEventArgs args) => UserInteracted?.Invoke(this, new UserInteractedEventArgs());
|
||||
|
||||
private void OnKeyUp(object? sender,
|
||||
KeyEventArgs args) => UserInteracted?.Invoke(this, new UserInteractedEventArgs());
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (topLevelProvider.Get() is TopLevel topLevel)
|
||||
{
|
||||
topLevel.RemoveHandler(InputElement.PointerMovedEvent, OnPointerMoved);
|
||||
topLevel.RemoveHandler(InputElement.KeyDownEvent, OnKeyDown);
|
||||
topLevel.RemoveHandler(InputElement.KeyUpEvent, OnKeyUp);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (topLevelProvider.Get() is TopLevel topLevel)
|
||||
{
|
||||
topLevel.AddHandler(InputElement.PointerMovedEvent, OnPointerMoved, RoutingStrategies.Tunnel);
|
||||
topLevel.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Tunnel);
|
||||
topLevel.AddHandler(InputElement.KeyUpEvent, OnKeyUp, RoutingStrategies.Tunnel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Activated
|
||||
{
|
||||
public static ActivatedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
|
||||
|
||||
public static ActivatedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ActivatedEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Activation
|
||||
{
|
||||
public static ActivationEventArgs<TSender, TValue> As<TSender, TValue>(TValue value) => new(value);
|
||||
|
||||
public static ActivationEventArgs<TSender> As<TSender>() =>
|
||||
new();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ActivationBuilder(IActivation Value, object? Key = null);
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ActivationEventArgs<TSynchronize, TValue>(TValue? Value = default) :
|
||||
IActivation;
|
||||
|
||||
public record ActivationEventArgs<TSynchronize>() :
|
||||
IActivation;
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class AesDecryptor :
|
||||
IDecryptor
|
||||
{
|
||||
private const int IvSize = 16;
|
||||
|
||||
public bool TryDecrypt(byte[] cipher,
|
||||
byte[] key,
|
||||
out byte[]? decryptedData)
|
||||
{
|
||||
decryptedData = null;
|
||||
|
||||
if (cipher is null || key is null || cipher.Length < IvSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Span<byte> iv = cipher.AsSpan(0, IvSize);
|
||||
ReadOnlySpan<byte> encryptedContent = cipher.AsSpan(IvSize);
|
||||
|
||||
using Aes aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv.ToArray();
|
||||
|
||||
using MemoryStream memoryStream = new(encryptedContent.ToArray());
|
||||
using ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||
using CryptoStream cryptoStream = new(memoryStream, decryptor, CryptoStreamMode.Read);
|
||||
|
||||
using MemoryStream resultStream = new();
|
||||
cryptoStream.CopyTo(resultStream);
|
||||
|
||||
decryptedData = resultStream.ToArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class AesEncryptor :
|
||||
IEncryptor
|
||||
{
|
||||
private const int IvSize = 16;
|
||||
|
||||
public bool TryEncrypt(byte[] data,
|
||||
byte[] key,
|
||||
out byte[]? encryptedData)
|
||||
{
|
||||
encryptedData = null;
|
||||
|
||||
if (data is null || key is null || key.Length != 32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using Aes aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.GenerateIV();
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
memoryStream.Write(aes.IV, 0, IvSize);
|
||||
|
||||
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
|
||||
using (CryptoStream cryptoStream = new(memoryStream, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
cryptoStream.Write(data, 0, data.Length);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
}
|
||||
|
||||
encryptedData = memoryStream.ToArray();
|
||||
return true;
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,24 @@
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class AppService(IEnumerable<IInitializer> initializers,
|
||||
public class AppService(IEnumerable<IInitialization> initializations,
|
||||
IEnumerable<IAsyncInitialization> asyncInitializations,
|
||||
IPublisher publisher) :
|
||||
IHostedService
|
||||
{
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (IInitializer initializer in initializers)
|
||||
foreach (IInitialization initialization in initializations)
|
||||
{
|
||||
await initializer.Initialize();
|
||||
initialization.Initialize();
|
||||
}
|
||||
|
||||
await publisher.Publish<Started>(cancellationToken);
|
||||
foreach (IAsyncInitialization initialization in asyncInitializations)
|
||||
{
|
||||
await initialization.Initialize();
|
||||
}
|
||||
|
||||
publisher.Publish<StartedEventArgs>();
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
@@ -2,18 +2,33 @@
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ActivityLock(IActivityIndicator activityIndicator) : AsyncLock
|
||||
{
|
||||
public override TaskAwaiter<AsyncLock> GetAwaiter()
|
||||
{
|
||||
activityIndicator.IsActive = true;
|
||||
return base.GetAwaiter();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
activityIndicator.IsActive = false;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public class AsyncLock(int initial = 1,
|
||||
int maximum = 1) :
|
||||
IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim semaphore = new(initial, maximum);
|
||||
|
||||
public void Dispose()
|
||||
public virtual void Dispose()
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
public TaskAwaiter<AsyncLock> GetAwaiter() => LockAsync().GetAwaiter();
|
||||
public virtual TaskAwaiter<AsyncLock> GetAwaiter() => LockAsync().GetAwaiter();
|
||||
|
||||
private async Task<AsyncLock> LockAsync()
|
||||
{
|
||||
|
||||
+164
-27
@@ -1,68 +1,205 @@
|
||||
using HyperX;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Collections;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class Cache<TValue>(IDisposer disposer) :
|
||||
public class Cache<TValue>(IComparer<TValue> comparer) :
|
||||
ICache<TValue>
|
||||
{
|
||||
private readonly List<TValue> cache = [];
|
||||
private readonly List<TValue> items = [];
|
||||
|
||||
public void Add(TValue value)
|
||||
public IEnumerable<TValue> Items =>
|
||||
items;
|
||||
|
||||
public TValue this[int index] =>
|
||||
items[index];
|
||||
|
||||
public void Add(TValue item)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return;
|
||||
int index = FindInsertIndex(item);
|
||||
items.Insert(index, item);
|
||||
}
|
||||
|
||||
disposer.Add(value, Disposable.Create(() => Remove(value)));
|
||||
cache.Add(value);
|
||||
public void Clear() =>
|
||||
items.Clear();
|
||||
|
||||
public bool Contains(TValue item)
|
||||
{
|
||||
int index = items.IndexOf(item);
|
||||
if (index >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear() => cache.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<TValue> GetEnumerator() => cache.GetEnumerator();
|
||||
public IEnumerator<TValue> GetEnumerator() =>
|
||||
items.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
|
||||
|
||||
public bool Remove(TValue value) => cache.Remove(value);
|
||||
public int IndexOf(TValue item) =>
|
||||
items.IndexOf(item);
|
||||
|
||||
public bool Remove(TValue item)
|
||||
{
|
||||
int index = items.IndexOf(item);
|
||||
if (index >= 0)
|
||||
{
|
||||
items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(TValue key, out TValue? item)
|
||||
{
|
||||
int index = items.IndexOf(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
item = items[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int FindInsertIndex(TValue item)
|
||||
{
|
||||
int low = 0, high = items.Count - 1;
|
||||
|
||||
while (low <= high)
|
||||
{
|
||||
int mid = (low + high) / 2;
|
||||
int cmp = comparer.Compare(items[mid], item);
|
||||
|
||||
if (cmp < 0)
|
||||
{
|
||||
low = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (low < items.Count && comparer.Compare(items[low], item) == 0)
|
||||
{
|
||||
low++;
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
}
|
||||
|
||||
public class Cache<TKey, TValue> :
|
||||
public class Cache<TKey, TValue>(IComparer<TKey> comparer) :
|
||||
ICache<TKey, TValue>
|
||||
where TKey :
|
||||
notnull
|
||||
where TValue :
|
||||
notnull
|
||||
{
|
||||
private readonly ConcurrentDictionary<TKey, TValue> cache = new();
|
||||
private readonly List<KeyValuePair<TKey, TValue?>> items = [];
|
||||
|
||||
public void Add(TKey key,
|
||||
TValue value)
|
||||
public TValue? this[TKey key]
|
||||
{
|
||||
cache.TryAdd(key, value);
|
||||
get
|
||||
{
|
||||
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default),
|
||||
new KeyValuePairComparer<TKey, TValue?>(comparer));
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
return items[index].Value;
|
||||
}
|
||||
|
||||
public void Clear() => cache.Clear();
|
||||
return default;
|
||||
}
|
||||
set
|
||||
{
|
||||
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default),
|
||||
new KeyValuePairComparer<TKey, TValue?>(comparer));
|
||||
|
||||
public bool ContainsKey(TKey key) => cache.ContainsKey(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
items[index] = new KeyValuePair<TKey, TValue?>(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Insert(~index, new KeyValuePair<TKey, TValue?>(key, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => cache.GetEnumerator();
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default),
|
||||
new KeyValuePairComparer<TKey, TValue?>(comparer));
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) => cache.Remove(key, out _);
|
||||
items.Insert(index, new KeyValuePair<TKey, TValue?>(key, value));
|
||||
}
|
||||
|
||||
public void Clear() => items.Clear();
|
||||
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0);
|
||||
if (index >= 0)
|
||||
{
|
||||
items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
|
||||
items.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
GetEnumerator();
|
||||
|
||||
public int IndexOf(TKey key) =>
|
||||
items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0);
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0);
|
||||
if (index >= 0)
|
||||
{
|
||||
items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue? value)
|
||||
{
|
||||
if (cache.TryGetValue(key, out value))
|
||||
int index = items.BinarySearch(new KeyValuePair<TKey, TValue?>(key, default(TValue)),
|
||||
new KeyValuePairComparer<TKey, TValue?>(comparer));
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
value = items[index].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private class KeyValuePairComparer<TK, TV>(IComparer<TK> comparer) :
|
||||
IComparer<KeyValuePair<TK, TV>>
|
||||
{
|
||||
private readonly IComparer<TK> comparer = comparer ?? Comparer<TK>.Default;
|
||||
|
||||
public int Compare(KeyValuePair<TK, TV> x, KeyValuePair<TK, TV> y) =>
|
||||
comparer.Compare(x.Key, y.Key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Cancel
|
||||
{
|
||||
public static CancelEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
|
||||
|
||||
public static CancelEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record CancelEventArgs<TSender>(TSender Sender);
|
||||
@@ -1,3 +1,8 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Changed<TValue>(TValue? Value = default) : INotification;
|
||||
public record Changed
|
||||
{
|
||||
public static ChangedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
|
||||
|
||||
public static ChangedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ChangedEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Clipboard;
|
||||
|
||||
public record Clipboard<TValue>(TValue Value);
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Close
|
||||
{
|
||||
public static CloseEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
|
||||
|
||||
public static CloseEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record CloseEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Closed
|
||||
{
|
||||
public static ClosedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
|
||||
|
||||
public static ClosedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ClosedEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public partial class CommandValueViewModel<TValue>(IServiceProvider serviceProvider,
|
||||
IServiceFactory serviceFactory,
|
||||
public partial class CommandValueViewModel<TValue>(IServiceProvider provider,
|
||||
IServiceFactory factory,
|
||||
IMediator mediator,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer) :
|
||||
ValueViewModel<TValue>(serviceProvider, serviceFactory, publisher, subscriber, disposer)
|
||||
Observable<TValue>(provider, factory, mediator, publisher, subscriber, disposer)
|
||||
where TValue : notnull
|
||||
{
|
||||
public IRelayCommand InvokeCommand =>
|
||||
new AsyncRelayCommand(InvokeAsync);
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public partial class CommandViewModel(IServiceProvider serviceProvider,
|
||||
IServiceFactory serviceFactory,
|
||||
public partial class CommandViewModel(IServiceProvider provider,
|
||||
IServiceFactory factory,
|
||||
IMediator mediator,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer) :
|
||||
ObservableViewModel(serviceProvider, serviceFactory, publisher, subscriber, disposer)
|
||||
Observable(provider, factory, mediator, publisher, subscriber, disposer)
|
||||
{
|
||||
public IRelayCommand InvokeCommand =>
|
||||
new AsyncRelayCommand(InvokeAsync);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class Component :
|
||||
IComponent
|
||||
{
|
||||
private readonly IComponentBuilder builder;
|
||||
|
||||
protected Component(IComponentBuilder builder) =>
|
||||
this.builder = builder;
|
||||
|
||||
public static TComponent Create<TComponent>(IServiceProvider provider,
|
||||
Action<IComponentBuilder> builderDelegate)
|
||||
where TComponent : class, IComponent
|
||||
{
|
||||
IServiceFactory factory = provider.GetRequiredService<IServiceFactory>();
|
||||
|
||||
IComponentBuilder builder = ComponentBuilder.Create();
|
||||
builderDelegate(builder);
|
||||
|
||||
return factory.Create<TComponent>(builder);
|
||||
}
|
||||
|
||||
public IComponentBuilder Configure(Action<IComponentBuilder>? componentDelegate = null)
|
||||
{
|
||||
if (componentDelegate is not null)
|
||||
{
|
||||
componentDelegate(builder);
|
||||
}
|
||||
|
||||
return Configuring(builder);
|
||||
}
|
||||
|
||||
public IComponentBuilder Configure<TConfiguration>(Action<IConfigurationBuilder<TConfiguration>>? configurationDelegate = null,
|
||||
Action<IComponentBuilder>? componentDelegate = null)
|
||||
where TConfiguration : class, new()
|
||||
{
|
||||
if (componentDelegate is not null)
|
||||
{
|
||||
componentDelegate(builder);
|
||||
}
|
||||
|
||||
if (configurationDelegate is not null)
|
||||
{
|
||||
ConfigurationBuilder<TConfiguration> configurationBuilder = new();
|
||||
|
||||
configurationDelegate(configurationBuilder);
|
||||
|
||||
if (configurationBuilder.Section is { Length: > 0 } section)
|
||||
{
|
||||
builder.AddConfiguration(section, configurationBuilder.Configuration ?? new TConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
return Configuring(builder);
|
||||
}
|
||||
|
||||
public virtual IComponentBuilder Configuring(IComponentBuilder builder) => builder;
|
||||
}
|
||||
@@ -9,78 +9,76 @@ public class ComponentBuilder :
|
||||
{
|
||||
private readonly IHostBuilder hostBuilder;
|
||||
|
||||
private bool configurationRegistered;
|
||||
public string ContentRoot { get; set; } = "Local";
|
||||
|
||||
public string ConfigurationFile { get; set; } = "Settings.json";
|
||||
|
||||
private ComponentBuilder()
|
||||
{
|
||||
hostBuilder = new HostBuilder()
|
||||
.UseContentRoot("Local", true)
|
||||
.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
config.AddJsonFile("Settings.json", true, true);
|
||||
})
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddScoped<IComponentHost, ComponentHost>();
|
||||
|
||||
services.AddScoped<IServiceFactory>(provider =>
|
||||
new ServiceFactory((type, parameters) =>
|
||||
ActivatorUtilities.CreateInstance(provider, type, parameters!)));
|
||||
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type,
|
||||
parameters?.Where(x => x is not null).ToArray()!)));
|
||||
|
||||
services.AddSingleton<IDisposer, Disposer>();
|
||||
|
||||
services.AddScoped<SubscriptionCollection>();
|
||||
services.AddScoped<ISubscriptionManager, SubscriptionManager>();
|
||||
|
||||
services.AddTransient<ISubscriber, Subscriber>();
|
||||
services.AddTransient<IHandlerProvider, HandlerProvider>();
|
||||
services.AddScoped<ISubscriber, Subscriber>();
|
||||
services.AddTransient<IPublisher, Publisher>();
|
||||
|
||||
services.AddTransient<IMediator, Mediator>();
|
||||
services.AddScoped<IDisposer, Disposer>();
|
||||
|
||||
services.AddTransient<INavigationScope, NavigationScope>();
|
||||
services.AddTransient<IValidation, Validation>();
|
||||
services.AddTransient<IValidatorCollection, ValidatorCollection>();
|
||||
|
||||
services.AddTransient<INavigationProvider, NavigationProvider>();
|
||||
services.AddScoped<INavigationContextCollection, NavigationContextCollection>();
|
||||
services.AddTransient<INavigationContextProvider, NavigationContextProvider>();
|
||||
services.AddTransient<IContentFactory, ContentFactory>();
|
||||
services.AddTransient<INavigation, Navigation>();
|
||||
|
||||
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>();
|
||||
services.AddTransient<INavigationRegionProvider, NavigationRegionProvider>();
|
||||
|
||||
services.AddHandler<NavigateHandler>();
|
||||
services.AddHandler<NavigateBackHandler>();
|
||||
});
|
||||
}
|
||||
|
||||
public static IComponentBuilder Create() =>
|
||||
new ComponentBuilder();
|
||||
public static IComponentBuilder Create() => new ComponentBuilder();
|
||||
|
||||
public IComponentBuilder AddConfiguration<TConfiguration>(Action<TConfiguration> configurationDelegate)
|
||||
where TConfiguration : ComponentConfiguration, new()
|
||||
where TConfiguration :
|
||||
class, new()
|
||||
{
|
||||
if (configurationRegistered)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
configurationRegistered = true;
|
||||
TConfiguration configuration = new();
|
||||
|
||||
if (configurationDelegate is not null)
|
||||
{
|
||||
configurationDelegate(configuration);
|
||||
}
|
||||
|
||||
hostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddConfiguration<ComponentConfiguration>(section: configuration.GetType().Name,
|
||||
configuration: configuration);
|
||||
AddConfiguration(typeof(TConfiguration).Name, configuration);
|
||||
return this;
|
||||
}
|
||||
|
||||
services.AddConfiguration(configuration);
|
||||
});
|
||||
public IComponentBuilder AddConfiguration<TConfiguration>(string section,
|
||||
TConfiguration? configuration = null)
|
||||
where TConfiguration :
|
||||
class, new()
|
||||
{
|
||||
hostBuilder.AddConfiguration(section, ConfigurationFile, configuration);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IComponentHost Build()
|
||||
public IComponentBuilder AddConfiguration<TConfiguration>(string section)
|
||||
where TConfiguration :
|
||||
class, new()
|
||||
{
|
||||
IHost host = hostBuilder.Build();
|
||||
return host.Services.GetRequiredService<IComponentHost>();
|
||||
AddConfiguration<TConfiguration>(section, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IComponentBuilder AddServices(Action<IServiceCollection> configureDelegate)
|
||||
@@ -88,4 +86,17 @@ public class ComponentBuilder :
|
||||
hostBuilder.ConfigureServices(configureDelegate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IComponentHost Build()
|
||||
{
|
||||
hostBuilder
|
||||
.UseContentRoot(ContentRoot, true)
|
||||
.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
config.AddJsonFile(ConfigurationFile, true, true);
|
||||
});
|
||||
|
||||
IHost host = hostBuilder.Build();
|
||||
return host.Services.GetRequiredService<IComponentHost>();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ComponentConfiguration
|
||||
{
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
internal Guid Id { get; set; } = Guid.NewGuid();
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public partial class ComponentConfigurationViewModel<TConfiguration, TValue, THeader, TDescription, TAction> :
|
||||
ValueViewModel<TValue>,
|
||||
IComponentConfigurationViewModel,
|
||||
INotificationHandler<Changed<TConfiguration>>
|
||||
where TConfiguration : class
|
||||
{
|
||||
public ComponentConfigurationViewModel(IServiceProvider serviceProvider,
|
||||
IServiceFactory serviceFactory,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer,
|
||||
THeader header,
|
||||
TDescription description,
|
||||
TAction action) : base(serviceProvider, serviceFactory, publisher, subscriber, disposer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Task Handle(Changed<TConfiguration> args,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ComponentConfigurationViewModel<TConfiguration, TValue, TAction>(IServiceProvider serviceProvider,
|
||||
IServiceFactory serviceFactory,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer,
|
||||
TAction action,
|
||||
TConfiguration configuration,
|
||||
Func<TConfiguration, TValue> valueDelegate,
|
||||
object header,
|
||||
object description) :
|
||||
ValueViewModel<TValue>(serviceProvider, serviceFactory, publisher, subscriber, disposer),
|
||||
IComponentConfigurationViewModel,
|
||||
INotificationHandler<Changed<TConfiguration>>
|
||||
where TConfiguration : class
|
||||
{
|
||||
[ObservableProperty]
|
||||
private TAction action = action;
|
||||
|
||||
[ObservableProperty]
|
||||
private object header = header;
|
||||
|
||||
[ObservableProperty]
|
||||
private object description = description;
|
||||
|
||||
public override Task Activated()
|
||||
{
|
||||
Value = valueDelegate.Invoke(configuration);
|
||||
return base.Activated();
|
||||
}
|
||||
public Task Handle(Changed<TConfiguration> args,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (args.Value is TConfiguration configuration)
|
||||
{
|
||||
Value = valueDelegate.Invoke(configuration);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ComponentConfigurationViewModel<TConfiguration, TValue, TDescription, TAction>(IServiceProvider serviceProvider,
|
||||
IServiceFactory serviceFactory,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer,
|
||||
TAction action,
|
||||
TDescription description,
|
||||
TConfiguration configuration,
|
||||
Func<TConfiguration, TValue> valueDelegate,
|
||||
object header) :
|
||||
ValueViewModel<TValue>(serviceProvider, serviceFactory, publisher, subscriber, disposer),
|
||||
IComponentConfigurationViewModel,
|
||||
INotificationHandler<Changed<TConfiguration>>
|
||||
where TConfiguration : class
|
||||
{
|
||||
[ObservableProperty]
|
||||
private TAction action = action;
|
||||
|
||||
[ObservableProperty]
|
||||
private object header = header;
|
||||
|
||||
[ObservableProperty]
|
||||
private TDescription description = description;
|
||||
|
||||
public override Task Activated()
|
||||
{
|
||||
Value = valueDelegate.Invoke(configuration);
|
||||
return base.Activated();
|
||||
}
|
||||
|
||||
public Task Handle(Changed<TConfiguration> args,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (args.Value is TConfiguration configuration)
|
||||
{
|
||||
Value = valueDelegate.Invoke(configuration);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ComponentFactory(IServiceProvider provider,
|
||||
IProxyServiceCollection<IComponentBuilder> proxy,
|
||||
IComponentScopeCollection scopes) :
|
||||
IComponentFactory
|
||||
{
|
||||
public IComponentHost? Create<TComponent, TConfiguration>(string key,
|
||||
Action<IConfigurationBuilder<TConfiguration>>? configurationDelegate = null,
|
||||
Action<IComponentBuilder>? componentDelegate = null,
|
||||
Action<IServiceCollection>? servicesDelegate = null)
|
||||
where TComponent : IComponent
|
||||
where TConfiguration : class, new()
|
||||
{
|
||||
if (provider.GetRequiredService<TComponent>() is TComponent component)
|
||||
{
|
||||
IComponentBuilder builder = component.Configure(configurationDelegate,
|
||||
componentDelegate);
|
||||
|
||||
builder.AddServices(services =>
|
||||
{
|
||||
services.AddTransient(_ =>
|
||||
provider.GetRequiredService<IProxyServiceCollection<IComponentBuilder>>());
|
||||
|
||||
services.AddTransient(_ =>
|
||||
provider.GetRequiredService<IComponentFactory>());
|
||||
|
||||
services.AddTransient(_ =>
|
||||
provider.GetRequiredService<IProxyService<IPublisher>>());
|
||||
|
||||
services.AddTransient(_ =>
|
||||
provider.GetRequiredService<IProxyService<IComponentHostCollection>>());
|
||||
|
||||
services.AddScoped(_ =>
|
||||
provider.GetRequiredService<INavigationRegionCollection>());
|
||||
|
||||
services.AddScoped(_ =>
|
||||
provider.GetRequiredService<INavigationRegionProvider>());
|
||||
|
||||
services.AddScoped(_ =>
|
||||
provider.GetRequiredService<IComponentScopeCollection>());
|
||||
|
||||
services.AddTransient(_ =>
|
||||
provider.GetRequiredService<IComponentScopeProvider>());
|
||||
|
||||
services.AddRange(proxy.Services);
|
||||
services.AddSingleton(new NamedComponent(key));
|
||||
|
||||
if (servicesDelegate is not null)
|
||||
{
|
||||
servicesDelegate(services);
|
||||
}
|
||||
});
|
||||
|
||||
IComponentHost host = builder.Build();
|
||||
|
||||
scopes.Add(new ComponentScopeDescriptor(key,
|
||||
host.Services.GetRequiredService<IServiceProvider>()));
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,34 @@ using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public sealed class ComponentHost(IServiceProvider services,
|
||||
IEnumerable<IInitializer> initializers,
|
||||
public class ComponentHost(IServiceProvider services,
|
||||
IEnumerable<IInitialization> initializations,
|
||||
IEnumerable<IAsyncInitialization> asyncInitializations,
|
||||
IEnumerable<IHostedService> hostedServices) :
|
||||
IComponentHost
|
||||
{
|
||||
public IServiceProvider Services => services;
|
||||
|
||||
public ComponentConfiguration? Configuration =>
|
||||
Services.GetService<ComponentConfiguration>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public TConfiguration? GetConfiguration<TConfiguration>()
|
||||
where TConfiguration : class
|
||||
{
|
||||
return Services.GetService<TConfiguration>();
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (IInitializer initializer in initializers)
|
||||
foreach (IInitialization initialization in initializations)
|
||||
{
|
||||
await initializer.Initialize();
|
||||
initialization.Initialize();
|
||||
}
|
||||
|
||||
foreach (IAsyncInitialization initialization in asyncInitializations)
|
||||
{
|
||||
await initialization.Initialize();
|
||||
}
|
||||
|
||||
foreach (IHostedService service in hostedServices)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
@@ -7,13 +8,13 @@ public class ComponentInitializer(IEnumerable<IComponent> components,
|
||||
IComponentHostCollection hosts,
|
||||
IComponentScopeCollection scopes,
|
||||
IServiceProvider provider) :
|
||||
IInitializer
|
||||
IInitialization
|
||||
{
|
||||
public async Task Initialize()
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (IComponent component in components)
|
||||
{
|
||||
IComponentBuilder builder = component.Create();
|
||||
IComponentBuilder builder = component.Configure();
|
||||
builder.AddServices(services =>
|
||||
{
|
||||
services.AddTransient(_ =>
|
||||
@@ -23,10 +24,10 @@ public class ComponentInitializer(IEnumerable<IComponent> components,
|
||||
provider.GetRequiredService<IProxyService<IComponentHostCollection>>());
|
||||
|
||||
services.AddScoped(_ =>
|
||||
provider.GetRequiredService<INavigationContextCollection>());
|
||||
provider.GetRequiredService<INavigationRegionCollection>());
|
||||
|
||||
services.AddScoped(_ =>
|
||||
provider.GetRequiredService<INavigationContextProvider>());
|
||||
provider.GetRequiredService<INavigationRegionProvider>());
|
||||
|
||||
services.AddScoped(_ =>
|
||||
provider.GetRequiredService<IComponentScopeCollection>());
|
||||
@@ -35,15 +36,17 @@ public class ComponentInitializer(IEnumerable<IComponent> components,
|
||||
provider.GetRequiredService<IComponentScopeProvider>());
|
||||
|
||||
services.AddRange(typedServices.Services);
|
||||
|
||||
services.AddSingleton(new NamedComponent(component.GetType().Name));
|
||||
});
|
||||
|
||||
IComponentHost host = builder.Build();
|
||||
|
||||
scopes.Add(component.GetType().Name,
|
||||
host.Services.GetRequiredService<IServiceProvider>());
|
||||
scopes.Add(new ComponentScopeDescriptor(component.GetType().Name,
|
||||
provider.GetRequiredService<IServiceProvider>()));
|
||||
|
||||
hosts.Add(host);
|
||||
await host.StartAsync();
|
||||
host.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ComponentScopeCollection : Dictionary<string, IServiceProvider>,
|
||||
public class ComponentScopeCollection : List<ComponentScopeDescriptor>,
|
||||
IComponentScopeCollection;
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ComponentScopeDescriptor(string Key, IServiceProvider Services);
|
||||
@@ -3,10 +3,9 @@
|
||||
public class ComponentScopeProvider(IComponentScopeCollection scopes) :
|
||||
IComponentScopeProvider
|
||||
{
|
||||
public IServiceProvider? Get(string name)
|
||||
public ComponentScopeDescriptor? Get(string key)
|
||||
{
|
||||
return scopes.TryGetValue(name,
|
||||
out IServiceProvider? scope) ? scope : default;
|
||||
return scopes.FirstOrDefault(x => x.Key == key) is ComponentScopeDescriptor
|
||||
descriptor ? descriptor : default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class Configuration<TConfiguration>(IConfigurationReader<TConfiguration> reader) :
|
||||
IConfiguration<TConfiguration>
|
||||
where TConfiguration :
|
||||
class
|
||||
{
|
||||
public TConfiguration Value => reader.Read();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationBuilder<TConfiguration> :
|
||||
IConfigurationBuilder<TConfiguration>
|
||||
where TConfiguration : class
|
||||
{
|
||||
public string? Section { get; set; }
|
||||
|
||||
public TConfiguration? Configuration { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationCache :
|
||||
IConfigurationCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object?> cache = new();
|
||||
|
||||
public void Set<TConfiguration>(string section,
|
||||
TConfiguration configuration) => cache[section] = configuration;
|
||||
|
||||
public bool Remove(string section) => cache.TryRemove(section, out _);
|
||||
|
||||
public bool TryGet<TConfiguration>(string section,
|
||||
out TConfiguration? configuration)
|
||||
{
|
||||
if (cache.TryGetValue(section, out object? cachedValue))
|
||||
{
|
||||
configuration = (TConfiguration?)cachedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
configuration = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationChangedHandler<TConfiguration, TValue>(ConfigurationValue<TConfiguration, TValue> configurationValue) :
|
||||
INotificationHandler<Changed<TConfiguration>>
|
||||
INotificationHandler<ChangedEventArgs<TConfiguration>>
|
||||
where TValue :
|
||||
class, new()
|
||||
{
|
||||
public Task Handle(Changed<TConfiguration> args,
|
||||
CancellationToken cancellationToken = default)
|
||||
public Task Handle(ChangedEventArgs<TConfiguration> args)
|
||||
{
|
||||
if (args.Value is TConfiguration configuration)
|
||||
if (args.Sender is TConfiguration configuration)
|
||||
{
|
||||
if (configurationValue.TryUpdate(configuration, out TValue value))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationDescriptor<TConfiguration>(string section,
|
||||
IConfigurationReader<TConfiguration> reader) :
|
||||
IConfigurationDescriptor<TConfiguration>
|
||||
where TConfiguration :
|
||||
class
|
||||
{
|
||||
public TConfiguration Value => reader.Read();
|
||||
|
||||
public string Section => section;
|
||||
|
||||
public string Name => section.Split(':').LastOrDefault() ?? section;
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationInitializer<TConfiguration>(IPublisher publisher,
|
||||
IConfigurationReader<TConfiguration> reader,
|
||||
public class ConfigurationInitializer<TConfiguration>(IConfigurationReader<TConfiguration> reader,
|
||||
IConfigurationWriter<TConfiguration> writer,
|
||||
IConfigurationFactory<TConfiguration> factory) :
|
||||
IConfigurationFactory<TConfiguration> factory,
|
||||
IPublisher publisher) :
|
||||
IConfigurationInitializer<TConfiguration>,
|
||||
IInitializer
|
||||
IInitialization
|
||||
where TConfiguration :
|
||||
class
|
||||
{
|
||||
public async Task Initialize()
|
||||
public void Initialize()
|
||||
{
|
||||
if (!reader.TryRead(out TConfiguration? configuration))
|
||||
{
|
||||
@@ -20,6 +20,6 @@ public class ConfigurationInitializer<TConfiguration>(IPublisher publisher,
|
||||
}
|
||||
}
|
||||
|
||||
await publisher.PublishUI(new Changed<TConfiguration>(configuration));
|
||||
publisher.PublishUI(new ActivatedEventArgs<TConfiguration>(configuration));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public static class ConfigurationLock
|
||||
{
|
||||
private static readonly ReaderWriterLockSlim readerWriterLock = new();
|
||||
|
||||
public static IDisposable EnterRead()
|
||||
{
|
||||
readerWriterLock.EnterReadLock();
|
||||
return new ConfigurationReaderLockDisposer(readerWriterLock);
|
||||
}
|
||||
|
||||
public static IDisposable EnterWrite()
|
||||
{
|
||||
readerWriterLock.EnterWriteLock();
|
||||
return new ConfigurationWriterLockDisposer(readerWriterLock);
|
||||
}
|
||||
|
||||
private class ConfigurationWriterLockDisposer(ReaderWriterLockSlim lockSlim) :
|
||||
IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
lockSlim.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfigurationReaderLockDisposer(ReaderWriterLockSlim lockSlim) :
|
||||
IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
lockSlim.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
namespace Toolkit.Foundation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfiguration> file,
|
||||
IConfigurationReader<TConfiguration> reader,
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationMonitor<TConfiguration>(string section,
|
||||
IConfigurationCache cache,
|
||||
IConfigurationFile<TConfiguration> file,
|
||||
IServiceProvider serviceProvider,
|
||||
IPublisher publisher) :
|
||||
IConfigurationMonitor<TConfiguration>
|
||||
where TConfiguration :
|
||||
@@ -11,12 +15,14 @@ public class ConfigurationMonitor<TConfiguration>(IConfigurationFile<TConfigurat
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
async void ChangedHandler(object sender,
|
||||
void ChangedHandler(object sender,
|
||||
FileSystemEventArgs args)
|
||||
{
|
||||
if (reader.Read() is { } configuration)
|
||||
if (serviceProvider.GetRequiredKeyedService<IConfigurationDescriptor<TConfiguration>>(section) is
|
||||
IConfigurationDescriptor<TConfiguration> configuration)
|
||||
{
|
||||
await publisher.PublishUI(new Changed<TConfiguration>(configuration));
|
||||
cache.Remove(section);
|
||||
publisher.PublishUI(new ChangedEventArgs<TConfiguration>(configuration.Value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,132 +1,192 @@
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Json.More;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationSource<TConfiguration>(IConfigurationFile<TConfiguration> configurationFile,
|
||||
string section,
|
||||
IConfigurationCache cache,
|
||||
JsonSerializerOptions? serializerOptions = null) :
|
||||
IConfigurationSource<TConfiguration>
|
||||
where TConfiguration :
|
||||
class
|
||||
{
|
||||
private readonly object lockingObject = new();
|
||||
|
||||
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = new(() =>
|
||||
private static readonly Func<JsonSerializerOptions> defaultSerializerOptions = () =>
|
||||
{
|
||||
return new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(),
|
||||
new DictionaryStringObjectJsonConverter()
|
||||
new DictionaryStringObjectJsonConverter(),
|
||||
new JsonArrayTupleConverter()
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
public void Set(TConfiguration value) => Set((object)value);
|
||||
|
||||
public void Set(object value)
|
||||
{
|
||||
lock (lockingObject)
|
||||
using (ConfigurationLock.EnterWrite())
|
||||
{
|
||||
IFileInfo fileInfo = configurationFile.FileInfo;
|
||||
EnsureFileExists(fileInfo.PhysicalPath);
|
||||
|
||||
if (!File.Exists(fileInfo.PhysicalPath))
|
||||
string? content;
|
||||
JsonNode? rootNode;
|
||||
|
||||
using (Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
|
||||
using (StreamReader reader = new(stream))
|
||||
{
|
||||
string? fileDirectoryPath = Path.GetDirectoryName(fileInfo.PhysicalPath);
|
||||
if (!string.IsNullOrEmpty(fileDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(fileDirectoryPath);
|
||||
content = reader.ReadToEnd();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
rootNode = JsonNode.Parse(content);
|
||||
}
|
||||
|
||||
File.WriteAllText(fileInfo.PhysicalPath!, "{}");
|
||||
}
|
||||
JsonNode? valueNode = JsonNode.Parse(JsonSerializer.SerializeToUtf8Bytes(value, serializerOptions ?? defaultSerializerOptions()));
|
||||
|
||||
static Stream OpenReadWrite(IFileInfo fileInfo)
|
||||
{
|
||||
return fileInfo.PhysicalPath is not null
|
||||
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
|
||||
: fileInfo.CreateReadStream();
|
||||
}
|
||||
ApplyConfigurationUpdates(ref rootNode, valueNode, section);
|
||||
|
||||
using Stream stream = OpenReadWrite(fileInfo);
|
||||
using StreamReader? reader = new(stream);
|
||||
using Stream stream2 = new FileStream(fileInfo.PhysicalPath!, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
JsonSerializer.Serialize(stream2, rootNode, serializerOptions ?? defaultSerializerOptions());
|
||||
|
||||
string? content = reader.ReadToEnd();
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(content);
|
||||
|
||||
using Stream stream2 = OpenReadWrite(fileInfo);
|
||||
Utf8JsonWriter writer = new(stream2, new JsonWriterOptions()
|
||||
{
|
||||
Indented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
});
|
||||
|
||||
writer.WriteStartObject();
|
||||
bool isWritten = false;
|
||||
|
||||
JsonDocument optionsElement = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value,
|
||||
serializerOptions ?? defaultSerializerOptions()));
|
||||
|
||||
foreach (JsonProperty element in jsonDocument.RootElement.EnumerateObject())
|
||||
{
|
||||
if (element.Name != section)
|
||||
{
|
||||
element.WriteTo(writer);
|
||||
continue;
|
||||
}
|
||||
writer.WritePropertyName(element.Name);
|
||||
optionsElement.WriteTo(writer);
|
||||
isWritten = true;
|
||||
}
|
||||
|
||||
if (!isWritten)
|
||||
{
|
||||
writer.WritePropertyName(section);
|
||||
optionsElement.WriteTo(writer);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
writer.Flush();
|
||||
stream2.SetLength(stream2.Position);
|
||||
cache.Set(section, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(out TConfiguration? value)
|
||||
{
|
||||
lock (lockingObject)
|
||||
if (cache.TryGet(section, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using (ConfigurationLock.EnterRead())
|
||||
{
|
||||
IFileInfo fileInfo = configurationFile.FileInfo;
|
||||
|
||||
if (File.Exists(fileInfo.PhysicalPath))
|
||||
if (!File.Exists(fileInfo.PhysicalPath))
|
||||
{
|
||||
static Stream OpenRead(IFileInfo fileInfo)
|
||||
{
|
||||
return fileInfo.PhysicalPath is not null
|
||||
? new FileStream(fileInfo.PhysicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)
|
||||
: fileInfo.CreateReadStream();
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
using Stream stream = OpenRead(fileInfo);
|
||||
using StreamReader? reader = new(stream);
|
||||
using Stream stream = new FileStream(fileInfo.PhysicalPath!, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
using StreamReader reader = new(stream);
|
||||
string content = reader.ReadToEnd();
|
||||
JsonNode? rootNode = JsonNode.Parse(content);
|
||||
|
||||
string? content = reader.ReadToEnd();
|
||||
string[] segments = section.Split(':');
|
||||
JsonNode? currentNode = rootNode;
|
||||
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(content);
|
||||
if (jsonDocument.RootElement.TryGetProperty(section, out JsonElement sectionValue))
|
||||
int lastIndex = segments.Length - 1;
|
||||
for (int i = 0; i < lastIndex; i++)
|
||||
{
|
||||
value = JsonSerializer.Deserialize<TConfiguration>(sectionValue.ToString(), serializerOptions ?? defaultSerializerOptions());
|
||||
if (currentNode is null)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
currentNode = currentNode[segments[i]];
|
||||
}
|
||||
|
||||
if (currentNode != null && currentNode[segments[lastIndex]] is JsonNode sectionNode)
|
||||
{
|
||||
value = JsonSerializer.Deserialize<TConfiguration>(sectionNode, serializerOptions ?? defaultSerializerOptions());
|
||||
cache.Set(section, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyConfigurationUpdates(ref JsonNode? rootNode, JsonNode? valueNode, string section)
|
||||
{
|
||||
string[] segments = section.Split(':');
|
||||
JsonNode? currentNode = rootNode;
|
||||
int lastIndex = segments.Length - 1;
|
||||
|
||||
for (int i = 0; i < lastIndex; i++)
|
||||
{
|
||||
if (currentNode is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string currentKey = segments[i];
|
||||
if (currentNode[currentKey] is null)
|
||||
{
|
||||
currentNode[currentKey] = new JsonObject();
|
||||
}
|
||||
|
||||
currentNode = currentNode[currentKey];
|
||||
}
|
||||
|
||||
if (currentNode is not null)
|
||||
{
|
||||
string lastKey = segments[lastIndex];
|
||||
if (valueNode is JsonArray)
|
||||
{
|
||||
currentNode[lastKey] = valueNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentNode[lastKey] = MergeNodes(currentNode[lastKey], valueNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode? MergeNodes(JsonNode? existingNode, JsonNode? newNode)
|
||||
{
|
||||
if (existingNode is JsonObject existingObject && newNode is JsonObject newObject)
|
||||
{
|
||||
foreach (KeyValuePair<string, JsonNode?> property in newObject)
|
||||
{
|
||||
existingObject[property.Key] = MergeNodes(existingObject[property.Key], CloneNode(property.Value));
|
||||
}
|
||||
return existingObject;
|
||||
}
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
private JsonNode? CloneNode(JsonNode? node)
|
||||
{
|
||||
if (node is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string serialized = JsonSerializer.Serialize(node, serializerOptions ?? defaultSerializerOptions());
|
||||
return JsonNode.Parse(serialized);
|
||||
}
|
||||
|
||||
|
||||
private void EnsureFileExists(string? filePath)
|
||||
{
|
||||
if (filePath == null || File.Exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string? directoryPath = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(filePath, "{}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public partial class ConfigurationValueViewModel<TConfiguration, TValue>(IServiceProvider provider,
|
||||
IServiceFactory factory,
|
||||
IMediator mediator,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer,
|
||||
TConfiguration configuration,
|
||||
IWritableConfiguration<TConfiguration> writer,
|
||||
Func<TConfiguration, TValue?> read,
|
||||
Action<TValue?, TConfiguration> write) :
|
||||
Observable<TValue>(provider, factory, mediator, publisher, subscriber, disposer),
|
||||
INotificationHandler<ChangedEventArgs<TConfiguration>>
|
||||
where TConfiguration : class
|
||||
{
|
||||
public async Task Handle(ChangedEventArgs<TConfiguration> args)
|
||||
{
|
||||
if (args.Sender is TConfiguration configuration)
|
||||
{
|
||||
// await Task.Run(() => Value = read(configuration));
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task OnActivated()
|
||||
{
|
||||
await Task.Run(() => Value = read(configuration));
|
||||
await base.OnActivated();
|
||||
}
|
||||
|
||||
protected override async void OnChanged(TValue? value)
|
||||
{
|
||||
if (IsActivated)
|
||||
{
|
||||
await Task.Run(() => writer.Write(args => write(value, args)));
|
||||
}
|
||||
|
||||
base.OnChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem> :
|
||||
ObservableCollection<TValue, TItem>,
|
||||
INotificationHandler<ChangedEventArgs<TConfiguration>>
|
||||
where TConfiguration : class
|
||||
where TItem : notnull,
|
||||
IDisposable
|
||||
{
|
||||
private readonly TConfiguration configuration;
|
||||
private readonly Func<TConfiguration, TValue?> read;
|
||||
private readonly Action<TValue?, TConfiguration> write;
|
||||
private readonly IWritableConfiguration<TConfiguration> writer;
|
||||
public ConfigurationValueViewModel(IServiceProvider provider,
|
||||
IServiceFactory factory,
|
||||
IMediator mediator,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer,
|
||||
TConfiguration configuration,
|
||||
IWritableConfiguration<TConfiguration> writer,
|
||||
Func<TConfiguration, TValue?> read,
|
||||
Action<TValue?, TConfiguration> write,
|
||||
TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, value)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.writer = writer;
|
||||
this.read = read;
|
||||
this.write = write;
|
||||
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public ConfigurationValueViewModel(IServiceProvider provider,
|
||||
IServiceFactory factory,
|
||||
IMediator mediator,
|
||||
IPublisher publisher,
|
||||
ISubscriber subscriber,
|
||||
IDisposer disposer,
|
||||
IEnumerable<TItem> items,
|
||||
TConfiguration configuration,
|
||||
IWritableConfiguration<TConfiguration> writer,
|
||||
Func<TConfiguration, TValue?> read,
|
||||
Action<TValue?, TConfiguration> write,
|
||||
TValue? value = default) : base(provider, factory, mediator, publisher, subscriber, disposer, items, value)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.writer = writer;
|
||||
this.read = read;
|
||||
this.write = write;
|
||||
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public async Task Handle(ChangedEventArgs<TConfiguration> args)
|
||||
{
|
||||
if (args.Sender is TConfiguration configuration)
|
||||
{
|
||||
// await Task.Run(() => Value = read(configuration));
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task OnActivated()
|
||||
{
|
||||
await Task.Run(() => Value = read(configuration));
|
||||
await base.OnActivated();
|
||||
}
|
||||
|
||||
protected override async void OnChanged(TValue? value)
|
||||
{
|
||||
if (IsActivated)
|
||||
{
|
||||
await Task.Run(() => writer.Write(args => write(value, args)));
|
||||
}
|
||||
|
||||
base.OnChanged(value);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ConfigurationWriter<TConfiguration>(IConfigurationSource<TConfiguration> source) :
|
||||
public class ConfigurationWriter<TConfiguration>(IConfigurationSource<TConfiguration> source,
|
||||
IConfigurationFactory<TConfiguration> factory) :
|
||||
IConfigurationWriter<TConfiguration>
|
||||
where TConfiguration :
|
||||
class
|
||||
{
|
||||
public void Write(Action<TConfiguration> updateDelegate)
|
||||
{
|
||||
if (source.TryGet(out TConfiguration? value))
|
||||
if (!source.TryGet(out TConfiguration? value))
|
||||
{
|
||||
value = (TConfiguration)factory.Create();
|
||||
}
|
||||
|
||||
if (value is not null)
|
||||
{
|
||||
updateDelegate?.Invoke(value);
|
||||
Write(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(object value) => source.Set(value);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Confirm
|
||||
{
|
||||
public static ConfirmEventArgs<TValue> As<TValue>(TValue value) =>
|
||||
new(value);
|
||||
|
||||
public static ConfirmEventArgs<TValue> As<TValue>() where TValue : new() =>
|
||||
new(new TValue());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record ConfirmEventArgs<TValue>(TValue Value);
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ContentFactory(IServiceProvider provider,
|
||||
IServiceFactory factory) :
|
||||
IContentFactory
|
||||
{
|
||||
public object? Create(IContentTemplateDescriptor descriptor,
|
||||
object[] parameters)
|
||||
{
|
||||
object? content = parameters is { Length: > 0 }
|
||||
? factory.Create(descriptor.ContentType, args =>
|
||||
{
|
||||
if (args is IInitialization initialization)
|
||||
{
|
||||
initialization.Initialize();
|
||||
}
|
||||
}, parameters)
|
||||
: provider.GetRequiredKeyedService(descriptor.ContentType, args =>
|
||||
{
|
||||
if (args is IInitialization initialization)
|
||||
{
|
||||
initialization.Initialize();
|
||||
}
|
||||
}, descriptor.Key);
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class ContentTemplateDescriptorProvider(IEnumerable<IContentTemplateDescriptor> contentTemplates) :
|
||||
IContentTemplateDescriptorProvider
|
||||
{
|
||||
public IContentTemplateDescriptor? Get(object key)
|
||||
{
|
||||
if (contentTemplates.FirstOrDefault(x => x.Key.Equals(key) || x.Key.Equals($"{key}".Replace("ViewModel", "")))
|
||||
is IContentTemplateDescriptor viewModelTemplate)
|
||||
{
|
||||
return viewModelTemplate;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Count
|
||||
{
|
||||
public static CountEventArgs<TSender> As<TSender>(TSender sender) =>
|
||||
new(sender);
|
||||
|
||||
public static CountEventArgs<TSender> As<TSender>() where TSender : new() =>
|
||||
new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record CountEventArgs<TSender>(TSender Sender);
|
||||
@@ -1,5 +1,10 @@
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Create<TValue>(TValue Value) :
|
||||
INotification;
|
||||
public record Create
|
||||
{
|
||||
public static CreateEventArgs<TSender> As<TSender>(TSender sender, params object[] parameters) =>
|
||||
new(sender, parameters);
|
||||
|
||||
public static CreateEventArgs<TSender> As<TSender>(params object[] parameters) where TSender : new() =>
|
||||
new(new TSender(), parameters);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record CreateEventArgs<TSender>(TSender? Sender = default,
|
||||
params object[] Parameters);
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Created
|
||||
{
|
||||
public static CreatedEventArgs<TSender> As<TSender>(TSender sender) =>
|
||||
new(sender);
|
||||
|
||||
public static CreatedEventArgs<TSender> As<TSender>() where TSender : new() =>
|
||||
new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record CreatedEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Deactivated
|
||||
{
|
||||
public static DeactivatedEventArgs<TSender> As<TSender>(TSender sender) => new(sender);
|
||||
|
||||
public static DeactivatedEventArgs<TSender> As<TSender>() where TSender : new() => new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record DeactivatedEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class DecoratorService<T> :
|
||||
IDecoratorService<T>
|
||||
{
|
||||
public T? Value { get; private set; }
|
||||
|
||||
public void Set(T? value) => Value = value;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Toolkit.Foundation;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class DefaultBuilder :
|
||||
HostBuilder
|
||||
{
|
||||
public static IHostBuilder Create()
|
||||
{
|
||||
return new HostBuilder()
|
||||
.UseContentRoot("Local", true)
|
||||
.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
config.AddJsonFile("Settings.json", true, true);
|
||||
})
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddScoped<IServiceFactory>(provider =>
|
||||
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type, parameters!)));
|
||||
|
||||
services.AddSingleton<IComponentHostCollection,
|
||||
ComponentHostCollection>();
|
||||
|
||||
services.AddScoped<SubscriptionCollection>();
|
||||
services.AddScoped<ISubscriptionManager, SubscriptionManager>();
|
||||
services.AddTransient<ISubscriber, Subscriber>();
|
||||
services.AddTransient<IPublisher, Publisher>();
|
||||
|
||||
services.AddScoped<IMediator, Mediator>();
|
||||
|
||||
services.AddScoped<IProxyService<IPublisher>>(provider =>
|
||||
new ProxyService<IPublisher>(provider.GetRequiredService<IPublisher>()));
|
||||
|
||||
services.AddScoped<IProxyService<INavigationContextProvider>>(provider =>
|
||||
new ProxyService<INavigationContextProvider>(provider.GetRequiredService<INavigationContextProvider>()));
|
||||
|
||||
services.AddScoped<IProxyService<IComponentHostCollection>>(provider =>
|
||||
new ProxyService<IComponentHostCollection>(provider.GetRequiredService<IComponentHostCollection>()));
|
||||
|
||||
services.AddScoped<IDisposer, Disposer>();
|
||||
|
||||
services.AddTransient<IContentTemplateDescriptorProvider, ContentTemplateDescriptorProvider>();
|
||||
|
||||
services.AddTransient<INavigationProvider, NavigationProvider>();
|
||||
|
||||
services.AddScoped<INavigationContextCollection, NavigationContextCollection>();
|
||||
services.AddTransient<INavigationContextProvider, NavigationContextProvider>();
|
||||
|
||||
services.AddTransient<INavigationScope, NavigationScope>();
|
||||
|
||||
services.AddScoped<IComponentScopeCollection, ComponentScopeCollection>(provider => new ComponentScopeCollection
|
||||
{
|
||||
{ "Default", provider.GetRequiredService<IServiceProvider>() }
|
||||
});
|
||||
|
||||
services.AddTransient<IComponentScopeProvider, ComponentScopeProvider>();
|
||||
|
||||
services.AddHandler<NavigateHandler>();
|
||||
services.AddHandler<NavigateBackHandler>();
|
||||
|
||||
services.AddInitializer<ComponentInitializer>();
|
||||
services.AddHostedService<AppService>();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class DefaultHostBuilder :
|
||||
HostBuilder
|
||||
{
|
||||
public static IHostBuilder Create()
|
||||
{
|
||||
return new HostBuilder()
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddScoped<IServiceFactory>(provider =>
|
||||
new ServiceFactory((type, parameters) => ActivatorUtilities.CreateInstance(provider, type,
|
||||
parameters?.Where(x => x is not null).ToArray()!)));
|
||||
|
||||
services.AddSingleton<IComponentHostCollection,
|
||||
ComponentHostCollection>();
|
||||
|
||||
services.AddSingleton<IDisposer, Disposer>();
|
||||
|
||||
services.AddScoped<SubscriptionCollection>();
|
||||
|
||||
services.AddTransient<IHandlerProvider, HandlerProvider>();
|
||||
services.AddScoped<ISubscriber, Subscriber>();
|
||||
services.AddTransient<IPublisher, Publisher>();
|
||||
services.AddTransient<IMediator, Mediator>();
|
||||
|
||||
services.AddTransient<IValidation, Validation>();
|
||||
services.AddTransient<IValidatorCollection, ValidatorCollection>();
|
||||
|
||||
services.AddScoped<IProxyService<IPublisher>>(provider =>
|
||||
new ProxyService<IPublisher>(provider.GetRequiredService<IPublisher>()));
|
||||
|
||||
services.AddScoped<IProxyService<INavigationRegionProvider>>(provider =>
|
||||
new ProxyService<INavigationRegionProvider>(provider.GetRequiredService<INavigationRegionProvider>()));
|
||||
|
||||
services.AddScoped<IProxyService<IComponentHostCollection>>(provider =>
|
||||
new ProxyService<IComponentHostCollection>(provider.GetRequiredService<IComponentHostCollection>()));
|
||||
|
||||
services.AddTransient<IContentFactory, ContentFactory>();
|
||||
|
||||
services.AddScoped<INavigationRegionCollection, NavigationRegionCollection>();
|
||||
services.AddTransient<INavigationRegionProvider, NavigationRegionProvider>();
|
||||
|
||||
services.AddScoped<INavigation, Navigation>();
|
||||
|
||||
services.AddSingleton(new NamedComponent("Root"));
|
||||
services.AddScoped<IComponentScopeCollection, ComponentScopeCollection>(provider =>
|
||||
[
|
||||
new ComponentScopeDescriptor("Root", provider.GetRequiredService<IServiceProvider>())
|
||||
]);
|
||||
|
||||
services.AddTransient<IComponentFactory, ComponentFactory>();
|
||||
services.AddTransient<IComponentScopeProvider, ComponentScopeProvider>();
|
||||
|
||||
services.AddHandler<NavigateHandler>();
|
||||
services.AddHandler<NavigateBackHandler>();
|
||||
|
||||
services.AddInitialization<ComponentInitializer>();
|
||||
services.AddHostedService<AppService>();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Delete
|
||||
{
|
||||
public static DeleteEventArgs<TSender> As<TSender>(TSender sender) =>
|
||||
new(sender);
|
||||
|
||||
public static DeleteEventArgs<TSender> As<TSender>() where TSender : new() =>
|
||||
new(new TSender());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record DeleteEventArgs<TSender>(TSender? Sender = default);
|
||||
@@ -56,20 +56,26 @@ public class DictionaryStringObjectJsonConverter :
|
||||
return date;
|
||||
}
|
||||
return reader.GetString();
|
||||
|
||||
case JsonTokenType.False:
|
||||
return false;
|
||||
|
||||
case JsonTokenType.True:
|
||||
return true;
|
||||
|
||||
case JsonTokenType.Null:
|
||||
return null;
|
||||
|
||||
case JsonTokenType.Number:
|
||||
if (reader.TryGetInt64(out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return reader.GetDecimal();
|
||||
|
||||
case JsonTokenType.StartObject:
|
||||
return Read(ref reader, null!, options);
|
||||
|
||||
case JsonTokenType.StartArray:
|
||||
List<object?> list = [];
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
|
||||
@@ -77,6 +83,7 @@ public class DictionaryStringObjectJsonConverter :
|
||||
list.Add(ExtractValue(ref reader, options));
|
||||
}
|
||||
return list;
|
||||
|
||||
default:
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class DirectoryObserver
|
||||
{
|
||||
public static async Task<string[]> EnumerateFiles(string path,
|
||||
string[] patterns,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
string[] files = [];
|
||||
List<Regex> regexPatterns = patterns.Select(pattern => new Regex(pattern, RegexOptions.IgnoreCase)).ToList();
|
||||
|
||||
bool IsBatchComplete()
|
||||
{
|
||||
files = Directory.EnumerateFiles(path, "*.*", SearchOption.TopDirectoryOnly)
|
||||
.Where(file => regexPatterns.Any(regex => regex.IsMatch(Path.GetFileName(file))))
|
||||
.ToArray();
|
||||
|
||||
if (files.Length != count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ConcurrentBag<bool> fileAccessResults = [];
|
||||
|
||||
Parallel.ForEach(files, file =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using FileStream fileStream = new(file, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||
fileAccessResults.Add(true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
fileAccessResults.Add(false);
|
||||
}
|
||||
});
|
||||
|
||||
return fileAccessResults.All(result => result);
|
||||
}
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
while (!IsBatchComplete())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await Task.Delay(5000, cancellationToken);
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Reactive.Disposables;
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Enumerate<TValue>(object? Key = null) : INotification;
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record Error(string Code, string Message)
|
||||
{
|
||||
public static readonly Error None = new(string.Empty, string.Empty);
|
||||
|
||||
public static readonly Error Null = new("Error.NullValue", "The specified result value is null.");
|
||||
|
||||
public static readonly Error ConditionNotMet = new("Error.ConditionNotMet", "The specified condition was not met.");
|
||||
|
||||
public static readonly Error Duplicated = new("Error.Duplicated", "The specified item already exists.");
|
||||
|
||||
public static readonly Error Failure = new("Error.Failure", "The operation has failed.");
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record FileDescriptor(string Name, string Path, long Size) :
|
||||
IFileDescriptor;
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record FileFilter(string Name, List<string> Extensions, bool AllowMultiple = false);
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public record FolderFilter(bool AllowMultiple = false);
|
||||
@@ -1,5 +1,4 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public delegate Task<TResponse> HandlerDelegate<TMessage, TResponse>(TMessage message,
|
||||
CancellationToken cancellationToken)
|
||||
where TMessage : IMessage;
|
||||
public delegate Task<TResponse> HandlerDelegate<TRequest, TResponse>(TRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class HandlerProvider(SubscriptionCollection subscriptions) :
|
||||
IHandlerProvider
|
||||
{
|
||||
public IEnumerable<object?> Get(object key)
|
||||
{
|
||||
var d = subscriptions;
|
||||
if (subscriptions.TryGetValue(key, out List<WeakReference>? subscribers))
|
||||
{
|
||||
foreach (WeakReference weakRef in subscribers.ToArray())
|
||||
{
|
||||
object? target = weakRef.Target;
|
||||
if (target != null)
|
||||
{
|
||||
yield return target;
|
||||
}
|
||||
else
|
||||
{
|
||||
subscribers.Remove(weakRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public class HandlerWrapper<TMessage, TReply>(IHandler<TMessage, TReply> handler,
|
||||
IEnumerable<IPipelineBehavior<TMessage, TReply>> pipelineBehaviours)
|
||||
where TMessage : class, IRequest<TReply>
|
||||
public class HandlerWrapper<TRequest, TResponse>(IHandler<TRequest, TResponse> handler,
|
||||
IEnumerable<IPipelineBehaviour<TRequest, TResponse>> pipelineBehaviours)
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly IEnumerable<IPipelineBehavior<TMessage, TReply>> pipelineBehaviours =
|
||||
private readonly IEnumerable<IPipelineBehaviour<TRequest, TResponse>> pipelineBehaviours =
|
||||
pipelineBehaviours.Reverse();
|
||||
|
||||
public async Task<TReply> Handle(TMessage message,
|
||||
public async Task<TResponse> Handle(TRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
HandlerDelegate<TMessage, TReply> currentHandler = handler.Handle;
|
||||
foreach (IPipelineBehavior<TMessage, TReply> behavior in pipelineBehaviours)
|
||||
HandlerDelegate<TRequest, TResponse> currentHandler = handler.Handle;
|
||||
foreach (IPipelineBehaviour<TRequest, TResponse> behaviour in pipelineBehaviours)
|
||||
{
|
||||
HandlerDelegate<TMessage, TReply> previousHandler = currentHandler;
|
||||
HandlerDelegate<TRequest, TResponse> previousHandler = currentHandler;
|
||||
currentHandler = async (args, token) =>
|
||||
{
|
||||
return await behavior.Handle(args, previousHandler, token);
|
||||
return await behaviour.Handle(args, previousHandler, token);
|
||||
};
|
||||
}
|
||||
|
||||
return await currentHandler(message, cancellationToken);
|
||||
return await currentHandler(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,5 @@
|
||||
|
||||
public interface IActivated
|
||||
{
|
||||
Task Activated();
|
||||
}
|
||||
|
||||
public interface IActivated<TResult>
|
||||
{
|
||||
Task Activated(TResult result);
|
||||
Task OnActivated();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public interface IActivation;
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public interface IActivityIndicator
|
||||
{
|
||||
bool IsActive { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public interface IInitializer
|
||||
public interface IAsyncInitialization
|
||||
{
|
||||
Task Initialize();
|
||||
}
|
||||
@@ -7,7 +7,13 @@ public interface ICache<TValue> :
|
||||
|
||||
void Clear();
|
||||
|
||||
bool Contains(TValue key);
|
||||
|
||||
int IndexOf(TValue value);
|
||||
|
||||
bool Remove(TValue value);
|
||||
|
||||
bool TryGetValue(TValue key, out TValue? item);
|
||||
}
|
||||
|
||||
public interface ICache<TKey, TValue> :
|
||||
@@ -17,12 +23,13 @@ public interface ICache<TKey, TValue> :
|
||||
where TValue :
|
||||
notnull
|
||||
{
|
||||
void Add(TKey key,
|
||||
TValue value);
|
||||
void Add(TKey key, TValue value);
|
||||
|
||||
void Clear();
|
||||
|
||||
bool ContainsKey(TKey key);
|
||||
bool Contains(TKey key);
|
||||
|
||||
int IndexOf(TKey key);
|
||||
|
||||
bool Remove(TKey key);
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Toolkit.Foundation;
|
||||
|
||||
public interface IClipboardWriter
|
||||
{
|
||||
Task Write<TContent>(TContent content);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user