//Copyright (c) 2019 Alessandro Ghidini.All rights reserved.
//Copyright (c) 2020 Jonny Larson and Meinrad Recheis
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
namespace Connected.Components;
///
public class SnackbarService : ISnackbar, IDisposable
{
public SnackbarConfiguration Configuration { get; }
public event Action OnSnackbarsUpdated;
private NavigationManager _navigationManager;
private ReaderWriterLockSlim SnackBarLock { get; }
private IList SnackBarList { get; }
public SnackbarService(NavigationManager navigationManager, SnackbarConfiguration configuration = null)
{
_navigationManager = navigationManager;
configuration ??= new SnackbarConfiguration();
Configuration = configuration;
Configuration.OnUpdate += ConfigurationUpdated;
navigationManager.LocationChanged += NavigationManager_LocationChanged;
SnackBarLock = new ReaderWriterLockSlim();
SnackBarList = new List();
}
public IEnumerable ShownSnackbars
{
get
{
SnackBarLock.EnterReadLock();
try
{
return SnackBarList.Take(Configuration.MaxDisplayedSnackbars);
}
finally
{
SnackBarLock.ExitReadLock();
}
}
}
private Snackbar Add(SnackbarMessage message, Severity severity = Severity.Normal, Action configure = null)
{
var options = new SnackbarOptions(severity, Configuration);
configure?.Invoke(options);
var snackbar = new Snackbar(message, options);
SnackBarLock.EnterWriteLock();
try
{
if (ResolvePreventDuplicates(options) && SnackbarAlreadyPresent(snackbar)) return null;
snackbar.OnClose += Remove;
SnackBarList.Add(snackbar);
}
finally
{
SnackBarLock.ExitWriteLock();
}
OnSnackbarsUpdated?.Invoke();
return snackbar;
}
///
/// Displays a snackbar containing a custom component specified by T.
///
/// The type of the custom component that specifies the content of the snackbar.
/// Any additional parameters needed by the custom component to display the message.
/// The severity of the snackbar. Dictates the color and default icon of the notification.
/// Additional configuration for the snackbar.
/// If a key is provided, this message will not be shown while any other message with the same key is being shown.
/// The snackbar created by the parameters.
public Snackbar Add(Dictionary componentParameters = null, Severity severity = Severity.Normal, Action configure = null, string key = "") where T : IComponent
{
var type = typeof(T);
var message = new SnackbarMessage(type, componentParameters, key);
return Add(message, severity, configure);
}
///
/// Displays a snackbar containing the RenderFragment.
///
/// The RenderFragment which specifies the content of the snackbar.
/// The severity of the snackbar. Dictates the color and default icon of the notification.
/// Additional configuration for the snackbar.
/// If a key is provided, this message will not be shown while any other message with the same key is being shown.
/// The snackbar created by the parameters.
public Snackbar Add(RenderFragment message, Severity severity = Severity.Normal, Action configure = null, string key = "")
{
if (message == null) return null;
var componentParams = new Dictionary()
{
{ "Message", message as object }
};
return Add
(
new SnackbarMessage(typeof(SnackbarMessageRenderFragment), componentParams, key),
severity,
configure
);
}
///
/// Displays a snackbar containing the text.
///
/// The string which specifies the content of the snackbar.
/// The severity of the snackbar. Dictates the color and default icon of the notification.
/// Additional configuration for the snackbar.
/// If no key is passed, defaults to the content of the message. This message will not be shown while any other message with the same key is being shown.
/// The snackbar created by the parameters.
public Snackbar Add(string message, Severity severity = Severity.Normal, Action configure = null, string key = "")
{
if (message.IsEmpty()) return null;
message = message.Trimmed();
var componentParams = new Dictionary() { { "Message", new MarkupString(message) } };
return Add
(
new SnackbarMessage(typeof(SnackbarMessageText), componentParams, string.IsNullOrEmpty(key) ? message : key) { Text = message },
severity,
configure
);
}
[Obsolete("Use Add instead.", true)]
[ExcludeFromCodeCoverage]
public Snackbar AddNew(Severity severity, string message, Action configure)
{
return Add(message, severity, configure);
}
public void Clear()
{
SnackBarLock.EnterWriteLock();
try
{
RemoveAllSnackbars(SnackBarList);
}
finally
{
SnackBarLock.ExitWriteLock();
}
OnSnackbarsUpdated?.Invoke();
}
public void Remove(Snackbar snackbar)
{
snackbar.OnClose -= Remove;
snackbar.Dispose();
SnackBarLock.EnterWriteLock();
try
{
var index = SnackBarList.IndexOf(snackbar);
if (index < 0) return;
SnackBarList.RemoveAt(index);
}
finally
{
SnackBarLock.ExitWriteLock();
}
OnSnackbarsUpdated?.Invoke();
}
private bool ResolvePreventDuplicates(SnackbarOptions options)
{
return options.DuplicatesBehavior == SnackbarDuplicatesBehavior.Prevent
|| (options.DuplicatesBehavior == SnackbarDuplicatesBehavior.GlobalDefault && Configuration.PreventDuplicates);
}
private bool SnackbarAlreadyPresent(Snackbar newSnackbar)
{
return !string.IsNullOrEmpty(newSnackbar.SnackbarMessage.Key) && SnackBarList.Any(snackbar => newSnackbar.SnackbarMessage.Key == snackbar.SnackbarMessage.Key);
}
private void ConfigurationUpdated()
{
OnSnackbarsUpdated?.Invoke();
}
private void NavigationManager_LocationChanged(object sender, LocationChangedEventArgs e)
{
if (Configuration.ClearAfterNavigation)
{
Clear();
}
else
{
ShownSnackbars.Where(s => s.State.Options.CloseAfterNavigation).ToList().ForEach(s => Remove(s));
}
}
public void Dispose()
{
Configuration.OnUpdate -= ConfigurationUpdated;
_navigationManager.LocationChanged -= NavigationManager_LocationChanged;
RemoveAllSnackbars(SnackBarList);
}
private void RemoveAllSnackbars(IEnumerable snackbars)
{
if (SnackBarList.Count == 0) return;
foreach (var snackbar in snackbars)
{
snackbar.OnClose -= Remove;
snackbar.Dispose();
}
SnackBarList.Clear();
}
}