1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Plugins - Added platform support

Settings - Fixed updating issues in plugin tab
UI - Don't initialize in design mode
UI - Move ReactiveCoreWindow to Artemis.Shared
Window service - Return window created by ShowWindow
Window service - Added CreateOpenFileDialog API
This commit is contained in:
Robert 2022-07-22 20:28:07 +02:00
parent e385165200
commit 75d85985a9
37 changed files with 600 additions and 375 deletions

View File

@ -26,6 +26,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Marks the feature to always be enabled as long as the plugin is enabled /// Marks the feature to always be enabled as long as the plugin is enabled
/// <para>Note: always <see langword="true"/> if this is the plugin's only feature</para>
/// </summary> /// </summary>
public bool AlwaysEnabled { get; set; } public bool AlwaysEnabled { get; set; }
} }

View File

@ -122,10 +122,11 @@ namespace Artemis.Core
} }
/// <summary> /// <summary>
/// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled /// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled.
/// <para>Note: always <see langword="true"/> if this is the plugin's only feature</para>
/// </summary> /// </summary>
[JsonProperty] [JsonProperty]
public bool AlwaysEnabled { get; } public bool AlwaysEnabled { get; internal set; }
/// <summary> /// <summary>
/// Gets a boolean indicating whether the feature is enabled in persistent storage /// Gets a boolean indicating whether the feature is enabled in persistent storage

View File

@ -24,7 +24,7 @@ namespace Artemis.Core
private Plugin _plugin = null!; private Plugin _plugin = null!;
private Version _version = null!; private Version _version = null!;
private bool _requiresAdmin; private bool _requiresAdmin;
private PluginPlatform? _platforms;
internal PluginInfo() internal PluginInfo()
{ {
@ -143,6 +143,16 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _requiresAdmin, value); internal set => SetAndNotify(ref _requiresAdmin, value);
} }
/// <summary>
/// Gets
/// </summary>
[JsonProperty]
public PluginPlatform? Platforms
{
get => _platforms;
internal set => _platforms = value;
}
/// <summary> /// <summary>
/// Gets the plugin this info is associated with /// Gets the plugin this info is associated with
/// </summary> /// </summary>
@ -165,6 +175,11 @@ namespace Artemis.Core
} }
} }
/// <summary>
/// Gets a boolean indicating whether this plugin is compatible with the current operating system
/// </summary>
public bool IsCompatible => Platforms.MatchesCurrentOperatingSystem();
internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}"; internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}";
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,4 +1,5 @@
using System; using System;
using Newtonsoft.Json;
namespace Artemis.Core; namespace Artemis.Core;
@ -6,15 +7,36 @@ namespace Artemis.Core;
/// Specifies OS platforms a plugin may support. /// Specifies OS platforms a plugin may support.
/// </summary> /// </summary>
[Flags] [Flags]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum PluginPlatform public enum PluginPlatform
{ {
/// <summary>The Windows platform.</summary> /// <summary>The Windows platform.</summary>
Windows = 0, Windows = 1,
/// <summary>The Linux platform.</summary> /// <summary>The Linux platform.</summary>
Linux = 1, Linux = 2,
/// <summary>The OSX platform.</summary> /// <summary>The OSX platform.</summary>
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
OSX = 2 OSX = 4
}
internal static class PluginPlatformExtensions
{
/// <summary>
/// Determines whether the provided platform matches the current operating system.
/// </summary>
internal static bool MatchesCurrentOperatingSystem(this PluginPlatform? platform)
{
if (platform == null)
return true;
if (OperatingSystem.IsWindows())
return platform.Value.HasFlag(PluginPlatform.Windows);
if (OperatingSystem.IsLinux())
return platform.Value.HasFlag(PluginPlatform.Linux);
if (OperatingSystem.IsMacOS())
return platform.Value.HasFlag(PluginPlatform.OSX);
return false;
}
} }

View File

@ -101,16 +101,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public bool AppliesToPlatform() public bool AppliesToPlatform()
{ {
if (Platform == null) return Platform.MatchesCurrentOperatingSystem();
return true;
if (OperatingSystem.IsWindows())
return Platform.Value.HasFlag(PluginPlatform.Windows);
if (OperatingSystem.IsLinux())
return Platform.Value.HasFlag(PluginPlatform.Linux);
if (OperatingSystem.IsMacOS())
return Platform.Value.HasFlag(PluginPlatform.OSX);
return false;
} }
/// <summary> /// <summary>

