1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Workshop - Fixed deep linking to an entry

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

View File

@ -118,11 +118,9 @@ internal class Navigation
Completed = true;
}
public bool PathEquals(string path, bool allowPartialMatch)
public bool PathEquals(string path, RouterNavigationOptions options)
{
if (allowPartialMatch)
return _resolution.Path.StartsWith(path, StringComparison.InvariantCultureIgnoreCase);
return string.Equals(_resolution.Path, path, StringComparison.InvariantCultureIgnoreCase);
return options.PathEquals(_resolution.Path, path);
}
private bool CancelIfRequested(NavigationArguments args, string stage, object screen)

View File

@ -58,11 +58,9 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
return false;
}
private bool PathEquals(string path, bool allowPartialMatch)
private bool PathEquals(string path, RouterNavigationOptions options)
{
if (allowPartialMatch)
return _currentRouteSubject.Value != null && _currentRouteSubject.Value.StartsWith(path, StringComparison.InvariantCultureIgnoreCase);
return string.Equals(_currentRouteSubject.Value, path, StringComparison.InvariantCultureIgnoreCase);
return _currentRouteSubject.Value != null && options.PathEquals(_currentRouteSubject.Value, path);
}
/// <inheritdoc />
@ -84,7 +82,7 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
{
if (_root == null)
throw new ArtemisRoutingException("Cannot navigate without a root having been set");
if (PathEquals(path, options.IgnoreOnPartialMatch) || (_currentNavigation != null && _currentNavigation.PathEquals(path, options.IgnoreOnPartialMatch)))
if (PathEquals(path, options) || (_currentNavigation != null && _currentNavigation.PathEquals(path, options)))
return;
string? previousPath = _currentRouteSubject.Value;

View File

@ -1,3 +1,5 @@
using System;
namespace Artemis.UI.Shared.Routing;
/// <summary>
@ -21,9 +23,31 @@ public class RouterNavigationOptions
/// <example>If set to true, a route change from <c>page/subpage1/subpage2</c> to <c>page/subpage1</c> will be ignored.</example>
public bool IgnoreOnPartialMatch { get; set; } = false;
/// <summary>
/// Gets or sets the path to use when determining whether the path is a partial match,
/// only has any effect if <see cref="IgnoreOnPartialMatch"/> is <see langword="true"/>.
/// </summary>
public string? PartialMatchOverride { get; set; }
/// <summary>
/// Gets or sets a boolean value indicating whether logging should be enabled.
/// <remarks>Errors and warnings are always logged.</remarks>
/// </summary>
public bool EnableLogging { get; set; } = true;
/// <summary>
/// Determines whether the given two paths are considered equal using these navigation options.
/// </summary>
/// <param name="current">The current path.</param>
/// <param name="target">The target path.</param>
/// <returns><see langword="true"/> if the paths are considered equal; otherwise <see langword="false"/>.</returns>
internal bool PathEquals(string current, string target)
{
if (PartialMatchOverride != null && IgnoreOnPartialMatch)
target = PartialMatchOverride;
if (IgnoreOnPartialMatch)
return current.StartsWith(target, StringComparison.InvariantCultureIgnoreCase);
return string.Equals(current, target, StringComparison.InvariantCultureIgnoreCase);
}
}

View File

@ -4,20 +4,20 @@ namespace Artemis.UI.Routing;
public class RouteViewModel
{
public RouteViewModel(string name, string path, string? mathPath = null)
public RouteViewModel(string name, string path, string? matchPath = null)
{
Path = path;
Name = name;
MathPath = mathPath;
MatchPath = matchPath;
}
public string Path { get; }
public string Name { get; }
public string? MathPath { get; }
public string? MatchPath { get; }
public bool Matches(string path)
{
return path.StartsWith(MathPath ?? Path, StringComparison.InvariantCultureIgnoreCase);
return path.StartsWith(MatchPath ?? Path, StringComparison.InvariantCultureIgnoreCase);
}
/// <inheritdoc />

View File

@ -34,7 +34,7 @@ public class EntriesViewModel : RoutableHostScreen<RoutableScreen>
// Navigate on tab change
this.WhenAnyValue(vm => vm.SelectedTab)
.WhereNotNull()
.Subscribe(s => router.Navigate(s.Path, new RouterNavigationOptions {IgnoreOnPartialMatch = true}))
.Subscribe(s => router.Navigate(s.Path, new RouterNavigationOptions {IgnoreOnPartialMatch = true, PartialMatchOverride = s.MatchPath}))
.DisposeWith(d);
});
}

