//
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//
namespace MUnique.OpenMU.Web.AdminPanel.Pages;
using System.ComponentModel;
using System.Threading;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.QuickGrid;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.Web.AdminPanel.Properties;
using MUnique.OpenMU.Web.Shared.Components.Toast;
using MUnique.OpenMU.Web.Shared.Services;
///
/// Razor page which shows objects of the specified type in a grid.
///
public partial class Merchants : ComponentBase, IAsyncDisposable
{
private readonly PaginationState _merchantPagination = new() { ItemsPerPage = 20 };
private readonly PaginationState _itemPagination = new() { ItemsPerPage = 20 };
private Task? _loadTask;
private CancellationTokenSource? _disposeCts;
private List? _viewModels;
private MerchantStorageViewModel? _selectedMerchant;
private IContext? _persistenceContext;
private IDisposable? _navigationLockDisposable;
///
/// Gets or sets the data source.
///
[Inject]
public IDataSource DataSource { get; set; } = null!;
///
/// Gets or sets the context provider.
///
[Inject]
public IPersistenceContextProvider ContextProvider { get; set; } = null!;
///
/// Gets or sets the toast service.
///
[Inject]
public IToastService ToastService { get; set; } = null!;
///
/// Gets or sets the navigation manager.
///
[Inject]
public NavigationManager NavigationManager { get; set; } = null!;
///
/// Gets or sets the java script runtime.
///
[Inject]
public IJSRuntime JavaScript { get; set; } = null!;
///
/// Gets or sets the logger.
///
[Inject]
public ILogger Logger { get; set; } = null!;
///
/// Gets or sets the loading overlay service.
///
[Inject]
public LoadingOverlayService LoadingService { get; set; } = null!;
private IQueryable? ViewModels => this._viewModels?.AsQueryable();
///
public async ValueTask DisposeAsync()
{
this._navigationLockDisposable?.Dispose();
this._navigationLockDisposable = null;
await (this._disposeCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false);
this._disposeCts?.Dispose();
this._disposeCts = null;
try
{
await (this._loadTask ?? Task.CompletedTask).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// we can ignore that ...
}
catch
{
// and we should not throw exceptions in the dispose method ...
}
}
///
protected override Task OnInitializedAsync()
{
this._navigationLockDisposable = this.NavigationManager.RegisterLocationChangingHandler(this.OnBeforeInternalNavigationAsync);
return base.OnInitializedAsync();
}
///
protected override async Task OnParametersSetAsync()
{
var cts = new CancellationTokenSource();
this._disposeCts = cts;
this._loadTask = Task.Run(() => this.LoadDataAsync(cts.Token));
await base.OnParametersSetAsync().ConfigureAwait(true);
}
private async ValueTask OnBeforeInternalNavigationAsync(LocationChangingContext context)
{
if (this._persistenceContext?.HasChanges is not true)
{
return;
}
var isConfirmed = await this.JavaScript.InvokeAsync(
"window.confirm",
Resources.UnsavedChangesQuestion)
.ConfigureAwait(true);
if (!isConfirmed)
{
context.PreventNavigation();
}
else
{
await this.DataSource.DiscardChangesAsync().ConfigureAwait(true);
}
}
private async Task LoadDataAsync(CancellationToken cancellationToken)
{
using var loading = this.LoadingService.ShowLoadingIndicator();
try
{
cancellationToken.ThrowIfCancellationRequested();
this._persistenceContext = await this.DataSource.GetContextAsync(cancellationToken).ConfigureAwait(true);
await this.DataSource.GetOwnerAsync(cancellationToken: cancellationToken).ConfigureAwait(true);
var data = this.DataSource.GetAll()
.Where(m => m is { ObjectKind: NpcObjectKind.PassiveNpc, MerchantStore: { } });
this._viewModels = data
.Select(o => new MerchantStorageViewModel(o))
.OrderBy(o => o.Name)
.ToList();
await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(true);
}
catch (OperationCanceledException)
{
// Expected when navigating away - ignore
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Error loading merchant data");
}
}
private async Task OnMerchantEditClickAsync(MerchantStorageViewModel context)
{
this._selectedMerchant = context;
await this.InvokeAsync(async () => await this._itemPagination.SetCurrentPageIndexAsync(0).ConfigureAwait(true)).ConfigureAwait(true);
}
private async Task OnSaveButtonClickAsync()
{
try
{
if (this._persistenceContext is { } context)
{
var success = await context.SaveChangesAsync().ConfigureAwait(true);
var text = success ? Resources.SavedChanges : Resources.NoChangesToSave;
this.ToastService.ShowSuccess(text);
}
else
{
this.ToastService.ShowError(Resources.FailedByUninitializedContext);
}
}
catch (Exception ex)
{
this.Logger.LogError(ex, $"An unexpected error occurred on save: {ex.Message}");
this.ToastService.ShowError(string.Format(Resources.UnexpectedErrorOccurred, ex.Message));
}
}
private async Task OnCancelButtonClickAsync()
{
if (this._persistenceContext?.HasChanges is true)
{
var previousMerchantId = this._selectedMerchant?.Id;
await this.DataSource.DiscardChangesAsync().ConfigureAwait(true);
await this.LoadDataAsync(this._disposeCts?.Token ?? default).ConfigureAwait(true);
if (previousMerchantId is { } id && this._viewModels is not null)
{
this._selectedMerchant = this._viewModels.FirstOrDefault(vm => vm.Id == id);
}
}
}
private async Task OnBackButtonClickAsync()
{
if (this._persistenceContext?.HasChanges is true)
{
var isConfirmed = await this.JavaScript.InvokeAsync(
"window.confirm",
Resources.UnsavedChangesQuestion)
.ConfigureAwait(true);
if (!isConfirmed)
{
return;
}
await this.OnCancelButtonClickAsync().ConfigureAwait(true);
}
this._selectedMerchant = null;
}
///
/// The view model for a merchant store.
///
public class MerchantStorageViewModel
{
///
/// Initializes a new instance of the class.
///
/// The merchant.
public MerchantStorageViewModel(MonsterDefinition merchant)
{
this.Merchant = merchant;
this.Id = merchant.GetId();
}
///
/// Gets the identifier.
///
[Browsable(false)]
public Guid Id { get; }
///
/// Gets the merchant definition.
///
[Browsable(false)]
public MonsterDefinition Merchant { get; }
///
/// Gets the name of the merchant.
///
[Browsable(false)]
public string Name => this.Merchant.Designation;
///
/// Gets the items of the merchant.
///
public ICollection- Items => this.Merchant.MerchantStore!.Items;
}
}