View File

@ -260,7 +260,7 @@ namespace Artemis.Core.Services
} }
} }
foreach (Plugin plugin in _plugins.Where(p => p.Entity.IsEnabled)) foreach (Plugin plugin in _plugins.Where(p => p.Info.IsCompatible && p.Entity.IsEnabled))
{ {
try try
{ {
@ -364,7 +364,13 @@ namespace Artemis.Core.Services
// Load the enabled state and if not found, default to true // Load the enabled state and if not found, default to true
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ?? PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName!}; new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName!};
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)))); PluginFeatureInfo feature = new(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)));
// If the plugin only has a single feature, it should always be enabled
if (featureTypes.Count == 1)
feature.AlwaysEnabled = true;
plugin.AddFeature(feature);
} }
if (!featureTypes.Any()) if (!featureTypes.Any())
@ -390,6 +396,9 @@ namespace Artemis.Core.Services
public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock) public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock)
{ {
if (!plugin.Info.IsCompatible)
throw new ArtemisPluginException(plugin, $"This plugin only supports the following operating system(s): {plugin.Info.Platforms}");
if (plugin.Assembly == null) if (plugin.Assembly == null)
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded"); throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
@ -446,7 +455,18 @@ namespace Artemis.Core.Services
// Activate features after they are all loaded // Activate features after they are all loaded
foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.EnabledInStorage || f.AlwaysEnabled))) foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.EnabledInStorage || f.AlwaysEnabled)))
{
try
{
EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock); EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
}
catch (Exception)
{
if (pluginFeature.AlwaysEnabled)
DisablePlugin(plugin, false);
throw;
}
}
if (saveState) if (saveState)
{ {

View File

@ -1,6 +1,7 @@
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Linux.Providers.Input; using Artemis.UI.Linux.Providers.Input;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
@ -26,6 +27,9 @@ namespace Artemis.UI.Linux
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
if (Design.IsDesignMode)
return;
ArtemisBootstrapper.Initialize(); ArtemisBootstrapper.Initialize();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args); _applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);

View File

@ -1,4 +1,5 @@
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.Threading;
using Ninject; using Ninject;
@ -20,6 +21,9 @@ namespace Artemis.UI.MacOS
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
if (Design.IsDesignMode)
return;
ArtemisBootstrapper.Initialize(); ArtemisBootstrapper.Initialize();
} }
} }

View File

@ -3,8 +3,8 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ApplicationIcon/> <ApplicationIcon />
<StartupObject/> <StartupObject />
<OutputPath>bin\</OutputPath> <OutputPath>bin\</OutputPath>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
@ -15,20 +15,20 @@
<ItemGroup> <ItemGroup>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15"/> <PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15"/> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15"/> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14"/> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
<PackageReference Include="DynamicData" Version="7.8.6"/> <PackageReference Include="DynamicData" Version="7.8.6" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1"/> <PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2"/> <PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="ReactiveUI" Version="17.1.50"/> <PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1"/> <PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease.32"/> <PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease.32" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1"/> <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj"/> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Controls\HotkeyBox.axaml.cs"> <Compile Update="Controls\HotkeyBox.axaml.cs">

View File

@ -4,7 +4,7 @@ using Avalonia.Controls;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// A ReactiveUI <see cref="Window" /> that implements the <see cref="IViewFor{TViewModel}" /> interface and will /// A ReactiveUI <see cref="Window" /> that implements the <see cref="IViewFor{TViewModel}" /> interface and will

View File

@ -0,0 +1,54 @@
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Artemis.UI.Shared.Services.Builders;
/// <summary>
/// Represents a builder that can create a <see cref="OpenFolderDialog" />.
/// </summary>
public class OpenFolderDialogBuilder
{
private readonly OpenFolderDialog _openFolderDialog;
private readonly Window _parent;
/// <summary>
/// Creates a new instance of the <see cref="OpenFolderDialogBuilder" /> class.
/// </summary>
/// <param name="parent">The parent window that will host the dialog.</param>
internal OpenFolderDialogBuilder(Window parent)
{
_parent = parent;
_openFolderDialog = new OpenFolderDialog();
}
/// <summary>
/// Set the title of the dialog
/// </summary>
public OpenFolderDialogBuilder WithTitle(string? title)
{
_openFolderDialog.Title = title;
return this;
}
/// <summary>
/// Set the initial directory of the dialog
/// </summary>
public OpenFolderDialogBuilder WithDirectory(string? directory)
{
_openFolderDialog.Directory = directory;
return this;
}
/// <summary>
/// Asynchronously shows the folder dialog.
/// </summary>
/// <returns>
/// A task that on completion returns an array containing the full path to the selected
/// folder, or null if the dialog was canceled.
/// </returns>
public async Task<string?> ShowAsync()
{
return await _openFolderDialog.ShowAsync(_parent);
}
}

