|
|
|
|
// Copyright (c) MudBlazor 2021
|
|
|
|
|
// MudBlazor licenses this file to you under the MIT license.
|
|
|
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
|
|
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
|
using Microsoft.AspNetCore.Components;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using Microsoft.JSInterop;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Components;
|
|
|
|
|
|
|
|
|
|
public interface IPopoverService
|
|
|
|
|
{
|
|
|
|
|
PopoverHandler Register(RenderFragment fragment);
|
|
|
|
|
Task<bool> Unregister(PopoverHandler hanlder);
|
|
|
|
|
ValueTask<int> CountProviders();
|
|
|
|
|
bool ThrowOnDuplicateProvider { get; }
|
|
|
|
|
IEnumerable<PopoverHandler> Handlers { get; }
|
|
|
|
|
Task InitializeIfNeeded();
|
|
|
|
|
event EventHandler FragmentsChanged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class PopoverHandler
|
|
|
|
|
{
|
|
|
|
|
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
|
|
|
|
private readonly IJSRuntime _runtime;
|
|
|
|
|
private readonly Action _updater;
|
|
|
|
|
private bool _detached;
|
|
|
|
|
|
|
|
|
|
public Guid Id { get; }
|
|
|
|
|
public RenderFragment Fragment { get; private set; }
|
|
|
|
|
public bool IsConnected { get; private set; }
|
|
|
|
|
public string Class { get; private set; }
|
|
|
|
|
public string Style { get; private set; }
|
|
|
|
|
public object Tag { get; private set; }
|
|
|
|
|
public bool ShowContent { get; private set; }
|
|
|
|
|
public DateTime? ActivationDate { get; private set; }
|
|
|
|
|
public Dictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();
|
|
|
|
|
public Render ElementReference { get; set; }
|
|
|
|
|
|
|
|
|
|
public PopoverHandler(RenderFragment fragment, IJSRuntime jsInterop, Action updater)
|
|
|
|
|
{
|
|
|
|
|
Fragment = fragment ?? throw new ArgumentNullException(nameof(fragment));
|
|
|
|
|
_runtime = jsInterop ?? throw new ArgumentNullException(nameof(jsInterop));
|
|
|
|
|
_updater = updater ?? throw new ArgumentNullException(nameof(updater));
|
|
|
|
|
Id = Guid.NewGuid();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetComponentBaseParameters(UIComponent componentBase, string @class, string style, bool showContent)
|
|
|
|
|
{
|
|
|
|
|
Class = @class;
|
|
|
|
|
Style = style;
|
|
|
|
|
Tag = componentBase.Tag;
|
|
|
|
|
UserAttributes = componentBase.UserAttributes;
|
|
|
|
|
ShowContent = showContent;
|
|
|
|
|
if (showContent == true)
|
|
|
|
|
{
|
|
|
|
|
ActivationDate = DateTime.Now;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ActivationDate = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void UpdateFragment(RenderFragment fragment,
|
|
|
|
|
UIComponent componentBase, string @class, string style, bool showContent)
|
|
|
|
|
{
|
|
|
|
|
Fragment = fragment;
|
|
|
|
|
SetComponentBaseParameters(componentBase, @class, @style, showContent);
|
|
|
|
|
// this basically calls StateHasChanged on the Popover
|
|
|
|
|
ElementReference?.ForceRender();
|
|
|
|
|
_updater?.Invoke(); // <-- this doesn't do anything anymore except making unit tests happy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task Initialize()
|
|
|
|
|
{
|
|
|
|
|
await _semaphore.WaitAsync();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_detached)
|
|
|
|
|
{
|
|
|
|
|
// If _detached is True, it means Detach() was invoked before Initialize() has had
|
|
|
|
|
// a chance to run. In this case, we just want to return and not do anything else
|
|
|
|
|
// otherwise we will end up with a memory leak.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IsConnected = await _runtime.InvokeVoidAsyncWithErrorHandling("mudPopover.connect", Id); ;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_semaphore.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task Detach()
|
|
|
|
|
{
|
|
|
|
|
await _semaphore.WaitAsync();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_detached = true;
|
|
|
|
|
|
|
|
|
|
if (IsConnected)
|
|
|
|
|
{
|
|
|
|
|
await _runtime.InvokeVoidAsyncWithErrorHandling("mudPopover.disconnect", Id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
IsConnected = false;
|
|
|
|
|
_semaphore.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class PopoverService : IPopoverService, IAsyncDisposable
|
|
|
|
|
{
|
|
|
|
|
private Dictionary<Guid, PopoverHandler> _handlers = new();
|
|
|
|
|
private bool _isInitialized = false;
|
|
|
|
|
private readonly IJSRuntime _jsRuntime;
|
|
|
|
|
private readonly PopoverOptions _options;
|
|
|
|
|
private SemaphoreSlim _semaphoreSlim = new(1, 1);
|
|
|
|
|
|
|
|
|
|
public event EventHandler FragmentsChanged;
|
|
|
|
|
|
|
|
|
|
public bool ThrowOnDuplicateProvider => _options.ThrowOnDuplicateProvider;
|
|
|
|
|
public IEnumerable<PopoverHandler> Handlers => _handlers.Values.AsEnumerable();
|
|
|
|
|
|
|
|
|
|
public PopoverService(IJSRuntime jsInterop, IOptions<PopoverOptions> options = null)
|
|
|
|
|
{
|
|
|
|
|
this._options = options?.Value ?? new PopoverOptions();
|
|
|
|
|
_jsRuntime = jsInterop ?? throw new ArgumentNullException(nameof(jsInterop));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task InitializeIfNeeded()
|
|
|
|
|
{
|
|
|
|
|
if (_isInitialized == true) { return; }
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _semaphoreSlim.WaitAsync();
|
|
|
|
|
if (_isInitialized == true) { return; }
|
|
|
|
|
|
|
|
|
|
await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudPopover.initialize", _options.ContainerClass, _options.FlipMargin);
|
|
|
|
|
_isInitialized = true;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_semaphoreSlim.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public PopoverHandler Register(RenderFragment fragment)
|
|
|
|
|
{
|
|
|
|
|
var handler = new PopoverHandler(fragment, _jsRuntime, () => { /*not doing anything on purpose for now*/ });
|
|
|
|
|
_handlers.Add(handler.Id, handler);
|
|
|
|
|
|
|
|
|
|
FragmentsChanged?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
|
|
|
|
|
return handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> Unregister(PopoverHandler handler)
|
|
|
|
|
{
|
|
|
|
|
if (handler == null) { return false; }
|
|
|
|
|
if (_handlers.Remove(handler.Id) == false) { return false; }
|
|
|
|
|
|
|
|
|
|
await handler.Detach();
|
|
|
|
|
|
|
|
|
|
FragmentsChanged?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async ValueTask<int> CountProviders()
|
|
|
|
|
{
|
|
|
|
|
if (!_isInitialized) { return -1; }
|
|
|
|
|
|
|
|
|
|
var (success, value) = await _jsRuntime.InvokeAsyncWithErrorHandling<int>("mudpopoverHelper.countProviders");
|
|
|
|
|
if (success)
|
|
|
|
|
return value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TO DO add js test
|
|
|
|
|
[ExcludeFromCodeCoverage]
|
|
|
|
|
public async ValueTask DisposeAsync()
|
|
|
|
|
{
|
|
|
|
|
if (_isInitialized == false) { return; }
|
|
|
|
|
|
|
|
|
|
await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudPopover.dispose");
|
|
|
|
|
}
|
|
|
|
|
}
|