Move campaign selector into header

This commit is contained in:
2026-05-18 20:13:14 +02:00
parent ff28f70b51
commit ecc799ae7f
10 changed files with 98 additions and 43 deletions

View File

@@ -1,9 +1,8 @@
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components
@using RpgRoller.Components.Pages.HomeControls
<CampaignManagementPanel
Campaigns="Workspace.State.Campaigns"
SelectedCampaignId="Workspace.State.SelectedCampaignId"
SelectedCampaign="Workspace.State.SelectedCampaign"
Rulesets="Workspace.State.Rulesets"
IsMutating="Workspace.State.IsMutating"
@@ -11,7 +10,6 @@
CanEditCharacter="Workspace.Campaigns.CanEditCharacter"
CanDeleteCharacter="Workspace.Campaigns.CanDeleteCharacter"
CanDeleteCampaign="Workspace.State.CanDeleteSelectedCampaign"
CampaignSelectionChanged="OnCampaignSelectionChangedAsync"
CampaignCreated="Workspace.Campaigns.OnCampaignCreatedAsync"
DeleteCampaignRequested="Workspace.Campaigns.DeleteSelectedCampaignAsync"
CreateCharacterRequested="Workspace.Campaigns.OpenCreateCharacterModal"
@@ -22,10 +20,4 @@
@code {
[Parameter, EditorRequired] public WorkspacePageContext Workspace { get; set; } = null!;
private async Task OnCampaignSelectionChangedAsync(ChangeEventArgs args)
{
await Workspace.Campaigns.OnCampaignSelectionChangedAsync(args);
await Workspace.RequestRefreshAsync();
}
}

View File

@@ -1,4 +1,4 @@
<header class="workspace-header">
<header class="workspace-header">
<div class="header-row">
<h1>@Title</h1>
@if (User is null)
@@ -15,7 +15,23 @@
}
@if (ShowCampaign)
{
<p class="header-campaign">Campaign: <strong>@(CampaignName ?? "No campaign selected")</strong></p>
<div class="header-campaign">
<label for="@CampaignSelectId">Campaign</label>
@if (Campaigns.Count == 0)
{
<span>No campaigns yet</span>
}
else
{
<select id="@CampaignSelectId"
@onchange="CampaignSelectionChanged">
@foreach (var campaign in Campaigns)
{
<option value="@campaign.Id" selected="@(campaign.Id == SelectedCampaignId)">@campaign.Name</option>
}
</select>
}
</div>
}
<div class="header-connection-cell">
@if (ShowConnectionState)

View File

@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using RpgRoller.Contracts;
@@ -18,7 +18,13 @@ public partial class AppHeader
[Parameter] public bool ShowCampaign { get; set; }
[Parameter] public string? CampaignName { get; set; }
[Parameter] public IReadOnlyList<CampaignSummary> Campaigns { get; set; } = [];
[Parameter] public Guid? SelectedCampaignId { get; set; }
[Parameter] public string CampaignSelectId { get; set; } = "header-campaign-select";
[Parameter] public EventCallback<ChangeEventArgs> CampaignSelectionChanged { get; set; }
[Parameter] public bool ShowConnectionState { get; set; } = true;
@@ -44,4 +50,4 @@ public sealed class AppHeaderMenuItem
public string Label { get; init; } = string.Empty;
public bool IsActive { get; init; }
public Func<Task>? OnSelected { get; init; }
}
}

View File

@@ -1,4 +1,4 @@
<main class="management-screen">
<main class="management-screen">
<section class="card">
<div class="section-head">
<h2>Campaign</h2>
@@ -9,13 +9,14 @@
}
else
{
<label for="campaign-select">Current campaign</label>
<select id="campaign-select" @onchange="CampaignSelectionChanged">
@foreach (var campaign in Campaigns)
<div class="campaign-current">
<span>Current campaign</span>
<strong>@(SelectedCampaign is null ? "No campaign selected" : SelectedCampaign.Name)</strong>
@if (SelectedCampaign is not null)
{
<option value="@campaign.Id" selected="@(campaign.Id == SelectedCampaignId)">@campaign.Name (@campaign.RulesetId), GM: @campaign.Gm.DisplayName, @campaign.CharacterCount characters</option>
<p>@SelectedCampaign.RulesetId, GM: @SelectedCampaign.Gm.DisplayName, @SelectedCampaign.Characters.Length characters</p>
}
</select>
</div>
}
<button type="button"
@@ -129,4 +130,4 @@
</form>
</section>
</div>
}
}

View File

