mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Release page - Added release installation
This commit is contained in:
parent
9e994840f6
commit
d6f1ba9aad
@ -45,6 +45,12 @@ public interface IRouter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A task containing a boolean value which indicates whether there was a forward path to go back to.</returns>
|
/// <returns>A task containing a boolean value which indicates whether there was a forward path to go back to.</returns>
|
||||||
Task<bool> GoForward();
|
Task<bool> GoForward();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously navigates upwards to the parent route.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> GoUp();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the navigation history.
|
/// Clears the navigation history.
|
||||||
|
|||||||
@ -161,6 +161,28 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> GoUp()
|
||||||
|
{
|
||||||
|
string? currentPath = _currentRouteSubject.Value;
|
||||||
|
|
||||||
|
// Keep removing segments until we find a parent route that resolves
|
||||||
|
while (currentPath != null && currentPath.Contains('/'))
|
||||||
|
{
|
||||||
|
string parentPath = currentPath[..currentPath.LastIndexOf('/')];
|
||||||
|
RouteResolution resolution = Resolve(parentPath);
|
||||||
|
if (resolution.Success)
|
||||||
|
{
|
||||||
|
await Navigate(parentPath, new RouterNavigationOptions {AddToHistory = false});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath = parentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void ClearHistory()
|
public void ClearHistory()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Column="2" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Right">
|
<Grid Grid.Column="2" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Right">
|
||||||
<avalonia:MaterialIcon Kind="BoxOutline" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
<avalonia:MaterialIcon Kind="File" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">File size</TextBlock>
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">File size</TextBlock>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
|||||||
@ -37,23 +37,47 @@
|
|||||||
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorLight1}" />
|
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorLight1}" />
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto,Auto">
|
<Grid RowDefinitions="Auto,Auto">
|
||||||
<Border Grid.Row="0" Classes="card" Margin="0 0 0 10">
|
<Border Grid.Row="0" Classes="card" Margin="0 0 0 10">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Classes="h4 no-margin">Release info</TextBlock>
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
|
<Button Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Classes="icon-button" Command="{CompiledBinding Close}">
|
||||||
|
<avalonia:MaterialIcon Kind="ArrowBack" />
|
||||||
|
</Button>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="1" Classes="h4 no-margin">Release info</TextBlock>
|
||||||
|
<Panel Grid.Column="2">
|
||||||
|
<!-- Install progress -->
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="5"
|
||||||
|
IsVisible="{CompiledBinding InstallationInProgress}">
|
||||||
|
<ProgressBar VerticalAlignment="Center"
|
||||||
|
Width="300"
|
||||||
|
Value="{CompiledBinding InstallProgress, FallbackValue=0}">
|
||||||
|
</ProgressBar>
|
||||||
|
<Button
|
||||||
|
Classes="accent"
|
||||||
|
Margin="15 0 0 0"
|
||||||
|
Width="80"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Command="{CompiledBinding Cancel}">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Install button -->
|
||||||
|
<Button IsVisible="{CompiledBinding !InstallationInProgress}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Classes="accent"
|
||||||
|
Width="80"
|
||||||
|
Command="{CompiledBinding Install}">
|
||||||
|
Install
|
||||||
|
</Button>
|
||||||
|
</Panel>
|
||||||
|
</Grid>
|
||||||
<Border Classes="card-separator" />
|
<Border Classes="card-separator" />
|
||||||
<Grid Margin="-5 -10" ColumnDefinitions="*,*,*">
|
<Grid Margin="-5 -10" ColumnDefinitions="*,*,*">
|
||||||
<Grid Grid.Column="0" ColumnDefinitions="*,*" RowDefinitions="*,*,*" Classes="info-container" HorizontalAlignment="Left">
|
<Grid Grid.Column="0" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Left">
|
||||||
<avalonia:MaterialIcon Kind="Calendar" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
|
||||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Release date</TextBlock>
|
|
||||||
<TextBlock Grid.Column="1"
|
|
||||||
Grid.Row="1"
|
|
||||||
Classes="info-body"
|
|
||||||
Text="{CompiledBinding Release.CreatedAt, Converter={StaticResource DateTimeConverter}}" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid Grid.Column="1" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Center">
|
|
||||||
<avalonia:MaterialIcon Kind="TickNetworkOutline" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
<avalonia:MaterialIcon Kind="TickNetworkOutline" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Version</TextBlock>
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Version</TextBlock>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
@ -63,8 +87,17 @@
|
|||||||
Text="{CompiledBinding Release.Version}" />
|
Text="{CompiledBinding Release.Version}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1" ColumnDefinitions="*,*" RowDefinitions="*,*,*" Classes="info-container" HorizontalAlignment="Center">
|
||||||
|
<avalonia:MaterialIcon Kind="Calendar" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">Release date</TextBlock>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="info-body"
|
||||||
|
Text="{CompiledBinding Release.CreatedAt, Converter={StaticResource DateTimeConverter}}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Column="2" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Right">
|
<Grid Grid.Column="2" ColumnDefinitions="*,*" RowDefinitions="*,*" Classes="info-container" HorizontalAlignment="Right">
|
||||||
<avalonia:MaterialIcon Kind="BoxOutline" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
<avalonia:MaterialIcon Kind="File" Grid.Column="0" Grid.RowSpan="2" Classes="info-icon" />
|
||||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">File size</TextBlock>
|
<TextBlock Grid.Column="1" Grid.Row="0" Classes="info-title">File size</TextBlock>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@ -81,7 +114,13 @@
|
|||||||
<TextBlock Grid.Row="0" Classes="h5 no-margin">Release notes</TextBlock>
|
<TextBlock Grid.Row="0" Classes="h5 no-margin">Release notes</TextBlock>
|
||||||
<Border Grid.Row="1" Classes="card-separator" />
|
<Border Grid.Row="1" Classes="card-separator" />
|
||||||
|
|
||||||
<mdxaml:MarkdownScrollViewer Grid.Row="2" Markdown="{CompiledBinding Release.Changelog}" MarkdownStyleName="FluentAvalonia">
|
<TextBlock Grid.Row="2" Classes="subtitle" IsVisible="{CompiledBinding Release.Changelog, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||||
|
There are no release notes for this release.
|
||||||
|
</TextBlock>
|
||||||
|
<mdxaml:MarkdownScrollViewer Grid.Row="2"
|
||||||
|
Markdown="{CompiledBinding Release.Changelog}"
|
||||||
|
MarkdownStyleName="FluentAvalonia"
|
||||||
|
IsVisible="{CompiledBinding Release.Changelog, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||||
<mdxaml:MarkdownScrollViewer.Styles>
|
<mdxaml:MarkdownScrollViewer.Styles>
|
||||||
<StyleInclude Source="/Styles/Markdown.axaml" />
|
<StyleInclude Source="/Styles/Markdown.axaml" />
|
||||||
</mdxaml:MarkdownScrollViewer.Styles>
|
</mdxaml:MarkdownScrollViewer.Styles>
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Screens.Workshop.Parameters;
|
using Artemis.UI.Screens.Workshop.Parameters;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
@ -11,20 +16,84 @@ namespace Artemis.UI.Screens.Workshop.EntryReleases;
|
|||||||
public partial class EntryReleaseViewModel : RoutableScreen<ReleaseDetailParameters>
|
public partial class EntryReleaseViewModel : RoutableScreen<ReleaseDetailParameters>
|
||||||
{
|
{
|
||||||
private readonly IWorkshopClient _client;
|
private readonly IWorkshopClient _client;
|
||||||
[Notify] private IGetReleaseById_Release? _release;
|
private readonly IRouter _router;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly EntryInstallationHandlerFactory _factory;
|
||||||
|
private readonly Progress<StreamProgress> _progress = new();
|
||||||
|
|
||||||
public EntryReleaseViewModel(IWorkshopClient client)
|
[Notify] private IGetReleaseById_Release? _release;
|
||||||
|
[Notify] private float _installProgress;
|
||||||
|
[Notify] private bool _installationInProgress;
|
||||||
|
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
|
public EntryReleaseViewModel(IWorkshopClient client, IRouter router, INotificationService notificationService, IWindowService windowService, EntryInstallationHandlerFactory factory)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_router = router;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
_windowService = windowService;
|
||||||
|
_factory = factory;
|
||||||
|
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Close()
|
||||||
|
{
|
||||||
|
await _router.GoUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Install()
|
||||||
|
{
|
||||||
|
if (Release == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
InstallProgress = 0;
|
||||||
|
InstallationInProgress = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IEntryInstallationHandler handler = _factory.CreateHandler(Release.Entry.EntryType);
|
||||||
|
EntryInstallResult result = await handler.InstallAsync(Release.Entry, Release, _progress, _cts.Token);
|
||||||
|
if (result.IsSuccess)
|
||||||
|
_notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show();
|
||||||
|
else if (!_cts.IsCancellationRequested)
|
||||||
|
_notificationService.CreateNotification().WithTitle("Installation failed").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_windowService.ShowExceptionDialog("Failed to install workshop entry", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
InstallationInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task OnNavigating(ReleaseDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
public override async Task OnNavigating(ReleaseDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IOperationResult<IGetReleaseByIdResult> result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken);
|
IOperationResult<IGetReleaseByIdResult> result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken);
|
||||||
if (result.IsErrorResult())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Release = result.Data?.Release;
|
Release = result.Data?.Release;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Overrides of RoutableScreen
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Task OnClosing(NavigationArguments args)
|
||||||
|
{
|
||||||
|
if (!InstallationInProgress)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
args.Cancel();
|
||||||
|
_notificationService.CreateNotification().WithMessage("Please wait for the installation to finish").Show();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -2,78 +2,38 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop"
|
xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop"
|
||||||
|
xmlns:entryReleases="clr-namespace:Artemis.UI.Screens.Workshop.EntryReleases"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryReleasesView"
|
x:Class="Artemis.UI.Screens.Workshop.EntryReleases.EntryReleasesView"
|
||||||
x:DataType="details:EntryReleasesViewModel">
|
x:DataType="entryReleases:EntryReleasesViewModel">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||||
<sharedConverters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
<sharedConverters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Releases</TextBlock>
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Releases</TextBlock>
|
||||||
<Border Classes="card-separator" />
|
<Border Classes="card-separator" />
|
||||||
|
|
||||||
<Button Margin="0 0 0 5"
|
<ListBox ItemsSource="{CompiledBinding Releases}" SelectedItem="{CompiledBinding SelectedRelease}" Classes="release-list">
|
||||||
HorizontalAlignment="Stretch"
|
<ListBox.ItemTemplate>
|
||||||
HorizontalContentAlignment="Stretch"
|
|
||||||
Command="{CompiledBinding NavigateToRelease}"
|
|
||||||
CommandParameter="{CompiledBinding LatestRelease}">
|
|
||||||
<Grid ColumnDefinitions="Auto,*">
|
|
||||||
<!-- Icon -->
|
|
||||||
<avalonia:MaterialIcon Grid.Column="0" Margin="0 6" Width="50" Height="50" Kind="BoxStar"/>
|
|
||||||
|
|
||||||
<!-- Body -->
|
|
||||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
|
||||||
<TextBlock Text="{CompiledBinding LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
|
||||||
<TextBlock Classes="subtitle">
|
|
||||||
<avalonia:MaterialIcon Kind="File" />
|
|
||||||
<Run Text="{CompiledBinding LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Calendar" />
|
|
||||||
<Run>Created</Run>
|
|
||||||
<Run Text="{CompiledBinding LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{CompiledBinding OtherReleases}">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<StackPanel Spacing="5" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="workshop:IRelease">
|
<DataTemplate x:DataType="workshop:IRelease">
|
||||||
<Button HorizontalAlignment="Stretch"
|
<Grid ColumnDefinitions="Auto,*" Margin="0 5">
|
||||||
HorizontalContentAlignment="Stretch"
|
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||||
Command="{Binding $parent[details:EntryReleasesView].DataContext.NavigateToRelease}"
|
<TextBlock Text="{CompiledBinding Version}"></TextBlock>
|
||||||
CommandParameter="{CompiledBinding}">
|
<TextBlock Classes="subtitle" ToolTip.Tip="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||||
<Grid ColumnDefinitions="Auto,*">
|
<avalonia:MaterialIcon Kind="Calendar" />
|
||||||
<!-- Icon -->
|
<Run>Created</Run>
|
||||||
<avalonia:MaterialIcon Grid.Column="0" Margin="0 6" Width="25" Height="25" Kind="Archive"/>
|
<Run Text="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||||
|
</TextBlock>
|
||||||
<!-- Body -->
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
</Grid>
|
||||||
<TextBlock Text="{CompiledBinding Version, FallbackValue=Version}"></TextBlock>
|
|
||||||
<TextBlock Classes="subtitle" ToolTip.Tip="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Calendar" />
|
|
||||||
<Run>Created</Run>
|
|
||||||
<Run Text="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Button>
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ItemsControl>
|
</ListBox>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,10 +1,9 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
namespace Artemis.UI.Screens.Workshop.EntryReleases;
|
||||||
|
|
||||||
public partial class EntryReleasesView : UserControl
|
public partial class EntryReleasesView : ReactiveUserControl<EntryReleasesViewModel>
|
||||||
{
|
{
|
||||||
public EntryReleasesView()
|
public EntryReleasesView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,51 +2,49 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Threading;
|
using System.Reactive.Disposables;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
|
||||||
using Artemis.UI.Shared.Utilities;
|
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
|
||||||
using Artemis.WebClient.Workshop.Models;
|
using Artemis.WebClient.Workshop.Models;
|
||||||
using Humanizer;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
namespace Artemis.UI.Screens.Workshop.EntryReleases;
|
||||||
|
|
||||||
public class EntryReleasesViewModel : ViewModelBase
|
public partial class EntryReleasesViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly EntryInstallationHandlerFactory _factory;
|
|
||||||
private readonly IWindowService _windowService;
|
|
||||||
private readonly INotificationService _notificationService;
|
|
||||||
private readonly IRouter _router;
|
private readonly IRouter _router;
|
||||||
|
[Notify] private IRelease? _selectedRelease;
|
||||||
|
|
||||||
public EntryReleasesViewModel(IEntryDetails entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService, IRouter router)
|
public EntryReleasesViewModel(IEntryDetails entry, IRouter router)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
|
||||||
_windowService = windowService;
|
|
||||||
_notificationService = notificationService;
|
|
||||||
_router = router;
|
_router = router;
|
||||||
|
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
LatestRelease = Entry.Releases.MaxBy(r => r.CreatedAt);
|
Releases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Take(5).Cast<IRelease>().ToList();
|
||||||
OtherReleases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Skip(1).Take(4).Cast<IRelease>().ToList();
|
|
||||||
|
|
||||||
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
|
||||||
OnInstallationStarted = Confirm;
|
|
||||||
NavigateToRelease = ReactiveCommand.CreateFromTask<IRelease>(ExecuteNavigateToRelease);
|
NavigateToRelease = ReactiveCommand.CreateFromTask<IRelease>(ExecuteNavigateToRelease);
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
router.CurrentPath.Subscribe(p => SelectedRelease = p != null && p.Contains("releases") && float.TryParse(p.Split('/').Last(), out float releaseId)
|
||||||
|
? Releases.FirstOrDefault(r => r.Id == releaseId)
|
||||||
|
: null)
|
||||||
|
.DisposeWith(d);
|
||||||
|
|
||||||
|
this.WhenAnyValue(vm => vm.SelectedRelease)
|
||||||
|
.WhereNotNull()
|
||||||
|
.Subscribe(s => ExecuteNavigateToRelease(s))
|
||||||
|
.DisposeWith(d);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEntryDetails Entry { get; }
|
public IEntryDetails Entry { get; }
|
||||||
public IRelease? LatestRelease { get; }
|
public List<IRelease> Releases { get; }
|
||||||
public List<IRelease> OtherReleases { get; }
|
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
|
||||||
public ReactiveCommand<IRelease, Unit> NavigateToRelease { get; }
|
public ReactiveCommand<IRelease, Unit> NavigateToRelease { get; }
|
||||||
|
|
||||||
public Func<IEntryDetails, IRelease, Task<bool>> OnInstallationStarted { get; set; }
|
public Func<IEntryDetails, IRelease, Task<bool>> OnInstallationStarted { get; set; }
|
||||||
public Func<InstalledEntry, Task>? OnInstallationFinished { get; set; }
|
public Func<InstalledEntry, Task>? OnInstallationFinished { get; set; }
|
||||||
|
|
||||||
@ -67,39 +65,4 @@ public class EntryReleasesViewModel : ViewModelBase
|
|||||||
throw new ArgumentOutOfRangeException(nameof(Entry.EntryType));
|
throw new ArgumentOutOfRangeException(nameof(Entry.EntryType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (LatestRelease == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (await OnInstallationStarted(Entry, LatestRelease))
|
|
||||||
return;
|
|
||||||
|
|
||||||
IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType);
|
|
||||||
EntryInstallResult result = await installationHandler.InstallAsync(Entry, LatestRelease, new Progress<StreamProgress>(), cancellationToken);
|
|
||||||
if (result.IsSuccess && result.Entry != null)
|
|
||||||
{
|
|
||||||
if (OnInstallationFinished != null)
|
|
||||||
await OnInstallationFinished(result.Entry);
|
|
||||||
_notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_notificationService.CreateNotification()
|
|
||||||
.WithTitle($"Failed to install {Entry.EntryType.Humanize(LetterCasing.LowerCase)}")
|
|
||||||
.WithMessage(result.Message)
|
|
||||||
.WithSeverity(NotificationSeverity.Error).Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> Confirm(IEntryDetails entryDetails, IRelease release)
|
|
||||||
{
|
|
||||||
bool confirm = await _windowService.ShowConfirmContentDialog(
|
|
||||||
"Install latest release",
|
|
||||||
$"Are you sure you want to download and install version {release.Version} of {entryDetails.Name}?"
|
|
||||||
);
|
|
||||||
|
|
||||||
return !confirm;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Screens.Workshop.Entries.Details;
|
using Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
using Artemis.UI.Screens.Workshop.EntryReleases;
|
||||||
using Artemis.UI.Screens.Workshop.Layout.Dialogs;
|
using Artemis.UI.Screens.Workshop.Layout.Dialogs;
|
||||||
using Artemis.UI.Screens.Workshop.Parameters;
|
using Artemis.UI.Screens.Workshop.Parameters;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
@ -40,13 +41,15 @@ public partial class LayoutDetailsViewModel : RoutableHostScreen<RoutableScreen,
|
|||||||
Func<IEntryDetails, EntryReleasesViewModel> getEntryReleasesViewModel,
|
Func<IEntryDetails, EntryReleasesViewModel> getEntryReleasesViewModel,
|
||||||
Func<IEntryDetails, EntryImagesViewModel> getEntryImagesViewModel)
|
Func<IEntryDetails, EntryImagesViewModel> getEntryImagesViewModel)
|
||||||
{
|
{
|
||||||
LayoutDescriptionViewModel = layoutDescriptionViewModel;
|
|
||||||
_client = client;
|
_client = client;
|
||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_getEntryInfoViewModel = getEntryInfoViewModel;
|
_getEntryInfoViewModel = getEntryInfoViewModel;
|
||||||
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
||||||
_getEntryImagesViewModel = getEntryImagesViewModel;
|
_getEntryImagesViewModel = getEntryImagesViewModel;
|
||||||
|
|
||||||
|
LayoutDescriptionViewModel = layoutDescriptionViewModel;
|
||||||
|
RecycleScreen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayoutDescriptionViewModel LayoutDescriptionViewModel { get; }
|
public LayoutDescriptionViewModel LayoutDescriptionViewModel { get; }
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Artemis.Core;
|
|||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Screens.Workshop.Entries.Details;
|
using Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
using Artemis.UI.Screens.Workshop.Entries.List;
|
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
|
using Artemis.UI.Screens.Workshop.EntryReleases;
|
||||||
using Artemis.UI.Screens.Workshop.Parameters;
|
using Artemis.UI.Screens.Workshop.Parameters;
|
||||||
using Artemis.UI.Screens.Workshop.Plugins.Dialogs;
|
using Artemis.UI.Screens.Workshop.Plugins.Dialogs;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
@ -41,13 +42,15 @@ public partial class PluginDetailsViewModel : RoutableHostScreen<RoutableScreen,
|
|||||||
Func<IEntryDetails, EntryReleasesViewModel> getEntryReleasesViewModel,
|
Func<IEntryDetails, EntryReleasesViewModel> getEntryReleasesViewModel,
|
||||||
Func<IEntryDetails, EntryImagesViewModel> getEntryImagesViewModel)
|
Func<IEntryDetails, EntryImagesViewModel> getEntryImagesViewModel)
|
||||||
{
|
{
|
||||||
PluginDescriptionViewModel = pluginDescriptionViewModel;
|
|
||||||
_client = client;
|
_client = client;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_getEntryInfoViewModel = getEntryInfoViewModel;
|
_getEntryInfoViewModel = getEntryInfoViewModel;
|
||||||
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
||||||
_getEntryImagesViewModel = getEntryImagesViewModel;
|
_getEntryImagesViewModel = getEntryImagesViewModel;
|
||||||
|
|
||||||
|
PluginDescriptionViewModel = pluginDescriptionViewModel;
|
||||||
|
RecycleScreen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PluginDescriptionViewModel PluginDescriptionViewModel { get; }
|
public PluginDescriptionViewModel PluginDescriptionViewModel { get; }
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Screens.Workshop.Entries.Details;
|
using Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
using Artemis.UI.Screens.Workshop.EntryReleases;
|
||||||
using Artemis.UI.Screens.Workshop.Parameters;
|
using Artemis.UI.Screens.Workshop.Parameters;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
@ -31,12 +32,13 @@ public partial class ProfileDetailsViewModel : RoutableHostScreen<RoutableScreen
|
|||||||
Func<IEntryDetails, EntryReleasesViewModel> getEntryReleasesViewModel,
|
Func<IEntryDetails, EntryReleasesViewModel> getEntryReleasesViewModel,
|
||||||
Func<IEntryDetails, EntryImagesViewModel> getEntryImagesViewModel)
|
Func<IEntryDetails, EntryImagesViewModel> getEntryImagesViewModel)
|
||||||
{
|
{
|
||||||
ProfileDescriptionViewModel = profileDescriptionViewModel;
|
|
||||||
|
|
||||||
_client = client;
|
_client = client;
|
||||||
_getEntryInfoViewModel = getEntryInfoViewModel;
|
_getEntryInfoViewModel = getEntryInfoViewModel;
|
||||||
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
||||||
_getEntryImagesViewModel = getEntryImagesViewModel;
|
_getEntryImagesViewModel = getEntryImagesViewModel;
|
||||||
|
|
||||||
|
ProfileDescriptionViewModel = profileDescriptionViewModel;
|
||||||
|
RecycleScreen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProfileDescriptionViewModel ProfileDescriptionViewModel { get; }
|
public ProfileDescriptionViewModel ProfileDescriptionViewModel { get; }
|
||||||
|
|||||||
@ -6,6 +6,6 @@ namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
|||||||
|
|
||||||
public interface IEntryInstallationHandler
|
public interface IEntryInstallationHandler
|
||||||
{
|
{
|
||||||
Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken);
|
Task<EntryInstallResult> InstallAsync(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken);
|
||||||
Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken);
|
Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
_defaultLayoutProvider = defaultLayoutProvider;
|
_defaultLayoutProvider = defaultLayoutProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
public async Task<EntryInstallResult> InstallAsync(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using MemoryStream stream = new();
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
public async Task<EntryInstallResult> InstallAsync(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Ensure there is an installed entry
|
// Ensure there is an installed entry
|
||||||
InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry.Id);
|
InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry.Id);
|
||||||
|
|||||||
@ -20,7 +20,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
_workshopService = workshopService;
|
_workshopService = workshopService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
public async Task<EntryInstallResult> InstallAsync(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using MemoryStream stream = new();
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public class InstalledEntry
|
|||||||
Load();
|
Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstalledEntry(IEntryDetails entry, IRelease release)
|
public InstalledEntry(IEntrySummary entry, IRelease release)
|
||||||
{
|
{
|
||||||
Entity = new EntryEntity();
|
Entity = new EntryEntity();
|
||||||
|
|
||||||
|
|||||||
@ -2,5 +2,8 @@ query GetReleaseById($id: Long!) {
|
|||||||
release(id: $id) {
|
release(id: $id) {
|
||||||
...release
|
...release
|
||||||
changelog
|
changelog
|
||||||
|
entry {
|
||||||
|
...entrySummary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user