Agent Tests for API, MAUI, and Slop Features
This commit is contained in:
+56
-4
@@ -1,5 +1,7 @@
|
||||
@inject IStorageService StorageService
|
||||
@inject IPermissionService PermissionService
|
||||
@inject IGlossaryDialogService GlossaryDialogService
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
@@ -18,15 +20,12 @@
|
||||
</Router>
|
||||
|
||||
<EntityDialogPortal/>
|
||||
<GlossaryDialogPortal/>
|
||||
<ToastPortal/>
|
||||
<SearchPortal/>
|
||||
<ConfirmationDialogPortal/>
|
||||
|
||||
|
||||
@if (PermissionService.GetIsDataCollectionEnabled())
|
||||
{
|
||||
<NavigationTracker/>
|
||||
}
|
||||
|
||||
|
||||
<style>
|
||||
@@ -83,6 +82,22 @@
|
||||
--dialog-border-color: black;
|
||||
--dialog-border-width: 2px;
|
||||
--dialog-radius: 6px;
|
||||
|
||||
--info-secondary: #1e1e2e;
|
||||
--info-secondary-border: #3a3a5c;
|
||||
}
|
||||
|
||||
.glossary-link {
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 2px;
|
||||
cursor: pointer;
|
||||
color: #8fc5ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.glossary-link:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -97,4 +112,41 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("eval", @"
|
||||
window.glossaryInterop = {
|
||||
dotNetRef: null,
|
||||
init: function(dotNetRef) {
|
||||
this.dotNetRef = dotNetRef;
|
||||
document.addEventListener('click', function(e) {
|
||||
var target = e.target;
|
||||
while (target) {
|
||||
if (target.classList && target.classList.contains('glossary-link')) {
|
||||
var id = target.getAttribute('data-glossary-id');
|
||||
if (id && window.glossaryInterop.dotNetRef) {
|
||||
window.glossaryInterop.dotNetRef.invokeMethodAsync('OpenGlossaryTerm', id);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
");
|
||||
await JsRuntime.InvokeVoidAsync("glossaryInterop.init", DotNetObjectReference.Create(this));
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void OpenGlossaryTerm(string termId)
|
||||
{
|
||||
GlossaryDialogService.AddDialog(termId);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
@implements IDisposable;
|
||||
@inject IGlossaryService glossaryService
|
||||
@inject IGlossaryDialogService glossaryDialogService
|
||||
|
||||
|
||||
<div class="dialogBackground" onclick="@CloseDialog">
|
||||
<div class="dialogContainer glossaryDialog"
|
||||
@onclick:preventDefault="true"
|
||||
@onclick:stopPropagation="true">
|
||||
|
||||
@if (term == null)
|
||||
{
|
||||
<div>Term is null</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="dialogHeader">
|
||||
@if (glossaryDialogService.HasHistory())
|
||||
{
|
||||
<button class="backButton" @onclick="glossaryDialogService.BackDialog">
|
||||
<div class="backButtonIcon"></div>
|
||||
</button>
|
||||
}
|
||||
<div class="dialogTitle">
|
||||
@term.Term
|
||||
</div>
|
||||
<div class="glossaryDialogCategory">@term.Category</div>
|
||||
</div>
|
||||
<div class="dialogContent">
|
||||
<div class="glossaryDefinition">@((MarkupString)RenderMarkdown(term.LongDefinition))</div>
|
||||
|
||||
@if (term.RelatedEntityIds.Count > 0)
|
||||
{
|
||||
<div class="glossaryRelatedSection">
|
||||
<div class="glossaryRelatedTitle">Related Entities</div>
|
||||
<div class="glossaryRelatedList">
|
||||
@foreach (var entityId in term.RelatedEntityIds)
|
||||
{
|
||||
<EntityLabelComponent EntityId="@entityId"/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (term.RelatedTermIds.Count > 0)
|
||||
{
|
||||
<div class="glossaryRelatedSection">
|
||||
<div class="glossaryRelatedTitle">Related Terms</div>
|
||||
<div class="glossaryRelatedList">
|
||||
@foreach (var relatedId in term.RelatedTermIds)
|
||||
{
|
||||
<GlossaryLabelComponent TermId="@relatedId"/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="dialogFooter"></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
.pageContents * {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
|
||||
.dialogBackground {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 64px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
|
||||
background-color: var(--background);
|
||||
border-width: var(--dialog-border-width);
|
||||
border-style: solid;
|
||||
border-color: var(--dialog-border-color);
|
||||
border-radius: var(--dialog-radius);
|
||||
|
||||
|
||||
box-shadow: 1px 2px 2px black;
|
||||
|
||||
}
|
||||
|
||||
.dialogHeader {
|
||||
width: 100%;
|
||||
background-color: var(--accent);
|
||||
border-top-left-radius: var(--dialog-radius);
|
||||
border-top-right-radius: var(--dialog-radius);
|
||||
border-bottom: 4px solid black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.backButton {
|
||||
margin-left: 16px;
|
||||
padding: 12px;
|
||||
|
||||
border: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
.backButton:hover {
|
||||
background-color: var(--primary-hover);
|
||||
border: 1px solid var(--primary-border-hover);
|
||||
}
|
||||
|
||||
.backButtonIcon {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border: solid white;
|
||||
border-width: 0 9px 9px 0;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.dialogTitle {
|
||||
padding: 16px;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dialogContent {
|
||||
flex-grow: 1;
|
||||
padding: 6px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
height: 800px;
|
||||
}
|
||||
|
||||
.dialogFooter {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background-color: var(--paper);
|
||||
}
|
||||
|
||||
.glossaryDialog {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.glossaryDialogCategory {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.glossaryDefinition {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.glossaryDefinition p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.glossaryRelatedSection {
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--info-secondary-border);
|
||||
}
|
||||
|
||||
.glossaryRelatedTitle {
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.glossaryRelatedList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private GlossaryTermModel? term;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
glossaryDialogService.Subscribe(OnUpdate);
|
||||
LoadTerm();
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
glossaryDialogService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
LoadTerm();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
void LoadTerm()
|
||||
{
|
||||
var id = glossaryDialogService.GetTermId();
|
||||
term = id != null ? glossaryService.GetTerm(id) : null;
|
||||
}
|
||||
|
||||
void CloseDialog()
|
||||
{
|
||||
glossaryDialogService.CloseDialog();
|
||||
}
|
||||
|
||||
string RenderMarkdown(string text)
|
||||
{
|
||||
return Markdown.ToHtml(text);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
@if (StyleType.Equals("Plain"))
|
||||
@inject IGlossaryService glossaryService
|
||||
|
||||
@if (StyleType.Equals("Plain"))
|
||||
{
|
||||
@if (Entity!.Info().Description != "")
|
||||
{
|
||||
<div>
|
||||
<b>Description:</b> @((MarkupString)Entity.Info().Description)
|
||||
<b>Description:</b> @((MarkupString)glossaryService.LinkifyText(Entity.Info().Description))
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Entity.Info().Notes != "")
|
||||
{
|
||||
<div>
|
||||
<b>Notes:</b> @((MarkupString)Entity.Info().Notes)
|
||||
<b>Notes:</b> @((MarkupString)glossaryService.LinkifyText(Entity.Info().Notes))
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Entity.Info().FlavorText != "")
|
||||
{
|
||||
<div>
|
||||
<i>@((MarkupString)Entity.Info().FlavorText)</i>
|
||||
<i>@((MarkupString)glossaryService.LinkifyText(Entity.Info().FlavorText))</i>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -59,14 +61,14 @@ else
|
||||
@if (Entity!.Info().Description != "")
|
||||
{
|
||||
<div>
|
||||
<b>Description:</b> @((MarkupString)Entity.Info().Description)
|
||||
<b>Description:</b> @((MarkupString)glossaryService.LinkifyText(Entity.Info().Description))
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Entity.Info().Notes != "")
|
||||
{
|
||||
<div>
|
||||
<b>Notes:</b> @((MarkupString)Entity.Info().Notes)
|
||||
<b>Notes:</b> @((MarkupString)glossaryService.LinkifyText(Entity.Info().Notes))
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
@layout PageLayout
|
||||
@inject IGlossaryService glossaryService
|
||||
@inject IGlossaryDialogService glossaryDialogService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/glossary/{TermId}"
|
||||
|
||||
<LayoutMediumContentComponent>
|
||||
@if (term != null)
|
||||
{
|
||||
<WebsiteTitleComponent>@term.Term</WebsiteTitleComponent>
|
||||
|
||||
<PaperComponent>
|
||||
<div class="detailCategory">@term.Category</div>
|
||||
<div class="detailDefinition">@((MarkupString)RenderMarkdown(term.LongDefinition))</div>
|
||||
</PaperComponent>
|
||||
|
||||
@if (term.RelatedEntityIds.Count > 0)
|
||||
{
|
||||
<PaperComponent>
|
||||
<div class="detailSectionTitle">Related Entities</div>
|
||||
<div class="detailRelatedList">
|
||||
@foreach (var entityId in term.RelatedEntityIds)
|
||||
{
|
||||
<EntityLabelComponent EntityId="@entityId"/>
|
||||
}
|
||||
</div>
|
||||
</PaperComponent>
|
||||
}
|
||||
|
||||
@if (term.RelatedTermIds.Count > 0)
|
||||
{
|
||||
<PaperComponent>
|
||||
<div class="detailSectionTitle">Related Terms</div>
|
||||
<div class="detailRelatedList">
|
||||
@foreach (var relatedId in term.RelatedTermIds)
|
||||
{
|
||||
<GlossaryLabelComponent TermId="@relatedId"/>
|
||||
}
|
||||
</div>
|
||||
</PaperComponent>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<WebsiteTitleComponent>Term not found</WebsiteTitleComponent>
|
||||
<PaperComponent>
|
||||
<p>The glossary term you're looking for doesn't exist.</p>
|
||||
</PaperComponent>
|
||||
}
|
||||
</LayoutMediumContentComponent>
|
||||
|
||||
<style>
|
||||
.detailCategory {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detailDefinition {
|
||||
line-height: 1.6;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.detailDefinition p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detailSectionTitle {
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detailRelatedList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter] public string TermId { get; set; } = "";
|
||||
|
||||
private GlossaryTermModel? term;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
term = glossaryService.GetTerm(TermId);
|
||||
}
|
||||
|
||||
string RenderMarkdown(string text)
|
||||
{
|
||||
return Markdown.ToHtml(text);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
@layout PageLayout
|
||||
@inherits BasePage
|
||||
@inject IGlossaryService glossaryService
|
||||
@inject IGlossaryDialogService glossaryDialogService
|
||||
|
||||
@page "/glossary"
|
||||
|
||||
<LayoutLargeContentComponent>
|
||||
<WebsiteTitleComponent>Glossary</WebsiteTitleComponent>
|
||||
|
||||
<div class="glossaryControls">
|
||||
<input class="glossarySearch" @bind="searchText" @bind:after="OnSearchChanged" placeholder="Search terms..."/>
|
||||
<div class="glossaryCategories">
|
||||
<button class="glossaryCategory @(selectedCategory == null ? "active" : "")"
|
||||
@onclick="() => SelectCategory(null)">All
|
||||
</button>
|
||||
@foreach (var cat in categories)
|
||||
{
|
||||
<button class="glossaryCategory @(selectedCategory == cat ? "active" : "")"
|
||||
@onclick="() => SelectCategory(cat)">@cat</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glossaryGrid">
|
||||
@foreach (var term in filteredTerms)
|
||||
{
|
||||
<PaperComponent>
|
||||
<div class="glossaryCard" @onclick="() => glossaryDialogService.AddDialog(term.Id)">
|
||||
<div class="glossaryCardHeader">
|
||||
<span class="glossaryCardTerm">@term.Term</span>
|
||||
<span class="glossaryCardCategory">@term.Category</span>
|
||||
</div>
|
||||
<div class="glossaryCardBody">@term.ShortDefinition</div>
|
||||
</div>
|
||||
</PaperComponent>
|
||||
}
|
||||
</div>
|
||||
</LayoutLargeContentComponent>
|
||||
|
||||
<style>
|
||||
.glossaryControls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.glossarySearch {
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid var(--primary-border);
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.glossarySearch::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.glossaryCategories {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.glossaryCategory {
|
||||
padding: 6px 14px;
|
||||
border-radius: 16px;
|
||||
border: 2px solid var(--primary-border);
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.glossaryCategory.active {
|
||||
background-color: var(--primary-hover);
|
||||
border-color: var(--primary-border-hover);
|
||||
}
|
||||
|
||||
.glossaryCategory:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.glossaryGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.glossaryCard {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.glossaryCardHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.glossaryCardTerm {
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.glossaryCardCategory {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.glossaryCardBody {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@@media only screen and (max-width: 1025px) {
|
||||
.glossaryGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private string searchText = "";
|
||||
private string? selectedCategory;
|
||||
private List<string> categories = new();
|
||||
private List<GlossaryTermModel> allTerms = new();
|
||||
private List<GlossaryTermModel> filteredTerms = new();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
allTerms = glossaryService.GetAllTerms();
|
||||
categories = glossaryService.GetCategories();
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
void OnSearchChanged()
|
||||
{
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
void SelectCategory(string? category)
|
||||
{
|
||||
selectedCategory = category;
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
void ApplyFilter()
|
||||
{
|
||||
var query = allTerms.AsEnumerable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
query = query.Where(t =>
|
||||
t.Term.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.ShortDefinition.Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (selectedCategory != null)
|
||||
{
|
||||
query = query.Where(t =>
|
||||
t.Category.Equals(selectedCategory, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
filteredTerms = query.ToList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
@layout PageLayout
|
||||
@inherits BasePage
|
||||
@inject TechTreeService techTreeService
|
||||
@inject IEntityDialogService entityDialogService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/tech-tree"
|
||||
|
||||
<LayoutLargeContentComponent>
|
||||
<WebsiteTitleComponent>Tech Tree</WebsiteTitleComponent>
|
||||
|
||||
<div class="techTreeControls">
|
||||
<div class="techTreeFilters">
|
||||
<span class="techTreeLabel">Faction:</span>
|
||||
<button class="techTreeFilter @(selectedFaction == null ? "active" : "")"
|
||||
@onclick="() => SelectFaction(null)">All
|
||||
</button>
|
||||
@foreach (var faction in factions)
|
||||
{
|
||||
var factionName = GetFactionName(faction);
|
||||
<button class="techTreeFilter @(selectedFaction == faction ? "active" : "")"
|
||||
@onclick="() => SelectFaction(faction)">@factionName</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="techTreeSearchRow">
|
||||
<input class="techTreeSearch" @bind="searchText" @bind:after="OnSearchChanged"
|
||||
placeholder="Search for an entity..."/>
|
||||
@if (!string.IsNullOrEmpty(highlightEntityId))
|
||||
{
|
||||
<button class="techTreeClearBtn" @onclick="ClearHighlight">Clear Highlight</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="techTreeLegend">
|
||||
<span class="legendItem"><span class="legendLine" style="background: #2ed573;"></span> Produces</span>
|
||||
<span class="legendItem"><span class="legendLine"
|
||||
style="background: #ffa502; border-top: 3px dashed #ffa502; height: 0;"></span> Requires Building</span>
|
||||
<span class="legendItem"><span class="legendLine"
|
||||
style="background: #a55eea; border-top: 3px dashed #a55eea; height: 0;"></span> Requires Research</span>
|
||||
<span class="legendItem"><span class="legendLine"
|
||||
style="background: #ff6b6b; border-top: 2px dotted #ff6b6b; height: 0;"></span> Morph</span>
|
||||
<span class="legendItem"><span class="legendLine" style="background: #1e90ff;"></span> Upgrade</span>
|
||||
</div>
|
||||
|
||||
@if (currentGraph != null && currentGraph.Nodes.Count > 0)
|
||||
{
|
||||
<TechTreeGraphComponent Graph="@currentGraph" HighlightEntityId="@highlightEntityId"
|
||||
OnEntitySelected="OnEntitySelected"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<PaperComponent>
|
||||
<p>No data available for the selected faction.</p>
|
||||
</PaperComponent>
|
||||
}
|
||||
</LayoutLargeContentComponent>
|
||||
|
||||
<style>
|
||||
.techTreeControls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.techTreeFilters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.techTreeLabel {
|
||||
font-weight: 700;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.techTreeFilter {
|
||||
padding: 6px 14px;
|
||||
border-radius: 16px;
|
||||
border: 2px solid var(--primary-border);
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.techTreeFilter.active {
|
||||
background-color: var(--primary-hover);
|
||||
border-color: var(--primary-border-hover);
|
||||
}
|
||||
|
||||
.techTreeFilter:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.techTreeSearchRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.techTreeSearch {
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid var(--primary-border);
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.techTreeSearch::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.techTreeClearBtn {
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid var(--primary-border);
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.techTreeClearBtn:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.techTreeLegend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background-color: var(--paper);
|
||||
border: 2px solid var(--paper-border);
|
||||
border-radius: 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.legendItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.legendLine {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private TechTreeGraphModel? currentGraph;
|
||||
private List<string> factions = new();
|
||||
private string? selectedFaction;
|
||||
private string searchText = "";
|
||||
private string? highlightEntityId;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
LoadGraph();
|
||||
}
|
||||
|
||||
void LoadGraph()
|
||||
{
|
||||
currentGraph = techTreeService.BuildGraph(selectedFaction);
|
||||
factions = techTreeService.GetFactions();
|
||||
}
|
||||
|
||||
void SelectFaction(string? faction)
|
||||
{
|
||||
selectedFaction = faction;
|
||||
highlightEntityId = null;
|
||||
LoadGraph();
|
||||
}
|
||||
|
||||
void OnSearchChanged()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
highlightEntityId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var found = currentGraph?.Nodes
|
||||
.FirstOrDefault(n => n.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
highlightEntityId = found?.Id;
|
||||
}
|
||||
|
||||
void ClearHighlight()
|
||||
{
|
||||
highlightEntityId = null;
|
||||
searchText = "";
|
||||
}
|
||||
|
||||
void OnEntitySelected(string entityId)
|
||||
{
|
||||
highlightEntityId = entityId;
|
||||
}
|
||||
|
||||
string GetFactionName(string factionId)
|
||||
{
|
||||
var entities = EntityData.Get();
|
||||
return entities.TryGetValue(factionId, out var entity)
|
||||
? entity.Info()?.Name ?? factionId
|
||||
: factionId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
@implements IDisposable;
|
||||
|
||||
@inject IGlossaryDialogService glossaryDialogService
|
||||
|
||||
@if (glossaryDialogService.HasDialog())
|
||||
{
|
||||
<GlossaryDialogComponent></GlossaryDialogComponent>
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
glossaryDialogService.Subscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
glossaryDialogService.Unsubscribe(OnUpdate);
|
||||
}
|
||||
|
||||
void OnUpdate()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
+3
-6
@@ -1,7 +1,6 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Blazor.Analytics;
|
||||
using Blazored.LocalStorage;
|
||||
using IGP;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
@@ -39,11 +38,6 @@ builder.Services.AddBlazoredLocalStorageAsSingleton(config =>
|
||||
config.JsonSerializerOptions.WriteIndented = false;
|
||||
});
|
||||
|
||||
#if DEBUG
|
||||
builder.Services.AddGoogleAnalytics("G-S96LW7TVFY");
|
||||
#else
|
||||
builder.Services.AddGoogleAnalytics(builder.Configuration["GATag"]);
|
||||
#endif
|
||||
|
||||
builder.Services.AddScoped<INavigationService, NavigationService>();
|
||||
builder.Services.AddScoped<IKeyService, KeyService>();
|
||||
@@ -56,6 +50,8 @@ builder.Services.AddScoped<IMemoryTesterService, MemoryTesterService>();
|
||||
builder.Services.AddScoped<IEntityFilterService, EntityFilterService>();
|
||||
builder.Services.AddScoped<IEntityDisplayService, EntityDisplayService>();
|
||||
builder.Services.AddScoped<IEntityDialogService, EntityDialogService>();
|
||||
builder.Services.AddScoped<IGlossaryService, GlossaryService>();
|
||||
builder.Services.AddScoped<IGlossaryDialogService, GlossaryDialogService>();
|
||||
builder.Services.AddScoped<IToastService, ToastService>();
|
||||
builder.Services.AddScoped<INoteService, NoteService>();
|
||||
builder.Services.AddScoped<ISearchService, SearchService>();
|
||||
@@ -65,6 +61,7 @@ builder.Services.AddScoped<IEconomyComparisonService, EconomyComparisionService>
|
||||
builder.Services.AddScoped<IDataCollectionService, DataCollectionService>();
|
||||
|
||||
builder.Services.AddScoped<IMyDialogService, MyDialogService>();
|
||||
builder.Services.AddScoped<TechTreeService>();
|
||||
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient
|
||||
|
||||
+4
-5
@@ -6,7 +6,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
<LangVersion>12</LangVersion>
|
||||
<AssemblyName>IGP.Web</AssemblyName>
|
||||
<AssemblyName>Web</AssemblyName>
|
||||
<RootNamespace>IGP</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazor-Analytics" Version="4.0.0"/>
|
||||
<PackageReference Include="Markdig" Version="1.1.1"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.5"/>
|
||||
@@ -36,9 +35,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Components\Components.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
<ProjectReference Include="..\Services\Services.csproj" />
|
||||
<ProjectReference Include="..\Components\Components.csproj"/>
|
||||
<ProjectReference Include="..\Model\Model.csproj"/>
|
||||
<ProjectReference Include="..\Services\Services.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+3
-3
@@ -1,5 +1,6 @@
|
||||
@using Components.Display
|
||||
@using Components.Feedback
|
||||
@using Components.TechTree
|
||||
@using Components.Form
|
||||
@using Components.Info
|
||||
@using Components.Inputs
|
||||
@@ -36,11 +37,13 @@
|
||||
@using Model.Entity.Data
|
||||
@using Model.Entity.Parts
|
||||
@using Model.Feedback
|
||||
@using Model.Glossary
|
||||
@using Model.Hotkeys
|
||||
@using Model.MemoryTester
|
||||
@using Model.Notes
|
||||
@using Model.RoadMap
|
||||
@using Model.RoadMap.Enums
|
||||
@using Model.TechTree
|
||||
@using Model.Types
|
||||
@using Model.Website
|
||||
@using Services
|
||||
@@ -48,8 +51,5 @@
|
||||
@using System.Globalization
|
||||
@using System.Reflection
|
||||
@using System.Timers
|
||||
@using Blazor.Analytics
|
||||
@using Blazor.Analytics.Components
|
||||
@using Blazor.Analytics.Abstractions
|
||||
@using MudBlazor
|
||||
@using MudBlazor.Services
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2"
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js"></script>
|
||||
<script src="javascript/download.js"></script>
|
||||
<script src="_content/Blazor-Analytics/blazor-analytics.js"></script>
|
||||
|
||||
<script src="fontawesome-pro-6.1.1-web/js/all.min.js"></script>
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user