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"
|
@page "/galaxy"
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
|
@using System.Text.RegularExpressions
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<PageTitle>Galaxy Map</PageTitle>
|
<PageTitle>Galaxy Map</PageTitle>
|
||||||
@@ -72,17 +73,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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)
|
@if (HasActiveFilters)
|
||||||
{
|
{
|
||||||
<button class="btn btn-sm btn-scifi-outline py-1 px-3 ms-auto" @onclick="ResetFilters">
|
<button class="btn btn-sm btn-scifi-outline py-1 px-3 ms-auto" @onclick="ResetFilters">
|
||||||
@@ -91,6 +81,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="galaxy-layout">
|
||||||
<div class="galaxy-map-container" @onmouseleave="OnContainerMouseLeave">
|
<div class="galaxy-map-container" @onmouseleave="OnContainerMouseLeave">
|
||||||
<svg viewBox="0 0 1000 1000" class="galaxy-svg" preserveAspectRatio="xMidYMid meet"
|
<svg viewBox="0 0 1000 1000" class="galaxy-svg" preserveAspectRatio="xMidYMid meet"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -276,6 +267,80 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -292,14 +357,16 @@
|
|||||||
private StarSystemCategory? _selectedCategory;
|
private StarSystemCategory? _selectedCategory;
|
||||||
private string _selectedMastery = string.Empty;
|
private string _selectedMastery = string.Empty;
|
||||||
private string _selectedYield = string.Empty;
|
private string _selectedYield = string.Empty;
|
||||||
private string _selectedOwner = string.Empty;
|
|
||||||
private int _matchingCount;
|
private int _matchingCount;
|
||||||
|
|
||||||
|
// Faction picker: one of the three factions is always selected.
|
||||||
|
private string _selectedFaction = StarSystemOwners.EdenAccord;
|
||||||
|
private bool _drawerCollapsed;
|
||||||
|
|
||||||
private bool HasActiveFilters =>
|
private bool HasActiveFilters =>
|
||||||
_selectedCategory.HasValue ||
|
_selectedCategory.HasValue ||
|
||||||
!string.IsNullOrEmpty(_selectedMastery) ||
|
!string.IsNullOrEmpty(_selectedMastery) ||
|
||||||
!string.IsNullOrEmpty(_selectedYield) ||
|
!string.IsNullOrEmpty(_selectedYield);
|
||||||
!string.IsNullOrEmpty(_selectedOwner);
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
@@ -356,11 +423,6 @@
|
|||||||
else if (_selectedYield == "Items" && !system.HigherChanceForItems) return false;
|
else if (_selectedYield == "Items" && !system.HigherChanceForItems) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_selectedOwner) && !string.Equals(system.Owner, _selectedOwner, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,10 +431,101 @@
|
|||||||
_selectedCategory = null;
|
_selectedCategory = null;
|
||||||
_selectedMastery = string.Empty;
|
_selectedMastery = string.Empty;
|
||||||
_selectedYield = string.Empty;
|
_selectedYield = string.Empty;
|
||||||
_selectedOwner = string.Empty;
|
|
||||||
ApplyFilters();
|
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)
|
private static IReadOnlyList<ConnectionEdge> BuildUniqueConnections(IReadOnlyList<StarSystemData> allSystems)
|
||||||
{
|
{
|
||||||
var edges = new List<ConnectionEdge>();
|
var edges = new List<ConnectionEdge>();
|
||||||
|
|||||||
Reference in New Issue
Block a user