@namespace DnsmasqWebUI.Components.EffectiveConfig @using DnsmasqWebUI.Extensions.DependencyInjection @using DnsmasqWebUI.Extensions.Interop @using Microsoft.AspNetCore.Components @using Microsoft.JSInterop @using System.Text.Json.Serialization @inject IHttpClientFactory HttpFactory @inject IJSRuntime JSRuntime @implements IAsyncDisposable @if (Visible) {
@Title
@if (_loading) {

Loading…

} else if (_error != null) {

@_error

} else if (_helpHtml != null) {
@((MarkupString)_helpHtml)
}
} @code { [Parameter] public string? HelpKey { get; set; } [Parameter] public string? AnchorId { get; set; } [Parameter] public bool Visible { get; set; } [Parameter] public string Title { get; set; } = "Option help"; [Parameter] public EventCallback OnClose { get; set; } [Parameter] public EventCallback OnMouseEnterModal { get; set; } [Parameter] public EventCallback OnMouseLeaveModal { get; set; } private string? _helpHtml; private string? _error; private bool _loading; private string? _lastKey; private string _positionStyle = ""; private readonly string _popoverId = "config-option-help-popover-" + Guid.NewGuid().ToString("N")[..8]; private DotNetObjectReference? _dotNetRef; private bool _scrollListenerAdded; private bool _mouseOverModal; private IJSObjectReference? _jsModule; private bool _moduleLoaded; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsModule = await JSRuntime.InvokeAsyncSafe("import", default, "./js/config-option-help.js"); _moduleLoaded = _jsModule != null; } if (Visible && !_scrollListenerAdded && _moduleLoaded && _jsModule != null) { _scrollListenerAdded = true; _dotNetRef = DotNetObjectReference.Create(this); await _jsModule.InvokeVoidAsyncSafe("addScrollCloseListener", _dotNetRef); } else if (!Visible && _scrollListenerAdded && _jsModule != null) { _scrollListenerAdded = false; await _jsModule.InvokeVoidAsyncSafe("removeScrollCloseListener"); _dotNetRef?.Dispose(); _dotNetRef = null; } } [JSInvokable] public async Task OnScrollClose() { await PositionUnderAnchorAsync(); StateHasChanged(); if (!_mouseOverModal) await OnClose.InvokeAsync(); } public async ValueTask DisposeAsync() { try { if (_scrollListenerAdded && _jsModule != null) { _scrollListenerAdded = false; await _jsModule.InvokeVoidAsyncSafe("removeScrollCloseListener"); } } finally { _dotNetRef?.Dispose(); _dotNetRef = null; await _jsModule.DisposeAsyncSafe(); _jsModule = null; } } protected override async Task OnParametersSetAsync() { if (!Visible || string.IsNullOrEmpty(HelpKey)) { _helpHtml = null; _error = null; _loading = false; _positionStyle = ""; _mouseOverModal = false; return; } if (HelpKey == _lastKey && _helpHtml != null) { await PositionUnderAnchorAsync(); return; } _lastKey = HelpKey; _loading = true; _error = null; _helpHtml = null; _positionStyle = "visibility: hidden;"; // avoid flash before position is set StateHasChanged(); try { var http = HttpFactory.CreateClient(ServiceCollectionExtensions.DnsmasqApiClientName); var response = await http.GetAsync($"option-help/{HelpKey}.html"); if (response.IsSuccessStatusCode) _helpHtml = await response.Content.ReadAsStringAsync(); else _error = "Help not available for this option."; } catch { _error = "Could not load help."; } finally { _loading = false; await PositionUnderAnchorAsync(); StateHasChanged(); } } private async Task PositionUnderAnchorAsync() { if (string.IsNullOrEmpty(AnchorId) || _jsModule == null) return; var rect = await _jsModule.InvokeAsyncSafe("getAnchorRect", AnchorId); if (rect is { } r) { var gap = 4; var topPx = r.Bottom + gap; var leftPx = r.Left; _positionStyle = $"position: fixed; top: {topPx}px; left: {leftPx}px; visibility: visible;"; } else { _positionStyle = "visibility: visible;"; } } private async Task OnModalMouseEnter() { _mouseOverModal = true; await OnMouseEnterModal.InvokeAsync(); } private async Task OnModalMouseLeave() { _mouseOverModal = false; await OnMouseLeaveModal.InvokeAsync(); } private sealed record BoundingRect( [property: JsonPropertyName("top")] double Top, [property: JsonPropertyName("left")] double Left, [property: JsonPropertyName("bottom")] double Bottom, [property: JsonPropertyName("width")] double Width, [property: JsonPropertyName("height")] double Height); }