View File

@ -22,7 +22,7 @@ namespace Artemis.UI.Shared.Services
/// Given a ViewModel, show its corresponding View as a window /// Given a ViewModel, show its corresponding View as a window
/// </summary> /// </summary>
/// <param name="viewModel">ViewModel to show the View for</param> /// <param name="viewModel">ViewModel to show the View for</param>
void ShowWindow(object viewModel); Window ShowWindow(object viewModel);
/// <summary> /// <summary>
/// Shows a dialog displaying the given exception /// Shows a dialog displaying the given exception
@ -61,6 +61,12 @@ namespace Artemis.UI.Shared.Services
/// </returns> /// </returns>
Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel"); Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel");
/// <summary>
/// Creates an open folder dialog, use the fluent API to configure it
/// </summary>
/// <returns>The builder that can be used to configure the dialog</returns>
OpenFolderDialogBuilder CreateOpenFolderDialog();
/// <summary> /// <summary>
/// Creates an open file dialog, use the fluent API to configure it /// Creates an open file dialog, use the fluent API to configure it
/// </summary> /// </summary>

View File

@ -30,7 +30,7 @@ namespace Artemis.UI.Shared.Services
return viewModel; return viewModel;
} }
public void ShowWindow(object viewModel) public Window ShowWindow(object viewModel)
{ {
Window? parent = GetCurrentWindow(); Window? parent = GetCurrentWindow();
@ -49,6 +49,8 @@ namespace Artemis.UI.Shared.Services
window.Show(parent); window.Show(parent);
else else
window.Show(); window.Show();
return window;
} }
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 (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
@ -86,7 +88,6 @@ namespace Artemis.UI.Shared.Services
Window window = (Window) Activator.CreateInstance(type)!; Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel; window.DataContext = viewModel;
viewModel.CloseRequested += (_, args) => window.Close(args.Result); viewModel.CloseRequested += (_, args) => window.Close(args.Result);
viewModel.CancelRequested += (_, _) => window.Close();
return await window.ShowDialog<TResult>(parent); return await window.ShowDialog<TResult>(parent);
} }
@ -119,6 +120,14 @@ namespace Artemis.UI.Shared.Services
return new ContentDialogBuilder(_kernel, currentWindow); return new ContentDialogBuilder(_kernel, currentWindow);
} }
public OpenFolderDialogBuilder CreateOpenFolderDialog()
{
Window? currentWindow = GetCurrentWindow();
if (currentWindow == null)
throw new ArtemisSharedUIException("Can't show an open folder dialog without any windows being shown.");
return new OpenFolderDialogBuilder(currentWindow);
}
public OpenFileDialogBuilder CreateOpenFileDialog() public OpenFileDialogBuilder CreateOpenFileDialog()
{ {
Window? currentWindow = GetCurrentWindow(); Window? currentWindow = GetCurrentWindow();

View File

@ -19,7 +19,7 @@
<Setter Property="MaxHeight" Value="0" /> <Setter Property="MaxHeight" Value="0" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="MaxHeight" Value="50" /> <Setter Property="MaxHeight" Value="600" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>

View File

@ -52,16 +52,7 @@ public abstract class DialogViewModelBase<TResult> : ValidatableViewModelBase
CloseRequested?.Invoke(this, new DialogClosedEventArgs<TResult>(result)); CloseRequested?.Invoke(this, new DialogClosedEventArgs<TResult>(result));
} }
/// <summary>
/// Closes the dialog without a result
/// </summary>
public void Cancel()
{
CancelRequested?.Invoke(this, EventArgs.Empty);
}
internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested; internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested;
internal event EventHandler? CancelRequested;
} }
/// <summary> /// <summary>

View File

@ -2,6 +2,7 @@ using Artemis.Core.Services;
using Artemis.UI.Windows.Ninject; using Artemis.UI.Windows.Ninject;
using Artemis.UI.Windows.Providers.Input; using Artemis.UI.Windows.Providers.Input;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.Threading;
@ -22,7 +23,7 @@ namespace Artemis.UI.Windows
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode)
return; return;
ArtemisBootstrapper.Initialize(); ArtemisBootstrapper.Initialize();

