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

Workshop - Fixed current user avatar not displaying

Workshop - Improve error handling while uploading new entries
This commit is contained in:
RobertBeekman 2024-01-30 13:14:24 +01:00
parent c29d30d222
commit 17bd62e673
8 changed files with 51 additions and 70 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@
xmlns:currentUser="clr-namespace:Artemis.UI.Screens.Workshop.CurrentUser" xmlns:currentUser="clr-namespace:Artemis.UI.Screens.Workshop.CurrentUser"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView" x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView"
x:DataType="currentUser:CurrentUserViewModel"> x:DataType="currentUser:CurrentUserViewModel">
@ -27,13 +28,16 @@
</Ellipse> </Ellipse>
<!-- Signed in --> <!-- Signed in -->
<Ellipse Height="{CompiledBinding Bounds.Height, ElementName=Container}" Width="{CompiledBinding Bounds.Height, ElementName=Container}" IsVisible="{CompiledBinding !IsAnonymous}" Name="UserMenu"> <Ellipse Height="{CompiledBinding Bounds.Height, ElementName=Container}"
Width="{CompiledBinding Bounds.Height, ElementName=Container}"
IsVisible="{CompiledBinding !IsAnonymous}"
Name="UserMenu">
<Ellipse.ContextFlyout> <Ellipse.ContextFlyout>
<Flyout> <Flyout>
<Grid ColumnDefinitions="Auto,*" RowDefinitions="*,*,*" MinWidth="300"> <Grid ColumnDefinitions="Auto,*" RowDefinitions="*,*,*" MinWidth="300">
<Ellipse Grid.Column="0" Grid.RowSpan="3" Height="50" Width="50" Margin="0 0 8 0" VerticalAlignment="Top"> <Ellipse Grid.Column="0" Grid.RowSpan="3" Height="50" Width="50" Margin="0 0 8 0" VerticalAlignment="Top">
<Ellipse.Fill> <Ellipse.Fill>
<ImageBrush Source="{CompiledBinding Avatar}" /> <ImageBrush asyncImageLoader:ImageBrushLoader.Source="{CompiledBinding AvatarUrl}" />
</Ellipse.Fill> </Ellipse.Fill>
</Ellipse> </Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{CompiledBinding Name}" Margin="0 4 0 0"></TextBlock> <TextBlock Grid.Column="1" Grid.Row="0" Text="{CompiledBinding Name}" Margin="0 4 0 0"></TextBlock>
@ -51,7 +55,7 @@
</Flyout> </Flyout>
</Ellipse.ContextFlyout> </Ellipse.ContextFlyout>
<Ellipse.Fill> <Ellipse.Fill>
<ImageBrush Source="{CompiledBinding Avatar}" /> <ImageBrush asyncImageLoader:ImageBrushLoader.Source="{CompiledBinding AvatarUrl}" />
</Ellipse.Fill> </Ellipse.Fill>
</Ellipse> </Ellipse>
</Panel> </Panel>

View File

