Plugins - Added prerequisites system

This commit is contained in:
Robert committed 2021-04-25 23:18:00 +02:00
1 parent 77227b7e6e
commit 19679c0ba6
22 files changed
+1210 -13

No files matched your search

@@ -0,0 +1,26 @@
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteActionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginPrerequisiteActionViewModel}">
<StackPanel>
<ProgressBar Value="{Binding Action.Progress.Percentage, Mode=OneWay}"
IsIndeterminate="{Binding Action.ProgressIndeterminate, Mode=OneWay}"
Visibility="{Binding ShowProgressBar, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
materialDesign:TransitionAssist.DisableTransitions="True"
Margin="0 10"/>
<ProgressBar Value="{Binding Action.SubProgress.Percentage, Mode=OneWay}"
IsIndeterminate="{Binding Action.SubProgressIndeterminate, Mode=OneWay}"
Visibility="{Binding ShowSubProgressBar, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
materialDesign:TransitionAssist.DisableTransitions="True"
Margin="0 10"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Action.Status, Mode=OneWay}" />
</StackPanel>
</UserControl>
@@ -0,0 +1,71 @@
using System;
using System.ComponentModel;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisiteActionViewModel : Screen
{
private bool _showProgressBar;
private bool _showSubProgressBar;
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
{
Action = action;
}
public PluginPrerequisiteAction Action { get; }
public bool ShowProgressBar
{
get => _showProgressBar;
set => SetAndNotify(ref _showProgressBar, value);
}
public bool ShowSubProgressBar
{
get => _showSubProgressBar;
set => SetAndNotify(ref _showSubProgressBar, value);
}
private void ActionOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Action.ProgressIndeterminate) || e.PropertyName == nameof(Action.SubProgressIndeterminate))
UpdateProgress();
}
private void ProgressReported(object? sender, EventArgs e)
{
UpdateProgress();
}
private void UpdateProgress()
{
ShowSubProgressBar = Action.SubProgress.Percentage != 0 || Action.SubProgressIndeterminate;
ShowProgressBar = ShowSubProgressBar || Action.Progress.Percentage != 0 || Action.ProgressIndeterminate;
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
Action.Progress.ProgressReported += ProgressReported;
Action.SubProgress.ProgressReported += ProgressReported;
Action.PropertyChanged += ActionOnPropertyChanged;
base.OnInitialActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
Action.Progress.ProgressReported -= ProgressReported;
Action.SubProgress.ProgressReported -= ProgressReported;
Action.PropertyChanged -= ActionOnPropertyChanged;
base.OnClose();
}
#endregion
}
}
@@ -0,0 +1,37 @@
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginPrerequisiteViewModel}">
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Text="{Binding PluginPrerequisite.Name, Mode=OneWay}" />
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" TextWrapping="Wrap" Text="{Binding PluginPrerequisite.Description, Mode=OneWay}" Margin="0 0 0 15" />
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
Visibility="{Binding HasMultipleActions, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Run Text="Step" />
<Run Text="{Binding ActiveStemNumber, Mode=OneWay}" /><Run Text="/" /><Run Text="{Binding Items.Count, Mode=OneWay}" />
<Run Text="-" />
<Run Text="{Binding ActiveItem.Action.Name, Mode=OneWay}" />
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
Text="{Binding ActiveItem.Action.Name, Mode=OneWay}"
Visibility="{Binding HasMultipleActions, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
<ContentControl s:View.Model="{Binding ActiveItem}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"
Margin="0 10 0 0" />
</StackPanel>
</UserControl>
@@ -0,0 +1,142 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisiteViewModel : Conductor<PluginPrerequisiteActionViewModel>.Collection.OneActive
{
private readonly ICoreService _coreService;
private readonly IDialogService _dialogService;
private bool _installing;
private bool _uninstalling;
private bool _isMet;
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, ICoreService coreService, IDialogService dialogService)
{
_coreService = coreService;
_dialogService = dialogService;
PluginPrerequisite = pluginPrerequisite;
}
public PluginPrerequisite PluginPrerequisite { get; }
public bool Installing
{
get => _installing;
set
{
SetAndNotify(ref _installing, value);
NotifyOfPropertyChange(nameof(Busy));
}
}
public bool Uninstalling
{
get => _uninstalling;
set
{
SetAndNotify(ref _uninstalling, value);
NotifyOfPropertyChange(nameof(Busy));
}
}
public bool IsMet
{
get => _isMet;
set => SetAndNotify(ref _isMet, value);
}
public bool Busy => Installing || Uninstalling;
public int ActiveStemNumber => Items.IndexOf(ActiveItem) + 1;
public bool HasMultipleActions => Items.Count > 1;
public async Task Install(CancellationToken cancellationToken)
{
if (Busy)
return;
if (PluginPrerequisite.RequiresElevation && !_coreService.IsElevated)
{
await _dialogService.ShowConfirmDialog("Install plugin prerequisite", "This plugin prerequisite admin rights to install (restart & elevate NYI)");
return;
}
Installing = true;
try
{
await PluginPrerequisite.Install(cancellationToken);
}
finally
{
Installing = false;
IsMet = await PluginPrerequisite.IsMet();
}
}
public async Task Uninstall(CancellationToken cancellationToken)
{
if (Busy)
return;
if (PluginPrerequisite.RequiresElevation && !_coreService.IsElevated)
{
await _dialogService.ShowConfirmDialog("Install plugin prerequisite", "This plugin prerequisite admin rights to install (restart & elevate NYI)");
return;
}
Uninstalling = true;
try
{
await PluginPrerequisite.Uninstall(cancellationToken);
}
finally
{
Uninstalling = false;
IsMet = await PluginPrerequisite.IsMet();
}
}
private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
ActivateCurrentAction();
}
private void ActivateCurrentAction()
{
ActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
NotifyOfPropertyChange(nameof(ActiveStemNumber));
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnClose()
{
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
base.OnClose();
}
/// <inheritdoc />
protected override void OnInitialActivate()
{
PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
Task.Run(async () => IsMet = await PluginPrerequisite.IsMet());
Items.AddRange(PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
Items.AddRange(PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
base.OnInitialActivate();
}
#endregion
}
}
@@ -0,0 +1,126 @@
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginPrerequisitesDialogViewModel}">
<UserControl.Resources>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
</UserControl.Resources>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition MinHeight="150"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="500" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
Plugin prerequisites
</TextBlock>
<ListBox Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding Prerequisites}"
SelectedItem="{Binding ActivePrerequisite, Mode=OneWay}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:PluginPrerequisiteViewModel}">
<Border Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}" VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToggleButton VerticalAlignment="Center"
Style="{StaticResource MaterialDesignActionToggleButton}"
Focusable="False"
IsHitTestVisible="False"
IsChecked="{Binding IsMet}">
<ToggleButton.Content>
<Border Background="#E74C4C" Width="32" Height="32">
<materialDesign:PackIcon Kind="Close" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</ToggleButton.Content>
<materialDesign:ToggleButtonAssist.OnContent>
<materialDesign:PackIcon Kind="Check" />
</materialDesign:ToggleButtonAssist.OnContent>
</ToggleButton>
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
<TextBlock FontWeight="Bold" Text="{Binding PluginPrerequisite.Name}" TextWrapping="Wrap" />
<TextBlock Text="{Binding PluginPrerequisite.Description}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
s:View.Model="{Binding ActivePrerequisite}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10 0"
IsTabStop="False"
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}}"/>
<TextBlock Grid.Row="1"
Grid.Column="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
TextWrapping="Wrap"
Margin="10 0"
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
In order for this plugin to function certain prerequisites must be met. <LineBreak /><LineBreak />
On the left side you can see all prerequisites and whether they are currently met or not.
Clicking install prerequisites will automatically set everything up for you.
</TextBlock>
<StackPanel Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0 8 0 0"
Visibility="{Binding IsFinished, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<Button Style="{StaticResource MaterialDesignFlatButton}"
Focusable="False"
IsCancel="True"
Command="{s:Action Cancel}"
Content="CANCEL" />
<Button x:Name="ConfirmButton"
Style="{StaticResource MaterialDesignFlatButton}"
IsDefault="True"
Focusable="True"
Command="{s:Action Install}"
Content="INSTALL PREREQUISITES" />
</StackPanel>
<StackPanel Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0 8 0 0"
Visibility="{Binding IsFinished, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<Button Style="{StaticResource MaterialDesignFlatButton}"
Focusable="False"
IsCancel="True"
Command="{s:Action Accept}"
Content="FINISH" />
</StackPanel>
</Grid>
</UserControl>
@@ -0,0 +1,127 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Stylet;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisitesDialogViewModel : DialogViewModelBase
{
private PluginPrerequisiteViewModel _activePrerequisite;
private bool _canInstall;
private bool _isFinished;
private CancellationTokenSource _tokenSource;
public PluginPrerequisitesDialogViewModel(object pluginOrFeature, IPrerequisitesVmFactory prerequisitesVmFactory)
{
// Constructor overloading doesn't work very well with Kernel.Get<T> :(
if (pluginOrFeature is Plugin plugin)
{
Plugin = plugin;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Prerequisites.Select(prerequisitesVmFactory.PluginPrerequisiteViewModel));
}
else if (pluginOrFeature is PluginFeature feature)
{
Feature = feature;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Prerequisites.Select(prerequisitesVmFactory.PluginPrerequisiteViewModel));
}
else
throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesDialogViewModel)}");
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
pluginPrerequisiteViewModel.ConductWith(this);
}
public PluginFeature Feature { get; }
public Plugin Plugin { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite
{
get => _activePrerequisite;
set => SetAndNotify(ref _activePrerequisite, value);
}
public bool CanInstall
{
get => _canInstall;
set => SetAndNotify(ref _canInstall, value);
}
public bool IsFinished
{
get => _isFinished;
set => SetAndNotify(ref _isFinished, value);
}
#region Overrides of DialogViewModelBase
/// <inheritdoc />
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
{
_tokenSource?.Cancel();
base.OnDialogClosed(sender, e);
}
#endregion
public async void Install()
{
CanInstall = false;
_tokenSource = new CancellationTokenSource();
try
{
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
ActivePrerequisite = pluginPrerequisiteViewModel;
ActivePrerequisite.IsMet = await ActivePrerequisite.PluginPrerequisite.IsMet();
if (ActivePrerequisite.IsMet)
continue;
await ActivePrerequisite.Install(_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;
}
catch (OperationCanceledException)
{
// ignored
}
finally
{
CanInstall = true;
_tokenSource.Dispose();
_tokenSource = null;
}
}
public void Accept()
{
Session?.Close(true);
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
base.OnInitialActivate();
CanInstall = Prerequisites.Any(p => !p.IsMet);
}
#endregion
}
}