Merge pull request #819 from eduardosmaniotto/bugfix/merchant-and-loading

bugfix: standardize loading states and add save spinner to Create pages
(cherry picked from commit 7fb61714feea55d4c765b094c2e5f513ee5da3ba)
This commit is contained in:
sven-n
2026-07-15 22:41:06 +02:00
committed by Acentech Dev
parent d22ec20a04
commit 30feae5a26
11 changed files with 74 additions and 58 deletions

View File

@@ -11,18 +11,8 @@
@if (this._viewModel is null)
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="visually-hidden">@Resources.Loading</span>
return;
}
@if (this._initState is { })
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="visually-hidden">@this._initState</span>
return;
}
<AutoForm Model="this._viewModel" OnValidSubmit="this.OnSaveButtonClickAsync"></AutoForm>
<AutoForm Model="this._viewModel" OnValidSubmit="this.OnSaveButtonClickAsync" IsProcessing="this._isProcessing"></AutoForm>

View File

@@ -12,6 +12,7 @@ using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.Web.AdminPanel.Properties;
using MUnique.OpenMU.Web.Shared.Services;
/// <summary>
/// Razor page which shows objects of the specified type in a grid.
@@ -22,7 +23,7 @@ public partial class CreateConnectServerConfig : ComponentBase, IAsyncDisposable
private CancellationTokenSource? _disposeCts;
private ConnectServerViewModel? _viewModel;
private string? _initState;
private bool _isProcessing;
/// <summary>
/// Gets or sets the context provider.
@@ -54,6 +55,12 @@ public partial class CreateConnectServerConfig : ComponentBase, IAsyncDisposable
[Inject]
public NavigationManager NavigationManager { get; set; } = null!;
/// <summary>
/// Gets or sets the loading overlay service.
/// </summary>
[Inject]
public LoadingOverlayService LoadingService { get; set; } = null!;
/// <inheritdoc />
public async ValueTask DisposeAsync()
{
@@ -86,6 +93,7 @@ public partial class CreateConnectServerConfig : ComponentBase, IAsyncDisposable
private async Task LoadDataAsync(CancellationToken cancellationToken)
{
using var loading = this.LoadingService.ShowLoadingIndicator();
cancellationToken.ThrowIfCancellationRequested();
var gameConfiguration = await this.DataSource.GetOwnerAsync(default, cancellationToken).ConfigureAwait(true);
@@ -134,11 +142,11 @@ public partial class CreateConnectServerConfig : ComponentBase, IAsyncDisposable
{
try
{
var gameConfiguration = await this.DataSource.GetOwnerAsync().ConfigureAwait(false);
this._isProcessing = true;
var gameConfiguration = await this.DataSource.GetOwnerAsync().ConfigureAwait(true);
using var saveContext = this.ContextProvider.CreateNewTypedContext(typeof(DataModel.Configuration.ConnectServerDefinition), true, gameConfiguration);
var existingServerDefinitions = (await saveContext.GetAsync<ConnectServerDefinition>().ConfigureAwait(false)).ToList();
var existingServerDefinitions = (await saveContext.GetAsync<ConnectServerDefinition>().ConfigureAwait(true)).ToList();
if (existingServerDefinitions.Any(def => def.ServerId == this._viewModel?.ServerId))
{
this.ToastService.ShowError(string.Format(Resources.ServerWithIdAlreadyExists, this._viewModel?.ServerId));
@@ -151,20 +159,13 @@ public partial class CreateConnectServerConfig : ComponentBase, IAsyncDisposable
return;
}
this._initState = Resources.CreatingConfigurationInfo;
await this.InvokeAsync(this.StateHasChanged);
var connectServerDefinition = await this.CreateDefinitionByViewModelAsync(saveContext).ConfigureAwait(false);
this._initState = Resources.SavingConfigurationInfo;
await this.InvokeAsync(this.StateHasChanged);
var connectServerDefinition = await this.CreateDefinitionByViewModelAsync(saveContext).ConfigureAwait(true);
var success = await saveContext.SaveChangesAsync().ConfigureAwait(true);
// if success, init new game server instance
if (success)
{
this.ToastService.ShowSuccess(Resources.ConnectionServerConfigurationSaved);
this._initState = Resources.InitializingConnectServerInfo;
await this.InvokeAsync(this.StateHasChanged);
await this.ServerInstanceManager.InitializeConnectServerAsync(connectServerDefinition.ConfigurationId);
await this.ServerInstanceManager.InitializeConnectServerAsync(connectServerDefinition.ConfigurationId).ConfigureAwait(true);
this.NavigationManager.NavigateTo("servers");
return;
}
@@ -175,8 +176,10 @@ public partial class CreateConnectServerConfig : ComponentBase, IAsyncDisposable
{
this.ToastService.ShowError(string.Format(Resources.UnexpectedErrorOccurred, ex.Message));
}
this._initState = null;
finally
{
this._isProcessing = false;
}
}
/// <summary>

View File

@@ -11,18 +11,8 @@
@if (this._viewModel is null)
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="visually-hidden">@Resources.Loading</span>
return;
}
@if (this._initState is { })
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="visually-hidden">@this._initState</span>
return;
}
<AutoForm Model="this._viewModel" OnValidSubmit="this.OnSaveButtonClickAsync"></AutoForm>
<AutoForm Model="this._viewModel" OnValidSubmit="this.OnSaveButtonClickAsync" IsProcessing="this._isProcessing"></AutoForm>

View File

@@ -13,6 +13,7 @@ using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.Web.AdminPanel.Properties;
using MUnique.OpenMU.Web.Shared.Components.Modal;
using MUnique.OpenMU.Web.Shared.Services;
/// <summary>
/// Razor page that shows objects of the specified type in a grid.
@@ -23,7 +24,7 @@ public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable
private CancellationTokenSource? _disposeCts;
private GameServerViewModel? _viewModel;
private string? _initState;
private bool _isProcessing;
/// <summary>
/// Gets or sets the context provider.
@@ -61,6 +62,12 @@ public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable
[Inject]
public NavigationManager NavigationManager { get; set; } = null!;
/// <summary>
/// Gets or sets the loading overlay service.
/// </summary>
[Inject]
public LoadingOverlayService LoadingService { get; set; } = null!;
/// <inheritdoc />
public async ValueTask DisposeAsync()
{
@@ -93,6 +100,7 @@ public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable
private async Task LoadDataAsync(CancellationToken cancellationToken)
{
using var loading = this.LoadingService.ShowLoadingIndicator();
cancellationToken.ThrowIfCancellationRequested();
var gameConfiguration = await this.DataSource.GetOwnerAsync(default, cancellationToken).ConfigureAwait(true);
@@ -150,11 +158,11 @@ public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable
{
try
{
var gameConfiguration = await this.DataSource.GetOwnerAsync().ConfigureAwait(false);
this._isProcessing = true;
var gameConfiguration = await this.DataSource.GetOwnerAsync().ConfigureAwait(true);
using var saveContext = this.ContextProvider.CreateNewTypedContext(typeof(DataModel.Configuration.GameServerDefinition), true, gameConfiguration);
var existingServerDefinitions = (await saveContext.GetAsync<GameServerDefinition>().ConfigureAwait(false)).ToList();
var existingServerDefinitions = (await saveContext.GetAsync<GameServerDefinition>().ConfigureAwait(true)).ToList();
if (existingServerDefinitions.Any(def => def.ServerID == this._viewModel?.ServerId))
{
this.ToastService.ShowError(string.Format(Resources.ServerWithIdAlreadyExists, this._viewModel?.ServerId));
@@ -167,20 +175,13 @@ public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable
return;
}
this._initState = Resources.CreatingConfigurationInfo;
await this.InvokeAsync(this.StateHasChanged);
var gameServerDefinition = await this.CreateDefinitionByViewModelAsync(saveContext).ConfigureAwait(false);
this._initState = Resources.SavingConfigurationInfo;
await this.InvokeAsync(this.StateHasChanged);
var gameServerDefinition = await this.CreateDefinitionByViewModelAsync(saveContext).ConfigureAwait(true);
var success = await saveContext.SaveChangesAsync().ConfigureAwait(true);
// if success, init new game server instance
if (success)
{
this.ToastService.ShowSuccess(Resources.GameServerConfigurationSavedInfo);
this._initState = Resources.InitializingGameServerInfo;
await this.InvokeAsync(this.StateHasChanged);
await this.ServerInstanceManager.InitializeGameServerAsync(gameServerDefinition.ServerID);
await this.ServerInstanceManager.InitializeGameServerAsync(gameServerDefinition.ServerID).ConfigureAwait(true);
this.NavigationManager.NavigateTo("servers");
return;
}
@@ -191,8 +192,10 @@ public partial class CreateGameServerConfig : ComponentBase, IAsyncDisposable
{
this.ToastService.ShowError(string.Format(Resources.UnexpectedErrorOccurred, ex.Message));
}
this._initState = null;
finally
{
this._isProcessing = false;
}
}
/// <summary>

View File

@@ -15,8 +15,6 @@
@if (this.ViewModels is null)
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="visually-hidden">@Resources.Loading</span>
return;
}

View File

@@ -80,6 +80,12 @@ public partial class EditConfigGrid : ComponentBase, IAsyncDisposable
[Inject]
public ILogger<EditConfigGrid> Logger { get; set; } = null!;
/// <summary>
/// Gets or sets the loading overlay service.
/// </summary>
[Inject]
public LoadingOverlayService LoadingService { get; set; } = null!;
/// <summary>
/// Gets or sets the type.
/// </summary>
@@ -145,6 +151,7 @@ public partial class EditConfigGrid : ComponentBase, IAsyncDisposable
private async Task LoadDataAsync(CancellationToken cancellationToken)
{
using var loading = this.LoadingService.ShowLoadingIndicator();
cancellationToken.ThrowIfCancellationRequested();
if (this.Type is null)
{

View File

@@ -13,8 +13,6 @@
@if (this.ViewModels is null)
{
<span class="spinner-border" role="status" aria-hidden="true"></span>
<span class="visually-hidden">@Resources.Loading</span>
return;
}

View File

@@ -16,6 +16,7 @@ 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.Services;
/// <summary>
/// Razor page which shows objects of the specified type in a grid.
@@ -68,6 +69,12 @@ public partial class Merchants : ComponentBase, IAsyncDisposable
[Inject]
public ILogger<Merchants> Logger { get; set; } = null!;
/// <summary>
/// Gets or sets the loading overlay service.
/// </summary>
[Inject]
public LoadingOverlayService LoadingService { get; set; } = null!;
private IQueryable<MerchantStorageViewModel>? ViewModels => this._viewModels?.AsQueryable();
/// <inheritdoc />
@@ -134,6 +141,7 @@ public partial class Merchants : ComponentBase, IAsyncDisposable
private async Task LoadDataAsync(CancellationToken cancellationToken)
{
using var loading = this.LoadingService.ShowLoadingIndicator();
try
{
cancellationToken.ThrowIfCancellationRequested();
@@ -192,8 +200,13 @@ public partial class Merchants : ComponentBase, IAsyncDisposable
{
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);
}
}
}

View File

@@ -19,7 +19,13 @@
<AutoFields HideCollections="@this.HideCollections" SearchTerm="@this._currentSearchTerm" />
<ValidationSummary />
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="submit" class="btn btn-primary" disabled="@this.IsProcessing">
@if (this.IsProcessing)
{
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
}
@Resources.Save
</button>
@if (this.OnRefresh != null)
{
<button type="button" class="btn btn-secondary" @onclick="this.OnRefresh.Value" >@Resources.Refresh</button>

View File

@@ -35,6 +35,14 @@ public partial class AutoForm<T>
[Parameter]
public EventCallback OnValidSubmit { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the form is in a processing state,
/// e.g. while saving data. When <see langword="true" />, the save button is
/// disabled and shows a spinner.
/// </summary>
[Parameter]
public bool IsProcessing { get; set; }
/// <summary>
/// Gets or sets the callback invoked when the cancel button is clicked.
/// When <see langword="null"/>, the cancel button is not rendered.

View File

@@ -201,7 +201,7 @@
<div class="form-actions">
@if (this.OnValidSubmit.HasDelegate)
{
<button type="submit" class="btn btn-primary">Save</button>
<button type="submit" class="btn btn-primary">@Resources.Save</button>
}
@if (this.OnCancel.HasDelegate)