@ -21,7 +21,6 @@ public partial class CurrentUserViewModel : ActivatableViewModelBase
{ {
private readonly IAuthenticationService _authenticationService; private readonly IAuthenticationService _authenticationService;
private readonly ObservableAsPropertyHelper<bool> _isAnonymous; private readonly ObservableAsPropertyHelper<bool> _isAnonymous;
private readonly HttpClient _httpClient;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
[Notify] private bool _allowLogout = true; [Notify] private bool _allowLogout = true;
@ -30,13 +29,13 @@ public partial class CurrentUserViewModel : ActivatableViewModelBase
[Notify(Setter.Private)] private bool _loading = true; [Notify(Setter.Private)] private bool _loading = true;
[Notify(Setter.Private)] private string? _name; [Notify(Setter.Private)] private string? _name;
[Notify(Setter.Private)] private string? _userId; [Notify(Setter.Private)] private string? _userId;
[Notify(Setter.Private)] private string? _avatarUrl;
public CurrentUserViewModel(ILogger logger, IAuthenticationService authenticationService, IWindowService windowService) public CurrentUserViewModel(ILogger logger, IAuthenticationService authenticationService, IWindowService windowService)
{ {
_logger = logger; _logger = logger;
_authenticationService = authenticationService; _authenticationService = authenticationService;
_windowService = windowService; _windowService = windowService;
_httpClient = new HttpClient();
Login = ReactiveCommand.CreateFromTask(ExecuteLogin); Login = ReactiveCommand.CreateFromTask(ExecuteLogin);
_isAnonymous = this.WhenAnyValue(vm => vm.Loading, vm => vm.Name, (l, n) => l || n == null).ToProperty(this, vm => vm.IsAnonymous); _isAnonymous = this.WhenAnyValue(vm => vm.Loading, vm => vm.Name, (l, n) => l || n == null).ToProperty(this, vm => vm.IsAnonymous);
@ -44,7 +43,7 @@ public partial class CurrentUserViewModel : ActivatableViewModelBase
this.WhenActivated(d => this.WhenActivated(d =>
{ {
Task.Run(AutoLogin); Task.Run(AutoLogin);
_authenticationService.IsLoggedIn.Subscribe(_ => Task.Run(LoadCurrentUser)).DisposeWith(d); _authenticationService.IsLoggedIn.Subscribe(_ => LoadCurrentUser()).DisposeWith(d);
}); });
} }
@ -66,7 +65,7 @@ public partial class CurrentUserViewModel : ActivatableViewModelBase
.ShowAsync(); .ShowAsync();
if (result == ContentDialogResult.Primary) if (result == ContentDialogResult.Primary)
await LoadCurrentUser(); LoadCurrentUser();
} }
private async Task AutoLogin() private async Task AutoLogin()
@ -74,7 +73,7 @@ public partial class CurrentUserViewModel : ActivatableViewModelBase
try try
{ {
await _authenticationService.AutoLogin(); await _authenticationService.AutoLogin();
await LoadCurrentUser(); LoadCurrentUser();
} }
catch (Exception e) catch (Exception e)
{ {
@ -86,33 +85,11 @@ public partial class CurrentUserViewModel : ActivatableViewModelBase
} }
} }
private async Task LoadCurrentUser() private void LoadCurrentUser()
{ {
UserId = _authenticationService.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; UserId = _authenticationService.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
Name = _authenticationService.Claims.FirstOrDefault(c => c.Type == "name")?.Value; Name = _authenticationService.Claims.FirstOrDefault(c => c.Type == "name")?.Value;
Email = _authenticationService.Claims.FirstOrDefault(c => c.Type == "email")?.Value; Email = _authenticationService.Claims.FirstOrDefault(c => c.Type == "email")?.Value;
AvatarUrl = $"{WorkshopConstants.AUTHORITY_URL}/user/avatar/{UserId}";
if (UserId != null)
{
await LoadAvatar(UserId);
}
else
{
Avatar?.Dispose();
Avatar = null;
}
}
private async Task LoadAvatar(string userId)
{
try
{
Avatar?.Dispose();
Avatar = new Bitmap(await _httpClient.GetStreamAsync($"{WorkshopConstants.AUTHORITY_URL}/user/avatar/{userId}"));
}
catch (Exception)
{
// ignored
}
} }
} }

View File

@ -17,21 +17,21 @@
</StackPanel.Styles> </StackPanel.Styles>
<StackPanel IsVisible="{CompiledBinding !Finished}"> <StackPanel IsVisible="{CompiledBinding !Finished}">
<Lottie Path="/Assets/Animations/busy.json" Width="250" Height="250" Margin="0 100" ></Lottie> <Lottie Path="/Assets/Animations/upload.json" Width="300" Height="300" Margin="0 75" ></Lottie>
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Uploading your submission...</TextBlock> <TextBlock Theme="{StaticResource TitleTextBlockStyle}">Uploading your submission...</TextBlock>
</StackPanel> </StackPanel>
<StackPanel IsVisible="{CompiledBinding Succeeded}"> <StackPanel IsVisible="{CompiledBinding Succeeded}">
<Lottie Path="/Assets/Animations/success.json" RepeatCount="1" Width="250" Height="250" Margin="0 100"></Lottie> <Lottie Path="/Assets/Animations/success.json" RepeatCount="1" Width="250" Height="250" Margin="0 100"></Lottie>
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">All done! Hit finish to view your submission.</TextBlock> <TextBlock Theme="{StaticResource TitleTextBlockStyle}">All done! Hit finish to view your submission.</TextBlock>
</StackPanel> </StackPanel>
<StackPanel IsVisible="{CompiledBinding Failed}"> <StackPanel IsVisible="{CompiledBinding Failed}">
<TextBlock FontSize="140" Margin="0 100">😢</TextBlock> <TextBlock FontSize="140" Margin="0 100">😢</TextBlock>
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}"> <TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">
Unfortunately something went wrong while uploading your submission. Unfortunately something went wrong while uploading your submission.
</TextBlock> </TextBlock>
<TextBlock>Hit finish to view your submission, from there you can try to upload a new release.</TextBlock> <TextBlock Text="{CompiledBinding FailureMessage}"></TextBlock>
<TextBlock Margin="0 10" Classes="subtitle">If this keeps occuring, hit us up on Discord</TextBlock> <TextBlock Margin="0 10" Classes="subtitle">If this keeps occuring, hit us up on Discord</TextBlock>
</StackPanel> </StackPanel>

View File

