Agent Tests for API, MAUI, and Slop Features
This commit is contained in:
@@ -30,8 +30,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
<ProjectReference Include="..\Services\Services.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj"/>
|
||||
<ProjectReference Include="..\Services\Services.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Inputs\"/>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
@inject IGlossaryService glossaryService
|
||||
|
||||
@if (TermId != null)
|
||||
{
|
||||
var term = glossaryService.GetTerm(TermId);
|
||||
if (term != null)
|
||||
{
|
||||
<div class="glossaryTooltipWrapper">
|
||||
<div class="glossaryTooltipContent">
|
||||
<div class="glossaryTooltipHeader">
|
||||
<span class="glossaryTooltipTerm">@term.Term</span>
|
||||
<span class="glossaryTooltipCategory">@term.Category</span>
|
||||
</div>
|
||||
<div class="glossaryTooltipBody">
|
||||
@term.ShortDefinition
|
||||
</div>
|
||||
</div>
|
||||
@ChildContent
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<style>
|
||||
.glossaryTooltipWrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.glossaryTooltipContent {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
max-width: 93vw;
|
||||
bottom: 100%;
|
||||
margin-bottom: 8px;
|
||||
padding: 12px;
|
||||
z-index: 2147483647;
|
||||
background-color: var(--info-secondary);
|
||||
border: 1px solid var(--info-secondary-border);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.glossaryTooltipWrapper:hover .glossaryTooltipContent {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.glossaryTooltipHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.glossaryTooltipTerm {
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.glossaryTooltipCategory {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.glossaryTooltipBody {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@@media only screen and (max-width: 1025px) {
|
||||
.glossaryTooltipContent {
|
||||
margin: auto;
|
||||
margin-bottom: 20px;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
|
||||
|
||||
[Parameter] public string TermId { get; set; } = default!;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
@using Model.MemoryTester
|
||||
@using Services.Immortal
|
||||
@implements IDisposable
|
||||
|
||||
@inject IMemoryTesterService MemoryTesterService
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
@inject IGlossaryService glossaryService
|
||||
@inject IGlossaryDialogService glossaryDialogService
|
||||
|
||||
@if (TermId == null)
|
||||
{
|
||||
<span>Missing term</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
var term = glossaryService.GetTerm(TermId);
|
||||
if (term != null)
|
||||
{
|
||||
<button class="glossaryLabel @term.Category.ToLowerInvariant()" @onclick="TermLabelClicked"
|
||||
title="@term.ShortDefinition">
|
||||
@term.Term
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public string TermId { get; set; } = default!;
|
||||
|
||||
void TermLabelClicked()
|
||||
{
|
||||
glossaryDialogService.AddDialog(TermId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
.glossaryLabel {
|
||||
font-weight: bolder;
|
||||
box-shadow: 1px 1px 0 0 rgba(0, 0, 0, 0.2);
|
||||
padding-right: 4px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.glossaryLabel:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.resource {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.mechanic {
|
||||
color: #8fc5ff;
|
||||
}
|
||||
|
||||
.faction {
|
||||
color: #da4e4e;
|
||||
}
|
||||
|
||||
.role {
|
||||
color: #87aa87;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
@inject IEntityDialogService entityDialogService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<div class="techTreeContainer" @ref="containerRef">
|
||||
<svg class="techTreeSvg" width="@svgWidth" height="@svgHeight">
|
||||
<!-- Edges -->
|
||||
@foreach (var edge in Graph.Edges)
|
||||
{
|
||||
var source = Graph.Nodes.FirstOrDefault(n => n.Id == edge.SourceId);
|
||||
var target = Graph.Nodes.FirstOrDefault(n => n.Id == edge.TargetId);
|
||||
if (source != null && target != null)
|
||||
{
|
||||
var color = EdgeColor(edge.EdgeType);
|
||||
var strokeWidth = edge.EdgeType == TechTreeEdgeType.Produces ? "2.5" : "1.5";
|
||||
var strokeDash = edge.EdgeType == TechTreeEdgeType.RequiresProduction || edge.EdgeType == TechTreeEdgeType.RequiresResearch ? "6,3" : "";
|
||||
strokeDash = edge.EdgeType == TechTreeEdgeType.Morph ? "2,2" : strokeDash;
|
||||
|
||||
<line x1="@(source.X + nodeWidth / 2)" y1="@(source.Y + nodeHeight / 2)"
|
||||
x2="@(target.X + nodeWidth / 2)" y2="@(target.Y + nodeHeight / 2)"
|
||||
stroke="@color" stroke-width="@strokeWidth"
|
||||
stroke-dasharray="@strokeDash"
|
||||
marker-end="url(#arrowhead-@(edge.EdgeType))"/>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Arrowhead markers -->
|
||||
<defs>
|
||||
@foreach (var edgeType in Graph.Edges.Select(e => e.EdgeType).Distinct())
|
||||
{
|
||||
<marker id="arrowhead-@edgeType" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="@EdgeColor(edgeType)"/>
|
||||
</marker>
|
||||
}
|
||||
</defs>
|
||||
|
||||
<!-- Nodes -->
|
||||
@foreach (var node in Graph.Nodes)
|
||||
{
|
||||
var color = NodeColor(node.Descriptive);
|
||||
var isHighlighted = HighlightEntityId == node.Id;
|
||||
|
||||
<g class="techTreeNode" @onclick="() => OnNodeClick(node.Id)">
|
||||
<rect x="@node.X" y="@node.Y" width="@nodeWidth" height="@nodeHeight" rx="6"
|
||||
fill="var(--paper)" stroke="@color" stroke-width="@(isHighlighted ? 3 : 1.5)"
|
||||
class="@(isHighlighted ? "highlighted" : "")"/>
|
||||
<text x="@(node.X + nodeWidth / 2)" y="@(node.Y + nodeHeight / 2)"
|
||||
text-anchor="middle" dominant-baseline="central"
|
||||
fill="white" font-size="12" font-weight="600">
|
||||
@TruncateText(node.Name, 20)
|
||||
</text>
|
||||
<title>@node.Name (@node.EntityType)</title>
|
||||
</g>
|
||||
}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.techTreeContainer {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border: 2px solid var(--paper-border);
|
||||
border-radius: 8px;
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.techTreeSvg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.techTreeNode {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.techTreeNode rect {
|
||||
transition: stroke-width 0.15s;
|
||||
}
|
||||
|
||||
.techTreeNode rect.highlighted {
|
||||
filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.5));
|
||||
}
|
||||
|
||||
.techTreeNode:hover rect {
|
||||
stroke-width: 3;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public TechTreeGraphModel Graph { get; set; } = new();
|
||||
|
||||
[Parameter] public string? HighlightEntityId { get; set; }
|
||||
|
||||
[Parameter] public EventCallback<string> OnEntitySelected { get; set; }
|
||||
|
||||
private ElementReference containerRef;
|
||||
private const float nodeWidth = 140;
|
||||
private const float nodeHeight = 36;
|
||||
private const float horizontalSpacing = 60;
|
||||
private const float verticalSpacing = 30;
|
||||
private const float topPadding = 40;
|
||||
private const float leftPadding = 40;
|
||||
|
||||
private float svgWidth = 800;
|
||||
private float svgHeight = 600;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
ComputeLayout();
|
||||
}
|
||||
|
||||
void ComputeLayout()
|
||||
{
|
||||
if (Graph.Nodes.Count == 0) return;
|
||||
|
||||
var layers = Graph.Nodes.GroupBy(n => n.Layer)
|
||||
.OrderBy(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
// Simple layered layout: left to right (layers are columns)
|
||||
for (var layerIdx = 0; layerIdx < layers.Count; layerIdx++)
|
||||
{
|
||||
var layerNodes = layers.ElementAt(layerIdx).OrderBy(n => n.Name).ToList();
|
||||
var layerHeight = layerNodes.Count * (nodeHeight + verticalSpacing) - verticalSpacing;
|
||||
|
||||
for (var nodeIdx = 0; nodeIdx < layerNodes.Count; nodeIdx++)
|
||||
{
|
||||
var node = layerNodes[nodeIdx];
|
||||
node.X = leftPadding + layerIdx * (nodeWidth + horizontalSpacing);
|
||||
node.Y = topPadding + nodeIdx * (nodeHeight + verticalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
svgWidth = layers.Count * (nodeWidth + horizontalSpacing) + leftPadding + 100;
|
||||
svgHeight = layers.Max(g => g.Count()) * (nodeHeight + verticalSpacing) + topPadding + 80;
|
||||
|
||||
if (svgWidth < 800) svgWidth = 800;
|
||||
if (svgHeight < 400) svgHeight = 400;
|
||||
}
|
||||
|
||||
void OnNodeClick(string entityId)
|
||||
{
|
||||
entityDialogService.AddDialog(entityId);
|
||||
OnEntitySelected.InvokeAsync(entityId);
|
||||
}
|
||||
|
||||
string NodeColor(string descriptive)
|
||||
{
|
||||
return descriptive.ToLowerInvariant() switch
|
||||
{
|
||||
"frontliner" => "#ff6b6b",
|
||||
"skirmisher" => "#ffa502",
|
||||
"support" => "#2ed573",
|
||||
"generalist" => "#1e90ff",
|
||||
"worker" => "#ffd43b",
|
||||
"stronghold" => "#ff4757",
|
||||
"upgrade" => "#a55eea",
|
||||
"technology" => "#a55eea",
|
||||
_ => "#8fc5ff"
|
||||
};
|
||||
}
|
||||
|
||||
string EdgeColor(string edgeType)
|
||||
{
|
||||
return edgeType switch
|
||||
{
|
||||
"Produces" => "#2ed573",
|
||||
"RequiresProduction" => "#ffa502",
|
||||
"RequiresResearch" => "#a55eea",
|
||||
"Morph" => "#ff6b6b",
|
||||
"Upgrades" => "#1e90ff",
|
||||
_ => "#666"
|
||||
};
|
||||
}
|
||||
|
||||
string TruncateText(string text, int maxLength)
|
||||
{
|
||||
return text.Length <= maxLength ? text : text.Substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,9 +4,13 @@
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Model.Feedback
|
||||
@using Model.Glossary
|
||||
@using Model.TechTree
|
||||
@using Model.Website
|
||||
@using Model.Website.Enums
|
||||
@using Services
|
||||
@using Services.Immortal
|
||||
@using Services.Website
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using System.Text
|
||||
|
||||
Reference in New Issue
Block a user