View File

@ -8,44 +8,44 @@
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**"/> <AvaloniaResource Include="Assets\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Artemis.UI.Avalonia.csproj.DotSettings"/> <None Remove="Artemis.UI.Avalonia.csproj.DotSettings" />
<None Remove="Artemis.UI.csproj.DotSettings"/> <None Remove="Artemis.UI.csproj.DotSettings" />
<None Remove="Assets\Images\Logo\application.ico"/> <None Remove="Assets\Images\Logo\application.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15"/> <PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0"/> <PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15"/> <PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15"/> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15"/> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14"/> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
<PackageReference Include="DynamicData" Version="7.8.6"/> <PackageReference Include="DynamicData" Version="7.8.6" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1"/> <PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="Flurl.Http" Version="3.2.4"/> <PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Live.Avalonia" Version="1.3.1"/> <PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2"/> <PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="ReactiveUI" Version="17.1.50"/> <PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1"/> <PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease.32"/> <PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease.32" />
<PackageReference Include="RGB.NET.Layout" Version="1.0.0-prerelease.32"/> <PackageReference Include="RGB.NET.Layout" Version="1.0.0-prerelease.32" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.1"/> <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
<PackageReference Include="Splat.Ninject" Version="14.1.45"/> <PackageReference Include="Splat.Ninject" Version="14.1.45" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj"/> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj"/> <ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
<ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj"/> <ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Assets\Images\Logo\bow-white.ico"/> <Content Include="Assets\Images\Logo\bow-white.ico" />
<Content Include="Assets\Images\Logo\bow.ico"/> <Content Include="Assets\Images\Logo\bow.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Assets\Images\Logo\bow-black.ico"/> <Resource Include="Assets\Images\Logo\bow-black.ico" />
<Resource Include="Assets\Images\Logo\bow-white.ico"/> <Resource Include="Assets\Images\Logo\bow-white.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="DefaultTypes\PropertyInput\StringPropertyInputView.axaml.cs"> <Compile Update="DefaultTypes\PropertyInput\StringPropertyInputView.axaml.cs">

View File

@ -1,5 +1,6 @@
using System; using System;
using Artemis.UI.Screens.Root; using Artemis.UI.Screens.Root;
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;

View File

@ -4,8 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView"> x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView"
x:DataType="plugins:PluginFeatureViewModel">
<Grid ColumnDefinitions="30,*,Auto"> <Grid ColumnDefinitions="30,*,Auto">
<Grid.ContextFlyout> <Grid.ContextFlyout>
<MenuFlyout> <MenuFlyout>
@ -24,14 +26,15 @@
<!-- Icon column --> <!-- Icon column -->
<shared:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.ResolvedIcon}" Icon="{CompiledBinding FeatureInfo.ResolvedIcon}"
Fill="False"
Width="20" Width="20"
Height="20" Height="20"
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNull}}" /> IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
<Button Grid.Column="0" <Button Grid.Column="0"
Classes="AppBarButton icon-button" Classes="AppBarButton icon-button"
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNotNull}}"
Foreground="#E74C4C" Foreground="#E74C4C"
ToolTip.Tip="An exception occurred while enabling this feature, click to view" ToolTip.Tip="An exception occurred while enabling this feature, click to view"
Command="{Binding ViewLoadException}"> Command="{Binding ViewLoadException}">
@ -40,15 +43,15 @@
<!-- Display name column --> <!-- Display name column -->
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Text="{Binding FeatureInfo.Name}" Text="{CompiledBinding FeatureInfo.Name}"
TextWrapping="Wrap" TextWrapping="Wrap"
VerticalAlignment="Center" VerticalAlignment="Center"
ToolTip.Tip="{Binding FeatureInfo.Description}" /> ToolTip.Tip="{CompiledBinding FeatureInfo.Description}" />
<!-- Enable toggle column --> <!-- Enable toggle column -->
<StackPanel Grid.Column="2" <StackPanel Grid.Column="2"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsVisible="{Binding !Enabling}" IsVisible="{CompiledBinding !Enabling}"
Orientation="Horizontal" Orientation="Horizontal"
ToolTip.Tip="This feature cannot be disabled without disabling the whole plugin"> ToolTip.Tip="This feature cannot be disabled without disabling the whole plugin">
<avalonia:MaterialIcon Kind="ShieldHalfFull" <avalonia:MaterialIcon Kind="ShieldHalfFull"
@ -57,12 +60,12 @@
Margin="0 0 5 0" Margin="0 0 5 0"
IsVisible="{Binding ShowShield}" /> IsVisible="{Binding ShowShield}" />
<CheckBox IsChecked="{Binding IsEnabled}" IsEnabled="{Binding CanToggleEnabled}"> <CheckBox IsChecked="{CompiledBinding IsEnabled}" IsEnabled="{CompiledBinding CanToggleEnabled}">
Feature enabled Enable feature
</CheckBox> </CheckBox>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7" IsVisible="{Binding Enabling}"> <StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7" IsVisible="{CompiledBinding Enabling}">
<ProgressBar Value="0" IsIndeterminate="True" /> <ProgressBar Value="0" IsIndeterminate="True" />
</StackPanel> </StackPanel>