View File

@ -1,15 +1,11 @@
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Extensions;
using Artemis.UI.Screens.Workshop.SubmissionWizard;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Home;
@ -17,13 +13,11 @@ namespace Artemis.UI.Screens.Workshop.Home;
public class WorkshopHomeViewModel : RoutableScreen
{
private readonly IWindowService _windowService;
private readonly IWorkshopService _workshopService;
private bool _workshopReachable;
public WorkshopHomeViewModel(IRouter router, IWindowService windowService, IWorkshopService workshopService)
{
_windowService = windowService;
_workshopService = workshopService;
AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission, this.WhenAnyValue(vm => vm.WorkshopReachable));
Navigate = ReactiveCommand.CreateFromTask<string>(async r => await router.Navigate(r), this.WhenAnyValue(vm => vm.WorkshopReachable));
@ -42,6 +36,6 @@ public class WorkshopHomeViewModel : RoutableScreen
private async Task ExecuteAddSubmission(CancellationToken arg)
{
await _windowService.ShowDialogAsync<SubmissionWizardViewModel, bool>();
await _windowService.ShowDialogAsync<SubmissionWizardViewModel>();
}
}

View File

@ -5,6 +5,7 @@
xmlns:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionDetailView"
x:DataType="library:SubmissionDetailViewModel">
@ -43,6 +44,9 @@
</StackPanel>
</StackPanel>
</Border>
<controls:HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center">
View workshop page
</controls:HyperlinkButton>
</StackPanel>
<ContentControl Grid.Column="1" Content="{CompiledBinding EntrySpecificationsViewModel}"></ContentControl>
</Grid>

View File

@ -7,9 +7,11 @@ using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Screens.Workshop.Entries;
using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Screens.Workshop.SubmissionWizard;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services;
using Avalonia.Media.Imaging;
using ReactiveUI;
using StrawberryShake;
@ -22,26 +24,32 @@ public class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailParameters
private readonly IHttpClientFactory _httpClientFactory;
private readonly Func<EntrySpecificationsViewModel> _getEntrySpecificationsViewModel;
private readonly IWindowService _windowService;
private readonly IWorkshopService _workshopService;
private readonly IRouter _router;
private IGetSubmittedEntryById_Entry? _entry;
private EntrySpecificationsViewModel? _entrySpecificationsViewModel;
public SubmissionDetailViewModel(IWorkshopClient client,
IHttpClientFactory httpClientFactory,
Func<EntrySpecificationsViewModel> entrySpecificationsViewModel,
IHttpClientFactory httpClientFactory,
IWindowService windowService,
IRouter router)
IWorkshopService workshopService,
IRouter router,
Func<EntrySpecificationsViewModel> entrySpecificationsViewModel)
{
_client = client;
_httpClientFactory = httpClientFactory;
_getEntrySpecificationsViewModel = entrySpecificationsViewModel;
_windowService = windowService;
_workshopService = workshopService;
_router = router;
_getEntrySpecificationsViewModel = entrySpecificationsViewModel;
Save = ReactiveCommand.CreateFromTask(ExecuteSave);
CreateRelease = ReactiveCommand.CreateFromTask(ExecuteCreateRelease);
DeleteSubmission = ReactiveCommand.CreateFromTask(ExecuteDeleteSubmission);
ViewWorkshopPage = ReactiveCommand.CreateFromTask(ExecuteViewWorkshopPage);
}
public ReactiveCommand<Unit, Unit> Save { get; }
public ReactiveCommand<Unit, Unit> CreateRelease { get; }
public ReactiveCommand<Unit, Unit> DeleteSubmission { get; }
@ -57,6 +65,8 @@ public class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailParameters
set => RaiseAndSetIfChanged(ref _entry, value);
}
public ReactiveCommand<Unit, Unit> ViewWorkshopPage { get; }
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{
IOperationResult<IGetSubmittedEntryByIdResult> result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken);
@ -107,16 +117,24 @@ public class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailParameters
}
}
private Task ExecuteCreateRelease(CancellationToken cancellationToken)
private async Task ExecuteSave(CancellationToken cancellationToken)
{
throw new NotImplementedException();
UpdateEntryInput input = new();
IOperationResult<IUpdateEntryResult> result = await _client.UpdateEntry.ExecuteAsync(input, cancellationToken);
result.EnsureNoErrors();
}
private async Task ExecuteCreateRelease(CancellationToken cancellationToken)
{
if (Entry != null)
await _windowService.ShowDialogAsync<ReleaseWizardViewModel>(Entry);
}
private async Task ExecuteDeleteSubmission(CancellationToken cancellationToken)
{
if (Entry == null)
return;
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Delete submission?",
"You cannot undo this by yourself.\r\n" +
@ -128,4 +146,10 @@ public class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailParameters
result.EnsureNoErrors();
await _router.Navigate("workshop/library/submissions");
}
private async Task ExecuteViewWorkshopPage()
{
if (Entry != null)
await _workshopService.NavigateToEntry(Entry.Id, Entry.EntryType);
}
}

