55 lines
1.5 KiB
C#
55 lines
1.5 KiB
C#
namespace decePubClient.Services;
|
|||
|
|
|
||
|
|
public enum ToastSeverity
|
||
|
|
{
|
||
|
|
Info,
|
||
|
|
Success,
|
||
|
|
Warning,
|
||
|
|
Error
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed record ToastMessage(Guid Id, ToastSeverity Severity, string Text);
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The app's snackbar: components add messages (usually through the SUtility extensions AddResponseError,
|
||
|
|
/// AddResponseSuccess, AddWarning, AddException) and Components/ToastHost.razor, placed once in MainLayout, shows them.
|
||
|
|
/// A message already on screen isn't added twice, and at most MaxMessages stay, oldest dropped first.
|
||
|
|
/// </summary>
|
||
|
|
public class ToastService
|
||
|
|
{
|
||
|
|
const int MaxMessages = 12;
|
||
|
|
|
||
|
|
readonly List<ToastMessage> messages = [];
|
||
|
|
|
||
|
|
public event Action Changed;
|
||
|
|
|
||
|
|
public IReadOnlyList<ToastMessage> Messages => messages;
|
||
|
|
|
||
|
|
/// <summary>Shows a message; with closeAfter it closes itself, otherwise it stays until the user closes it.</summary>
|
||
|
|
public void Add(ToastSeverity severity, string text, TimeSpan? closeAfter = null)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(text) || messages.Any(m => m.Severity == severity && m.Text == text))
|
||
|
|
return;
|
||
|
|
|
||
|
|
var message = new ToastMessage(Guid.NewGuid(), severity, text);
|
||
|
|
messages.Add(message);
|
||
|
|
if (messages.Count > MaxMessages)
|
||
|
|
messages.RemoveAt(0);
|
||
|
|
Changed?.Invoke();
|
||
|
|
|
||
|
|
if (closeAfter is { } delay)
|
||
|
|
_ = CloseLater(message.Id, delay);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Close(Guid id)
|
||
|
|
{
|
||
|
|
if (messages.RemoveAll(m => m.Id == id) > 0)
|
||
|
|
Changed?.Invoke();
|
||
|
|
}
|
||
|
|
|
||
|
|
async Task CloseLater(Guid id, TimeSpan delay, CancellationToken cancellationToken = default)
|
||
|
|
{
|
||
|
|
await Task.Delay(delay, cancellationToken);
|
||
|
|
Close(id);
|
||
|
|
}
|
||
|
|
}
|