156 lines
6.0 KiB
Plaintext
156 lines
6.0 KiB
Plaintext
@using NexusReader.UI.Shared.Components.Atoms
|
|
@using NexusReader.UI.Shared.Services
|
|
@using NexusReader.Application.Utilities
|
|
@namespace NexusReader.UI.Shared.Components.Organisms
|
|
@inject IReaderInteractionService InteractionService
|
|
|
|
<div class="nexus-unified-mobile-toolbar">
|
|
<!-- LEFT SLOT: Progress & Section Checkpoints -->
|
|
<div class="toolbar-slot left-slot" @onclick="ToggleCheckpoints" title="Rozdziały i checkpoints">
|
|
<div class="progress-ring-wrapper">
|
|
<svg class="progress-ring" width="38" height="38">
|
|
<circle class="progress-ring-track" stroke="rgba(255,255,255,0.06)" stroke-width="2.5" fill="transparent" r="16" cx="19" cy="19" />
|
|
<circle class="progress-ring-indicator" stroke="var(--nexus-neon, #00FF99)" stroke-width="2.5" fill="transparent" r="16" cx="19" cy="19"
|
|
stroke-dasharray="100.53" stroke-dashoffset="@GetDashOffset()" />
|
|
</svg>
|
|
<span class="progress-text">@ScrollPercentage%</span>
|
|
</div>
|
|
<div class="progress-info">
|
|
<span class="slot-label">Postęp</span>
|
|
<span class="slot-desc">Checkpoints</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CENTER SLOT: Global AI Assistant Glowing Trigger -->
|
|
<div class="toolbar-slot center-slot">
|
|
<button class="btn-nexus-ai-core" @onclick="HandleAssistantClick" aria-label="Asystent AI">
|
|
<div class="pulse-ring"></div>
|
|
<div class="pulse-ring-outer"></div>
|
|
<NexusIcon Name="robot" Size="22" Class="ai-core-icon" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- RIGHT SLOT: Context View Toggles -->
|
|
<div class="toolbar-slot right-slot">
|
|
<button class="nav-toggle-btn @(ActiveTab == MobileReaderTab.Reader ? "active" : "")"
|
|
@onclick="() => ChangeTab(MobileReaderTab.Reader)"
|
|
aria-label="Tekst">
|
|
<NexusIcon Name="book-open" Size="18" />
|
|
<span>Tekst</span>
|
|
</button>
|
|
<button class="nav-toggle-btn @(ActiveTab == MobileReaderTab.Graph ? "active" : "")"
|
|
@onclick="() => ChangeTab(MobileReaderTab.Graph)"
|
|
aria-label="Graf">
|
|
<NexusIcon Name="share-2" Size="18" />
|
|
<span>Graf</span>
|
|
</button>
|
|
<button class="nav-toggle-btn @(ActiveTab == MobileReaderTab.Concepts ? "active" : "")"
|
|
@onclick="() => ChangeTab(MobileReaderTab.Concepts)"
|
|
aria-label="Mapa">
|
|
<NexusIcon Name="map" Size="18" />
|
|
<span>Mapa</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SECTION CHECKPOINTS OVERLAY -->
|
|
<div class="checkpoints-overlay @(IsCheckpointsOpen ? "is-open" : "")">
|
|
<div class="checkpoints-backdrop" @onclick="ToggleCheckpoints"></div>
|
|
<div class="checkpoints-sheet">
|
|
<div class="sheet-drag-handle"></div>
|
|
<header class="checkpoints-header">
|
|
<h4>Checkpoints Sekcji</h4>
|
|
<button class="close-checkpoints-btn" @onclick="ToggleCheckpoints">
|
|
<NexusIcon Name="close" Size="16" />
|
|
</button>
|
|
</header>
|
|
<div class="checkpoints-body">
|
|
@if (Checkpoints == null || !Checkpoints.Any())
|
|
{
|
|
<div class="empty-checkpoints">
|
|
<NexusIcon Name="info" Size="20" />
|
|
<p>Brak punktów kontrolnych w tym rozdziale.</p>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="checkpoints-list">
|
|
@foreach (var cp in Checkpoints)
|
|
{
|
|
var isCurrent = cp == InteractionService.CurrentBlockId;
|
|
<div class="checkpoint-item @(isCurrent ? "active" : "")" @onclick="() => SelectCheckpoint(cp)">
|
|
<div class="checkpoint-indicator">
|
|
<div class="indicator-dot"></div>
|
|
<div class="indicator-line"></div>
|
|
</div>
|
|
<div class="checkpoint-details">
|
|
<span class="checkpoint-id">@cp.ToUpper()</span>
|
|
<span class="checkpoint-label">@(isCurrent ? "Aktualna sekcja" : "Przejdź do sekcji")</span>
|
|
</div>
|
|
<NexusIcon Name="chevron-right" Size="14" Class="arrow-icon" />
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter] public int ScrollPercentage { get; set; }
|
|
[Parameter] public MobileReaderTab ActiveTab { get; set; }
|
|
[Parameter] public EventCallback<MobileReaderTab> OnTabChanged { get; set; }
|
|
[Parameter] public EventCallback OnAssistantClick { get; set; }
|
|
[Parameter] public List<string> Checkpoints { get; set; } = new();
|
|
|
|
private bool IsCheckpointsOpen { get; set; }
|
|
|
|
public enum MobileReaderTab
|
|
{
|
|
Reader,
|
|
Graph,
|
|
Concepts
|
|
}
|
|
|
|
private double GetDashOffset()
|
|
{
|
|
// Circumference of r=16 is 2 * pi * 16 = 100.53
|
|
double circumference = 100.53;
|
|
double progress = Math.Clamp(ScrollPercentage, 0, 100);
|
|
return circumference - (progress / 100.0) * circumference;
|
|
}
|
|
|
|
private void ToggleCheckpoints()
|
|
{
|
|
IsCheckpointsOpen = !IsCheckpointsOpen;
|
|
}
|
|
|
|
private async Task SelectCheckpoint(string checkpointId)
|
|
{
|
|
IsCheckpointsOpen = false;
|
|
// Scroll to the targeted block
|
|
await InteractionService.RequestScrollToBlock(checkpointId);
|
|
// Ensure user is on the text reading tab to see the scroll happen
|
|
if (ActiveTab != MobileReaderTab.Reader)
|
|
{
|
|
await ChangeTab(MobileReaderTab.Reader);
|
|
}
|
|
}
|
|
|
|
private async Task ChangeTab(MobileReaderTab tab)
|
|
{
|
|
if (OnTabChanged.HasDelegate)
|
|
{
|
|
await OnTabChanged.InvokeAsync(tab);
|
|
}
|
|
}
|
|
|
|
private async Task HandleAssistantClick()
|
|
{
|
|
if (OnAssistantClick.HasDelegate)
|
|
{
|
|
await OnAssistantClick.InvokeAsync();
|
|
}
|
|
}
|
|
}
|