Extract workspace live state controller

This commit is contained in:
2026-04-05 00:18:29 +02:00
parent 4d5d112168
commit 93c19f0705
3 changed files with 156 additions and 91 deletions

View File

@@ -213,90 +213,29 @@ public partial class Workspace : IAsyncDisposable
private bool CanEditSkill(CharacterSheetSkill skill) => Play.CanEditSkill(skill);
[JSInvokable]
public async Task OnStateEventReceived(CampaignStateSnapshot state)
{
if (StateRefreshInProgress)
return;
if (!SelectedCampaignId.HasValue || state.CampaignId != SelectedCampaignId.Value)
return;
StateRefreshInProgress = true;
try
{
if (CurrentCampaignState is null)
{
CurrentCampaignState = state;
return;
}
var previousState = CurrentCampaignState;
var previousSelectedCharacterId = SelectedCharacterId;
var previousSelectedCharacterVersion = GetCharacterVersion(previousState, previousSelectedCharacterId);
var rosterChanged = state.RosterVersion != previousState.RosterVersion;
var logChanged = IsPlayScreen && state.LogVersion != previousState.LogVersion;
if (rosterChanged)
await RefreshCampaignRosterAsync();
var selectedCharacterChanged = previousSelectedCharacterId != SelectedCharacterId;
var selectedCharacterVersionChanged = IsPlayScreen &&
!selectedCharacterChanged &&
GetCharacterVersion(state, SelectedCharacterId) != previousSelectedCharacterVersion;
if (IsPlayScreen && (selectedCharacterChanged || selectedCharacterVersionChanged))
await RefreshSelectedCharacterSheetAsync();
if (logChanged)
await RefreshCampaignLogAsync(CampaignLogCursor);
CurrentCampaignState = state;
}
finally
{
StateRefreshInProgress = false;
await InvokeAsync(StateHasChanged);
}
}
public Task OnStateEventReceived(CampaignStateSnapshot state) => Live.OnStateEventReceivedAsync(state);
[JSInvokable]
public Task OnConnectionStateChanged(string state)
public Task OnConnectionStateChanged(string state) => Live.OnConnectionStateChangedAsync(state);
private Task SyncStateEventsAsync() => Live.SyncStateEventsAsync();
private Task StopStateEventsAsync() => Live.StopStateEventsAsync();
public async ValueTask DisposeAsync()
{
ConnectionState = state switch
{
"connected" => "connected",
"reconnecting" => "reconnecting",
_ => "offline"
};
if (ConnectionState == "reconnecting")
Announce("Reconnecting to live updates.");
if (ConnectionState == "offline")
Announce("Live updates offline. Use manual refresh.");
return InvokeAsync(StateHasChanged);
await StopStateEventsAsync();
DotNetRef?.Dispose();
}
private async Task SyncStateEventsAsync()
private async Task StartStateEventsCoreAsync(Guid campaignId)
{
if (User is null || !SelectedCampaignId.HasValue || IsAdminScreen)
{
await StopStateEventsAsync();
ConnectionState = "offline";
return;
}
DotNetRef ??= DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("rpgRollerApi.startStateEvents", SelectedCampaignId.Value.ToString(), DotNetRef);
ConnectionState = "reconnecting";
await JS.InvokeVoidAsync("rpgRollerApi.startStateEvents", campaignId.ToString(), DotNetRef);
}
private async Task StopStateEventsAsync()
private async Task StopStateEventsCoreAsync()
{
if (!HasInteractiveRenderStarted)
return;
try
{
await JS.InvokeVoidAsync("rpgRollerApi.stopStateEvents");
@@ -309,12 +248,6 @@ public partial class Workspace : IAsyncDisposable
}
}
public async ValueTask DisposeAsync()
{
await StopStateEventsAsync();
DotNetRef?.Dispose();
}
private static bool IsStaticRenderInteropException(InvalidOperationException exception)
{
return exception.Message.Contains("statically rendered", StringComparison.OrdinalIgnoreCase);
@@ -401,16 +334,6 @@ public partial class Workspace : IAsyncDisposable
private void ResetCampaignStateTracking() => Play.ResetCampaignStateTracking();
private static long GetCharacterVersion(CampaignStateSnapshot snapshot, Guid? characterId)
{
if (!characterId.HasValue)
return 0;
return snapshot.CharacterVersions
.FirstOrDefault(version => version.CharacterId == characterId.Value)
?.Version ?? 0;
}
[Inject]
private IJSRuntime JS { get; set; } = null!;
@@ -492,6 +415,15 @@ public partial class Workspace : IAsyncDisposable
private bool IsPlayScreen => State.IsPlayScreen;
private bool IsManagementScreen => State.IsManagementScreen;
private bool IsAdminScreen => State.IsAdminScreen;
private WorkspaceLiveStateController Live => m_Live ??= new(
State,
Feedback,
StartStateEventsCoreAsync,
StopStateEventsCoreAsync,
RefreshCampaignRosterAsync,
RefreshSelectedCharacterSheetAsync,
RefreshCampaignLogAsync,
() => InvokeAsync(StateHasChanged));
private WorkspacePlayCoordinator Play => m_Play ??= new(
State,
Feedback,
@@ -561,6 +493,7 @@ public partial class Workspace : IAsyncDisposable
private const string MobilePanelSessionKey = "play-panel";
private const int CampaignLogWindowSize = 25;
private WorkspaceLiveStateController? m_Live;
private WorkspacePlayCoordinator? m_Play;
private WorkspaceCampaignCoordinator? m_CampaignsFlow;
private WorkspaceAdminCoordinator? m_Admin;