View File

@ -71,7 +71,7 @@ namespace Artemis.UI.Screens.Plugins
public bool IsEnabled public bool IsEnabled
{ {
get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled; get => FeatureInfo.AlwaysEnabled || FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
set => Dispatcher.UIThread.InvokeAsync(() => UpdateEnabled(value)); set => Dispatcher.UIThread.InvokeAsync(() => UpdateEnabled(value));
} }

View File

@ -0,0 +1,11 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginPlatformView"
x:DataType="plugins:PluginPlatformViewModel">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" ToolTip.Tip="{CompiledBinding DisplayName}"/>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPlatformView : UserControl
{
public PluginPlatformView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,16 @@
using Material.Icons;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public class PluginPlatformViewModel : ReactiveObject
{
public PluginPlatformViewModel(string displayName, MaterialIconKind icon)
{
DisplayName = displayName;
Icon = icon;
}
public string DisplayName { get; set; }
public MaterialIconKind Icon { get; set; }
}

View File

@ -11,8 +11,9 @@
x:DataType="plugins:PluginSettingsViewModel"> x:DataType="plugins:PluginSettingsViewModel">
<Border Classes="card" Padding="15" Margin="0 5"> <Border Classes="card" Padding="15" Margin="0 5">
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*"> <Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*"> <Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*, Auto">
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}" <shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
Fill="False"
Width="48" Width="48"
Height="48" Height="48"
Margin="0 5 0 0" Margin="0 5 0 0"
@ -22,13 +23,23 @@
<TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{CompiledBinding Plugin.Info.Name}" /> <TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{CompiledBinding Plugin.Info.Name}" />
<ItemsControl Grid.Column="2" Grid.Row="0" IsVisible="{CompiledBinding Platforms.Count}" Items="{CompiledBinding Platforms}" HorizontalAlignment="Right">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="5" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1" Grid.Row="1"
Classes="subtitle" Classes="subtitle"
Text="{CompiledBinding Plugin.Info.Author}" Text="{CompiledBinding Plugin.Info.Author}"
IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="2" Grid.Row="2"
TextWrapping="Wrap" TextWrapping="Wrap"
Margin="0 5" Margin="0 5"
@ -88,13 +99,15 @@
</controls:HyperlinkButton> </controls:HyperlinkButton>
</StackPanel> </StackPanel>
<CheckBox Grid.Row="0" <CheckBox Name="EnabledToggle"
Grid.Row="0"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsVisible="{CompiledBinding !Enabling}" IsVisible="{CompiledBinding !Enabling}"
IsChecked="{CompiledBinding IsEnabled}"> IsChecked="{CompiledBinding IsEnabled, Mode=OneWay}"
IsEnabled="{CompiledBinding Plugin.Info.IsCompatible}">
<StackPanel x:Name="EnableText" Orientation="Horizontal"> <StackPanel x:Name="EnableText" Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock> <TextBlock>Enable plugin</TextBlock>
<avalonia:MaterialIcon Kind="ShieldHalfFull" <avalonia:MaterialIcon Kind="ShieldHalfFull"
Margin="5 0 0 0" Margin="5 0 0 0"
ToolTip.Tip="Plugin requires admin rights" ToolTip.Tip="Plugin requires admin rights"

View File

