Plugins - Fixed cancelling prerequisite installation

Plugins - Tweaked prerequisite installation popup size
Account settings - Added personal access token management
Workshop - Added plugin loading, installing and removal
This commit is contained in:
RobertBeekman committed 2024-02-16 23:04:24 +01:00
1 parent 21b8112de5
commit f2c8de1746
33 files changed
+516 -113

No files matched your search

@@ -9,7 +9,6 @@ using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using Humanizer;
using ReactiveUI;
@@ -29,11 +28,13 @@ public class EntryReleasesViewModel : ViewModelBase
Entry = entry;
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
OnInstallationStarted = Confirm;
}
public IGetEntryById_Entry Entry { get; }
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
public Func<IEntryDetails, Task<bool>> OnInstallationStarted { get; set; }
public Func<InstalledEntry, Task>? OnInstallationFinished { get; set; }
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
@@ -41,11 +42,7 @@ public class EntryReleasesViewModel : ViewModelBase
if (Entry.LatestRelease == null)
return;
bool confirm = await _windowService.ShowConfirmContentDialog(
"Install latest release",
$"Are you sure you want to download and install version {Entry.LatestRelease.Version} of {Entry.Name}?"
);
if (!confirm)
if (await OnInstallationStarted(Entry))
return;
IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType);
@@ -64,4 +61,14 @@ public class EntryReleasesViewModel : ViewModelBase
.WithSeverity(NotificationSeverity.Error).Show();
}
}
private async Task<bool> Confirm(IEntryDetails entryDetails)
{
bool confirm = await _windowService.ShowConfirmContentDialog(
"Install latest release",
$"Are you sure you want to download and install version {entryDetails.LatestRelease?.Version} of {entryDetails.Name}?"
);
return !confirm;
}
}
@@ -59,7 +59,7 @@
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/entries/plugins/1" VerticalContentAlignment="Top">
<StackPanel>
<avalonia:MaterialIcon Kind="Plugin" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
<avalonia:MaterialIcon Kind="Connection" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
<TextBlock TextWrapping="Wrap" FontSize="16" Margin="0 5">Plugins</TextBlock>
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Plugins add new functionality to Artemis.</TextBlock>
</StackPanel>
@@ -1,7 +1,12 @@
using System;
using System.Linq;
using System.Reactive;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
@@ -20,14 +25,24 @@ public partial class InstalledTabItemViewModel : ViewModelBase
private readonly IRouter _router;
private readonly EntryInstallationHandlerFactory _factory;
private readonly IWindowService _windowService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
[Notify(Setter.Private)] private bool _isRemoved;
public InstalledTabItemViewModel(InstalledEntry installedEntry, IWorkshopService workshopService, IRouter router, EntryInstallationHandlerFactory factory, IWindowService windowService)
public InstalledTabItemViewModel(InstalledEntry installedEntry,
IWorkshopService workshopService,
IRouter router,
EntryInstallationHandlerFactory factory,
IWindowService windowService,
IPluginManagementService pluginManagementService,
ISettingsVmFactory settingsVmFactory)
{
_workshopService = workshopService;
_router = router;
_factory = factory;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
_settingsVmFactory = settingsVmFactory;
InstalledEntry = installedEntry;
ViewWorkshopPage = ReactiveCommand.CreateFromTask(ExecuteViewWorkshopPage);
@@ -58,9 +73,25 @@ public partial class InstalledTabItemViewModel : ViewModelBase
bool confirmed = await _windowService.ShowConfirmContentDialog("Do you want to uninstall this entry?", "Both the entry and its contents will be removed.");
if (!confirmed)
return;
// Ideally the installation handler does this but it doesn't have access to the required view models
if (InstalledEntry.EntryType == EntryType.Plugin)
await UninstallPluginPrerequisites();
IEntryInstallationHandler handler = _factory.CreateHandler(InstalledEntry.EntryType);
await handler.UninstallAsync(InstalledEntry, cancellationToken);
IsRemoved = true;
}
private async Task UninstallPluginPrerequisites()
{
if (!InstalledEntry.TryGetMetadata("PluginId", out Guid pluginId))
return;
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
if (plugin == null)
return;
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
await pluginViewModel.ExecuteRemovePrerequisites(true);
}
}
@@ -0,0 +1,19 @@
<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:dialogs="clr-namespace:Artemis.UI.Screens.Workshop.Plugins.Dialogs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Plugins.Dialogs.PluginDialogView"
x:DataType="dialogs:PluginDialogViewModel">
<Grid ColumnDefinitions="4*,5*" Width="800" Height="160">
<ContentControl Grid.Column="0" Content="{CompiledBinding PluginViewModel}" />
<Border Grid.Column="1" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Grid RowDefinitions="Auto,*">
<TextBlock Classes="h5">Plugin features</TextBlock>
<ListBox Grid.Row="1" MaxHeight="135" ItemsSource="{CompiledBinding PluginFeatures}" />
</Grid>
</Border>
</Grid>
</UserControl>
@@ -0,0 +1,11 @@
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Plugins.Dialogs;
public partial class PluginDialogView : ReactiveUserControl<PluginDialogViewModel>
{
public PluginDialogView()
{
InitializeComponent();
}
}
@@ -0,0 +1,23 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Plugins.Features;
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Plugins.Dialogs;
public class PluginDialogViewModel : ContentDialogViewModelBase
{
public PluginDialogViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory)
{
PluginViewModel = settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => {}, Observable.Empty<bool>()));
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>(plugin.Features.Select(f => settingsVmFactory.PluginFeatureViewModel(f, false)));
}
public PluginViewModel PluginViewModel { get; }
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
}
@@ -2,11 +2,11 @@
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:plugin="clr-namespace:Artemis.UI.Screens.Workshop.Plugin"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Workshop.Plugins"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Plugin.PluginDetailsView"
x:DataType="plugin:PluginDetailsViewModel">
x:Class="Artemis.UI.Screens.Workshop.Plugins.PluginDetailsView"
x:DataType="plugins:PluginDetailsViewModel">
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="10">
<Border Classes="card" VerticalAlignment="Top">
@@ -1,9 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Plugin;
namespace Artemis.UI.Screens.Workshop.Plugins;
public partial class PluginDetailsView : ReactiveUserControl<PluginDetailsViewModel>
{
@@ -1,18 +1,26 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Workshop.Entries.Details;
using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Screens.Workshop.Plugins.Dialogs;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Models;
using PropertyChanged.SourceGenerator;
using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Plugin;
namespace Artemis.UI.Screens.Workshop.Plugins;
public partial class PluginDetailsViewModel : RoutableScreen<WorkshopDetailParameters>
{
private readonly IWorkshopClient _client;
private readonly IWindowService _windowService;
private readonly IPluginManagementService _pluginManagementService;
private readonly Func<IGetEntryById_Entry, EntryInfoViewModel> _getEntryInfoViewModel;
private readonly Func<IGetEntryById_Entry, EntryReleasesViewModel> _getEntryReleasesViewModel;
private readonly Func<IGetEntryById_Entry, EntryImagesViewModel> _getEntryImagesViewModel;
@@ -22,16 +30,20 @@ public partial class PluginDetailsViewModel : RoutableScreen<WorkshopDetailParam
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
public PluginDetailsViewModel(IWorkshopClient client,
IWindowService windowService,
IPluginManagementService pluginManagementService,
Func<IGetEntryById_Entry, EntryInfoViewModel> getEntryInfoViewModel,
Func<IGetEntryById_Entry, EntryReleasesViewModel> getEntryReleasesViewModel,
Func<IGetEntryById_Entry, EntryImagesViewModel> getEntryImagesViewModel)
{
_client = client;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
_getEntryInfoViewModel = getEntryInfoViewModel;
_getEntryReleasesViewModel = getEntryReleasesViewModel;
_getEntryImagesViewModel = getEntryImagesViewModel;
}
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{
await GetEntry(parameters.EntryId, cancellationToken);
@@ -54,6 +66,32 @@ public partial class PluginDetailsViewModel : RoutableScreen<WorkshopDetailParam
EntryInfoViewModel = _getEntryInfoViewModel(Entry);
EntryReleasesViewModel = _getEntryReleasesViewModel(Entry);
EntryImagesViewModel = _getEntryImagesViewModel(Entry);
EntryReleasesViewModel.OnInstallationStarted = OnInstallationStarted;
EntryReleasesViewModel.OnInstallationFinished = OnInstallationFinished;
}
}
private async Task<bool> OnInstallationStarted(IEntryDetails entryDetails)
{
bool confirm = await _windowService.ShowConfirmContentDialog(
"Installing plugin",
$"You are about to install version {entryDetails.LatestRelease?.Version} of {entryDetails.Name}. \r\n\r\n" +
"Plugins are NOT verified by Artemis and could harm your PC, if you have doubts about a plugin please ask on Discord!",
"I trust this plugin, install it"
);
return !confirm;
}
private async Task OnInstallationFinished(InstalledEntry installedEntry)
{
if (!installedEntry.TryGetMetadata("PluginId", out Guid pluginId))
return;
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
if (plugin == null)
return;
await _windowService.CreateContentDialog().WithTitle("Manage plugin").WithViewModel(out PluginDialogViewModel _, plugin).WithFullScreen().ShowAsync();
}
}