@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using RpgRoller.Contracts;
@@ -74,9 +74,6 @@ public partial class CampaignManagementPanel
[Parameter]
public IReadOnlyList<CampaignSummary> Campaigns { get; set; } = [];
[Parameter]
public Guid? SelectedCampaignId { get; set; }
[Parameter]
public CampaignRoster? SelectedCampaign { get; set; }
@@ -98,9 +95,6 @@ public partial class CampaignManagementPanel
[Parameter]
public bool CanDeleteCampaign { get; set; }
[Parameter]
public EventCallback<ChangeEventArgs> CampaignSelectionChanged { get; set; }
[Parameter]
public EventCallback<Guid> CampaignCreated { get; set; }

View File

@@ -1,4 +1,4 @@
<section class="card character-panel">
<section class="card character-panel">
@if (IsCampaignDataLoading)
{
<div class="skeleton-stack">
@@ -9,7 +9,7 @@
}
else if (SelectedCampaign is null)
{
<p class="empty">No campaign selected. Choose one in Campaign Management.</p>
<p class="empty">No campaign selected. Choose one in the header.</p>
}
else if (SelectedCampaign.Characters.Length == 0)
{

View File

@@ -1,4 +1,4 @@
@using RpgRoller.Components.Pages.HomeControls
@using RpgRoller.Components.Pages.HomeControls
<div class="@AppCssClass">
<p class="sr-only" aria-live="polite">@State.LiveAnnouncement</p>
@@ -17,7 +17,9 @@
<AppHeader
User="State.User"
ShowCampaign="@ShowCampaignInHeader"
CampaignName="@State.SelectedCampaignName"
Campaigns="State.Campaigns"
SelectedCampaignId="State.SelectedCampaignId"
CampaignSelectionChanged="OnHeaderCampaignSelectionChangedAsync"
ShowConnectionState="@ShowConnectionStateInHeader"
ConnectionStateLabel="@State.ConnectionStateLabel"
ConnectionStateCssClass="@State.ConnectionStateCssClass"

View File

@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using RpgRoller.Components.Pages.HomeControls;
@@ -84,6 +84,12 @@ public partial class Workspace : IAsyncDisposable
return Task.CompletedTask;
}
private async Task OnHeaderCampaignSelectionChangedAsync(ChangeEventArgs args)
{
await Campaigns.OnCampaignSelectionChangedAsync(args);
await RequestRefreshAsync();
}
private Task RedirectToPlayAsync()
{
if (IsPlayRoute)
@@ -220,4 +226,4 @@ public partial class Workspace : IAsyncDisposable
private WorkspaceCampaignScopeCoordinator? m_Scope;
private WorkspaceSessionCoordinator? m_Session;
private Task? InitializationTask { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
using RpgRoller.Components.Pages.HomeControls;
using RpgRoller.Components.Pages.HomeControls;
using RpgRoller.Contracts;
using RpgRoller.Domain;
@@ -91,10 +91,6 @@ public sealed class WorkspaceState
public HashSet<Guid> CampaignLogDetailsLoading { get; } = [];
public Dictionary<Guid, string> CampaignLogDetailErrors { get; } = [];
public string? SelectedCampaignName => SelectedCampaign?.Name ??
Campaigns.FirstOrDefault(campaign => campaign.Id == SelectedCampaignId)
?.Name;
public CharacterSummary? SelectedCharacter =>
SelectedCampaign?.Characters.FirstOrDefault(character => character.Id == SelectedCharacterId);
@@ -185,4 +181,4 @@ public sealed class WorkspaceState
"reconnecting" => "warn",
_ => "offline"
};
}
}

View File

@@ -1,4 +1,4 @@
:root {
:root {
--bg-top: #f7f0d8;
--bg-bottom: #ecdfc4;
--button-hover: #dccfb4;
@@ -120,14 +120,33 @@ h3 {
font-size: 1.15rem;
}
.header-identity,
.header-campaign {
.header-identity {
margin: 0;
white-space: nowrap;
}
.header-campaign {
display: flex;
align-items: center;
gap: 0.35rem;
color: var(--muted);
min-width: 12rem;
white-space: nowrap;
}
.header-campaign label {
font-weight: 700;
}
.header-campaign select {
max-width: 16rem;
min-width: 9rem;
padding: 0.25rem 0.45rem;
}
.header-campaign span {
font-weight: 700;
color: var(--text);
}
.header-connection-cell {
@@ -156,6 +175,20 @@ h3 {
gap: 0.75rem;
}
.campaign-current {
display: grid;
gap: 0.15rem;
}
.campaign-current span,
.campaign-current p {
color: var(--muted);
}
.campaign-current p {
margin: 0;
}
.auth-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1180,6 +1213,15 @@ select:focus-visible {
white-space: normal;
}
.header-campaign {
flex-wrap: wrap;
min-width: 0;
}
.header-campaign select {
max-width: 100%;
}
.mobile-bottom-nav {
display: flex;
}