@ -1,18 +1,30 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Screens.Plugins
{ {
public partial class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel> public partial class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel>
{ {
private readonly CheckBox _enabledToggle;
public PluginSettingsView() public PluginSettingsView()
{ {
InitializeComponent(); InitializeComponent();
_enabledToggle = this.Find<CheckBox>("EnabledToggle");
_enabledToggle.Click += EnabledToggleOnClick;
} }
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void EnabledToggleOnClick(object? sender, RoutedEventArgs e)
{
Dispatcher.UIThread.Post(() => ViewModel?.UpdateEnabled(!ViewModel.Plugin.IsEnabled));
}
} }
} }

View File

@ -12,14 +12,18 @@ using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using Material.Icons;
using Material.Icons.Avalonia;
using Ninject; using Ninject;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Screens.Plugins;
public class PluginSettingsViewModel : ActivatableViewModelBase
{ {
public class PluginSettingsViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly INotificationService _notificationService; private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
@ -30,6 +34,7 @@ namespace Artemis.UI.Screens.Plugins
private bool _enabling; private bool _enabling;
private bool _isSettingsPopupOpen; private bool _isSettingsPopupOpen;
private Plugin _plugin; private Plugin _plugin;
private Window? _window;
public PluginSettingsViewModel(Plugin plugin, public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory, ISettingsVmFactory settingsVmFactory,
@ -50,8 +55,19 @@ namespace Artemis.UI.Screens.Plugins
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
Platforms = new ObservableCollection<PluginPlatformViewModel>();
if (Plugin.Info.Platforms != null)
{
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Windows))
Platforms.Add(new PluginPlatformViewModel("Windows", MaterialIconKind.MicrosoftWindows));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Linux))
Platforms.Add(new PluginPlatformViewModel("Linux", MaterialIconKind.Linux));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.OSX))
Platforms.Add(new PluginPlatformViewModel("OSX", MaterialIconKind.Apple));
}
Reload = ReactiveCommand.CreateFromTask(ExecuteReload); Reload = ReactiveCommand.CreateFromTask(ExecuteReload);
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null)); OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings); RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove); Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites)); InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
@ -61,27 +77,29 @@ namespace Artemis.UI.Screens.Plugins
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled; Plugin.Enabled += OnPluginToggled;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled; Plugin.Disabled += OnPluginToggled;
Disposable.Create(() => Disposable.Create(() =>
{ {
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled; Plugin.Enabled -= OnPluginToggled;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled; Plugin.Disabled -= OnPluginToggled;
_window?.Close();
}).DisposeWith(d); }).DisposeWith(d);
}); });
} }
public ReactiveCommand<Unit,Unit> Reload { get; } public ReactiveCommand<Unit, Unit> Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; } public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit,Unit> RemoveSettings { get; } public ReactiveCommand<Unit, Unit> RemoveSettings { get; }
public ReactiveCommand<Unit,Unit> Remove { get;} public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; } public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; } public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit,Unit> ShowLogsFolder { get; } public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit,Unit> OpenPluginDirectory { get; } public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; } public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
public Plugin Plugin public Plugin Plugin
{ {
@ -96,12 +114,7 @@ namespace Artemis.UI.Screens.Plugins
} }
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled => Plugin.IsEnabled;
public bool IsEnabled
{
get => Plugin.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
public bool IsSettingsPopupOpen public bool IsSettingsPopupOpen
{ {
@ -130,13 +143,21 @@ namespace Artemis.UI.Screens.Plugins
if (Plugin.ConfigurationDialog == null) if (Plugin.ConfigurationDialog == null)
return; return;
if (_window != null)
{
_window.WindowState = WindowState.Normal;
_window.Activate();
return;
}
try try
{ {
PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel; PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel;
if (viewModel == null) if (viewModel == null)
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}"); throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
_windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); _window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
_window.Closed += (_, _) => _window = null;
} }
catch (Exception e) catch (Exception e)
{ {
@ -192,7 +213,6 @@ namespace Artemis.UI.Screens.Plugins
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any()))) if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
{ {
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel"); await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
this.RaisePropertyChanged(nameof(IsEnabled));
} }
} }
@ -252,32 +272,42 @@ namespace Artemis.UI.Screens.Plugins
} }
} }
private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e) public async Task UpdateEnabled(bool enable)
{
if (Enabling)
return;
if (!enable)
{
try
{
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
}
catch (Exception e)
{
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to disable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{ {
this.RaisePropertyChanged(nameof(IsEnabled)); this.RaisePropertyChanged(nameof(IsEnabled));
} }
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return; return;
} }
if (enable) try
{ {
Enabling = true; Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated) if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{ {
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?"); bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed) if (!confirmed)
{
CancelEnable();
return; return;
} }
}
// Check if all prerequisites are met async // Check if all prerequisites are met async
List<IPrerequisitesSubject> subjects = new() {Plugin.Info}; List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
@ -287,21 +317,15 @@ namespace Artemis.UI.Screens.Plugins
{ {
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects); await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet())) if (!subjects.All(s => s.ArePrerequisitesMet()))
{
CancelEnable();
return; return;
} }
}
await Task.Run(async () => await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true));
{
try
{
_pluginManagementService.EnablePlugin(Plugin, true, true);
} }
catch (Exception e) catch (Exception e)
{ {
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification() await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}") .WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show()); .Show());
@ -309,23 +333,11 @@ namespace Artemis.UI.Screens.Plugins
finally finally
{ {
Enabling = false; Enabling = false;
}
});
}
else
{
_pluginManagementService.DisablePlugin(Plugin, true);
}
this.RaisePropertyChanged(nameof(IsEnabled)); this.RaisePropertyChanged(nameof(IsEnabled));
} }
private void CancelEnable()
{
Enabling = false;
this.RaisePropertyChanged(nameof(IsEnabled));
} }
private void CheckPrerequisites() private void CheckPrerequisites()
{ {
CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() || CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
@ -333,5 +345,14 @@ namespace Artemis.UI.Screens.Plugins
CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) || CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any())); Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
} }
private void OnPluginToggled(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
this.RaisePropertyChanged(nameof(IsEnabled));
if (!IsEnabled)
_window?.Close();
});
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;

