AI: Galaxy page faction bonus drawer replaces faction filter
- Removed the faction <select> filter from the Galaxy filter bar. - Added a right-side drawer; exactly one of the three factions is always selected (Eden Accord default), user picks via picker. - Drawer aggregates occupational bonuses across the selected faction's planets: buffs grouped by type with magnitudes summed (e.g. two +15% cooldown planets total +30%), plus planet counts. - Drawer also lists the faction's planets linking to detail pages, and collapses via the header toggle.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
@page "/galaxy"
|
||||
@using System.Globalization
|
||||
@using System.Text.RegularExpressions
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>Galaxy Map</PageTitle>
|
||||
@@ -72,17 +73,6 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 filter-item">
|
||||
<label class="filter-label text-dim small fw-semibold text-uppercase">Faction:</label>
|
||||
<select class="form-select form-select-sm scifi-select" @bind="_selectedOwner" @bind:after="ApplyFilters">
|
||||
<option value="">All Factions</option>
|
||||
@foreach (var owner in StarSystemOwners.All)
|
||||
{
|
||||
<option value="@owner">@owner</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@if (HasActiveFilters)
|
||||
{
|
||||
<button class="btn btn-sm btn-scifi-outline py-1 px-3 ms-auto" @onclick="ResetFilters">
|
||||
@@ -91,6 +81,7 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="galaxy-layout">
|
||||
<div class="galaxy-map-container" @onmouseleave="OnContainerMouseLeave">
|
||||
<svg viewBox="0 0 1000 1000" class="galaxy-svg" preserveAspectRatio="xMidYMid meet"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -276,6 +267,80 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<aside class="faction-drawer zs-glass-card @(_drawerCollapsed ? "is-collapsed" : "")" aria-label="Faction occupational bonuses">
|
||||
<div class="drawer-header d-flex align-items-center justify-content-between gap-2">
|
||||
<div>
|
||||
<h2 class="drawer-title text-light fw-bold mb-0">Faction Bonuses</h2>
|
||||
<p class="drawer-subtitle text-secondary mb-0">Occupation buffs by faction</p>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-scifi-outline drawer-toggle" @onclick="ToggleDrawer"
|
||||
aria-expanded="@(!_drawerCollapsed)"
|
||||
title="@(_drawerCollapsed ? "Expand bonus panel" : "Collapse bonus panel")">
|
||||
@(_drawerCollapsed ? "»" : "«")
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (!_drawerCollapsed)
|
||||
{
|
||||
<div class="drawer-body">
|
||||
<div class="faction-picker" role="radiogroup" aria-label="Select faction">
|
||||
@foreach (var owner in StarSystemOwners.All)
|
||||
{
|
||||
var isActive = string.Equals(owner, _selectedFaction, StringComparison.Ordinal);
|
||||
<button role="radio" aria-checked="@isActive"
|
||||
class="faction-pick @(isActive ? "is-active " + GetOwnerCssClass(owner) : "")"
|
||||
@onclick="() => SelectFaction(owner)">
|
||||
<span class="legend-dot @FactionDotClass(owner)"></span>
|
||||
<span class="faction-pick-name">@ShortFactionName(owner)</span>
|
||||
<span class="faction-pick-count">@FactionSystemCount(owner)</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="drawer-section">
|
||||
<h3 class="drawer-section-title">Occupational bonuses</h3>
|
||||
<p class="drawer-summary text-secondary">
|
||||
<strong class="text-light">@FactionSystems.Count</strong> systems owned by
|
||||
<strong class="text-light">@ShortFactionName(_selectedFaction)</strong>
|
||||
</p>
|
||||
@if (FactionBonuses.Count == 0)
|
||||
{
|
||||
<p class="text-secondary small mb-0">No occupation buffs recorded for this faction's systems.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ul class="bonus-list list-unstyled mb-0">
|
||||
@foreach (var bonus in FactionBonuses)
|
||||
{
|
||||
<li class="bonus-row">
|
||||
<span class="bonus-total">@FormatBonusTotal(bonus)</span>
|
||||
<span class="bonus-label">@bonus.Label</span>
|
||||
<span class="bonus-count">@bonus.PlanetCount @(bonus.PlanetCount == 1 ? "planet" : "planets")</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="drawer-section">
|
||||
<h3 class="drawer-section-title">Planets (@FactionSystems.Count)</h3>
|
||||
<ul class="planet-list list-unstyled mb-0">
|
||||
@foreach (var system in FactionSystems)
|
||||
{
|
||||
<li>
|
||||
<button class="planet-row" @onclick="() => OnNodeClick(system.Id)" title="View @system.Name details">
|
||||
<span class="planet-name">@system.Name</span>
|
||||
<span class="planet-buff">@(system.OccupationBuff ?? "—")</span>
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@@ -292,14 +357,16 @@
|
||||
private StarSystemCategory? _selectedCategory;
|
||||
private string _selectedMastery = string.Empty;
|
||||
private string _selectedYield = string.Empty;
|
||||
private string _selectedOwner = string.Empty;
|
||||
private int _matchingCount;
|
||||
|
||||
// Faction picker: one of the three factions is always selected.
|
||||
private string _selectedFaction = StarSystemOwners.EdenAccord;
|
||||
private bool _drawerCollapsed;
|
||||
|
||||
private bool HasActiveFilters =>
|
||||
_selectedCategory.HasValue ||
|
||||
!string.IsNullOrEmpty(_selectedMastery) ||
|
||||
!string.IsNullOrEmpty(_selectedYield) ||
|
||||
!string.IsNullOrEmpty(_selectedOwner);
|
||||
!string.IsNullOrEmpty(_selectedYield);
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@@ -356,11 +423,6 @@
|
||||
else if (_selectedYield == "Items" && !system.HigherChanceForItems) return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_selectedOwner) && !string.Equals(system.Owner, _selectedOwner, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -369,10 +431,101 @@
|
||||
_selectedCategory = null;
|
||||
_selectedMastery = string.Empty;
|
||||
_selectedYield = string.Empty;
|
||||
_selectedOwner = string.Empty;
|
||||
ApplyFilters();
|
||||
}
|
||||
|
||||
// ----- faction bonus drawer -----
|
||||
|
||||
public sealed record FactionBonus(string Label, bool IsPercent, double Total, int PlanetCount);
|
||||
|
||||
private void SelectFaction(string owner)
|
||||
{
|
||||
if (string.Equals(_selectedFaction, owner, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedFaction = owner;
|
||||
}
|
||||
|
||||
private void ToggleDrawer() => _drawerCollapsed = !_drawerCollapsed;
|
||||
|
||||
private int FactionSystemCount(string owner) =>
|
||||
_systems.Count(s => string.Equals(s.Owner, owner, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private IReadOnlyList<StarSystemData> FactionSystems =>
|
||||
_systems
|
||||
.Where(s => string.Equals(s.Owner, _selectedFaction, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(s => s.Name)
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Groups the selected faction's occupation buffs by bonus type and sums
|
||||
/// the magnitudes, so e.g. two "+15% cooldown reduction" planets total +30%.
|
||||
/// </summary>
|
||||
private IReadOnlyList<FactionBonus> FactionBonuses
|
||||
{
|
||||
get
|
||||
{
|
||||
var groups = new Dictionary<(string Label, bool IsPercent), (double Total, int Count)>();
|
||||
foreach (var system in _systems)
|
||||
{
|
||||
if (!string.Equals(system.Owner, _selectedFaction, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(system.OccupationBuff))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var (amount, isPercent, label) = ParseOccupationBuff(system.OccupationBuff);
|
||||
var key = (label, isPercent);
|
||||
groups.TryGetValue(key, out var current);
|
||||
groups[key] = (current.Total + amount, current.Count + 1);
|
||||
}
|
||||
|
||||
return groups
|
||||
.Select(g => new FactionBonus(g.Key.Label, g.Key.IsPercent, g.Value.Total, g.Value.Count))
|
||||
.OrderByDescending(b => b.PlanetCount)
|
||||
.ThenBy(b => b.Label)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Parses buffs shaped like "+50 Starting Hexite" or "+15% cooldown reduction".</summary>
|
||||
private static (double Amount, bool IsPercent, string Label) ParseOccupationBuff(string buff)
|
||||
{
|
||||
var match = Regex.Match(buff.Trim(), @"^\+\s*([0-9]*\.?[0-9]+)\s*(%?)\s*(.+)$");
|
||||
if (match.Success &&
|
||||
double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var amount))
|
||||
{
|
||||
return (amount, match.Groups[2].Value == "%", match.Groups[3].Value.Trim());
|
||||
}
|
||||
|
||||
return (0, false, buff.Trim());
|
||||
}
|
||||
|
||||
private static string FormatBonusTotal(FactionBonus bonus) =>
|
||||
$"+{bonus.Total.ToString("0.##", CultureInfo.InvariantCulture)}{(bonus.IsPercent ? "%" : string.Empty)}";
|
||||
|
||||
private static string ShortFactionName(string owner) => owner switch
|
||||
{
|
||||
StarSystemOwners.EdenAccord => "Eden Accord",
|
||||
StarSystemOwners.KeeperCouncil => "Keeper Council",
|
||||
StarSystemOwners.ObsidianOrder => "Obsidian Order",
|
||||
_ => owner
|
||||
};
|
||||
|
||||
private static string FactionDotClass(string owner) => owner switch
|
||||
{
|
||||
StarSystemOwners.EdenAccord => "dot-eden",
|
||||
StarSystemOwners.KeeperCouncil => "dot-keeper",
|
||||
StarSystemOwners.ObsidianOrder => "dot-obsidian",
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
private static IReadOnlyList<ConnectionEdge> BuildUniqueConnections(IReadOnlyList<StarSystemData> allSystems)
|
||||
{
|
||||
var edges = new List<ConnectionEdge>();
|
||||
|
||||
Reference in New Issue
Block a user