@ -5,6 +5,7 @@ using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities; using Artemis.UI.Shared.Utilities;
@ -31,6 +32,7 @@ public partial class UploadStepViewModel : SubmissionViewModel
[Notify] private bool _failed; [Notify] private bool _failed;
[Notify] private bool _finished; [Notify] private bool _finished;
[Notify] private bool _succeeded; [Notify] private bool _succeeded;
[Notify] private string? _failureMessage;
/// <inheritdoc /> /// <inheritdoc />
public UploadStepViewModel(ILogger logger, public UploadStepViewModel(ILogger logger,
@ -56,37 +58,33 @@ public partial class UploadStepViewModel : SubmissionViewModel
private 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 try
{ {
// Use the existing entry or create a new one
_entryId = State.EntryId ?? await CreateEntry(cancellationToken);
if (_entryId == null)
{
Failed = true;
return;
}
// Create a release for the new entry
IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType); IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType);
EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, cancellationToken); EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, cancellationToken);
if (!uploadResult.IsSuccess) if (!uploadResult.IsSuccess)
{ throw new ArtemisWorkshopException(uploadResult.Message);
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; Succeeded = true;
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Error(e, "Failed to upload submission for entry {EntryId}", _entryId); _logger.Error(e, "Failed to upload submission for entry {EntryId}", _entryId);
FailureMessage = e.Message;
// 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; Failed = true;
// If something went wrong halfway through, delete the entry
if (_entryId != null)
await _workshopClient.RemoveEntry.ExecuteAsync(_entryId.Value, CancellationToken.None);
} }
finally finally
{ {
@ -94,10 +92,12 @@ public partial class UploadStepViewModel : SubmissionViewModel
} }
} }
private async Task<long?> CreateEntry(CancellationToken cancellationToken) private async Task<long> CreateEntry(CancellationToken cancellationToken)
{ {
await Task.Delay(2000); // Let the UI settle before making the thread busy
await Task.Delay(500, cancellationToken);
// Create entry
IOperationResult<IAddEntryResult> result = await _workshopClient.AddEntry.ExecuteAsync(new CreateEntryInput IOperationResult<IAddEntryResult> result = await _workshopClient.AddEntry.ExecuteAsync(new CreateEntryInput
{ {
EntryType = State.EntryType, EntryType = State.EntryType,
@ -108,18 +108,16 @@ public partial class UploadStepViewModel : SubmissionViewModel
Tags = State.Tags Tags = State.Tags
}, cancellationToken); }, cancellationToken);
long? entryId = result.Data?.AddEntry?.Id; result.EnsureNoErrors();
if (result.IsErrorResult() || entryId == null) if (result.Data?.AddEntry == null)
{ throw new ArtemisWorkshopException("AddEntry returned result");
await _windowService.ShowConfirmContentDialog("Failed to create workshop entry", string.Join("\r\n", result.Errors.Select(e => e.Message)), "Close", null); long entryId = result.Data.AddEntry.Id;
State.ChangeScreen<SubmitStepViewModel>();
return null; // Upload images
}
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
foreach (ImageUploadRequest image in State.Images.ToList()) foreach (ImageUploadRequest image in State.Images.ToList())
{ {
await TryImageUpload(async () => await _workshopService.UploadEntryImage(entryId.Value, image, cancellationToken)); await TryImageUpload(async () => await _workshopService.UploadEntryImage(entryId, image, cancellationToken));
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
@ -127,8 +125,7 @@ public partial class UploadStepViewModel : SubmissionViewModel
return entryId; return entryId;
// Upload icon // Upload icon
await TryImageUpload(async () => await _workshopService.SetEntryIcon(entryId.Value, State.Icon, cancellationToken)); await TryImageUpload(async () => await _workshopService.SetEntryIcon(entryId, State.Icon, cancellationToken));
return entryId; return entryId;
} }
@ -151,7 +148,7 @@ public partial class UploadStepViewModel : SubmissionViewModel
{ {
State.Close(); State.Close();
if (_entryId != null) if (Succeeded && _entryId != null)
await _router.Navigate($"workshop/library/submissions/{_entryId.Value}"); await _router.Navigate($"workshop/library/submissions/{_entryId.Value}");
} }
} }

View File

@ -45,6 +45,9 @@
<GraphQL Update="Mutations\UpdateEntryImage.graphql"> <GraphQL Update="Mutations\UpdateEntryImage.graphql">
<Generator>MSBuild:GenerateGraphQLCode</Generator> <Generator>MSBuild:GenerateGraphQLCode</Generator>
</GraphQL> </GraphQL>
<GraphQL Update="Mutations\CreateEntry.graphql">
<Generator>MSBuild:GenerateGraphQLCode</Generator>
</GraphQL>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>