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:
parent
bf3d5fc75d
commit
2ee170b803
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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 />
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -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>();
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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&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&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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
mutation UpdateEntry ($input: UpdateEntryInput!) {
|
||||
updateEntry(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user