Files
deepdrft/DeepDrftPublic.Client/Pages/TracksView.razor.cs
T
daniel-c-harvey d055c2a548 WASM State Fixes
2026-06-06 09:59:43 -04:00

118 lines
4.0 KiB
C#

using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Services;
using DeepDrftPublic.Client.ViewModels;
using Microsoft.AspNetCore.Components;
using Models.Common;
namespace DeepDrftPublic.Client.Pages;
public partial class TracksView : ComponentBase, IDisposable
{
private const string PersistKey = "tracks-page";
[Inject] public required TracksViewModel ViewModel { get; set; }
[Inject] public required PersistentComponentState PersistentState { get; set; }
[CascadingParameter] public required IStreamingPlayerService PlayerService { get; set; }
private TrackDto? _selectedTrack = null;
private IStreamingPlayerService? _subscribedService;
private PersistingComponentStateSubscription _persistingSubscription;
protected override async Task OnInitializedAsync()
{
// Carry the prerendered page across the prerender -> interactive (WASM) seam.
// Without this, the WASM pass gets a fresh scoped ViewModel (Page == null),
// re-renders the skeleton, re-fetches, and replaces the gallery DOM a few
// seconds in — replaying TrackCard entrance animations. Mirror the dark-mode
// PersistentComponentState bridge: persist on the way out of prerender,
// restore on the interactive pass, and only fetch on a miss.
_persistingSubscription = PersistentState.RegisterOnPersisting(PersistTracks);
if (PersistentState.TryTakeFromJson<PagedResult<TrackDto>>(PersistKey, out var restored) && restored is not null)
{
ViewModel.Page = restored;
ViewModel.PageNumber = restored.Page;
}
else
{
await SetPage(ViewModel.PageNumber);
}
}
protected override void OnParametersSet()
{
// The Stop/Close buttons on the player bar reset the player directly,
// bypassing PlayTrack — so the gallery's selection must follow player
// state rather than only its own clicks. Subscribe to the multicast
// side-channel (the cascade is IsFixed, so provider re-renders don't
// reach us) and clear the highlight when nothing is loaded.
if (PlayerService != null && !ReferenceEquals(PlayerService, _subscribedService))
{
if (_subscribedService != null)
_subscribedService.StateChanged -= OnPlayerStateChanged;
PlayerService.StateChanged += OnPlayerStateChanged;
_subscribedService = PlayerService;
}
}
private void OnPlayerStateChanged()
{
// Sync the gallery selection to the player. When the player is no longer
// loaded (stopped/closed/ended) drop the highlight; guard against a
// redundant re-render when nothing actually changed.
if (!PlayerService.IsLoaded && _selectedTrack != null)
{
_selectedTrack = null;
InvokeAsync(StateHasChanged);
}
}
private Task PersistTracks()
{
if (ViewModel.Page is not null)
{
PersistentState.PersistAsJson(PersistKey, ViewModel.Page);
}
return Task.CompletedTask;
}
private async Task SetPage(int newPage)
{
var result = await ViewModel.TrackData.GetPage(newPage, ViewModel.PageSize, ViewModel.SortBy, ViewModel.IsDescending);
if (result is { Success: true, Value: PagedResult<TrackDto> pageResult })
{
ViewModel.Page = pageResult;
ViewModel.PageSize = pageResult.PageSize;
}
}
private async Task PlayTrack(TrackDto? track)
{
if (track == null && _selectedTrack == null || track?.Id == _selectedTrack?.Id) return;
if (track is null)
{
await PlayerService.Unload();
}
else
{
await PlayerService.SelectTrack(track);
}
_selectedTrack = track;
}
public void Dispose()
{
_persistingSubscription.Dispose();
if (_subscribedService != null)
{
_subscribedService.StateChanged -= OnPlayerStateChanged;
_subscribedService = null;
}
}
}