From 2ee170b80374d847466229a3a3bda91a9c5807f7 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 4 Sep 2023 20:30:57 +0200 Subject: [PATCH] Workshop - Fixed deep linking to an entry Workshop - Added the ability to upload new releases to existing submissions --- .../Routing/Router/Navigation.cs | 6 +- .../Routing/Router/Router.cs | 8 +- .../Routing/Router/RouterNavigationOptions.cs | 24 ++++ src/Artemis.UI/Routing/RouteViewModel.cs | 8 +- .../Workshop/Entries/EntriesViewModel.cs | 2 +- .../Workshop/Home/WorkshopHomeViewModel.cs | 8 +- .../Library/SubmissionDetailView.axaml | 4 + .../Library/SubmissionDetailViewModel.cs | 38 ++++- .../Library/Tabs/SubmissionsTabViewModel.cs | 2 +- .../IWorkshopWizardViewModel.cs | 7 + .../SubmissionWizard/ReleaseWizardView.axaml | 61 ++++++++ .../ReleaseWizardView.axaml.cs | 35 +++++ .../ReleaseWizardViewModel.cs | 51 +++++++ .../Steps/EntryTypeStepViewModel.cs | 3 +- .../ProfileAdaptionHintsStepViewModel.cs | 5 +- .../Profile/ProfileSelectionStepViewModel.cs | 43 +++--- .../Steps/UploadStepViewModel.cs | 136 ++++++++++-------- .../SubmissionWizard/SubmissionViewModel.cs | 1 - .../SubmissionWizard/SubmissionWizardState.cs | 17 ++- .../SubmissionWizardView.axaml | 2 +- .../SubmissionWizardView.axaml.cs | 2 + .../SubmissionWizardViewModel.cs | 16 ++- .../Artemis.WebClient.Workshop.csproj | 3 + .../Queries/UpdateEntry.graphql | 5 + .../Services/IWorkshopService.cs | 21 ++- 25 files changed, 375 insertions(+), 133 deletions(-) create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/IWorkshopWizardViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardViewModel.cs create mode 100644 src/Artemis.WebClient.Workshop/Queries/UpdateEntry.graphql diff --git a/src/Artemis.UI.Shared/Routing/Router/Navigation.cs b/src/Artemis.UI.Shared/Routing/Router/Navigation.cs index 50505a706..0d0f6083c 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Navigation.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Navigation.cs @@ -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) diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index 84e44c4ac..282511c40 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -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); } /// @@ -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; diff --git a/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs b/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs index a5c63ab88..273f85c2c 100644 --- a/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs +++ b/src/Artemis.UI.Shared/Routing/Router/RouterNavigationOptions.cs @@ -1,3 +1,5 @@ +using System; + namespace Artemis.UI.Shared.Routing; /// @@ -21,9 +23,31 @@ public class RouterNavigationOptions /// If set to true, a route change from page/subpage1/subpage2 to page/subpage1 will be ignored. public bool IgnoreOnPartialMatch { get; set; } = false; + /// + /// Gets or sets the path to use when determining whether the path is a partial match, + /// only has any effect if is . + /// + public string? PartialMatchOverride { get; set; } + /// /// Gets or sets a boolean value indicating whether logging should be enabled. /// Errors and warnings are always logged. /// public bool EnableLogging { get; set; } = true; + + /// + /// Determines whether the given two paths are considered equal using these navigation options. + /// + /// The current path. + /// The target path. + /// if the paths are considered equal; otherwise . + 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); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Routing/RouteViewModel.cs b/src/Artemis.UI/Routing/RouteViewModel.cs index 78cc458c9..df49bc79c 100644 --- a/src/Artemis.UI/Routing/RouteViewModel.cs +++ b/src/Artemis.UI/Routing/RouteViewModel.cs @@ -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); } /// diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs index 903a4d649..0d966ba89 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs @@ -34,7 +34,7 @@ public class EntriesViewModel : RoutableHostScreen // 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); }); } diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs index 07fb24452..a0f82de17 100644 --- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs @@ -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(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(); + await _windowService.ShowDialogAsync(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml index 4967d463b..c42098365 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml @@ -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 @@ + + View workshop page + diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs index 869080c80..11d35afb1 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs @@ -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 _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, + IHttpClientFactory httpClientFactory, IWindowService windowService, - IRouter router) + IWorkshopService workshopService, + IRouter router, + Func 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 Save { get; } public ReactiveCommand CreateRelease { get; } public ReactiveCommand DeleteSubmission { get; } @@ -57,6 +65,8 @@ public class SubmissionDetailViewModel : RoutableScreen RaiseAndSetIfChanged(ref _entry, value); } + public ReactiveCommand ViewWorkshopPage { get; } + public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { IOperationResult result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken); @@ -107,16 +117,24 @@ public class SubmissionDetailViewModel : RoutableScreen result = await _client.UpdateEntry.ExecuteAsync(input, cancellationToken); + result.EnsureNoErrors(); + } + + private async Task ExecuteCreateRelease(CancellationToken cancellationToken) + { + if (Entry != null) + await _windowService.ShowDialogAsync(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(); + await _windowService.ShowDialogAsync(); } private async Task ExecuteNavigateToEntry(IGetSubmittedEntries_SubmittedEntries entry, CancellationToken cancellationToken) diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/IWorkshopWizardViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/IWorkshopWizardViewModel.cs new file mode 100644 index 000000000..58724c14c --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/IWorkshopWizardViewModel.cs @@ -0,0 +1,7 @@ +namespace Artemis.UI.Screens.Workshop.SubmissionWizard; + +public interface IWorkshopWizardViewModel +{ + SubmissionViewModel Screen { get; set; } + bool ShouldClose { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml new file mode 100644 index 000000000..522bd4d33 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs new file mode 100644 index 000000000..50bbc4ace --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs @@ -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 +{ + 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); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardViewModel.cs new file mode 100644 index 000000000..8ad0db0f6 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardViewModel.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/EntryTypeStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/EntryTypeStepViewModel.cs index cce78ea9d..620b0e6f4 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/EntryTypeStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/EntryTypeStepViewModel.cs @@ -35,7 +35,6 @@ public class EntryTypeStepViewModel : SubmissionViewModel return; State.EntryType = SelectedEntryType.Value; - if (State.EntryType == EntryType.Profile) - State.ChangeScreen(); + State.StartForCurrentEntry(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs index 768ca5405..b9ec09c15 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs @@ -62,6 +62,9 @@ public class ProfileAdaptionHintsStepViewModel : SubmissionViewModel if (Layers.Any(l => l.AdaptionHintCount == 0)) return; - State.ChangeScreen(); + if (State.EntryId == null) + State.ChangeScreen(); + else + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs index 3cc2b409a..86b49a6d4 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs @@ -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; /// @@ -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 Profiles { get; } public ProfilePreviewViewModel ProfilePreview { get; } @@ -84,6 +58,21 @@ public class ProfileSelectionStepViewModel : SubmissionViewModel /// public override ReactiveCommand 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) diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs index fc726e908..c98e44efa 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs @@ -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 _progress = new(); + private readonly ObservableAsPropertyHelper _progressIndeterminate; + private readonly ObservableAsPropertyHelper _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 _progress = new(); - private readonly ObservableAsPropertyHelper _progressPercentage; - private readonly ObservableAsPropertyHelper _progressIndeterminate; private Guid? _entryId; + private bool _failed; private bool _finished; private bool _succeeded; - private bool _failed; /// 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 CreateEntry(CancellationToken cancellationToken) { IOperationResult 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(); - return; + return null; } if (cancellationToken.IsCancellationRequested) - return; + { + State.ChangeScreen(); + 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}"); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionViewModel.cs index 3cd79cb65..fec465981 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionViewModel.cs @@ -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 Continue { get; } diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardState.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardState.cs index 3484c8c36..b6f8d53fc 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardState.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardState.cs @@ -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(); + else + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml index e313f0f9f..d64ed0199 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml @@ -18,7 +18,7 @@ - + diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs index cb510d01e..6c7ece92f 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs @@ -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 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) diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs index f2361b8ed..a18c63e71 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs @@ -6,16 +6,19 @@ using DryIoc; namespace Artemis.UI.Screens.Workshop.SubmissionWizard; -public class SubmissionWizardViewModel : DialogViewModelBase +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 get => _screen; set { - value.WizardViewModel = this; value.State = _state; RaiseAndSetIfChanged(ref _screen, value); } } + + public bool ShouldClose + { + get => _shouldClose; + set => RaiseAndSetIfChanged(ref _shouldClose, value); + } } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj index 32d841d41..389fa75e5 100644 --- a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj +++ b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj @@ -44,5 +44,8 @@ MSBuild:GenerateGraphQLCode + + MSBuild:GenerateGraphQLCode + diff --git a/src/Artemis.WebClient.Workshop/Queries/UpdateEntry.graphql b/src/Artemis.WebClient.Workshop/Queries/UpdateEntry.graphql new file mode 100644 index 000000000..fe53ec3e5 --- /dev/null +++ b/src/Artemis.WebClient.Workshop/Queries/UpdateEntry.graphql @@ -0,0 +1,5 @@ +mutation UpdateEntry ($input: UpdateEntryInput!) { + updateEntry(input: $input) { + id + } +} diff --git a/src/Artemis.WebClient.Workshop/Services/IWorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/IWorkshopService.cs index f859a4f2c..5c7dbd35f 100644 --- a/src/Artemis.WebClient.Workshop/Services/IWorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/IWorkshopService.cs @@ -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 SetEntryIcon(Guid entryId, Progress progress, Stream icon, CancellationToken cancellationToken); Task GetWorkshopStatus(CancellationToken cancellationToken); Task ValidateWorkshopStatus(CancellationToken cancellationToken); - + Task NavigateToEntry(Guid entryId, EntryType entryType); + public record WorkshopStatus(bool IsReachable, string Message); } \ No newline at end of file