|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
|
using Connected.Interop;
|
|
|
|
|
using Microsoft.AspNetCore.Components;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using Microsoft.JSInterop;
|
|
|
|
|
|
|
|
|
|
namespace Connected.Services;
|
|
|
|
|
|
|
|
|
|
public class ResizeObserver : IResizeObserver, IDisposable, IAsyncDisposable
|
|
|
|
|
{
|
|
|
|
|
private Boolean _isDisposed = false;
|
|
|
|
|
|
|
|
|
|
private readonly DotNetObjectReference<ResizeObserver> _dotNetRef;
|
|
|
|
|
private readonly IJSRuntime _jsRuntime;
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<Guid, ElementReference> _cachedValueIds = new();
|
|
|
|
|
private readonly Dictionary<ElementReference, BoundingClientRect> _cachedValues = new();
|
|
|
|
|
|
|
|
|
|
private Guid _id = Guid.NewGuid();
|
|
|
|
|
private ResizeObserverOptions _options;
|
|
|
|
|
|
|
|
|
|
[DynamicDependency(nameof(OnSizeChanged))]
|
|
|
|
|
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(SizeChangeUpdateInfo))]
|
|
|
|
|
public ResizeObserver(IJSRuntime jsRuntime, ResizeObserverOptions options)
|
|
|
|
|
{
|
|
|
|
|
_dotNetRef = DotNetObjectReference.Create(this);
|
|
|
|
|
_jsRuntime = jsRuntime;
|
|
|
|
|
_options = options;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ResizeObserver(IJSRuntime jsRuntime, IOptions<ResizeObserverOptions> options = null) : this(jsRuntime, options?.Value ?? new ResizeObserverOptions())
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<BoundingClientRect> Observe(ElementReference element) => (await Observe(new[] { element })).FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
public async Task<IEnumerable<BoundingClientRect>> Observe(IEnumerable<ElementReference> elements)
|
|
|
|
|
{
|
|
|
|
|
var filteredElements = elements.Where(x => x.Context != null && _cachedValues.ContainsKey(x) == false).ToList();
|
|
|
|
|
if (filteredElements.Any() == false)
|
|
|
|
|
{
|
|
|
|
|
return Array.Empty<BoundingClientRect>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<Guid> elementIds = new();
|
|
|
|
|
|
|
|
|
|
foreach (var item in filteredElements)
|
|
|
|
|
{
|
|
|
|
|
var id = Guid.NewGuid();
|
|
|
|
|
elementIds.Add(id);
|
|
|
|
|
_cachedValueIds.Add(id, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result = await _jsRuntime.InvokeAsync<IEnumerable<BoundingClientRect>>("mudResizeObserver.connect", _id, _dotNetRef, filteredElements, elementIds, _options) ?? Array.Empty<BoundingClientRect>();
|
|
|
|
|
var counter = 0;
|
|
|
|
|
foreach (var item in result)
|
|
|
|
|
{
|
|
|
|
|
_cachedValues.Add(filteredElements.ElementAt(counter), item);
|
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task Unobserve(ElementReference element)
|
|
|
|
|
{
|
|
|
|
|
var elementId = _cachedValueIds.FirstOrDefault(x => x.Value.Id == element.Id).Key;
|
|
|
|
|
if (elementId == default) { return; }
|
|
|
|
|
|
|
|
|
|
//if the unobserve happens during a component teardown, the try-catch is a safe guard to prevent a "pseudo" exception
|
|
|
|
|
try { await _jsRuntime.InvokeVoidAsync($"mudResizeObserver.disconnect", _id, elementId); } catch (Exception) { }
|
|
|
|
|
|
|
|
|
|
_cachedValueIds.Remove(elementId);
|
|
|
|
|
_cachedValues.Remove(element);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsElementObserved(ElementReference reference) => _cachedValues.ContainsKey(reference);
|
|
|
|
|
|
|
|
|
|
public record SizeChangeUpdateInfo(Guid Id, BoundingClientRect Size);
|
|
|
|
|
|
|
|
|
|
[JSInvokable]
|
|
|
|
|
public void OnSizeChanged(IEnumerable<SizeChangeUpdateInfo> changes)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<ElementReference, BoundingClientRect> parsedChanges = new();
|
|
|
|
|
foreach (var item in changes)
|
|
|
|
|
{
|
|
|
|
|
if (_cachedValueIds.ContainsKey(item.Id) == false) { continue; }
|
|
|
|
|
|
|
|
|
|
var elementRef = _cachedValueIds[item.Id];
|
|
|
|
|
_cachedValues[elementRef] = item.Size;
|
|
|
|
|
parsedChanges.Add(elementRef, item.Size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OnResized?.Invoke(parsedChanges);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public event SizeChanged OnResized;
|
|
|
|
|
|
|
|
|
|
public BoundingClientRect GetSizeInfo(ElementReference reference)
|
|
|
|
|
{
|
|
|
|
|
if (_cachedValues.ContainsKey(reference) == false)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _cachedValues[reference];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double GetHeight(ElementReference reference) => GetSizeInfo(reference)?.Height ?? 0.0;
|
|
|
|
|
public double GetWidth(ElementReference reference) => GetSizeInfo(reference)?.Width ?? 0.0;
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing && _isDisposed == false)
|
|
|
|
|
{
|
|
|
|
|
_isDisposed = true;
|
|
|
|
|
_dotNetRef.Dispose();
|
|
|
|
|
_cachedValueIds.Clear();
|
|
|
|
|
_cachedValues.Clear();
|
|
|
|
|
|
|
|
|
|
//in a fire and forget manner, we just "trying" to cancel the listener. So, we are not interested in an potential error
|
|
|
|
|
try { _ = _jsRuntime.InvokeVoidAsync($"mudResizeObserver.cancelListener", _id); } catch (Exception) { }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async ValueTask DisposeAsync()
|
|
|
|
|
{
|
|
|
|
|
if (_isDisposed == true) { return; }
|
|
|
|
|
|
|
|
|
|
_isDisposed = true;
|
|
|
|
|
|
|
|
|
|
_dotNetRef.Dispose();
|
|
|
|
|
_cachedValueIds.Clear();
|
|
|
|
|
_cachedValues.Clear();
|
|
|
|
|
|
|
|
|
|
//in a fire and forget manner, we just "trying" to cancel the listener. So, we are not interested in an potential error
|
|
|
|
|
try { await _jsRuntime.InvokeVoidAsync($"mudResizeObserver.cancelListener", _id); } catch (Exception) { }
|
|
|
|
|
}
|
|
|
|
|
}
|