mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Submission wizard - Upload entry icon
Entry lists - Show entry icon
This commit is contained in:
parent
6014bb9e3c
commit
176a28761f
@ -1,4 +1,6 @@
|
|||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
@ -24,21 +26,23 @@ public class BitmapExtensions
|
|||||||
|
|
||||||
private static Bitmap Resize(SKBitmap source, int size)
|
private static Bitmap Resize(SKBitmap source, int size)
|
||||||
{
|
{
|
||||||
int newWidth, newHeight;
|
// Get smaller dimension.
|
||||||
float aspectRatio = (float) source.Width / source.Height;
|
int minDim = Math.Min(source.Width, source.Height);
|
||||||
|
|
||||||
if (aspectRatio > 1)
|
// Calculate crop rectangle position for center crop.
|
||||||
{
|
int deltaX = (source.Width - minDim) / 2;
|
||||||
newWidth = size;
|
int deltaY = (source.Height - minDim) / 2;
|
||||||
newHeight = (int) (size / aspectRatio);
|
|
||||||
}
|
// Create crop rectangle.
|
||||||
else
|
SKRectI rect = new(deltaX, deltaY, deltaX + minDim, deltaY + minDim);
|
||||||
{
|
|
||||||
newWidth = (int) (size * aspectRatio);
|
// Do the actual cropping of the bitmap.
|
||||||
newHeight = size;
|
using SKBitmap croppedBitmap = new(minDim, minDim);
|
||||||
}
|
source.ExtractSubset(croppedBitmap, rect);
|
||||||
|
|
||||||
|
// Resize to the desired size after cropping.
|
||||||
|
using SKBitmap resizedBitmap = croppedBitmap.Resize(new SKImageInfo(size, size), SKFilterQuality.High);
|
||||||
|
|
||||||
using SKBitmap resizedBitmap = source.Resize(new SKImageInfo(newWidth, newHeight), SKFilterQuality.High);
|
|
||||||
return new Bitmap(resizedBitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream());
|
return new Bitmap(resizedBitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,8 +23,9 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0 0 10 0"
|
Margin="0 0 10 0"
|
||||||
Width="80"
|
Width="80"
|
||||||
Height="80">
|
Height="80"
|
||||||
<avalonia:MaterialIcon Kind="HandOkay" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Width="70" Height="70" />
|
ClipToBounds="True">
|
||||||
|
<Image Source="{CompiledBinding EntryIcon^}" Stretch="UniformToFill"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
|
|||||||
@ -1,26 +1,34 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries;
|
||||||
|
|
||||||
public class EntryListViewModel : ViewModelBase
|
public class EntryListViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly IRouter _router;
|
private readonly IRouter _router;
|
||||||
|
private readonly ObservableAsPropertyHelper<Bitmap?> _entryIcon;
|
||||||
|
|
||||||
public EntryListViewModel(IGetEntries_Entries_Items entry, IRouter router)
|
public EntryListViewModel(IGetEntries_Entries_Items entry, IRouter router, IWorkshopService workshopService)
|
||||||
{
|
{
|
||||||
_router = router;
|
_router = router;
|
||||||
|
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
EntryIcon = workshopService.GetEntryIcon(entry.Id, CancellationToken.None);
|
||||||
NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry);
|
NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IGetEntries_Entries_Items Entry { get; }
|
public IGetEntries_Entries_Items Entry { get; }
|
||||||
public ReactiveCommand<Unit,Unit> NavigateToEntry { get; }
|
public Task<Bitmap?> EntryIcon { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> NavigateToEntry { get; }
|
||||||
|
|
||||||
private async Task ExecuteNavigateToEntry()
|
private async Task ExecuteNavigateToEntry()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -13,6 +13,7 @@ using Artemis.UI.Shared.Routing;
|
|||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
using DryIoc.ImTools;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
@ -20,8 +21,8 @@ namespace Artemis.UI.Screens.Workshop.Profile;
|
|||||||
|
|
||||||
public class ProfileListViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopListParameters>, IWorkshopViewModel
|
public class ProfileListViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopListParameters>, IWorkshopViewModel
|
||||||
{
|
{
|
||||||
private readonly IRouter _router;
|
|
||||||
private readonly INotificationService _notificationService;
|
private readonly INotificationService _notificationService;
|
||||||
|
private readonly Func<IGetEntries_Entries_Items, EntryListViewModel> _getEntryListViewModel;
|
||||||
private readonly IWorkshopClient _workshopClient;
|
private readonly IWorkshopClient _workshopClient;
|
||||||
private readonly ObservableAsPropertyHelper<bool> _showPagination;
|
private readonly ObservableAsPropertyHelper<bool> _showPagination;
|
||||||
private readonly ObservableAsPropertyHelper<bool> _isLoading;
|
private readonly ObservableAsPropertyHelper<bool> _isLoading;
|
||||||
@ -31,18 +32,22 @@ public class ProfileListViewModel : RoutableScreen<ActivatableViewModelBase, Wor
|
|||||||
private int _totalPages = 1;
|
private int _totalPages = 1;
|
||||||
private int _entriesPerPage = 10;
|
private int _entriesPerPage = 10;
|
||||||
|
|
||||||
public ProfileListViewModel(IWorkshopClient workshopClient, IRouter router, CategoriesViewModel categoriesViewModel, INotificationService notificationService)
|
public ProfileListViewModel(IWorkshopClient workshopClient,
|
||||||
|
IRouter router,
|
||||||
|
CategoriesViewModel categoriesViewModel,
|
||||||
|
INotificationService notificationService,
|
||||||
|
Func<IGetEntries_Entries_Items, EntryListViewModel> getEntryListViewModel)
|
||||||
{
|
{
|
||||||
_workshopClient = workshopClient;
|
_workshopClient = workshopClient;
|
||||||
_router = router;
|
|
||||||
_notificationService = notificationService;
|
_notificationService = notificationService;
|
||||||
|
_getEntryListViewModel = getEntryListViewModel;
|
||||||
_showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
|
_showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
|
||||||
_isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
|
_isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
|
||||||
|
|
||||||
CategoriesViewModel = categoriesViewModel;
|
CategoriesViewModel = categoriesViewModel;
|
||||||
|
|
||||||
// Respond to page changes
|
// Respond to page changes
|
||||||
this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => _router.Navigate($"workshop/profiles/{p}")));
|
this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"workshop/profiles/{p}")));
|
||||||
// Respond to filter changes
|
// Respond to filter changes
|
||||||
this.WhenActivated(d => CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ =>
|
this.WhenActivated(d => CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ =>
|
||||||
{
|
{
|
||||||
@ -111,7 +116,7 @@ public class ProfileListViewModel : RoutableScreen<ActivatableViewModelBase, Wor
|
|||||||
|
|
||||||
if (entries.Data?.Entries?.Items != null)
|
if (entries.Data?.Entries?.Items != null)
|
||||||
{
|
{
|
||||||
Entries = entries.Data.Entries.Items.Select(n => new EntryListViewModel(n, _router)).ToList();
|
Entries = entries.Data.Entries.Items.Select(n => _getEntryListViewModel(n)).ToList();
|
||||||
TotalPages = (int) Math.Ceiling(entries.Data.Entries.TotalCount / (double) EntriesPerPage);
|
TotalPages = (int) Math.Ceiling(entries.Data.Entries.TotalCount / (double) EntriesPerPage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -13,12 +13,14 @@ using Artemis.Core;
|
|||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using System;
|
using System;
|
||||||
using Artemis.UI.Shared.Utilities;
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
||||||
|
|
||||||
public class UploadStepViewModel : SubmissionViewModel
|
public class UploadStepViewModel : SubmissionViewModel
|
||||||
{
|
{
|
||||||
private readonly IWorkshopClient _workshopClient;
|
private readonly IWorkshopClient _workshopClient;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
private readonly EntryUploadHandlerFactory _entryUploadHandlerFactory;
|
private readonly EntryUploadHandlerFactory _entryUploadHandlerFactory;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly IRouter _router;
|
private readonly IRouter _router;
|
||||||
@ -30,9 +32,10 @@ public class UploadStepViewModel : SubmissionViewModel
|
|||||||
private Guid? _entryId;
|
private Guid? _entryId;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public UploadStepViewModel(IWorkshopClient workshopClient, EntryUploadHandlerFactory entryUploadHandlerFactory, IWindowService windowService, IRouter router)
|
public UploadStepViewModel(IWorkshopClient workshopClient, IWorkshopService workshopService, EntryUploadHandlerFactory entryUploadHandlerFactory, IWindowService windowService, IRouter router)
|
||||||
{
|
{
|
||||||
_workshopClient = workshopClient;
|
_workshopClient = workshopClient;
|
||||||
|
_workshopService = workshopService;
|
||||||
_entryUploadHandlerFactory = entryUploadHandlerFactory;
|
_entryUploadHandlerFactory = entryUploadHandlerFactory;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_router = router;
|
_router = router;
|
||||||
@ -88,6 +91,10 @@ public class UploadStepViewModel : SubmissionViewModel
|
|||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Upload image
|
||||||
|
if (State.Icon != null)
|
||||||
|
await _workshopService.SetEntryIcon(entryId.Value, _progress, State.Icon, cancellationToken);
|
||||||
|
|
||||||
// Create the workshop entry
|
// Create the workshop entry
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@ -44,6 +44,7 @@ public static class ContainerExtensions
|
|||||||
|
|
||||||
container.Register<IAuthenticationRepository, AuthenticationRepository>(Reuse.Singleton);
|
container.Register<IAuthenticationRepository, AuthenticationRepository>(Reuse.Singleton);
|
||||||
container.Register<IAuthenticationService, AuthenticationService>(Reuse.Singleton);
|
container.Register<IAuthenticationService, AuthenticationService>(Reuse.Singleton);
|
||||||
|
container.Register<IWorkshopService, WorkshopService>(Reuse.Singleton);
|
||||||
|
|
||||||
container.Register<EntryUploadHandlerFactory>(Reuse.Transient);
|
container.Register<EntryUploadHandlerFactory>(Reuse.Transient);
|
||||||
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryUploadHandler>(), Reuse.Transient);
|
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryUploadHandler>(), Reuse.Transient);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
query GetCategories {
|
query GetCategories {
|
||||||
categories {
|
categories(order: {name: ASC}) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
icon
|
icon
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
query GetEntries($filter: EntryFilterInput $skip: Int $take: Int) {
|
query GetEntries($filter: EntryFilterInput $skip: Int $take: Int) {
|
||||||
entries(where: $filter skip: $skip take: $take) {
|
entries(where: $filter skip: $skip take: $take, order: {createdAt: DESC}) {
|
||||||
totalCount
|
totalCount
|
||||||
items {
|
items {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -26,6 +26,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
private readonly BehaviorSubject<bool> _isLoggedInSubject = new(false);
|
private readonly BehaviorSubject<bool> _isLoggedInSubject = new(false);
|
||||||
|
|
||||||
private AuthenticationToken? _token;
|
private AuthenticationToken? _token;
|
||||||
|
private bool _noStoredRefreshToken;
|
||||||
|
|
||||||
public AuthenticationService(IHttpClientFactory httpClientFactory, IDiscoveryCache discoveryCache, IAuthenticationRepository authenticationRepository)
|
public AuthenticationService(IHttpClientFactory httpClientFactory, IDiscoveryCache discoveryCache, IAuthenticationRepository authenticationRepository)
|
||||||
{
|
{
|
||||||
@ -50,7 +51,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
private void SetCurrentUser(TokenResponse response)
|
private void SetCurrentUser(TokenResponse response)
|
||||||
{
|
{
|
||||||
_token = new AuthenticationToken(response);
|
_token = new AuthenticationToken(response);
|
||||||
_authenticationRepository.SetRefreshToken(_token.RefreshToken);
|
SetStoredRefreshToken(_token.RefreshToken);
|
||||||
|
|
||||||
JwtSecurityTokenHandler handler = new();
|
JwtSecurityTokenHandler handler = new();
|
||||||
JwtSecurityToken? token = handler.ReadJwtToken(response.IdentityToken);
|
JwtSecurityToken? token = handler.ReadJwtToken(response.IdentityToken);
|
||||||
@ -80,7 +81,10 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
if (response.IsError)
|
if (response.IsError)
|
||||||
{
|
{
|
||||||
if (response.Error is OidcConstants.TokenErrors.ExpiredToken or OidcConstants.TokenErrors.InvalidGrant)
|
if (response.Error is OidcConstants.TokenErrors.ExpiredToken or OidcConstants.TokenErrors.InvalidGrant)
|
||||||
|
{
|
||||||
|
SetStoredRefreshToken(null);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
throw new ArtemisWebClientException("Failed to request refresh token: " + response.Error);
|
throw new ArtemisWebClientException("Failed to request refresh token: " + response.Error);
|
||||||
}
|
}
|
||||||
@ -118,8 +122,9 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
{
|
{
|
||||||
// If not logged in, attempt to auto login first
|
// If not logged in, attempt to auto login first
|
||||||
if (!_isLoggedInSubject.Value)
|
if (!_isLoggedInSubject.Value)
|
||||||
await AutoLogin();
|
await InternalAutoLogin();
|
||||||
|
|
||||||
|
// If there is no token, even after an auto-login, there's no bearer to add
|
||||||
if (_token == null)
|
if (_token == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -142,14 +147,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!force && _isLoggedInSubject.Value)
|
return await InternalAutoLogin(force);
|
||||||
return true;
|
|
||||||
|
|
||||||
string? refreshToken = _authenticationRepository.GetRefreshToken();
|
|
||||||
if (refreshToken == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return await UseRefreshToken(refreshToken);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -157,6 +155,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task Login(CancellationToken cancellationToken)
|
public async Task Login(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -240,8 +239,36 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
|||||||
{
|
{
|
||||||
_token = null;
|
_token = null;
|
||||||
_claims.Clear();
|
_claims.Clear();
|
||||||
_authenticationRepository.SetRefreshToken(null);
|
SetStoredRefreshToken(null);
|
||||||
|
|
||||||
_isLoggedInSubject.OnNext(false);
|
_isLoggedInSubject.OnNext(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> InternalAutoLogin(bool force = false)
|
||||||
|
{
|
||||||
|
if (!force && _isLoggedInSubject.Value)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (_noStoredRefreshToken)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string? refreshToken = GetStoredRefreshToken();
|
||||||
|
if (refreshToken == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return await UseRefreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetStoredRefreshToken()
|
||||||
|
{
|
||||||
|
string? token = _authenticationRepository.GetRefreshToken();
|
||||||
|
_noStoredRefreshToken = token == null;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetStoredRefreshToken(string? token)
|
||||||
|
{
|
||||||
|
_authenticationRepository.SetRefreshToken(token);
|
||||||
|
_noStoredRefreshToken = token == null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
101
src/Artemis.WebClient.Workshop/Services/IWorkshopService.cs
Normal file
101
src/Artemis.WebClient.Workshop/Services/IWorkshopService.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Artemis.UI.Shared.Services.MainWindow;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop.UploadHandlers;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
namespace Artemis.WebClient.Workshop.Services;
|
||||||
|
|
||||||
|
public class WorkshopService : IWorkshopService
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Guid, Stream> _entryIconCache = new();
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly SemaphoreSlim _iconCacheLock = new(1);
|
||||||
|
|
||||||
|
public WorkshopService(IHttpClientFactory httpClientFactory, IMainWindowService mainWindowService)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
mainWindowService.MainWindowClosed += (_, _) => Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
ClearCache();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<Bitmap?> GetEntryIcon(Guid entryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _iconCacheLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_entryIconCache.TryGetValue(entryId, out Stream? cachedBitmap))
|
||||||
|
{
|
||||||
|
cachedBitmap.Seek(0, SeekOrigin.Begin);
|
||||||
|
return new Bitmap(cachedBitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_iconCacheLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await client.GetAsync($"entries/{entryId}/icon", cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
Stream data = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||||
|
|
||||||
|
_entryIconCache[entryId] = data;
|
||||||
|
return new Bitmap(data);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
icon.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
// Submit the archive
|
||||||
|
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||||
|
|
||||||
|
// Construct the request
|
||||||
|
MultipartFormDataContent content = new();
|
||||||
|
ProgressableStreamContent streamContent = new(icon, progress);
|
||||||
|
streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
|
||||||
|
content.Add(streamContent, "file", "file.png");
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
HttpResponseMessage response = await client.PostAsync($"entries/{entryId}/icon", content, cancellationToken);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return ImageUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
||||||
|
return ImageUploadResult.FromSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearCache()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<Stream> values = _entryIconCache.Values.ToList();
|
||||||
|
_entryIconCache.Clear();
|
||||||
|
foreach (Stream bitmap in values)
|
||||||
|
bitmap.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IWorkshopService
|
||||||
|
{
|
||||||
|
Task<Bitmap?> GetEntryIcon(Guid entryId, CancellationToken cancellationToken);
|
||||||
|
Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
namespace Artemis.WebClient.Workshop.UploadHandlers;
|
||||||
|
|
||||||
|
public class ImageUploadResult
|
||||||
|
{
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
public static ImageUploadResult FromFailure(string? message)
|
||||||
|
{
|
||||||
|
return new ImageUploadResult
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageUploadResult FromSuccess()
|
||||||
|
{
|
||||||
|
return new ImageUploadResult {IsSuccess = true};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user