//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(); } }