View File

@ -75,7 +75,7 @@ public class SubmissionsTabViewModel : RoutableScreen
private async Task ExecuteAddSubmission(CancellationToken arg)
{
await _windowService.ShowDialogAsync<SubmissionWizardViewModel, bool>();
await _windowService.ShowDialogAsync<SubmissionWizardViewModel>();
}
private async Task ExecuteNavigateToEntry(IGetSubmittedEntries_SubmittedEntries entry, CancellationToken cancellationToken)

View File

@ -0,0 +1,7 @@
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
public interface IWorkshopWizardViewModel
{
SubmissionViewModel Screen { get; set; }
bool ShouldClose { get; set; }
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -35,7 +35,6 @@ public class EntryTypeStepViewModel : SubmissionViewModel
return;
State.EntryType = SelectedEntryType.Value;
if (State.EntryType == EntryType.Profile)
State.ChangeScreen<ProfileSelectionStepViewModel>();
State.StartForCurrentEntry();
}
}

View File

@ -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>();
}
}

View File

@ -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)

View File

@ -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}");
}
}

View File

@ -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; }

View File

@ -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();
}
}

View File

@ -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">

View File

@ -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)

View File

@ -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);
}
}

View File

@ -44,5 +44,8 @@
<GraphQL Update="Queries\RemoveEntry.graphql">
<Generator>MSBuild:GenerateGraphQLCode</Generator>
</GraphQL>
<GraphQL Update="Queries\UpdateEntry.graphql">
<Generator>MSBuild:GenerateGraphQLCode</Generator>
</GraphQL>
</ItemGroup>
</Project>

View File

@ -0,0 +1,5 @@
mutation UpdateEntry ($input: UpdateEntryInput!) {
updateEntry(input: $input) {
id
}
}

View File

@ -64,6 +64,24 @@ public class WorkshopService : IWorkshopService
await _router.Navigate($"workshop/offline/{status.Message}");
return status.IsReachable;
}
public async Task NavigateToEntry(Guid entryId, EntryType entryType)
{
switch (entryType)
{
case EntryType.Profile:
await _router.Navigate($"workshop/entries/profiles/details/{entryId}");
break;
case EntryType.Layout:
await _router.Navigate($"workshop/entries/layouts/details/{entryId}");
break;
case EntryType.Plugin:
await _router.Navigate($"workshop/entries/plugins/details/{entryId}");
break;
default:
throw new ArgumentOutOfRangeException(nameof(entryType));
}
}
}
public interface IWorkshopService
@ -71,6 +89,7 @@ public interface IWorkshopService
Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
Task<WorkshopStatus> GetWorkshopStatus(CancellationToken cancellationToken);
Task<bool> ValidateWorkshopStatus(CancellationToken cancellationToken);
Task NavigateToEntry(Guid entryId, EntryType entryType);
public record WorkshopStatus(bool IsReachable, string Message);
}