Workshop - Fixed deep linking to an entry

Workshop - Added the ability to upload new releases to existing submissions
This commit is contained in:
Robert committed 2023-09-04 20:30:57 +02:00
1 parent bf3d5fc75d
commit 2ee170b803
25 files changed
+375 -133

No files matched your search

@@ -0,0 +1,7 @@
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
public interface IWorkshopWizardViewModel
{
SubmissionViewModel Screen { get; set; }
bool ShouldClose { get; set; }
}
@@ -0,0 +1,61 @@
<Window 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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:submissionWizard="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:ui="clr-namespace:Artemis.UI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.ReleaseWizardView"
x:DataType="submissionWizard:ReleaseWizardViewModel"
Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Workshop release wizard"
Width="1000"
Height="950"
WindowStartupLocation="CenterOwner">
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto" Margin="0 0 0 15">
<ContentControl Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Margin="0 0 20 0" Content="{CompiledBinding CurrentUserViewModel}"/>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="{CompiledBinding CurrentUserViewModel.Name}" IsVisible="{CompiledBinding !CurrentUserViewModel.IsAnonymous}"/>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}"/>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Top"
Classes="subtitle"
Text="New workshop release" />
</Grid>
<Border Classes="card" Grid.Row="1" Grid.Column="0">
<controls:Frame Name="Frame" IsNavigationStackEnabled="False" CacheSize="0">
<controls:Frame.NavigationPageFactory>
<ui:PageFactory/>
</controls:Frame.NavigationPageFactory>
</controls:Frame>
</Border>
<StackPanel IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNotNull}}"
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="5"
Margin="0 15 0 0">
<Button Command="{CompiledBinding Screen.GoBack}" IsVisible="{CompiledBinding Screen.ShowGoBack}">
Back
</Button>
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80" Content="{CompiledBinding Screen.ContinueText}"/>
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80">
Finish
</Button>
</StackPanel>
</Grid>
</Window>
@@ -0,0 +1,35 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
public partial class ReleaseWizardView: ReactiveAppWindow<ReleaseWizardViewModel>
{
public ReleaseWizardView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d));
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.ShouldClose).Where(c => c).Subscribe(_ => Close()).DisposeWith(d));
}
private void Navigate(SubmissionViewModel viewModel)
{
try
{
Dispatcher.UIThread.Invoke(() => Frame.NavigateFromObject(viewModel));
}
catch (Exception e)
{
ViewModel?.WindowService.ShowExceptionDialog("Wizard screen failed to activate", e);
}
}
}
@@ -0,0 +1,51 @@
using Artemis.UI.Screens.Workshop.CurrentUser;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using DryIoc;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
public class ReleaseWizardViewModel : ActivatableViewModelBase, IWorkshopWizardViewModel
{
private readonly SubmissionWizardState _state;
private SubmissionViewModel? _screen;
private bool _shouldClose;
public ReleaseWizardViewModel(IContainer container, IWindowService windowService, CurrentUserViewModel currentUserViewModel, IGetSubmittedEntryById_Entry entry)
{
_state = new SubmissionWizardState(this, container, windowService)
{
EntryType = entry.EntryType,
EntryId = entry.Id
};
WindowService = windowService;
CurrentUserViewModel = currentUserViewModel;
CurrentUserViewModel.AllowLogout = false;
Entry = entry;
_state.StartForCurrentEntry();
}
public IWindowService WindowService { get; }
public IGetSubmittedEntryById_Entry Entry { get; }
public CurrentUserViewModel CurrentUserViewModel { get; }
public SubmissionViewModel? Screen
{
get => _screen;
set
{
if (value != null)
value.State = _state;
RaiseAndSetIfChanged(ref _screen, value);
}
}
public bool ShouldClose
{
get => _shouldClose;
set => RaiseAndSetIfChanged(ref _shouldClose, value);
}
}
@@ -35,7 +35,6 @@ public class EntryTypeStepViewModel : SubmissionViewModel
return;
State.EntryType = SelectedEntryType.Value;
if (State.EntryType == EntryType.Profile)
State.ChangeScreen<ProfileSelectionStepViewModel>();
State.StartForCurrentEntry();
}
}
@@ -62,6 +62,9 @@ public class ProfileAdaptionHintsStepViewModel : SubmissionViewModel
if (Layers.Any(l => l.AdaptionHintCount == 0))
return;
State.ChangeScreen<SpecificationsStepViewModel>();
if (State.EntryId == null)
State.ChangeScreen<SpecificationsStepViewModel>();
else
State.ChangeScreen<UploadStepViewModel>();
}
}
@@ -1,34 +1,22 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces;
using Artemis.UI.Extensions;
using Artemis.UI.Screens.Workshop.Profile;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Material.Icons;
using Material.Icons.Avalonia;
using ReactiveUI;
using SkiaSharp;
using Path = Avalonia.Controls.Shapes.Path;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
public class ProfileSelectionStepViewModel : SubmissionViewModel
{
private readonly IProfileService _profileService;
private readonly IProfileCategoryRepository _profileCategoryRepository;
private ProfileConfiguration? _selectedProfile;
/// <inheritdoc />
@@ -49,26 +37,12 @@ public class ProfileSelectionStepViewModel : SubmissionViewModel
this.WhenAnyValue(vm => vm.SelectedProfile).Subscribe(p => Update(p));
this.WhenActivated((CompositeDisposable _) =>
{
ShowGoBack = State.EntryId == null;
if (State.EntrySource is ProfileConfiguration profileConfiguration)
SelectedProfile = Profiles.FirstOrDefault(p => p.ProfileId == profileConfiguration.ProfileId);
});
}
private void Update(ProfileConfiguration? profileConfiguration)
{
ProfilePreview.ProfileConfiguration = null;
foreach (ProfileConfiguration configuration in Profiles)
{
if (configuration == profileConfiguration)
_profileService.ActivateProfile(configuration);
else
_profileService.DeactivateProfile(configuration);
}
ProfilePreview.ProfileConfiguration = profileConfiguration;
}
public ObservableCollection<ProfileConfiguration> Profiles { get; }
public ProfilePreviewViewModel ProfilePreview { get; }
@@ -84,6 +58,21 @@ public class ProfileSelectionStepViewModel : SubmissionViewModel
/// <inheritdoc />
public override ReactiveCommand<Unit, Unit> GoBack { get; }
private void Update(ProfileConfiguration? profileConfiguration)
{
ProfilePreview.ProfileConfiguration = null;
foreach (ProfileConfiguration configuration in Profiles)
{
if (configuration == profileConfiguration)
_profileService.ActivateProfile(configuration);
else
_profileService.DeactivateProfile(configuration);
}
ProfilePreview.ProfileConfiguration = profileConfiguration;
}
private void ExecuteContinue()
{
if (SelectedProfile == null)
@@ -1,37 +1,36 @@
using System;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Exceptions;
using Artemis.WebClient.Workshop.Services;
using Artemis.WebClient.Workshop.UploadHandlers;
using ReactiveUI;
using StrawberryShake;
using System.Reactive.Disposables;
using Artemis.Core;
using Artemis.UI.Shared.Routing;
using System;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.Services;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
public class UploadStepViewModel : SubmissionViewModel
{
private readonly EntryUploadHandlerFactory _entryUploadHandlerFactory;
private readonly Progress<StreamProgress> _progress = new();
private readonly ObservableAsPropertyHelper<bool> _progressIndeterminate;
private readonly ObservableAsPropertyHelper<int> _progressPercentage;
private readonly IRouter _router;
private readonly IWindowService _windowService;
private readonly IWorkshopClient _workshopClient;
private readonly IWorkshopService _workshopService;
private readonly EntryUploadHandlerFactory _entryUploadHandlerFactory;
private readonly IWindowService _windowService;
private readonly IRouter _router;
private readonly Progress<StreamProgress> _progress = new();
private readonly ObservableAsPropertyHelper<int> _progressPercentage;
private readonly ObservableAsPropertyHelper<bool> _progressIndeterminate;
private Guid? _entryId;
private bool _failed;
private bool _finished;
private bool _succeeded;
private bool _failed;
/// <inheritdoc />
public UploadStepViewModel(IWorkshopClient workshopClient, IWorkshopService workshopService, EntryUploadHandlerFactory entryUploadHandlerFactory, IWindowService windowService, IRouter router)
@@ -83,7 +82,45 @@ public class UploadStepViewModel : SubmissionViewModel
set => RaiseAndSetIfChanged(ref _failed, value);
}
public async Task ExecuteUpload(CancellationToken cancellationToken)
private async Task ExecuteUpload(CancellationToken cancellationToken)
{
// Use the existing entry or create a new one
_entryId = State.EntryId ?? await CreateEntry(cancellationToken);
// If a new entry had to be created but that failed, stop here, CreateEntry will send the user back
if (_entryId == null)
return;
try
{
IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType);
EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, _progress, cancellationToken);
if (!uploadResult.IsSuccess)
{
string? message = uploadResult.Message;
if (message != null)
message += "\r\n\r\n";
else
message = "";
message += "Your submission has still been saved, you may try to upload a new release";
await _windowService.ShowConfirmContentDialog("Failed to upload workshop entry", message, "Close", null);
}
Succeeded = true;
}
catch (Exception)
{
// Something went wrong when creating a release :c
// We'll keep the workshop entry so that the user can make changes and try again
Failed = true;
}
finally
{
Finished = true;
}
}
private async Task<Guid?> CreateEntry(CancellationToken cancellationToken)
{
IOperationResult<IAddEntryResult> result = await _workshopClient.AddEntry.ExecuteAsync(new CreateEntryInput
{
@@ -100,68 +137,45 @@ public class UploadStepViewModel : SubmissionViewModel
{
await _windowService.ShowConfirmContentDialog("Failed to create workshop entry", result.Errors.ToString() ?? "Not even an error message", "Close", null);
State.ChangeScreen<SubmitStepViewModel>();
return;
return null;
}
if (cancellationToken.IsCancellationRequested)
return;
{
State.ChangeScreen<SubmitStepViewModel>();
return null;
}
if (State.Icon == null)
return entryId;
// Upload image
if (State.Icon != null)
await _workshopService.SetEntryIcon(entryId.Value, _progress, State.Icon, cancellationToken);
// Create the workshop entry
try
{
IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType);
EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(entryId.Value, State.EntrySource!, _progress, cancellationToken);
if (!uploadResult.IsSuccess)
{
string? message = uploadResult.Message;
if (message != null)
message += "\r\n\r\n";
else
message = "";
message += "Your submission has still been saved, you may try to upload a new release";
await _windowService.ShowConfirmContentDialog("Failed to upload workshop entry", message, "Close", null);
return;
}
_entryId = entryId;
Succeeded = true;
ImageUploadResult imageUploadResult = await _workshopService.SetEntryIcon(entryId.Value, _progress, State.Icon, cancellationToken);
if (!imageUploadResult.IsSuccess)
throw new ArtemisWorkshopException(imageUploadResult.Message);
}
catch (Exception e)
{
// Something went wrong when creating a release :c
// We'll keep the workshop entry so that the user can make changes and try again
Failed = true;
}
finally
{
Finished = true;
// It's not critical if this fails
await _windowService.ShowConfirmContentDialog(
"Failed to upload icon",
"Your submission will continue, you can try upload a new image afterwards\r\n" + e.Message,
"Continue",
null
);
}
return entryId;
}
private async Task ExecuteContinue()
{
State.Finish();
State.Close();
if (_entryId == null)
return;
switch (State.EntryType)
{
case EntryType.Layout:
await _router.Navigate($"workshop/entries/layouts/{_entryId.Value}");
break;
case EntryType.Plugin:
await _router.Navigate($"workshop/entries/plugins/{_entryId.Value}");
break;
case EntryType.Profile:
await _router.Navigate($"workshop/entries/profiles/{_entryId.Value}");
break;
default:
throw new ArgumentOutOfRangeException();
}
if (_entryId != null)
await _router.Navigate($"workshop/library/submissions/{_entryId.Value}");
}
}
@@ -11,7 +11,6 @@ public abstract class SubmissionViewModel : ValidatableViewModelBase
private bool _showGoBack = true;
private bool _showHeader = true;
public SubmissionWizardViewModel WizardViewModel { get; set; } = null!;
public SubmissionWizardState State { get; set; } = null!;
public abstract ReactiveCommand<Unit, Unit> Continue { get; }
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using DryIoc;
@@ -11,9 +12,9 @@ public class SubmissionWizardState
{
private readonly IContainer _container;
private readonly IWindowService _windowService;
private readonly SubmissionWizardViewModel _wizardViewModel;
private readonly IWorkshopWizardViewModel _wizardViewModel;
public SubmissionWizardState(SubmissionWizardViewModel wizardViewModel, IContainer container, IWindowService windowService)
public SubmissionWizardState(IWorkshopWizardViewModel wizardViewModel, IContainer container, IWindowService windowService)
{
_wizardViewModel = wizardViewModel;
_container = container;
@@ -21,6 +22,7 @@ public class SubmissionWizardState
}
public EntryType EntryType { get; set; }
public Guid? EntryId { get; set; }
public string Name { get; set; } = string.Empty;
public Stream? Icon { get; set; }
@@ -45,13 +47,16 @@ public class SubmissionWizardState
}
}
public void Finish()
public void Close()
{
_wizardViewModel.Close(true);
_wizardViewModel.ShouldClose = true;
}
public void Cancel()
public void StartForCurrentEntry()
{
_wizardViewModel.Close(false);
if (EntryType == EntryType.Profile)
ChangeScreen<ProfileSelectionStepViewModel>();
else
throw new NotImplementedException();
}
}
@@ -18,7 +18,7 @@
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto" Margin="0 0 0 15">
<ContentControl Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Margin="0 0 20 0" Content="{CompiledBinding CurrentUserViewModel}"/>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="{CompiledBinding CurrentUserViewModel.Name}" IsVisible="{CompiledBinding !CurrentUserViewModel.IsAnonymous}"/>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Anon 🕵️" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}"/>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom" Text="Not logged in" IsVisible="{CompiledBinding CurrentUserViewModel.IsAnonymous}"/>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=workshop-wizard">
@@ -1,5 +1,6 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Threading;
@@ -17,6 +18,7 @@ public partial class SubmissionWizardView : ReactiveAppWindow<SubmissionWizardVi
#endif
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d));
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.ShouldClose).Where(c => c).Subscribe(_ => Close()).DisposeWith(d));
}
private void Navigate(SubmissionViewModel viewModel)
@@ -6,16 +6,19 @@ using DryIoc;
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
public class SubmissionWizardViewModel : DialogViewModelBase<bool>
public class SubmissionWizardViewModel : ActivatableViewModelBase, IWorkshopWizardViewModel
{
private readonly SubmissionWizardState _state;
private SubmissionViewModel _screen;
private bool _shouldClose;
public SubmissionWizardViewModel(IContainer container, IWindowService windowService, CurrentUserViewModel currentUserViewModel, WelcomeStepViewModel welcomeStepViewModel)
public SubmissionWizardViewModel(IContainer container,
IWindowService windowService,
CurrentUserViewModel currentUserViewModel,
WelcomeStepViewModel welcomeStepViewModel)
{
_state = new SubmissionWizardState(this, container, windowService);
_screen = welcomeStepViewModel;
_screen.WizardViewModel = this;
_screen.State = _state;
WindowService = windowService;
@@ -31,9 +34,14 @@ public class SubmissionWizardViewModel : DialogViewModelBase<bool>
get => _screen;
set
{
value.WizardViewModel = this;
value.State = _state;
RaiseAndSetIfChanged(ref _screen, value);
}
}
public bool ShouldClose
{
get => _shouldClose;
set => RaiseAndSetIfChanged(ref _shouldClose, value);
}
}