Overhaul workspace UX for denser play workflow

This commit is contained in:
2026-02-26 11:53:36 +01:00
parent e7114d8798
commit c3aa0d4e88
10 changed files with 355 additions and 160 deletions

View File

@@ -145,7 +145,6 @@ public partial class Workspace : IAsyncDisposable
SelectedCampaign = null;
CampaignLog = [];
SelectedCharacterId = null;
SelectedSkillId = null;
ConnectionState = "offline";
return;
}
@@ -157,7 +156,6 @@ public partial class Workspace : IAsyncDisposable
SelectedCampaign = await ApiClient.RequestAsync<CampaignDetails>("GET", $"/api/campaigns/{campaignId}");
CampaignLog = (await ApiClient.RequestAsync<IReadOnlyList<CampaignLogEntry>>("GET", $"/api/campaigns/{campaignId}/log")).ToList();
SyncSelectedCharacter();
SyncSelectedSkill();
await EnsureSelectedCharacterActiveAsync();
}
catch (ApiRequestException ex) when (ex.StatusCode == 401)
@@ -226,6 +224,7 @@ public partial class Workspace : IAsyncDisposable
private async Task SwitchScreenAsync(string screen)
{
CurrentScreen = string.Equals(screen, "management", StringComparison.OrdinalIgnoreCase) ? "management" : "play";
IsScreenMenuOpen = false;
await JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", ScreenSessionKey, CurrentScreen);
}
@@ -264,6 +263,7 @@ public partial class Workspace : IAsyncDisposable
await JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", CampaignSessionKey, campaignId.ToString());
await RefreshCampaignScopeAsync();
await SyncStateEventsAsync();
IsScreenMenuOpen = false;
}
private async Task OnCampaignCreatedAsync(Guid campaignId)
@@ -327,7 +327,6 @@ public partial class Workspace : IAsyncDisposable
private async Task SelectCharacterAsync(Guid characterId)
{
SelectedCharacterId = characterId;
SyncSelectedSkill();
await EnsureSelectedCharacterActiveAsync();
}
@@ -367,25 +366,37 @@ public partial class Workspace : IAsyncDisposable
SetStatus("Skill created.", false);
}
private async Task OnSkillUpdatedAsync(Guid skillId)
private async Task OnSkillUpdatedAsync(Guid _)
{
SelectedSkillId = skillId;
await RefreshCampaignScopeAsync();
SetStatus("Skill updated.", false);
}
private async Task RollSelectedSkillAsync()
private async Task RollSkillAsync(Guid skillId)
{
if (SelectedSkill is null)
if (SelectedCampaign is null)
{
SetStatus("Select a skill to roll.", true);
SetStatus("No campaign selected.", true);
return;
}
var selectedSkill = SelectedCampaign.Skills.FirstOrDefault(skill => skill.Id == skillId);
if (selectedSkill is null)
{
SetStatus("Skill is no longer available. Refresh campaign data.", true);
return;
}
if (!CanRollSkill(selectedSkill))
{
SetStatus("You are not allowed to roll this skill.", true);
return;
}
IsMutating = true;
try
{
LastRoll = await ApiClient.RequestAsync<RollResult>("POST", $"/api/skills/{SelectedSkill.Id}/roll", new RollSkillRequest(RollVisibility));
LastRoll = await ApiClient.RequestAsync<RollResult>("POST", $"/api/skills/{selectedSkill.Id}/roll", new RollSkillRequest(RollVisibility));
await RefreshCampaignScopeAsync();
SetStatus("Roll recorded.", false);
@@ -407,11 +418,6 @@ public partial class Workspace : IAsyncDisposable
return Task.CompletedTask;
}
private void SelectSkill(Guid skillId)
{
SelectedSkillId = skillId;
}
private bool CanEditSkill(SkillSummary skill)
{
if (SelectedCampaign is null)
@@ -526,21 +532,6 @@ public partial class Workspace : IAsyncDisposable
SelectedCharacterId = SelectedCampaign.Characters[0].Id;
}
private void SyncSelectedSkill()
{
var skills = SelectedCharacterSkills;
if (skills.Count == 0)
{
SelectedSkillId = null;
return;
}
if (SelectedSkillId.HasValue && skills.Any(skill => skill.Id == SelectedSkillId.Value))
return;
SelectedSkillId = skills[0].Id;
}
private string OwnerLabel(Guid ownerUserId)
{
if (User is not null && ownerUserId == User.Id)
@@ -624,7 +615,6 @@ public partial class Workspace : IAsyncDisposable
Campaigns = [];
CampaignLog = [];
SelectedCharacterId = null;
SelectedSkillId = null;
LastRoll = null;
ShowCreateCharacterModal = false;
ShowEditCharacterModal = false;
@@ -646,6 +636,11 @@ public partial class Workspace : IAsyncDisposable
LiveAnnouncement = message;
}
private void ToggleScreenMenu()
{
IsScreenMenuOpen = !IsScreenMenuOpen;
}
[Inject]
private IJSRuntime JS { get; set; } = null!;
@@ -660,7 +655,6 @@ public partial class Workspace : IAsyncDisposable
private List<CampaignLogEntry> CampaignLog { get; set; } = [];
private List<RulesetDefinition> Rulesets { get; set; } = [];
private Guid? SelectedCharacterId { get; set; }
private Guid? SelectedSkillId { get; set; }
private RollResult? LastRoll { get; set; }
private string RollVisibility { get; set; } = "public";
@@ -674,6 +668,7 @@ public partial class Workspace : IAsyncDisposable
private string MobilePanel { get; set; } = "character";
private string ConnectionState { get; set; } = "offline";
private string LiveAnnouncement { get; set; } = string.Empty;
private bool IsScreenMenuOpen { get; set; }
private bool ShowCreateCharacterModal { get; set; }
private bool ShowEditCharacterModal { get; set; }
@@ -694,12 +689,6 @@ public partial class Workspace : IAsyncDisposable
private CharacterSummary? SelectedCharacter =>
SelectedCampaign?.Characters.FirstOrDefault(c => c.Id == SelectedCharacterId);
private SkillSummary? SelectedSkill =>
SelectedCampaign?.Skills.FirstOrDefault(s => s.Id == SelectedSkillId);
private string? ActiveCharacterName =>
SelectedCampaign?.Characters.FirstOrDefault(c => c.Id == SelectedCharacterId)?.Name;
private bool IsCurrentUserGm =>
SelectedCampaign is not null && User is not null && SelectedCampaign.Gm.Id == User.Id;
@@ -711,6 +700,7 @@ public partial class Workspace : IAsyncDisposable
private bool IsPlayScreen => string.Equals(CurrentScreen, "play", StringComparison.OrdinalIgnoreCase);
private bool IsManagementScreen => !IsPlayScreen;
private string CurrentScreenLabel => IsPlayScreen ? "Play" : "Campaign Management";
private string ConnectionStateLabel => ConnectionState switch
{
@@ -731,4 +721,4 @@ public partial class Workspace : IAsyncDisposable
private const string ScreenSessionKey = "screen";
private const string CampaignSessionKey = "campaign";
private const string MobilePanelSessionKey = "play-panel";
}
}