feat: implement dynamic absolute positioning for the AI selection toolbar via JS to handle scroll and boundary collisions
This commit is contained in:
@@ -4,10 +4,11 @@
|
||||
@inject KnowledgeCoordinator Coordinator
|
||||
@inject IReaderInteractionService InteractionService
|
||||
@inject IQuizStateService QuizService
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@if (IsVisible)
|
||||
{
|
||||
<div class="selection-ai-panel @(PositionBelow ? "below" : "")" style="@PanelStyle">
|
||||
<div class="selection-ai-panel @(_positionBelow ? "below" : "")" style="@_style">
|
||||
<button class="toolbar-btn primary @(IsLoadingSummary ? "loading" : "") @(IsAnyLoading ? "disabled" : "")"
|
||||
disabled="@IsAnyLoading"
|
||||
@onclick="RequestSummaryAsync">
|
||||
@@ -50,22 +51,53 @@
|
||||
private bool IsLoadingSummary = false;
|
||||
private bool IsLoadingQuiz = false;
|
||||
private bool IsAnyLoading => IsLoadingSummary || IsLoadingQuiz;
|
||||
private bool PositionBelow => Coordinates != null && Coordinates.Top < 250;
|
||||
|
||||
private string _style = "visibility: hidden; opacity: 0; pointer-events: none;";
|
||||
private bool _positionBelow = false;
|
||||
private SelectionCoordinates? _lastCoordinates;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
Console.WriteLine($"[SelectionAiPanel] Parameters set. SelectedText: {SelectedText.Length} chars, Coordinates: {Coordinates?.Top}, PositionBelow: {PositionBelow}");
|
||||
Console.WriteLine($"[SelectionAiPanel] Parameters set. SelectedText: {SelectedText.Length} chars, Coordinates: {Coordinates?.Top}");
|
||||
|
||||
if (Coordinates != _lastCoordinates)
|
||||
{
|
||||
_lastCoordinates = Coordinates;
|
||||
_style = "visibility: hidden; opacity: 0; pointer-events: none;";
|
||||
_positionBelow = false;
|
||||
}
|
||||
|
||||
// Reset loading states when parameters change
|
||||
IsLoadingSummary = false;
|
||||
IsLoadingQuiz = false;
|
||||
}
|
||||
|
||||
private string PanelStyle => Coordinates != null
|
||||
? string.Create(System.Globalization.CultureInfo.InvariantCulture,
|
||||
$"top: {(PositionBelow ? Coordinates.Bottom + 16 : Coordinates.Top - 16):F1}px !important; " +
|
||||
$"left: {Math.Max(140.0, Math.Min(Coordinates.ViewportWidth - 140.0, Coordinates.Left + Coordinates.Width / 2.0)):F1}px !important; " +
|
||||
$"transform: translate(-50%, {(PositionBelow ? "0" : "-100%")}) !important;")
|
||||
: "";
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (IsVisible && _style.Contains("visibility: hidden"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/selectionHandler.js");
|
||||
var result = await module.InvokeAsync<PositionResult>("positionToolbar");
|
||||
if (result != null)
|
||||
{
|
||||
_style = string.Create(System.Globalization.CultureInfo.InvariantCulture,
|
||||
$"left: {result.Left:F1}px !important; " +
|
||||
$"top: {result.Top:F1}px !important; " +
|
||||
$"visibility: visible !important; " +
|
||||
$"opacity: 1 !important; " +
|
||||
$"pointer-events: auto !important;");
|
||||
_positionBelow = result.Below;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[SelectionAiPanel] Error positioning toolbar: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RequestSummaryAsync()
|
||||
{
|
||||
@@ -131,5 +163,13 @@
|
||||
{
|
||||
await InteractionService.NotifyTextSelected(string.Empty, string.Empty, null!);
|
||||
}
|
||||
|
||||
private class PositionResult
|
||||
{
|
||||
public double Left { get; set; }
|
||||
public double Top { get; set; }
|
||||
public bool Below { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user