fix(ui): replace inline save indicator with Bootstrap toast notification
SettingBox base now uses JS interop to call showToast() which renders a Bootstrap Toast at the bottom-right corner. Removed all SaveIndicator RenderFragments and IsSaving/IsSaved state from the four child components. Toast auto-hides after 2s.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
@using Butter.Dtos.Settings
|
||||
@using MilkStream.Client.Services
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
@inject SettingsService SettingsService
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
@@ -9,9 +11,6 @@
|
||||
|
||||
private CancellationTokenSource? _debounceCts;
|
||||
|
||||
protected bool IsSaving { get; set; }
|
||||
protected bool IsSaved { get; set; }
|
||||
|
||||
protected virtual SettingDto? GetSetting() {
|
||||
return Setting;
|
||||
}
|
||||
@@ -28,18 +27,11 @@
|
||||
var setting = GetSetting();
|
||||
if (setting == null) return;
|
||||
|
||||
IsSaving = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JSRuntime.InvokeVoidAsync("showToast", "Saving\u2026", "text-bg-secondary");
|
||||
|
||||
await SettingsService.UpdateSettingAsync(setting);
|
||||
|
||||
IsSaving = false;
|
||||
IsSaved = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Task.Delay(2000);
|
||||
IsSaved = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await JSRuntime.InvokeVoidAsync("showToast", "Saved", "text-bg-success");
|
||||
} catch (OperationCanceledException) {
|
||||
// Debounce cancelled by another change
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<p class="card-text">@(Setting?.Description ?? "No description provided")</p>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<input type="number" id="@Setting?.Name" class="form-control text-end flex-grow-1" @bind="currentValue" @bind:after="() => OnValueChanged()" />
|
||||
@SaveIndicator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,15 +29,4 @@
|
||||
return Setting;
|
||||
}
|
||||
|
||||
private RenderFragment SaveIndicator => builder =>
|
||||
{
|
||||
string cls = "";
|
||||
string text = "";
|
||||
if (IsSaving) { cls = "text-secondary"; text = "Saving..."; }
|
||||
else if (IsSaved) { cls = "text-success"; text = "Saved"; }
|
||||
builder.OpenElement(0, "small");
|
||||
builder.AddAttribute(1, "class", cls);
|
||||
builder.AddContent(2, text);
|
||||
builder.CloseElement();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<input type="range" id="@Setting?.Name" class="form-range flex-grow-1"
|
||||
@bind:event="oninput" @bind="@currentValue" @bind:after="() => OnValueChanged()"
|
||||
min="@effectiveMin" max="@effectiveMax" step="@Step"/>
|
||||
@SaveIndicator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,15 +55,4 @@
|
||||
return Setting;
|
||||
}
|
||||
|
||||
private RenderFragment SaveIndicator => builder =>
|
||||
{
|
||||
string cls = "";
|
||||
string text = "";
|
||||
if (IsSaving) { cls = "text-secondary"; text = "Saving..."; }
|
||||
else if (IsSaved) { cls = "text-success"; text = "Saved"; }
|
||||
builder.OpenElement(0, "small");
|
||||
builder.AddAttribute(1, "class", cls);
|
||||
builder.AddContent(2, text);
|
||||
builder.CloseElement();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<p class="card-text">@(Setting?.Description ?? "No description provided")</p>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<input type="text" id="@Setting?.Name" class="form-control" @bind="@currentValue" @bind:after="() => OnValueChanged()" />
|
||||
@SaveIndicator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,15 +27,4 @@
|
||||
return Setting;
|
||||
}
|
||||
|
||||
private RenderFragment SaveIndicator => builder =>
|
||||
{
|
||||
string cls = "";
|
||||
string text = "";
|
||||
if (IsSaving) { cls = "text-secondary"; text = "Saving..."; }
|
||||
else if (IsSaved) { cls = "text-success"; text = "Saved"; }
|
||||
builder.OpenElement(0, "small");
|
||||
builder.AddAttribute(1, "class", cls);
|
||||
builder.AddContent(2, text);
|
||||
builder.CloseElement();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<div class="form-switch">
|
||||
<input type="checkbox" id="@Setting?.Name" class="form-check-input m-1" @bind="isChecked" @bind:after="() => OnValueChanged()"/>
|
||||
</div>
|
||||
@SaveIndicator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,15 +31,4 @@
|
||||
return Setting;
|
||||
}
|
||||
|
||||
private RenderFragment SaveIndicator => builder =>
|
||||
{
|
||||
string cls = "";
|
||||
string text = "";
|
||||
if (IsSaving) { cls = "text-secondary"; text = "Saving..."; }
|
||||
else if (IsSaved) { cls = "text-success"; text = "Saved"; }
|
||||
builder.OpenElement(0, "small");
|
||||
builder.AddAttribute(1, "class", cls);
|
||||
builder.AddContent(2, text);
|
||||
builder.CloseElement();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,3 +226,25 @@ window.justifiedLayout = {
|
||||
container.style.height = Math.max(top - gap, 0) + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
window.showToast = function (message, cls) {
|
||||
let container = document.querySelector('.settings-toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.className = 'settings-toast-container toast-container position-fixed bottom-0 end-0 p-3';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast align-items-center border-0 ' + (cls || '');
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'assertive');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
toast.innerHTML = '<div class="d-flex"><div class="toast-body">' + message + '</div><button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast"></button></div>';
|
||||
container.appendChild(toast);
|
||||
const bsToast = new bootstrap.Toast(toast, { delay: 2000 });
|
||||
bsToast.show();
|
||||
toast.addEventListener('hidden.bs.toast', function () {
|
||||
toast.remove();
|
||||
if (container.children.length === 0) container.remove();
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user