Plugins - Ported prerequisites UI

Scripting - Ported scripting UI
This commit is contained in:
Robert committed 2022-07-10 23:25:34 +02:00
1 parent 0256a0d625
commit 3b4194cb9d
56 files changed
+1301 -274

No files matched your search

@@ -0,0 +1,84 @@
<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.PluginPrerequisitesInstallDialogView"
x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel">
<UserControl.Styles>
<Styles>
<Style Selector="Border.status-border">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="CornerRadius" Value="16" />
</Style>
<Style Selector="Border.status-border avalonia|MaterialIcon">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</Styles>
</UserControl.Styles>
<Grid ColumnDefinitions="250,*">
<Grid.RowDefinitions>
<RowDefinition MinHeight="200" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
Grid.Column="0"
Items="{CompiledBinding Prerequisites}"
SelectedItem="{CompiledBinding ActivePrerequisite, Mode=OneWay}"
IsHitTestVisible="False">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type plugins:PluginPrerequisiteViewModel}">
<Grid ColumnDefinitions="Auto,*" Margin="0 6">
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding !IsMet}" Background="#ff3838">
<avalonia:MaterialIcon Kind="Close" />
</Border>
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding IsMet}" Background="#32a852">
<avalonia:MaterialIcon Kind="Check" />
</Border>
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
<TextBlock FontWeight="Bold" Text="{CompiledBinding PluginPrerequisite.Name}" TextWrapping="Wrap" />
<TextBlock Text="{CompiledBinding PluginPrerequisite.Description}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="0"
Grid.Column="1"
Content="{CompiledBinding ActivePrerequisite}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10 0"
IsTabStop="False"
IsVisible="{CompiledBinding ShowProgress, Mode=OneWay}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
TextWrapping="Wrap"
Margin="10 0"
IsVisible="{CompiledBinding ShowIntro, Mode=OneWay}">
In order for this plugin to work the prerequisites on the left must be met. Clicking install will automatically set everything up for you.
</TextBlock>
<StackPanel Grid.Row="0"
Grid.Column="1"
Margin="10 0"
IsVisible="{CompiledBinding ShowFailed, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock>Installing</TextBlock>
<TextBlock Text="{CompiledBinding ActivePrerequisite.PluginPrerequisite.Name, Mode=OneWay}" FontWeight="SemiBold" TextWrapping="Wrap" />
<TextBlock>failed.</TextBlock>
</StackPanel>
<TextBlock TextWrapping="Wrap">You may try again to see if that helps, otherwise install the prerequisite manually or contact the plugin developer.</TextBlock>
</StackPanel>
</Grid>
</UserControl>
@@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisitesInstallDialogView : ReactiveUserControl<PluginPrerequisitesInstallDialogViewModel>
{
public PluginPrerequisitesInstallDialogView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
@@ -9,11 +10,14 @@ using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool>
public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase
{
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canInstall;
@@ -26,22 +30,23 @@ namespace Artemis.UI.Screens.Plugins
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
{
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites))
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false));
Install = ReactiveCommand.CreateFromTask(ExecuteInstall, this.WhenAnyValue(vm => vm.CanInstall));
CanInstall = false;
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
Dispatcher.UIThread.Post(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d =>
{
Disposable.Create(() =>
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit> Install { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
@@ -80,13 +85,18 @@ namespace Artemis.UI.Screens.Plugins
set => RaiseAndSetIfChanged(ref _canInstall, value);
}
public async Task Install()
private async Task ExecuteInstall()
{
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
CanInstall = false;
ShowFailed = false;
ShowIntro = false;
ShowProgress = true;
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
try
@@ -110,30 +120,31 @@ namespace Artemis.UI.Screens.Plugins
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
await Task.Delay(1000);
}
ShowInstall = false;
if (deferral != null)
deferral.Complete();
else
ContentDialog?.Hide(ContentDialogResult.Primary);
}
catch (OperationCanceledException)
{
// ignored
}
finally
{
_tokenSource.Dispose();
_tokenSource = null;
}
}
public void Accept()
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
{
Close(true);
}
public static async Task<bool> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
{
return await windowService.ShowDialogAsync<PluginPrerequisitesInstallDialogViewModel, bool>(("subjects", subjects));
await windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, ("subjects", subjects))
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
}
}
}
@@ -0,0 +1,72 @@
<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.PluginPrerequisitesUninstallDialogView"
x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel">
<UserControl.Styles>
<Styles>
<Style Selector="Border.status-border">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="CornerRadius" Value="16" />
</Style>
<Style Selector="Border.status-border avalonia|MaterialIcon">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</Styles>
</UserControl.Styles>
<Grid ColumnDefinitions="250,*">
<Grid.RowDefinitions>
<RowDefinition MinHeight="200" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
Grid.Column="0"
Items="{CompiledBinding Prerequisites}"
SelectedItem="{CompiledBinding ActivePrerequisite, Mode=OneWay}"
IsHitTestVisible="False">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type plugins:PluginPrerequisiteViewModel}">
<Grid ColumnDefinitions="Auto,*" Margin="0 6">
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding !IsMet}" Background="#ff3838">
<avalonia:MaterialIcon Kind="Close" />
</Border>
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding IsMet}" Background="#32a852">
<avalonia:MaterialIcon Kind="Check" />
</Border>
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
<TextBlock FontWeight="Bold" Text="{CompiledBinding PluginPrerequisite.Name}" TextWrapping="Wrap" />
<TextBlock Text="{CompiledBinding PluginPrerequisite.Description}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="0"
Grid.Column="1"
Content="{CompiledBinding ActivePrerequisite}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10 0"
IsTabStop="False"
IsVisible="{CompiledBinding ActivePrerequisite, Converter={x:Static ObjectConverters.IsNotNull}}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
TextWrapping="Wrap"
Margin="10 0"
IsVisible="{CompiledBinding ActivePrerequisite, Converter={x:Static ObjectConverters.IsNull}}">
This plugin/feature installed certain prerequisites in order to function. In this screen you can chose to remove these, this will mean the plugin/feature will no longer work until you reinstall the prerequisites.
</TextBlock>
</Grid>
</UserControl>
@@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisitesUninstallDialogView : ReactiveUserControl<PluginPrerequisitesUninstallDialogViewModel>
{
public PluginPrerequisitesUninstallDialogView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
@@ -10,34 +11,38 @@ using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase<bool>
public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase
{
private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects;
private readonly IWindowService _windowService;
private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canUninstall;
private bool _isFinished;
private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService,
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects,
IPrerequisitesVmFactory prerequisitesVmFactory,
IWindowService windowService,
IPluginManagementService pluginManagementService)
{
_subjects = subjects;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
CancelLabel = cancelLabel;
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites))
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true));
Uninstall = ReactiveCommand.CreateFromTask(ExecuteUninstall, this.WhenAnyValue(vm => vm.CanUninstall));
// Could be slow so take it off of the UI thread
Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()));
Dispatcher.UIThread.Post(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d =>
{
@@ -45,11 +50,12 @@ namespace Artemis.UI.Screens.Plugins
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d);
});
}
public string CancelLabel { get; }
public ReactiveCommand<Unit, Unit> Uninstall { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite
@@ -64,20 +70,19 @@ namespace Artemis.UI.Screens.Plugins
set => RaiseAndSetIfChanged(ref _canUninstall, value);
}
public bool IsFinished
private async Task ExecuteUninstall()
{
get => _isFinished;
set => RaiseAndSetIfChanged(ref _isFinished, value);
}
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
public async Task Uninstall()
{
CanUninstall = false;
// Disable all subjects that are plugins, this will disable their features too
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is PluginInfo pluginInfo) _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
if (prerequisitesSubject is PluginInfo pluginInfo)
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
// Disable all subjects that are features if still required
@@ -88,9 +93,11 @@ namespace Artemis.UI.Screens.Plugins
// Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled)
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
else if (featureInfo.Instance != null) _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
else if (featureInfo.Instance != null)
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource();
try
@@ -98,29 +105,34 @@ namespace Artemis.UI.Screens.Plugins
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (!pluginPrerequisiteViewModel.IsMet) continue;
if (!pluginPrerequisiteViewModel.IsMet)
continue;
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Uninstall(_tokenSource.Token);
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last()) await Task.Delay(1000);
}
if (Prerequisites.All(p => !p.IsMet))
{
IsFinished = true;
return;
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
await Task.Delay(1000);
}
if (deferral != null)
deferral.Complete();
else
ContentDialog?.Hide(ContentDialogResult.Primary);
// This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case)
// but at least give some feedback
Close(false);
await _windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
.ShowAsync();
await Show(_windowService, _subjects);
if (Prerequisites.Any(p => p.IsMet))
{
await _windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
.ShowAsync();
await Show(_windowService, _subjects);
}
}
catch (OperationCanceledException)
{
@@ -129,19 +141,18 @@ namespace Artemis.UI.Screens.Plugins
finally
{
CanUninstall = true;
_tokenSource.Dispose();
_tokenSource = null;
}
}
public void Accept()
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
{
Close(true);
}
public static async Task<object> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
{
return await windowService.ShowDialogAsync<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel));
await windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, ("subjects", subjects))
.WithCloseButtonText(cancelLabel)
.HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
}
}
}