Merge branch 'feature/updating' into feature/gh-actions

This commit is contained in:
Robert committed 2023-02-12 16:48:30 +01:00
commit f97d328f32
200 files changed
+3843 -1713

No files matched your search

@@ -8,6 +8,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using FluentAvalonia.Core;
using Humanizer;
using Material.Icons;
@@ -47,7 +48,8 @@ public class HotkeyBox : UserControl
Hotkey.Key = (KeyboardKey?) e.Key;
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers;
UpdateDisplayTextBox();
HotkeyChanged?.Invoke(this, EventArgs.Empty);
e.Handled = true;
}
@@ -134,4 +136,13 @@ public class HotkeyBox : UserControl
}
#endregion
#region Events
/// <summary>
/// Occurs when the hotkey changes.
/// </summary>
public event TypedEventHandler<HotkeyBox, EventArgs>? HotkeyChanged;
#endregion
}
@@ -0,0 +1,21 @@
using System.Reflection;
using Artemis.UI.Shared.Services;
using DryIoc;
namespace Artemis.UI.Shared.DryIoc;
/// <summary>
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
/// </summary>
public static class UIContainerExtensions
{
/// <summary>
/// Registers shared UI services into the container.
/// </summary>
/// <param name="container">The builder building the current container</param>
public static void RegisterSharedUI(this IContainer container)
{
Assembly artemisShared = typeof(IArtemisSharedUIService).GetAssembly();
container.RegisterMany(new[] { artemisShared }, type => type.IsAssignableTo<IArtemisSharedUIService>(), Reuse.Singleton);
}
}
@@ -1,30 +0,0 @@
using System;
using Artemis.UI.Shared.Services;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
namespace Artemis.UI.Shared.Ninject;
/// <summary>
/// The main <see cref="NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
/// </summary>
public class SharedUIModule : NinjectModule
{
/// <inheritdoc />
public override void Load()
{
if (Kernel == null)
throw new ArgumentNullException("Kernel shouldn't be null here.");
// Bind all shared UI services as singletons
Kernel.Bind(x =>
{
x.FromAssemblyContaining<IArtemisSharedUIService>()
.IncludingNonPublicTypes()
.SelectAllClasses()
.InheritedFrom<IArtemisSharedUIService>()
.BindAllInterfaces()
.Configure(c => c.InSingletonScope());
});
}
}
@@ -1,31 +0,0 @@
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for custom cursors.
/// </summary>
public interface IUpdateProvider
{
/// <summary>
/// Asynchronously checks whether an update is available.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <returns>A task returning <see langword="true" /> if an update is available; otherwise <see langword="false" />.</returns>
Task<bool> CheckForUpdate(string channel);
/// <summary>
/// Applies any available updates.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <param name="silent">Whether or not to update silently.</param>
Task ApplyUpdate(string channel, bool silent);
/// <summary>
/// Offer to install the update to the user.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <param name="windowOpen">A boolean indicating whether the main window is open.</param>
/// <returns>A task returning <see langword="true" /> if the user chose to update; otherwise <see langword="false" />.</returns>
Task OfferUpdate(string channel, bool windowOpen);
}
@@ -3,9 +3,8 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using DryIoc;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;
using ReactiveUI;
namespace Artemis.UI.Shared.Services.Builders;
@@ -16,13 +15,13 @@ namespace Artemis.UI.Shared.Services.Builders;
public class ContentDialogBuilder
{
private readonly ContentDialog _contentDialog;
private readonly IKernel _kernel;
private readonly IContainer _container;
private readonly Window _parent;
private ContentDialogViewModelBase? _viewModel;
internal ContentDialogBuilder(IKernel kernel, Window parent)
internal ContentDialogBuilder(IContainer container, Window parent)
{
_kernel = kernel;
_container = container;
_parent = parent;
_contentDialog = new ContentDialog
{
@@ -129,12 +128,11 @@ public class ContentDialogBuilder
/// </summary>
/// <typeparam name="T">The type of the view model to host.</typeparam>
/// <param name="viewModel">The resulting view model.</param>
/// <param name="parameters">Optional parameters to pass to the constructor of the view model, case and order sensitive.</param>
/// <param name="parameters">Optional parameters to pass to the constructor of the view model.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params object[] parameters) where T : ContentDialogViewModelBase
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
viewModel = _kernel.Get<T>(paramsArray);
viewModel = _container.Resolve<T>(parameters);
viewModel.ContentDialog = _contentDialog;
_contentDialog.Content = viewModel;
@@ -8,22 +8,21 @@ using Artemis.Core.Services;
using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
using Ninject;
using Ninject.Parameters;
using DryIoc;
namespace Artemis.UI.Shared.Services;
internal class DataModelUIService : IDataModelUIService
{
private readonly IDataModelService _dataModelService;
private readonly IKernel _kernel;
private readonly IContainer _container;
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
public DataModelUIService(IDataModelService dataModelService, IKernel kernel)
public DataModelUIService(IDataModelService dataModelService, IContainer container)
{
_dataModelService = dataModelService;
_kernel = kernel;
_container = container;
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
@@ -36,18 +35,8 @@ internal class DataModelUIService : IDataModelUIService
// This assumes the type can be converted, that has been checked when the VM was created
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
IParameter[] parameters =
{
new ConstructorArgument("targetDescription", description),
new ConstructorArgument("initialValue", initialValue)
};
// If this ever happens something is likely wrong with the plugin unload detection
if (registration.Plugin.Kernel == null)
throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin");
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters);
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Resolve(registration.ViewModelType, description, initialValue);
viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
return viewModel;
}
@@ -133,7 +122,7 @@ internal class DataModelUIService : IDataModelUIService
return existing;
}
_kernel.Bind(viewModelType).ToSelf();
_container.Register(viewModelType, ifAlreadyRegistered: IfAlreadyRegistered.Replace);
// Create the registration
DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType)
@@ -162,7 +151,7 @@ internal class DataModelUIService : IDataModelUIService
return existing;
}
_kernel.Bind(viewModelType).ToSelf();
_container.Register(viewModelType);
DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType);
_registeredDataModelDisplays.Add(registration);
return registration;
@@ -178,7 +167,8 @@ internal class DataModelUIService : IDataModelUIService
registration.Unsubscribe();
_registeredDataModelEditors.Remove(registration);
_kernel.Unbind(registration.ViewModelType);
_container.Unregister(registration.ViewModelType);
_container.ClearCache(registration.ViewModelType);
}
}
}
@@ -192,7 +182,8 @@ internal class DataModelUIService : IDataModelUIService
registration.Unsubscribe();
_registeredDataModelDisplays.Remove(registration);
_kernel.Unbind(registration.ViewModelType);
_container.Unregister(registration.ViewModelType);
_container.ClearCache(registration.ViewModelType);
}
}
}
@@ -205,21 +196,11 @@ internal class DataModelUIService : IDataModelUIService
DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
if (match != null)
{
// If this ever happens something is likely wrong with the plugin unload detection
if (match.Plugin.Kernel == null)
throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin");
result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType);
}
result = (DataModelDisplayViewModel) match.Plugin.Resolve(match.ViewModelType);
else if (!fallBackToDefault)
{
result = null;
}
else
{
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
}
result = _container.Resolve<DefaultDataModelDisplayViewModel>();
if (result != null)
result.PropertyDescription = description;
@@ -16,7 +16,7 @@ public interface IWindowService : IArtemisSharedUIService
/// </summary>
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
/// <returns>The created view model</returns>
TViewModel ShowWindow<TViewModel>(params (string name, object value)[] parameters);
TViewModel ShowWindow<TViewModel>(params object[] parameters);
/// <summary>
/// Given a ViewModel, show its corresponding View as a window
@@ -37,7 +37,7 @@ public interface IWindowService : IArtemisSharedUIService
/// </summary>
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
/// <returns>The created view model</returns>
Task<TViewModel> ShowDialogAsync<TViewModel>(params (string name, object value)[] parameters);
Task<TViewModel> ShowDialogAsync<TViewModel>(params object[] parameters);
/// <summary>
/// Given a ViewModel, show its corresponding View as a dialog
@@ -60,7 +60,7 @@ public interface IWindowService : IArtemisSharedUIService
/// <typeparam name="TViewModel">The view model type</typeparam>
/// <typeparam name="TResult">The return type</typeparam>
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params object[] parameters) where TViewModel : DialogViewModelBase<TResult>;
/// <summary>
/// Shows a content dialog asking the user to confirm an action
@@ -1,5 +1,4 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
@@ -3,19 +3,18 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Ninject;
using Ninject.Parameters;
using DryIoc;
namespace Artemis.UI.Shared.Services.PropertyInput;
internal class PropertyInputService : IPropertyInputService
{
private readonly IKernel _kernel;
private readonly IContainer _container;
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
public PropertyInputService(IKernel kernel)
public PropertyInputService(IContainer container)
{
_kernel = kernel;
_container = container;
_registeredPropertyEditors = new List<PropertyInputRegistration>();
RegisteredPropertyEditors = new ReadOnlyCollection<PropertyInputRegistration>(_registeredPropertyEditors);
}
@@ -55,7 +54,7 @@ internal class PropertyInputService : IPropertyInputService
return existing;
}
_kernel.Bind(viewModelType).ToSelf();
_container.Register(viewModelType);
PropertyInputRegistration registration = new(this, plugin, supportedType, viewModelType);
_registeredPropertyEditors.Add(registration);
return registration;
@@ -71,7 +70,8 @@ internal class PropertyInputService : IPropertyInputService
registration.Unsubscribe();
_registeredPropertyEditors.Remove(registration);
_kernel.Unbind(registration.ViewModelType);
_container.Unregister(registration.ViewModelType);
_container.ClearCache(registration.ViewModelType);
}
}
}
@@ -110,10 +110,9 @@ internal class PropertyInputService : IPropertyInputService
if (viewModelType == null)
return null;
ConstructorArgument parameter = new("layerProperty", layerProperty);
// ReSharper disable once InconsistentlySynchronizedField
// When you've just spent the last 2 hours trying to figure out a deadlock and reach this line, I'm so, so sorry. I thought this would be fine.
IKernel kernel = registration?.Plugin.Kernel ?? _kernel;
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
IContainer container = registration?.Plugin.Container ?? _container;
return (PropertyInputViewModel<T>) container.Resolve(viewModelType, args: new object[] { layerProperty });
}
}
@@ -6,34 +6,33 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using DryIoc;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;
namespace Artemis.UI.Shared.Services;
internal class WindowService : IWindowService
{
private readonly IKernel _kernel;
private readonly IContainer _container;
private bool _exceptionDialogOpen;
public WindowService(IKernel kernel)
public WindowService(IContainer container)
{
_kernel = kernel;
_container = container;
}
public T ShowWindow<T>(params (string name, object value)[] parameters)
public T ShowWindow<T>(params object[] parameters)
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
T viewModel = _kernel.Get<T>(paramsArray)!;
T viewModel = _container.Resolve<T>(parameters);
if (viewModel == null)
throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(T).Name}, could not create instance.");
ShowWindow(viewModel);
return viewModel;
}
public Window ShowWindow(object viewModel)
{
Window? parent = GetCurrentWindow();
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name);
@@ -45,18 +44,17 @@ internal class WindowService : IWindowService
Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel;
if (parent != null)
window.Show(parent);
else
window.Show();
window.Show();
return window;
}
public async Task<T> ShowDialogAsync<T>(params (string name, object value)[] parameters)
public async Task<T> ShowDialogAsync<T>(params object[] parameters)
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
T viewModel = _kernel.Get<T>(paramsArray)!;
T viewModel = _container.Resolve<T>(parameters);
if (viewModel == null)
throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(T).Name}, could not create instance.");
await ShowDialogAsync(viewModel);
return viewModel;
}
@@ -79,10 +77,12 @@ internal class WindowService : IWindowService
await window.ShowDialog(parent);
}
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params object[] parameters) where TViewModel : DialogViewModelBase<TResult>
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
TViewModel viewModel = _kernel.Get<TViewModel>(paramsArray)!;
TViewModel viewModel = _container.Resolve<TViewModel>(parameters);
if (viewModel == null)
throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(TViewModel).Name}, could not create instance.");
return await ShowDialogAsync(viewModel);
}
@@ -129,7 +129,7 @@ internal class WindowService : IWindowService
{
try
{
await ShowDialogAsync(new ExceptionDialogViewModel(title, exception, _kernel.Get<INotificationService>()));
await ShowDialogAsync(new ExceptionDialogViewModel(title, exception, _container.Resolve<INotificationService>()));
}
finally
{
@@ -143,7 +143,7 @@ internal class WindowService : IWindowService
Window? currentWindow = GetCurrentWindow();
if (currentWindow == null)
throw new ArtemisSharedUIException("Can't show a content dialog without any windows being shown.");
return new ContentDialogBuilder(_kernel, currentWindow);
return new ContentDialogBuilder(_container, currentWindow);
}
public OpenFolderDialogBuilder CreateOpenFolderDialog()
@@ -39,6 +39,9 @@
<dataModelPicker:DataModelPickerButton></dataModelPicker:DataModelPickerButton>
<dataModelPicker:DataModelPickerButton Classes="condensed"></dataModelPicker:DataModelPickerButton>
<controls1:HotkeyBox></controls1:HotkeyBox>
<controls1:HotkeyBox Classes="condensed"></controls1:HotkeyBox>
</StackPanel>
</Border>
</Design.PreviewWith>
@@ -110,4 +113,10 @@
<Setter Property="MinHeight" Value="24" />
</Style>
<Style Selector="controls1|HotkeyBox.condensed > TextBox#DisplayTextBox">
<Setter Property="Padding" Value="6 0" />
<Setter Property="FontSize" Value="13" />
<Setter Property="MinHeight" Value="24" />
</Style>
</Styles>