Files
chapter-organizer/WebApp/Components/Shared/Components/PageNoteButton.razor
T
poprhythm 083e81aa25 Refactor PageNoteButton component to include popover for note content display
This commit enhances the PageNoteButton component by wrapping the MudButton in a div that manages mouse events to control the visibility of a MudPopover. The popover displays detailed note content when the button is hovered over, improving user interaction and accessibility of page notes. Additionally, the component's state management is updated to handle the new popover functionality, ensuring a seamless user experience.
2026-01-25 21:59:31 -05:00

170 lines
5.1 KiB
Plaintext

@namespace WebApp.Components.Shared.Components
@inject INotesService NotesService
@inject IDialogService DialogService
@implements IAsyncDisposable
<div @ref="_anchorElement"
@onmouseenter="@(() => { if (_hasContent) _popoverOpen = true; })"
@onmouseleave="@(() => _popoverOpen = false)"
style="display: inline-block;">
<MudButton StartIcon="@IconValue"
OnClick="OpenDialog"
Variant="@Variant"
Size="@Size"
Color="@ButtonColor"
Tooltip="@TooltipText"
Class="@MarkdownHelper.GetNoteColorClass(_noteId)">
@if (!string.IsNullOrEmpty(ButtonText))
{
@ButtonText
}
</MudButton>
</div>
<MudPopover @bind-Open="_popoverOpen"
AnchorOrigin="Origin.BottomCenter"
TransformOrigin="Origin.TopCenter"
Elevation="8"
Anchor="@_anchorElement">
<ChildContent>
@if (_hasContent && !string.IsNullOrWhiteSpace(_noteContent))
{
<MudPaper Elevation="0"
Class="pa-3"
Style="max-width: 500px; max-height: 400px; overflow-y: auto;"
@onmouseenter="@(() => _popoverOpen = true)"
@onmouseleave="@(() => _popoverOpen = false)">
<MudText Typo="Typo.subtitle1" Class="mb-2">@PageIdentifier</MudText>
<MudDivider Class="my-2" />
<div class="markdown-content" style="max-height: 350px; overflow-y: auto;">
@((MarkupString)MarkdownHelper.ToHtml(_noteContent))
</div>
</MudPaper>
}
</ChildContent>
</MudPopover>
@code {
[Parameter]
public string PageIdentifier { get; set; } = null!;
[Parameter]
public string? Icon { get; set; }
[Parameter]
public Variant Variant { get; set; } = Variant.Outlined;
[Parameter]
public Size Size { get; set; } = Size.Medium;
[Parameter]
public string? ButtonText { get; set; }
[Parameter]
public string? Tooltip { get; set; }
private string IconValue => Icon ?? Icons.Material.Filled.Note;
private string TooltipText => Tooltip ?? $"Page notes for {PageIdentifier}";
private bool _hasContent = false;
private int _noteId = 0;
private string _noteContent = string.Empty;
private bool _popoverOpen = false;
private ElementReference _anchorElement;
private Color ButtonColor => _hasContent ? Color.Success : (Variant == Variant.Filled ? Color.Primary : Color.Default);
private CancellationTokenSource? _cancellationTokenSource;
private bool _isDisposed = false;
protected override void OnInitialized()
{
_cancellationTokenSource = new CancellationTokenSource();
}
protected override async Task OnInitializedAsync()
{
await CheckNoteContent();
}
private async Task CheckNoteContent()
{
if (_isDisposed) return;
try
{
var note = await NotesService.GetPageNoteAsync(PageIdentifier);
_hasContent = note != null && !string.IsNullOrWhiteSpace(note.Content);
_noteId = note?.Id ?? 0;
_noteContent = note?.Content ?? string.Empty;
if (!_isDisposed)
{
StateHasChanged();
}
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception)
{
// Error checking note - ignore
}
}
private async Task OpenDialog()
{
if (_isDisposed) return;
try
{
var parameters = new DialogParameters
{
["PageIdentifier"] = PageIdentifier
};
var options = new DialogOptions
{
MaxWidth = MaxWidth.Medium,
FullWidth = true,
CloseButton = true
};
var dialog = await DialogService.ShowAsync<PageNoteDialog>($"Page Notes: {PageIdentifier}", parameters, options);
var result = await dialog.Result;
// Always refresh content indicator after dialog closes
// (content may have been saved even if dialog was closed with Cancel)
if (!_isDisposed)
{
await CheckNoteContent();
}
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception)
{
// Error opening dialog - could show snackbar if we had access
}
}
public async ValueTask DisposeAsync()
{
if (!_isDisposed)
{
_isDisposed = true;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
await ValueTask.CompletedTask;
}
}