View File

@ -1,4 +1,5 @@
using Avalonia; using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;

View File

@ -1,4 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;

View File

@ -1,4 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;

View File

@ -1,4 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;

View File

@ -4,12 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Settings.DevicesTabView"> x:Class="Artemis.UI.Screens.Settings.DevicesTabView">
<Grid MaxWidth="1050"> <Grid RowDefinitions="Auto,*">
<Grid.RowDefinitions> <StackPanel MaxWidth="1050">
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Classes="h4">Device management</TextBlock> <TextBlock Classes="h4">Device management</TextBlock>
<TextBlock> <TextBlock>
Below you view and manage the devices that were detected by Artemis. Below you view and manage the devices that were detected by Artemis.
@ -20,7 +16,7 @@
</StackPanel> </StackPanel>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl Items="{Binding Devices}" Margin="-5 0"> <ItemsControl Items="{Binding Devices}" Margin="-5 0" MaxWidth="1050">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<WrapPanel /> <WrapPanel />

View File

@ -6,11 +6,18 @@
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Settings.PluginsTabView" x:Class="Artemis.UI.Screens.Settings.PluginsTabView"
x:DataType="settings:PluginsTabViewModel"> x:DataType="settings:PluginsTabViewModel">
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,*" Width="900"> <Grid RowDefinitions="Auto,*">
<TextBox Grid.Row="0" Grid.Column="0" Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 10" /> <Grid Grid.Row="0" Grid.Column="0" MaxWidth="900" Margin="0 10">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="165" MaxWidth="400"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 0 10 0"/>
<Button Grid.Row="0" Grid.Column="1" Classes="accent" Command="{CompiledBinding ImportPlugin}" HorizontalAlignment="Right">Import plugin</Button> <Button Grid.Row="0" Grid.Column="1" Classes="accent" Command="{CompiledBinding ImportPlugin}" HorizontalAlignment="Right">Import plugin</Button>
<ScrollViewer Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> </Grid>
<ItemsControl Items="{CompiledBinding Plugins}" />
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl Items="{CompiledBinding Plugins}" MaxWidth="900" VerticalAlignment="Center"/>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -90,7 +90,7 @@ namespace Artemis.UI.Screens.Settings
// Enable it via the VM to enable the prerequisite dialog // Enable it via the VM to enable the prerequisite dialog
PluginSettingsViewModel? pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin); PluginSettingsViewModel? pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin);
if (pluginViewModel is {IsEnabled: false}) if (pluginViewModel is {IsEnabled: false})
pluginViewModel.IsEnabled = true; await pluginViewModel.UpdateEnabled(true);
_notificationService.CreateNotification() _notificationService.CreateNotification()
.WithTitle("Plugin imported") .WithTitle("Plugin imported")

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;

View File

@ -1,3 +1,4 @@
using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;