|
|
|
|
//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;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public class SnackbarService : ISnackbar, IDisposable
|
|
|
|
|
{
|
|
|
|
|
public SnackbarConfiguration Configuration { get; }
|
|
|
|
|
public event Action OnSnackbarsUpdated;
|
|
|
|
|
|
|
|
|
|
private NavigationManager _navigationManager;
|
|
|
|
|
private ReaderWriterLockSlim SnackBarLock { get; }
|
|
|
|
|
private IList<Snackbar> 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<Snackbar>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<Snackbar> ShownSnackbars
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
SnackBarLock.EnterReadLock();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return SnackBarList.Take(Configuration.MaxDisplayedSnackbars);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
SnackBarLock.ExitReadLock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Snackbar Add(SnackbarMessage message, Severity severity = Severity.Normal, Action<SnackbarOptions> 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Displays a snackbar containing a custom component specified by T.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">The type of the custom component that specifies the content of the snackbar.</typeparam>
|
|
|
|
|
/// <param name="componentParameters">Any additional parameters needed by the custom component to display the message.</param>
|
|
|
|
|
/// <param name="severity">The severity of the snackbar. Dictates the color and default icon of the notification.</param>
|
|
|
|
|
/// <param name="configure">Additional configuration for the snackbar.</param>
|
|
|
|
|
/// <param name="key">If a key is provided, this message will not be shown while any other message with the same key is being shown.</param>
|
|
|
|
|
/// <returns>The snackbar created by the parameters.</returns>
|
|
|
|
|
public Snackbar Add<T>(Dictionary<string, object> componentParameters = null, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null, string key = "") where T : IComponent
|
|
|
|
|
{
|
|
|
|
|
var type = typeof(T);
|
|
|
|
|
var message = new SnackbarMessage(type, componentParameters, key);
|
|
|
|
|
|
|
|
|
|
return Add(message, severity, configure);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Displays a snackbar containing the RenderFragment.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="message">The RenderFragment which specifies the content of the snackbar.</param>
|
|
|
|
|
/// <param name="severity">The severity of the snackbar. Dictates the color and default icon of the notification.</param>
|
|
|
|
|
/// <param name="configure">Additional configuration for the snackbar.</param>
|
|
|
|
|
/// <param name="key">If a key is provided, this message will not be shown while any other message with the same key is being shown.</param>
|
|
|
|
|
/// <returns>The snackbar created by the parameters.</returns>
|
|
|
|
|
public Snackbar Add(RenderFragment message, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null, string key = "")
|
|
|
|
|
{
|
|
|
|
|
if (message == null) return null;
|
|
|
|
|
|
|
|
|
|
var componentParams = new Dictionary<string, object>()
|
|
|
|
|
{
|
|
|
|
|
{ "Message", message as object }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Add
|
|
|
|
|
(
|
|
|
|
|
new SnackbarMessage(typeof(SnackbarMessageRenderFragment), componentParams, key),
|
|
|
|
|
severity,
|
|
|
|
|
configure
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Displays a snackbar containing the text.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="message">The string which specifies the content of the snackbar.</param>
|
|
|
|
|
/// <param name="severity">The severity of the snackbar. Dictates the color and default icon of the notification.</param>
|
|
|
|
|
/// <param name="configure">Additional configuration for the snackbar.</param>
|
|
|
|
|
/// <param name="key">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.</param>
|
|
|
|
|
/// <returns>The snackbar created by the parameters.</returns>
|
|
|
|
|
public Snackbar Add(string message, Severity severity = Severity.Normal, Action<SnackbarOptions> configure = null, string key = "")
|
|
|
|
|
{
|
|
|
|
|
if (message.IsEmpty()) return null;
|
|
|
|
|
message = message.Trimmed();
|
|
|
|
|
|
|
|
|
|
var componentParams = new Dictionary<string, object>() { { "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<SnackbarOptions> 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<Snackbar> snackbars)
|
|
|
|
|
{
|
|
|
|
|
if (SnackBarList.Count == 0) return;
|
|
|
|
|
|
|
|
|
|
foreach (var snackbar in snackbars)
|
|
|
|
|
{
|
|
|
|
|
snackbar.OnClose -= Remove;
|
|
|
|
|
snackbar.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SnackBarList.Clear();
|
|
|
|
|
}
|
|
|
|
|
}
|