Initial Commit

This commit is contained in:
2026-05-29 14:17:46 -04:00
commit b7d0676d5b
498 changed files with 30308 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# .NET
bin/
obj/
*.user
*.suo
*.sln.docstates
*.userprefs
packages/
*.nupkg
# .NET Aspire
AppHost/
# IDE - Rider
.idea/
*.sln.ide/
# IDE - VS
.vs/
*.csproj.user
*.dbmdl
*.jfm
# IDE - VS Code
.vscode/
# OS
.DS_Store
Thumbs.db
# Build results
[Dd]ebug/
[Rr]elease/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# NuGet
*.nuget.props
*.nuget.targets
# Node
node_modules/
# Coverlet
*.coverage
*.coverage.xml
# Publish
*.pubxml
publish/
+38
View File
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.28.1"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.14"/>
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.14"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Inputs\"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj"/>
<ProjectReference Include="..\Services\Services.csproj"/>
</ItemGroup>
<ItemGroup>
<None Remove="Inputs\"/>
</ItemGroup>
</Project>
@@ -0,0 +1,26 @@
<div class="code">
@ChildContent
</div>
<style>
.code {
font-family: monospace;
border: 2px solid white;
color: white;
padding: 8px;
background-color: black;
white-space: pre;
overflow-x: scroll;
}
.code::-webkit-scrollbar {
height: 0;
background: transparent;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
}
@@ -0,0 +1,73 @@
@if (isOnDev)
{
<div class="devOnlyContainer">
<div class="devOnlyTitleContainer">
<div class="devOnlyTitle">
DevOnly UI
</div>
</div>
<div class="devOnlyContentContainer">
<div class="devOnlyContent">
@ChildContent
</div>
</div>
</div>
<style>
.devOnlyContainer {
display: flex;
flex-direction: column;
}
.devOnlyTitle {
background-color: rgba(20, 20, 20, 0.75);
padding: 10px;
color: orange;
font-weight: bolder;
font-size: 1.5rem;
}
.devOnlyContent {
background-color: rgba(20, 20, 20, 0.75);
width: 100%;
padding: 10px;
}
.devOnlyTitleContainer {
background: repeating-linear-gradient(45deg, blue, blue 50px, orange 51px, orange 100px);
margin-right: auto;
padding: 10px;
border-left: 6px dashed orange;
border-right: 6px dashed orange;
border-top: 6px dashed orange;
text-shadow: 4px 4px 1px blue;
}
.devOnlyContentContainer {
border: 6px dashed orange;
background: repeating-linear-gradient(45deg, blue, blue 50px, orange 51px, orange 100px);
box-shadow: 5px 5px 5px blue;
padding: 20px;
}
</style>
}
@code {
[Inject] NavigationManager NavigationManager { get; set; } = default!;
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
bool isOnDev;
protected override void OnInitialized()
{
base.OnInitialized();
isOnDev = NavigationManager.BaseUri.Contains("https://localhost");
}
}
@@ -0,0 +1,76 @@
<div class="entityDisplaySection">
<div class="entityDisplayHeader">
<div class="entityDisplayTitle">
@Title
</div>
<div class="entityDisplayBorder">
</div>
</div>
@ChildContent
</div>
<style>
.entityDisplaySection {
position: relative;
padding: 8px;
display: flex;
gap: 12px;
flex-direction: column;
margin-top: 14px;
margin-top: 20px;
padding: 12px;
background-color: var(--info);
border-top-right-radius: 12px;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
.entityDisplayHeader {
bottom: 100%;
position: absolute;
white-space: pre;
width: 100%;
line-height: 0px;
right: 0px;
top: -4px;
display: flex;
}
.entityDisplayTitle {
font-weight: 800;
font-size: 1.4rem;
padding-right: 8px;
text-shadow: 3px 0 0 var(--info), -3px 0 0 var(--info), 0 3px 0 var(--info), 0 -3px 0 var(--info), 2px 2px var(--info), -2px -2px 0 var(--info), 2px -2px 0 var(--info), -2px 2px 0 var(--info);
}
@@media only screen and (max-width: 1025px) {
.entityDisplayHeader {
position: inherit;
width: 100%;
margin: 0px;
}
.entityDisplaySection {
position: inherit;
width: 100%;
margin: 0px;
max-width: none;
border-top-right-radius: 0px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.entityDisplayTitle {
position: inherit;
margin: 0px;
}
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public string Title { get; set; } = default!;
}
@@ -0,0 +1,64 @@
<div class="tooltipWrapper">
<div class="tooltipContent">
@((MarkupString)InfoText)
</div>
@ChildContent
</div>
<style>
.tooltipWrapper {
position: relative;
display: inline-block;
width: 100%;
}
.tooltipContent {
visibility: hidden;
position: absolute;
width: 520px;
max-width: 93vw;
bottom: 100%;
margin-top: 60px;
margin-left: -60px;
margin-bottom: 36px;
padding-left: 20px;
padding-right: 20px;
padding-bottom: 20px;
padding-top: 20px;
white-space: break-spaces;
z-index: 2147483647;
background-color: var(--info-secondary);
border: 1px solid var(--info-secondary-border);
border-radius: 2px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5);
}
.tooltipWrapper:hover .tooltipContent {
visibility: visible;
}
@@media only screen and (max-width: 1025px) {
.tooltipContent {
margin: auto;
margin-bottom: 20px;
position: absolute;
}
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public string InfoText { get; set; } = default!;
[Parameter] public int? Margin { get; set; }
}
@@ -0,0 +1,111 @@
<div class="makingOfContainer">
<div class="makingInfo">
<div class="makingTitle">@Title</div>
<div class="makingDescription">@Description</div>
</div>
<div class="makingofGrid">
<div class="makingofItem">
<details open>
<summary>Example</summary>
<div class="shownCode">
@Example
</div>
</details>
</div>
<div class="makingofItem">
<details open>
<summary>Usage</summary>
<div class="shownCode">
@Usage
</div>
</details>
</div>
<div class="makingofItem">
<details open>
<summary>Code</summary>
<div class="shownCode">
@Code
</div>
</details>
</div>
</div>
</div>
<style>
.makingInfo {
margin-top: 6px;
margin-bottom: 12px;
}
.makingTitle {
font-weight: bolder;
font-size: 1.4rem;
}
.makingOfContainer {
display: flex;
flex-direction: column;
gap: 6px;
}
.makingofGrid {
display: flex;
flex-wrap: wrap;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.makingofItem {
width: 100%;
}
.makingOfContainer details {
border: 2px dashed black;
border-radius: 4px;
}
.makingOfContainer summary {
font-weight: bold;
padding: 12px;
background-color: rgba(0, 0, 0, 0.1);
}
.shownCode {
visibility: hidden;
padding: 12px;
background-color: rgba(0, 0, 0, 0.1);
}
.makingOfContainer details[open] .shownCode {
visibility: visible;
}
.makingOfContainer details[open] summary {
border-bottom: 2px dashed black;
}
@@media only screen and (max-width: 1025px) {
.makingofGrid {
display: flex;
flex-direction: column;
}
}
</style>
@code {
[Parameter] public RenderFragment Title { get; set; } = default!;
[Parameter] public RenderFragment Description { get; set; } = default!;
[Parameter] public RenderFragment Example { get; set; } = default!;
[Parameter] public RenderFragment Usage { get; set; } = default!;
[Parameter] public RenderFragment Code { get; set; } = default!;
}
@@ -0,0 +1,30 @@
<details class="makingOfSection">
<summary>
@Title
</summary>
@ChildContent
</details>
<style>
.makingOfSection {
width: 100%;
background-color: rgba(0, 0, 0, 0.1);
padding: 25px;
border-radius: 8px;
border: 2px black dashed;
}
.makingOfSection > summary {
font-size: 3.4em;
}
</style>
@code {
[Parameter] public string Title { get; set; } = default!;
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
}
@@ -0,0 +1,20 @@
<div class="panelDisplay">
@ChildContent
</div>
<style>
.panelDisplay {
border: 2px solid var(--paper-border);
padding: 20px;
background-color: var(--paper);
display: flex;
flex-direction: column;
flex-grow: 1;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
}
@@ -0,0 +1,25 @@
<div class="paperDisplay">
@ChildContent
</div>
<style>
.paperDisplay {
padding-top: 24px;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
overflow-y: auto;
overflow-x: hidden;
border: 4px solid var(--paper-border);
background-color: var(--paper);
box-shadow: 0px 6px var(--paper-border);
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public string Title { get; set; } = default!;
}
@@ -0,0 +1,61 @@
<div class="alertContainer @Type.ToLower()">
@if (Title != null)
{
<div class="alertTitle">
@Title
</div>
}
@if (Message != null)
{
<div>
@Message
</div>
}
</div>
<style>
.alertContainer {
border: 4px solid;
border-radius: 4px;
padding: 16px;
display: flex;
flex-direction: column;
justify-items: stretch;
width: 100%;
}
.alertContainer.@SeverityType.Warning.ToLower() {
background-color: var(--severity-warning-color);
border-color: var(--severity-warning-border-color);
}
.alertContainer.@SeverityType.Error.ToLower() {
background-color: var(--severity-error-color);
border-color: var(--severity-error-border-color);
}
.alertContainer.@SeverityType.Information.ToLower() {
background-color: var(--severity-information-color);
border-color: var(--severity-information-border-color);
}
.alertContainer.@SeverityType.Success.ToLower() {
background-color: var(--severity-success-color);
border-color: var(--severity-success-border-color);
}
.alertTitle {
font-weight: 800;
}
</style>
@code {
[Parameter] public RenderFragment? Title { get; set; }
[Parameter] public RenderFragment? Message { get; set; }
[Parameter] public string Type { get; set; } = SeverityType.Warning;
}
@@ -0,0 +1,46 @@
<div class="loadingContainer">
<div style="flex-grow: 1"></div>
<div class="loadingText">
Loading...
</div>
<div style="flex-grow: 3"></div>
</div>
<style>
@@keyframes loadingKeyframes {
0% {
font-size: 1.0rem
}
50% {
font-size: 1.4rem
}
100% {
font-size: 1.0rem
}
}
.loadingContainer {
width: 100%;
height: 100%;
display: flex;
flex-grow: 1;
justify-content: center;
background-color: black;
border-radius: 4px;
flex-direction: column;
}
.loadingText {
margin-left: auto;
margin-right: auto;
animation-name: loadingKeyframes;
animation-duration: 6s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
</style>
@code {
}
@@ -0,0 +1,100 @@
@inject IToastService toastService
@implements IDisposable
@if (Toast == null)
{
<div>Add toast object...</div>
}
else
{
<div onclick="@Dismiss" style="opacity: @Opacity()" class="toastContainer @Toast.SeverityType.ToLower()">
<div class="toastTitle">
@Toast.Title
</div>
<div>
@Toast.Message
</div>
</div>
}
<style>
.toastContainer {
border: 4px solid;
border-radius: 4px;
padding: 16px;
display: flex;
flex-direction: column;
justify-items: stretch;
width: 250px;
cursor: pointer;
}
.@SeverityType.Warning.ToLower() {
background-color: var(--severity-warning-color);
border-color: var(--severity-warning-border-color);
}
.@SeverityType.Error.ToLower() {
background-color: var(--severity-error-color);
border-color: var(--severity-error-border-color);
}
.@SeverityType.Information.ToLower() {
background-color: var(--severity-information-color);
border-color: var(--severity-information-border-color);
}
.@SeverityType.Success.ToLower() {
background-color: var(--severity-success-color);
border-color: var(--severity-success-border-color);
}
.toastTitle {
font-weight: 800;
}
</style>
@code {
[Parameter] public ToastModel? Toast { get; set; }
private readonly float removalTime = 1300;
private readonly float fadeoutTime = 1200;
private float Opacity()
{
if (Toast!.Age < fadeoutTime)
{
return 1;
}
return 1.0f - (Toast.Age - fadeoutTime) / (removalTime - fadeoutTime);
}
protected override void OnInitialized()
{
base.OnInitialized();
toastService.Subscribe(OnUpdate);
}
void Dismiss()
{
toastService.RemoveToast(Toast!);
}
void IDisposable.Dispose()
{
toastService.Unsubscribe(OnUpdate);
}
void OnUpdate()
{
if (Toast!.Age > removalTime)
{
toastService.RemoveToast(Toast);
}
}
}
@@ -0,0 +1,72 @@
<div class="formContainer">
<div class="formTextContainer">
<div class="formLabel">
@Label:
</div>
<input readonly="@ReadOnly"
class="formCheckboxInput"
type="checkbox"
id="@labelId"
checked="@Value"
@onchange="OnChange"
@oninput="OnChange"/>
</div>
@if (Info != "")
{
<div class="formInfo">
@Info
</div>
}
</div>
<style>
.formContainer {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.formTextContainer {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.formLabel {
font-weight: 800;
}
.formInfo {
font-size: 0.8rem;
font-style: italic;
}
.formCheckboxInput {
background-color: var(--primary);
color: var(--primary);
border: 2px solid var(--primary-border);
}
</style>
@code {
[Parameter] public string Label { get; set; } = "";
[Parameter] public string Info { get; set; } = "";
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
[Parameter] public bool ReadOnly { get; set; }
[Parameter] public bool Value { get; set; }
private string labelId = "";
protected override void OnInitialized()
{
base.OnInitialized();
labelId = Label.ToLower().Replace(" ", "_");
}
}
@@ -0,0 +1,52 @@
<div class="displayContainer">
@if (Label != "")
{
<div class="formLabel">
@Label
</div>
}
<div class="displayContent">
@Display
</div>
@if (Info != "")
{
<div class="formInfo">
@Info
</div>
}
</div>
<style>
.displayContainer {
display: flex;
width: 100%;
flex-direction: column;
gap: 6px;
}
.displayContent {
background-color: var(--accent);
width: 100%;
border: 1px solid var(--primary-border);
border-radius: 1px;
padding: 8px;
min-height: 42px;
}
</style>
@code {
//TODO Clean up
[Parameter] public string Label { get; set; } = default!;
[Parameter] public string Info { get; set; } = default!;
[Parameter] public RenderFragment? Display { get; set; }
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
[Parameter] public bool? ReadOnly { get; set; }
[Parameter] public string? Value { get; set; }
}
@@ -0,0 +1,30 @@
@using System.Web
<div class="escapeCodeContainer">
<textarea style="background-color: #2C2E33; width: 100%; border:3px solid #A8ADB9; border-radius:1px; padding: 8px;"
rows="8"
@onchange="OnChange"/>
<textarea style="background-color: #2C2E33; width: 100%; border:3px solid #A8ADB9; border-radius:1px; padding: 8px;"
rows="8"
@bind="Output"/>
</div>
<style>
.escapeCodeContainer {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
</style>
@code {
string Output = "";
public void OnChange(ChangeEventArgs changeEventArgs)
{
var encoded = HttpUtility.HtmlEncode(changeEventArgs.Value!.ToString());
Output = encoded?.Replace("@", "@@")!;
Output = Output.Replace("\n", "<br />");
}
}
@@ -0,0 +1,163 @@
@using Model.MemoryTester
@using Services.Immortal
@implements IDisposable
@inject IMemoryTesterService MemoryTesterService
<div class="formGuessContainer">
@if (MemoryQuestion.Name != "")
{
<div class="formLabel">
@MemoryQuestion.Name
</div>
}
<div>
<input readonly="@MemoryQuestion.IsRevealed"
class="formTextInput @(MemoryQuestion.IsRevealed ? "revealed" : IsSubmitted == false ? "guess" : int.Parse(guess ?? string.Empty) == MemoryQuestion.Answer ? "correct" : "wrong")"
placeholder="guess..."
type="number"
value="@guess"
id="@labelId"
@onchange="OnGuessChanged"/>
</div>
</div>
<style>
.formGuessContainer {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.formLabel {
font-weight: 800;
}
.formInfo {
font-size: 0.8rem;
font-style: italic;
}
.formTextInput {
background-color: #2C2E33;
border: 3px solid #A8ADB9;
border-radius: 1px;
padding: 8px;
display: block;
width: 100%;
}
.guess {
background-color: var(--primary);
border: 4px solid var(--primary-border);
border-radius: 1px;
}
::placeholder {
color: white;
opacity: 1;
}
.formTextInput.correct {
border: 3px solid green;
border-radius: 2px;
}
.formTextInput.wrong {
border: 3px solid red;
border-radius: 2px;
font-weight: bold;
}
</style>
@code {
[Parameter] public string Label { get; set; } = "";
[Parameter] public string Info { get; set; } = "";
[Parameter] public EventCallback<AnswerEventArgs> OnChange { get; set; }
[Parameter] public MemoryQuestionModel MemoryQuestion { get; set; } = default!;
[Parameter] public bool IsSubmitted { get; set; }
private string? guess = "";
private string labelId = "";
protected override void OnInitialized()
{
base.OnInitialized();
labelId = Label.ToLower().Replace(" ", "_") + MemoryQuestion.Id;
MemoryTesterService.Subscribe(OnMemoryEvent);
if (MemoryQuestion.IsRevealed)
{
guess = MemoryQuestion.Answer.ToString();
}
OnRefresh();
}
void IDisposable.Dispose()
{
MemoryTesterService.Unsubscribe(OnMemoryEvent);
}
void OnMemoryEvent(MemoryTesterEvent memoryTesterEvent)
{
if (memoryTesterEvent == MemoryTesterEvent.OnVerify)
{
OnVerify();
}
if (memoryTesterEvent == MemoryTesterEvent.OnRefresh)
{
OnRefresh();
}
}
protected override void OnAfterRender(bool firstRender)
{
if (MemoryQuestion.IsRevealed)
{
guess = MemoryQuestion.Answer.ToString();
}
}
void OnVerify()
{
IsSubmitted = true;
}
void OnRefresh()
{
guess = "";
if (MemoryQuestion.IsRevealed)
{
guess = MemoryQuestion.Answer.ToString();
}
IsSubmitted = false;
}
void OnGuessChanged(ChangeEventArgs changeEventArgs)
{
guess = changeEventArgs.Value!.ToString()!;
OnChange.InvokeAsync(new AnswerEventArgs
{
Name = MemoryQuestion.Name,
IsCorrect = int.Parse(guess) == MemoryQuestion.Answer,
Guess = int.Parse(guess)
});
}
}
@@ -0,0 +1,11 @@
<div style="font-size:0.8rem; font-style:italic;">
<i>
@ChildContent
</i>
</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,9 @@
<div style="font-weight:800">
@ChildContent:
</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,9 @@
<div style="display: flex; gap: 16px; width: 100%; flex-direction: column;">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,80 @@
<div class="formNumberContainer">
@if (FormLabelComponent != null)
{
<FormLabelComponent>@FormLabelComponent</FormLabelComponent>
}
<div>
<input readonly="@ReadOnly"
id="@Id"
class="numberInput"
type="number"
min="@Min"
max="@Max"
value="@Value"
@onchange="OnInputChanged"/>
</div>
@if (FormInfoComponent != null)
{
<FormInfoComponent>@FormInfoComponent</FormInfoComponent>
}
</div>
<style>
.formNumberContainer {
display: flex;
width: 100%;
flex-direction: column;
gap: 6px;
}
.numberInput {
width: 100%;
background-color: var(--primary);
border: 4px solid var(--primary-border);
border-radius: 1px;
padding: 8px;
}
</style>
@code {
[Parameter] public RenderFragment? FormLabelComponent { get; set; }
[Parameter] public string Id { get; set; } = default!;
[Parameter] public RenderFragment? FormInfoComponent { get; set; }
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
void OnInputChanged(ChangeEventArgs changeEventArgs)
{
var valueWas = Value;
var newValue = int.Parse(changeEventArgs.Value!.ToString()!);
if (newValue > Max)
{
newValue = Max;
}
if (newValue < Min)
{
newValue = Min;
}
if (valueWas != newValue)
{
Value = newValue;
changeEventArgs.Value = newValue;
OnChange.InvokeAsync(changeEventArgs);
}
}
[Parameter] public bool ReadOnly { get; set; }
[Parameter] public int Value { get; set; }
[Parameter] public int Min { get; set; }
[Parameter] public int Max { get; set; } = 2048;
}
@@ -0,0 +1,28 @@
<div style="display: flex; width: 100%; flex-direction: column; gap:6px;">
@if (FormLabelComponent != null)
{
<FormLabelComponent>@FormLabelComponent</FormLabelComponent>
}
<select style="background-color: #2C2E33; width: 100%; border:3px solid #A8ADB9; border-radius:1px; padding: 8px;"
@onchange="OnChange">
@ChildContent
</select>
@if (FormInfoComponent != null)
{
<FormInfoComponent>@FormInfoComponent</FormInfoComponent>
}
</div>
@code {
[Parameter] public RenderFragment? FormLabelComponent { get; set; }
[Parameter] public RenderFragment? FormInfoComponent { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
}
@@ -0,0 +1,85 @@
<div class="form-text-container">
@if (Label != "")
{
<div class="form-label">
@Label
</div>
}
<div>
<textarea readonly="@ReadOnly"
class="textAreaInput"
type="text"
rows="@Rows"
value="@Value"
@onchange="OnChange"/>
</div>
@if (Info != "")
{
<div class="form-info">
@Info
</div>
}
</div>
<style>
.form-text-container {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.textAreaInput {
width: 100%;
border-radius: 1px;
padding: 8px;
background-color: var(--primary);
border: 4px solid var(--primary-border);
}
.form-text-container .form-label {
font-weight: 800;
}
.form-text-container .form-info {
font-size: 0.8rem;
font-style: italic;
}
.form-text-container .form-text-input {
background-color: #2C2E33;
border: 3px solid #A8ADB9;
border-radius: 1px;
padding: 8px;
}
</style>
@code {
[Parameter] public RenderFragment? FormLabelComponent { get; set; }
[Parameter] public RenderFragment? FormInfoComponent { get; set; }
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
[Parameter] public bool? ReadOnly { get; set; }
[Parameter] public string? Value { get; set; }
[Parameter] public int Rows { get; set; } = 4;
[Parameter] public string Label { get; set; } = "";
[Parameter] public string Info { get; set; } = "";
[Parameter] public string Placeholder { get; set; } = "";
private string labelId = "";
protected override void OnInitialized()
{
base.OnInitialized();
labelId = Label.ToLower().Replace(" ", "_");
}
}
@@ -0,0 +1,84 @@
<div class="formContainer">
@if (Label != "")
{
<div class="formLabel">
@Label
</div>
}
<div>
<input readonly="@ReadOnly"
class="formTextInput"
placeholder="@Placeholder"
type="text"
value="@Value"
id="@Id"
@onfocus="OnFocus"
@oninput="OnInput"
@onchange="OnChange"/>
</div>
@if (Info != "")
{
<div class="formInfo">
@Info
</div>
}
</div>
<style>
.formContainer {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.formLabel {
font-weight: 800;
}
.formInfo {
font-size: 0.8rem;
font-style: italic;
}
.formTextInput {
border-radius: 1px;
padding: 8px;
display: block;
width: 100%;
background-color: var(--primary);
border: 4px solid var(--primary-border);
}
</style>
@code {
[Parameter] public string Id { get; set; } = "";
[Parameter] public string Label { get; set; } = "";
[Parameter] public string Info { get; set; } = "";
[Parameter] public string Placeholder { get; set; } = "";
[Parameter] public EventCallback<ChangeEventArgs> OnInput { get; set; }
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
[Parameter] public EventCallback OnFocus { get; set; }
[Parameter] public bool ReadOnly { get; set; }
[Parameter] public string Value { get; set; } = "";
private string labelId = "";
protected override void OnInitialized()
{
base.OnInitialized();
labelId = Label.ToLower().Replace(" ", "_");
}
}
@@ -0,0 +1,121 @@
<div class="formContainer">
<div class="formTextContainer">
<div class="formLabel">
@Label:
</div>
<label class="switch">
<input readonly="@ReadOnly"
type="checkbox"
id="@labelId"
class="@ClassStyle"
checked="@Value"
@oninput="OnChange"/>
<span class="slider"></span>
</label>
</div>
@if (Info != "")
{
<div class="formInfo">
@Info
</div>
}
</div>
<style>
.formContainer {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.formTextContainer {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.formLabel {
font-weight: 800;
}
.formInfo {
font-size: 0.8rem;
font-style: italic;
}
.switch {
position: relative;
width: 60px;
height: 34px;
display: flex;
align-items: center;
top: 4px;
}
.switch input {
opacity: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--paper-border);
-webkit-transition: .4s;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
.checked + .slider {
background-color: #7838df;
}
.checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
</style>
@code {
[Parameter] public string Label { get; set; } = "";
[Parameter] public string Info { get; set; } = "";
[Parameter] public EventCallback<ChangeEventArgs> OnChange { get; set; }
[Parameter] public bool ReadOnly { get; set; }
[Parameter] public bool Value { get; set; }
private string labelId = "";
private string ClassStyle => Value ? "checked" : "";
protected override void OnInitialized()
{
base.OnInitialized();
labelId = Label.ToLower().Replace(" ", "_");
}
}
@@ -0,0 +1,44 @@
<div class="infoContainer">
@if (InfoQuestionComponent != null)
{
<div class="infoTitle">@InfoQuestionComponent</div>
}
@if (InfoAnswerComponent != null)
{
<div>@InfoAnswerComponent</div>
}
</div>
<style>
.infoContainer {
display: flex;
flex-direction: column;
border-radius: 8px;
padding-top: 16px;
padding-left: 16px;
padding-right: 16px;
padding-bottom: 16px;
margin: auto;
width: 100%;
gap: 8px;
}
.infoTitle {
font-weight: 800;
font-size: 1.3rem;
}
@@media only screen and (max-width: 1025px) {
.infoContainer {
padding: 2px;
}
}
</style>
@code {
[Parameter] public RenderFragment? InfoQuestionComponent { get; set; }
[Parameter] public RenderFragment? InfoAnswerComponent { get; set; }
}
@@ -0,0 +1,50 @@
<button class="buttonContainer @MyButtonType.ToString().ToLower()" @onclick="ButtonClicked">@ChildContent</button>
<style>
.buttonContainer {
padding: 16px;
border: 1px solid;
border-radius: 8px;
font-weight: 800;
font-size: 1.2rem;
}
.@(MyButtonType.Primary.ToString().ToLower()) {
border-color: var(--primary);
background-color: var(--primary);
}
.@MyButtonType.Secondary.ToString().ToLower() {
border-color: var(--secondary);
background-color: var(--secondary);
}
.@MyButtonType.Primary.ToString().ToLower():hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
color: white;
}
.@MyButtonType.Secondary.ToString().ToLower():hover {
background-color: var(--secondary-hover);
border-color: var(--secondary-border-hover);
color: white;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public EventCallback<EventArgs> OnClick { get; set; }
[Parameter] public MyButtonType MyButtonType { get; set; }
private void ButtonClicked(EventArgs eventArgs)
{
OnClick.InvokeAsync(eventArgs);
}
}
@@ -0,0 +1,92 @@
<div class="groupButtonContainerContainer">
<div class="groupButtonContainer">
@foreach (var choice in Choices)
{
var styleClass = "";
if (choice.Equals(Choice))
{
styleClass = "selected";
}
<button @onclick="@(e => OnChangeChoice(choice))" class="groupChoiceButton @styleClass">@choice</button>
}
</div>
</div>
<style>
.groupButtonContainerContainer {
margin: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
justify-items: flex-start;
}
.groupButtonContainer {
display: flex;
background-color: var(--background);
gap: 2px;
margin-right: auto;
border-radius: 8px;
}
.groupChoiceButton {
background-color: var(--primary);
color: white;
padding: 12px;
border: 1px solid var(--primary);
}
.groupChoiceButton:hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
}
.selected {
background-color: var(--secondary);
color: white;
font-style: normal;
font-weight: bold;
}
.selected:hover {
background-color: var(--secondary-hover);
border-color: var(--secondary-border-hover);
}
.groupButtonContainer .groupChoiceButton:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.groupButtonContainer .groupChoiceButton:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
</style>
@code {
[Parameter] public string Choice { get; set; } = default!;
[Parameter] public List<string> Choices { get; set; } = default!;
[Parameter] public EventCallback<string> OnClick { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
}
void OnChangeChoice(string choice)
{
Choice = choice;
OnClick.InvokeAsync(choice);
}
}
@@ -0,0 +1,28 @@
<a href="@Href" target="_blank" class="codeLinkButton">
View on GitHub <i class="fa-brands fa-github" style="font-size: 24px; margin-left: 5px;"></i>
</a>
<style>
.codeLinkButton {
color: white;
background-color: var(--info);
border: 2px solid var(--info-border);
padding: 16px;
border-radius: 3px;
display: block;
width: 200px;
text-align: center;
}
.codeLinkButton:hover {
color: white;
background-color: var(--info-hover);
border: 2px solid var(--info-border-hover);
}
</style>
@code {
[Parameter] public string Href { get; set; } = "";
}
@@ -0,0 +1,28 @@
<a href="@Href" target="_blank" class="editLinkButton">
Edit on GitHub
</a>
<style>
.editLinkButton {
color: white;
background-color: var(--info);
border: 1px solid var(--info-border);
padding: 16px;
border-radius: 3px;
display: block;
width: 180px;
text-align: center;
}
.editLinkButton:hover {
color: white;
background-color: var(--info-hover);
border: 2px solid var(--info-border-hover);
}
</style>
@code {
[Parameter] public string Href { get; set; } = "";
}
@@ -0,0 +1,26 @@
@using Model.Entity
@using Model.Entity.Data
@inject IEntityDialogService entityDialogService
@if (EntityId == null)
{
<div>Add a entity</div>
}
else
{
<button class="entityLabel @Entity.Descriptive.ToLower()" @onclick="EntityLabelClicked">@Entity.Info().Name</button>
}
@code {
[Parameter] public string EntityId { get; set; } = default!;
private EntityModel Entity => EntityData.Get()[EntityId];
void EntityLabelClicked()
{
entityDialogService.AddDialog(EntityId);
}
}
@@ -0,0 +1,27 @@
.entityLabel {
font-weight: bolder;
box-shadow: 1px 1px 0 0 rgba(0, 0, 0, 0.2);
padding-right: 4px;
}
.entityLabel:hover {
background-color: var(--primary-hover);
}
.army {
color: cyan;
}
.building {
color: greenyellow;
}
.ability {
color: red;
}
.passive {
color: yellow;
}
@@ -0,0 +1,33 @@
<a href="@Href" class="linkButtonContainer">
@ChildContent
</a>
<style>
.linkButtonContainer {
padding: 16px;
border: 1px solid;
border-radius: 8px;
font-weight: 800;
font-size: 1.2rem;
border-color: var(--primary);
background-color: var(--primary);
}
.linkButtonContainer:hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
color: white;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public string Href { get; set; } = "";
}
+7
View File
@@ -0,0 +1,7 @@
namespace Components.Inputs;
public enum MyButtonType
{
Primary, // Positive Actions
Secondary // Destruction Action
}
@@ -0,0 +1,66 @@
@inject ISearchService SearchService
@inject NavigationManager NavigationManager
@inject IJSRuntime JsRuntime
<button id="@Id" class="searchButtonContainer" @onclick="ButtonClicked">
<div class="searchText">
<i class="fa-solid fa-magnifying-glass" style="margin-left: 3px; margin-right: 6px;"></i> Search...
</div>
<div class="searchHotkey">
@if (IsMac)
{
<span><i class="fa-solid fa-command"></i>K</span>
}
else
{
<span>CTRL + K</span>
}
</div>
</button>
<style>
.searchButtonContainer {
background-color: var(--primary);
border: 2px solid var(--primary-border);
border-radius: 8px;
font-weight: 800;
width: 350px;
padding: 5px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.searchHotkey {
padding: 4px;
background-color: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.25);
border-radius: 4px;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public string Id { get; set; } = default!;
private string _userAgent = "";
bool IsMac => _userAgent.Contains("Mac OS");
private void ButtonClicked(EventArgs eventArgs)
{
SearchService.Show();
}
protected override async Task OnInitializedAsync()
{
_userAgent = await JsRuntime.InvokeAsync<string>("getUserAgent");
}
}
@@ -0,0 +1,41 @@
@inject ISearchService SearchService
@inject NavigationManager NavigationManager
@inject IJSRuntime JsRuntime
<button id="@Id" class="searchIconButtonContainer" @onclick="ButtonClicked">
<div class="searchIcon">
<i class="fa-solid fa-magnifying-glass"></i>
</div>
</button>
<style>
.searchIconButtonContainer {
border-radius: 8px;
font-weight: 800;
width: 100%;
padding: 5px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.searchIcon {
font-size: 28px;
text-align: center;
margin: auto;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public string Id { get; set; } = default!;
private void ButtonClicked(EventArgs eventArgs)
{
SearchService.Show();
}
}
@@ -0,0 +1,13 @@
<div class="divider">
</div>
<style>
.divider {
width: 100%;
border-bottom: 2px solid black;
margin-top: 6px;
margin-bottom: 6px;
height: 12px;
background-color: #545454;
}
</style>
@@ -0,0 +1,17 @@
<div class="columnContainer">
@ChildContent
</div>
<style>
.columnContainer {
display: flex;
flex-direction: column;
flex: 1;
}
</style>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,38 @@
<div class="lrg_container">
@ChildContent
</div>
<style>
.lrg_container {
display: flex;
gap: 16px;
flex-direction: column;
width: 75%;
min-width: 1000px;
margin-left: auto;
margin-right: auto;
padding-top: 10px;
padding-bottom: 30px;
}
@@media only screen and (max-width: 1025px) {
.lrg_container {
width: 100%;
min-width: 350px;
}
}
@@media only screen and (min-width: 1024px) {
.lrg_container {
margin-top: 50px;
}
}
</style>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,38 @@
<div class="med_container">
@ChildContent
</div>
<style>
.med_container {
display: flex;
gap: 16px;
flex-direction: column;
width: 50%;
min-width: 1000px;
margin-left: auto;
margin-right: auto;
padding-top: 10px;
padding-bottom: 30px;
}
@@media only screen and (max-width: 1025px) {
.med_container {
width: 99%;
min-width: 350px;
}
}
@@media only screen and (min-width: 1024px) {
.med_container {
margin-top: 50px;
}
}
</style>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,34 @@
<div class="med_container">
@ChildContent
</div>
<style>
.med_container {
display: flex;
gap: 16px;
flex-direction: column;
width: 50%;
margin-left: auto;
margin-right: auto;
padding-top: 10px;
padding-left: 20px;
padding-right: 20px;
padding-bottom: 30px;
border: 2px solid black;
}
@@media only screen and (max-width: 1025px) {
.med_container {
width: 85%;
}
}
</style>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,26 @@
<div class="rowContainer">
@ChildContent
</div>
<style>
.rowContainer {
display: flex;
flex-direction: row;
gap: 30px;
width: 100%;
flex-wrap: wrap;
}
@@media only screen and (max-width: 1025px) {
.rowContainer {
gap: 3px;
flex-direction: column;
}
}
</style>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,56 @@
<div class="layoutWithSidebar">
<div class="layoutSidebar">
@Sidebar
</div>
<div class="layoutContent">
@Content
</div>
</div>
<style scoped>
.layoutWithSidebar {
display: grid;
gap: 16px;
width: 65%;
min-width: 1000px;
margin-left: auto;
margin-right: auto;
padding-top: 10px;
padding-bottom: 30px;
grid-template-columns: 412px 1fr;
}
.layoutSidebar {
background: var(--paper);
padding: 16px;
}
.layoutContent {
}
@@media only screen and (max-width: 1025px) {
.layoutWithSidebar {
flex-direction: column-reverse;
width: 100%;
min-width: 350px;
}
}
@@media only screen and (min-width: 1024px) {
.layoutWithSidebar {
margin-top: 50px;
}
}
</style>
@code {
[Parameter] public RenderFragment Sidebar { get; set; } = default!;
[Parameter] public RenderFragment Content { get; set; } = default!;
}
@@ -0,0 +1,29 @@
<div class="content">
@ChildContent
</div>
<style>
.content {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 20px;
padding: 20px;
border: 2px solid black;
min-height: 50vh;
padding-bottom: 124px
}
@@media only screen and (max-width: 1025px) {
.content {
padding: 10px;
border: 0px solid black;
}
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
}
@@ -0,0 +1,26 @@
<h1 class="title">
@ChildContent
</h1>
<style>
.title {
padding-top: 35px;
padding-bottom: 5px;
font-size: 1.5rem;
font-weight: bolder;
}
@@media only screen and (max-width: 1025px) {
.title {
padding: 10px;
border: 0px solid black;
}
}
</style>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,17 @@
@using Markdig
@((MarkupString)MarkdownText)
@code {
[Inject] protected HttpClient Http { get; set; } = default!;
[Parameter] public string MarkdownFileName { get; set; } = default!;
private string MarkdownText { get; set; } = "";
protected override async Task OnInitializedAsync()
{
MarkdownText = Markdown.ToHtml(await Http.GetStringAsync($"markdown/{MarkdownFileName}.md"));
}
}
@@ -0,0 +1,200 @@
@inherits LayoutComponentBase
@inject INavigationService NavigationService
@implements IDisposable
@{
var visibleStyle = NavigationService.GetNavigationSectionId() > 0 ? "clickOffVisible" : "";
}
<div onclick="@MenuClosed" class="clickOffBackground @visibleStyle">
</div>
<div class="desktopNavContainer">
<div class="menuHeader">
<NavLink id="desktop-homeLink" href="/" class="websiteTitle">
<i class="fa-solid fa-house" style="margin-right: 4px;"></i> IGP Fan Reference
</NavLink>
<div class="sectionNavs">
@foreach (var webSection in WebSections)
{
var isSelected = NavigationService.GetNavigationSectionId().Equals(webSection.Id);
var sectionButtonStyle = "sectionButton";
if (isSelected)
{
sectionButtonStyle += " sectionButtonSelected";
}
<div class="sectionNav">
<button onclick="@(() => { MenuClicked(webSection.Id); })" class="@sectionButtonStyle">
<i class="fa-solid @webSection.Icon"></i>
@if (!webSection.OnlyIcon)
{
<span style="margin-left: 12px">@webSection.Name</span>
}
</button>
@if (isSelected)
{
<div class="navMenuPosition">
<div class="navMenuContainer">
<DesktopNavSectionComponent Section="@webSection"/>
</div>
</div>
}
</div>
}
</div>
<SearchButtonComponent Id="desktop-searchButton"/>
</div>
</div>
<style>
.clickOffBackground {
top: 0;
left: 0;
position: fixed;
width: 100vw;
height: 100vh;
visibility: hidden;
}
.clickOffBackground.clickOffVisible {
visibility: visible;
}
.sectionButton {
cursor: pointer;
padding: 12px;
position: relative;
z-index: 50000;
}
.desktopNavContainer {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 40px;
background-color: rgba(255, 255, 255, 0.1);
}
.menuHeader {
border-bottom: 4px solid black;
position: fixed;
width: 100%;
padding-top: 12px;
padding-bottom: 12px;
padding-left: 24px;
padding-right: 24px;
display: flex;
background-color: var(--accent);
justify-content: space-between;
align-items: center;
}
.sectionNavs {
display: flex;
gap: 16px;
align-items: center;
}
.sectionNav {
display: flex;
align-items: center;
height: 100%;
}
.websiteTitle {
font-weight: bold;
color: white;
margin-right: 32px;
}
.navMenuPosition {
position: relative;
top: 18px;
left: calc(-50% + -330px / 2);
width: 0;
height: 0;
}
.navMenuContainer {
width: 330px;
flex-direction: row;
gap: 20px;
align-items: flex-start;
justify-content: center;
flex-wrap: wrap;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 4px;
padding-right: 4px;
background-color: rgba(22, 22, 24, 0.95);
border: 1px solid black;
border-radius: 4px;
display: flex;
}
.sectionButtonSelected {
box-shadow: 0 2px 3px black;
background-color: var(--info);
transform: translateY(-1px) scale(1.08);
border-radius: 12px;
}
@@media only screen and (max-width: 1025px) {
.desktopNavContainer {
display: none;
}
}
@@media only screen and (max-width: 480px) {
.desktopNavContainer {
display: none;
}
}
</style>
@code {
[Parameter] public List<WebSectionModel> WebSections { get; set; } = default!;
[Parameter] public List<WebPageModel> WebPages { get; set; } = default!;
protected override void OnInitialized()
{
base.OnInitialized();
NavigationService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
NavigationService.Unsubscribe(StateHasChanged);
}
void MenuClicked(int menuName)
{
NavigationService.ChangeNavigationSectionId(menuName);
}
void MenuClosed()
{
NavigationService.ChangeNavigationSectionId(-1);
}
void HoverOut(MouseEventArgs mouseEventArgs)
{
NavigationService.ChangeNavigationState(NavigationStateType.Default);
}
}
@@ -0,0 +1,93 @@
@using Components.Utils
@inject INavigationService NavigationService;
@inject NavigationManager NavigationManager;
@if (isOnPage)
{
<NavLink href="@Page.Href" class="desktopNavLink navSelected">
<div class="navName">
@Page.Name
</div>
</NavLink>
}
else
{
<NavLink target="@Links.GetTarget(Page.Href)"
@onclick="() => { NavigationService.ChangeNavigationState(NavigationStateType.Default); NavigationService.ChangeNavigationSectionId(-1); }"
href="@Page.Href" class="desktopNavLink">
<div class="navName">
@Page.Name
</div>
</NavLink>
}
<style>
.navName {
margin: auto;
color: white;
font-size: 1.3rem;
font-weight: 600;
text-align: center;
}
.desktopNavLink {
cursor: pointer;
height: 60px;
width: 100%;
display: flex;
background-color: var(--primary);
border: 1px solid var(--primary);
}
.desktopNavLink:first-of-type {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.desktopNavLink:last-of-type {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.desktopNavLink:hover {
color: #8EC3FF;
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
}
.navSelected {
background-color: var(--primary-hover);
}
</style>
@code {
[Parameter] public WebPageModel Page { get; set; } = default!;
bool isOnPage;
protected override Task OnParametersSetAsync()
{
var uri = NavigationManager.Uri.Remove(0, NavigationManager.BaseUri.Count()).ToLower();
isOnPage = Page.Href.ToLower().Equals(uri);
return Task.CompletedTask;
}
void OnNavigationChanged()
{
StateHasChanged();
}
void OnBack()
{
NavigationService.Back();
}
}
@@ -0,0 +1,30 @@
<div class="sectionContainer">
@foreach (var childPage in Section.WebPageModels)
{
if (childPage.IsPrivate.Equals("True"))
{
continue;
}
<DesktopNavLinkComponent Page=childPage></DesktopNavLinkComponent>
}
</div>
<style>
.sectionContainer {
display: flex;
flex-direction: column;
gap: 4px;
justify-content: flex-start;
position: relative;
width: 100%;
}
</style>
@code {
[Parameter] public WebSectionModel Section { get; set; } = default!;
}
@@ -0,0 +1,186 @@
<div class="mobileFooter">
<div class="mobileNavSectionsContainer">
@foreach (var webSection in WebSections)
{
<div class="mobileNavSectionButton" @onclick="() => OnSectionClicked(webSection)"
@onclick:preventDefault="true" @onclick:stopPropagation="true">
<div class="mobileNavSectionButtonText">
<i class="fa-solid @webSection.Icon" style="font-size: 28px;"></i>
</div>
</div>
}
<SearchIconButtonComponent/>
</div>
<div class="fullPageButton @(selectedSection != null)" @onclick="OnPageClicked" @onclick:stopPropagation="false"
@onclick:preventDefault="false">
</div>
@if (selectedSection != null)
{
List<WebPageModel?> webPages = (from page in WebPages
where page.WebSectionModelId == selectedSection.Id
select page).ToList()!;
<div class="mobileNavPagesContainer">
@foreach (var webPage in webPages)
{
if (webPage!.IsPrivate.Equals("True"))
{
continue;
}
<div class="mobileNavPageButton" @onclick="() => OnPageLinkClicked(webPage)"
@onclick:preventDefault="true" @onclick:stopPropagation="true">
<div class="mobileNavPageButtonText">
@webPage.Name
</div>
</div>
}
</div>
}
</div>
<style>
.fullPageButton {
position: fixed;
width: 100vw;
height: 100vh;
bottom: 0;
display: none;
background-color: rgba(0, 0, 0, 0.6);
}
.fullPageButton.True {
display: block;
}
.mobileFooter {
position: fixed;
background-color: rgba(0, 0, 0, 1);
width: 100vw;
bottom: 0;
display: none;
}
.mobileNavPagesContainer {
position: fixed;
display: flex;
flex-direction: column;
bottom: 0;
width: 100vw;
background-color: black;
padding-bottom: 6px;
padding-top: 6px;
padding-left: 4px;
padding-right: 4px;
gap: 2px;
}
.mobileNavSectionsContainer {
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
width: 100%;
padding-bottom: 6px;
padding-top: 6px;
padding-left: 4px;
padding-right: 4px;
gap: 2px;
}
.mobileNavSectionButton {
border: 1px solid var(--primary);
width: 100%;
height: 64px;
border-radius: 2px;
display: flex;
background-color: var(--primary);
cursor: pointer;
}
.mobileNavPageButton {
border: 1px solid var(--primary);
width: 100%;
height: 64px;
display: flex;
background-color: var(--primary);
cursor: pointer;
}
.mobileNavPageButton:hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
}
.mobileNavSectionButton:hover {
background-color: var(--primary-hover);
border-color: var(--primary-border-hover);
}
.mobileNavSectionButtonText {
font-size: 0.75rem;
text-align: center;
margin: auto;
font-weight: bolder;
}
.mobileNavPageButtonText {
font-size: 1.1rem;
text-align: center;
margin: auto;
font-weight: bolder;
}
@@media only screen and (max-width: 480px) {
.mobileFooter {
display: block;
}
}
</style>
@code {
#if NO_SQL
[Parameter] public List<WebSectionModel> WebSections { get; set; } = default!;
[Parameter] public List<WebPageModel> WebPages { get; set; } = default!;
#else
[Parameter]
public DbSet<WebSectionModel> WebSections { get; set; }
[Parameter]
public DbSet<WebPageModel> WebPages { get; set; }
#endif
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
private WebSectionModel? selectedSection;
private WebPageModel? selectedPage;
void OnSectionClicked(WebSectionModel? webSection)
{
selectedSection = webSection;
}
void OnPageLinkClicked(WebPageModel? webPage)
{
selectedPage = webPage;
selectedSection = null;
NavigationManager.NavigateTo(webPage?.Href!);
}
void OnPageClicked(EventArgs eventArgs)
{
selectedPage = null;
selectedSection = null;
}
}
@@ -0,0 +1,184 @@
@inherits LayoutComponentBase
<div class="tablet">
<div class="tabletHeader" @onclick="OnNavClicked" @onclick:preventDefault="true" @onclick:stopPropagation="true">
<div class="tabletTitle">
IGP Fan Reference
</div>
<div class="tabletButtons">
<SearchButtonComponent/>
<div class="tabletButton">
<div class="tabletMenuTitle">
<i class="fa-solid fa-bars" style="font-size: 32px; margin:auto"></i>
</div>
</div>
</div>
</div>
<div class="fullPageButton @navOpen" @onclick="OnNavClicked" @onclick:stopPropagation="false"
@onclick:preventDefault="false">
</div>
<div class="tabletNav @navOpen">
@foreach (var webSection in WebSections)
{
var pages = (from page in WebPages
where page.WebSectionModelId == webSection.Id
select page).ToList();
<div>
<div>
@webSection.Name
</div>
<div class="tabletNavItems">
@foreach (var webPage in pages)
{
if (webPage.IsPrivate.Equals("True"))
{
continue;
}
<NavLink href="@webPage.Href" class="tabletNavItem" @onclick="OnPageClicked">
@webPage.Name
</NavLink>
}
</div>
</div>
}
</div>
</div>
<style>
.fullPageButton {
position: fixed;
width: 100vw;
height: 100vh;
bottom: 0;
display: none;
background-color: rgba(0, 0, 0, 0.6);
}
.fullPageButton.True {
display: block;
}
.tablet {
display: none;
}
.tabletNav {
position: fixed;
background-color: rgba(30, 30, 30, 0.98);
display: none;
height: 100vh;
padding: 32px;
overflow-y: auto;
overflow-x: hidden;
top: 0px;
gap: 22px;
flex-direction: column;
}
.tabletButtons {
display: flex;
gap: 12px;
}
.tabletNavItem {
padding: 8px;
}
.tabletNavItems {
display: flex;
flex-direction: column;
}
.tabletNav.True {
display: flex;
}
.tabletHeader {
height: 60px;
width: 100vw;
position: fixed;
top: 0;
display: flex;
background-color: var(--accent);
border-bottom: 4px solid rgba(0, 0, 0, 0.95);
justify-content: space-between;
}
.tabletMenuTitle {
margin: auto;
font-weight: 700;
}
.tabletTitle {
margin: auto;
font-weight: 700;
font-size: 1.4rem;
}
.tabletButton {
border: 2px solid black;
background-color: rgba(0, 0, 0, 0.3);
width: 80px;
height: 100%;
display: flex;
cursor: pointer;
}
.tabletButton:hover {
background-color: rgba(0, 0, 0, 0.7);
}
@@media only screen and (max-width: 1025px) {
.tablet {
display: block;
}
}
@@media only screen and (max-width: 480px) {
.tablet {
display: none;
}
}
</style>
@code {
#if NO_SQL
[Parameter] public List<WebSectionModel> WebSections { get; set; } = default!;
[Parameter] public List<WebPageModel> WebPages { get; set; } = default!;
#else
[Parameter]
public DbSet<WebSectionModel> WebSections { get; set; }
[Parameter]
public DbSet<WebPageModel> WebPages { get; set; }
#endif
bool navOpen = true;
void OnNavClicked(EventArgs eventArgs)
{
navOpen = !navOpen;
}
void OnPageClicked(EventArgs eventArgs)
{
navOpen = false;
}
}
@@ -0,0 +1,21 @@
@if (isDisplayable)
{
@ChildContent
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Parameter] public WebDeploymentType DeploymentType { get; set; }
[Inject] public NavigationManager MyNavigationManager { get; set; } = default!;
bool isDisplayable;
protected override void OnInitialized()
{
isDisplayable = DeploymentType == WebDeploymentModel.DeploymentType;
}
}
@@ -0,0 +1,27 @@
@if (isDisplayable)
{
@ChildContent
}
else
{
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
}
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Inject] public NavigationManager MyNavigationManager { get; set; } = default!;
bool isDisplayable;
protected override void OnInitialized()
{
base.OnInitialized();
var page = MyNavigationManager.Uri.Remove(0, MyNavigationManager.BaseUri.Length);
isDisplayable = WebDeploymentModel.Get().Contains(page);
}
}
@@ -0,0 +1,64 @@
<div class="footerContainer" xmlns="http://www.w3.org/1999/html">
<div class="footerDivider"></div>
<div class="footerDisclaimer">
This website is fan-made and not affiliated with <b>SunSpear Games</b> in any way.
</div>
</div>
<style>
.footerIcon {
font-size: 24px;
padding: 8px;
background-color: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
line-height: 0;
}
.footerIcon:hover {
color: #8fc5ff;
background-color: rgba(200, 200, 255, 0.1);
border: 2px solid rgba(140, 140, 255, 0.4);
}
.footerContainer {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding-top: 8px;
padding-bottom: 102px;
gap: 24px;
}
.footerSocials {
display: flex;
flex-direction: row;
gap: 16px;
justify-content: center;
margin: auto;
}
.footerDivider {
background-color: rgba(255, 255, 255, 0.12);
height: 1px;
width: 128px;
margin: auto;
}
.footerLastUpdated {
text-align: center;
margin: auto;
}
.footerDisclaimer {
text-align: center;
margin: auto;
margin-top: 12px;
}
</style>
@@ -0,0 +1,4 @@
@inherits LayoutComponentBase
@Body
@@ -0,0 +1,17 @@
<div style="display:inline; width: 12px;">
<div style="height: 0px; display:flex; flex-direction:column; align-items:center;">
<div style="height: 0px;">
</div>
<div style="height: 0px;position: relative; top: -6px; left:0px; white-space:pre">@Dividee</div>
<div style="height: 0px; position: relative; top: 6px; left:0px; white-space:pre">@Divider</div>
</div>
</div>
@code {
[Parameter] public RenderFragment Dividee { get; set; } = default!;
[Parameter] public RenderFragment Divider { get; set; } = default!;
}
@@ -0,0 +1,22 @@
<div
style="display:flex; flex-direction:column; align-items:center;padding-right: 12px;padding-left: 4px; font-family:monospace">
<div style="height: 0px;display:flex; flex-direction:row; ">
<div style="font-size: 18px; height: 0px;">
&#8721;
</div>
<div style="position:relative; left: 2px; top: 1px;">@IndexSymbol</div>
</div>
<div style="height: 0px;position: relative; top: -18px; left:0px; font-size: 80%; display:flex;">@LoopEnd</div>
<div style="height: 0px; position: relative; top: 22px; left:0px; font-size: 80%; display:flex;">@LoopStart</div>
</div>
@code {
[Parameter] public RenderFragment LoopEnd { get; set; } = default!;
[Parameter] public RenderFragment LoopStart { get; set; } = default!;
[Parameter] public RenderFragment IndexSymbol { get; set; } = default!;
}
@@ -0,0 +1,27 @@
<span class="spoiler">
<u>@ChildContent</u>
</span>
<style>
.spoiler {
color: black;
background-color: black;
}
u {
text-decoration-color: inherit;
text-decoration-thickness: 1px;
}
.spoiler:hover {
color: inherit;
background-color: inherit;
}
</style>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
}
+11
View File
@@ -0,0 +1,11 @@
namespace Components.Utils;
public static class Links
{
public static string GetTarget(string link)
{
if (link.StartsWith("https://")) return "_blank";
return "_self";
}
}
+18
View File
@@ -0,0 +1,18 @@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Model.Feedback
@using Model.Website
@using Model.Website.Enums
@using Services
@using System.Net.Http
@using System.Net.Http.Json
@using System.Text
@using System.Text.Json
@using System.Threading.Tasks;
@using System.Timers;
@using System;
@using YamlDotNet.Serialization
@using Components.Inputs;
+91
View File
@@ -0,0 +1,91 @@
@inject IStorageService StorageService
@inject IPermissionService PermissionService
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
@if (isLoaded)
{
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
}
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
<EntityDialogPortal/>
<ToastPortal/>
<SearchPortal/>
<ConfirmationDialogPortal/>
@if (PermissionService.GetIsDataCollectionEnabled())
{
<NavigationTracker/>
}
<style>
a {
color: white;
font-weight: 700;
}
a:hover {
color: white;
text-decoration: underline;
text-decoration-color: #8fc5ff;
text-decoration-thickness: 3px;
}
:root {
--severity-warning-color: #2a2000;
--severity-warning-border-color: #755c13;
--severity-error-color: #290102;
--severity-error-border-color: #4C2C33;
--severity-information-color: #030129;
--severity-information-border-color: #2c3a4c;
--severity-success-color: #042901;
--severity-success-border-color: #2E4C2C;
--accent: #432462;
--primary: #4308a3;
--primary-border: #2c0b62;
--primary-hover: #5e00f7;
--primary-border-hover: #a168ff;
--background: #161618;
--secondary: #23133e;
--secondary-hover: #2a0070;
--secondary-border-hover: #a168ff;
--paper: #252526;
--paper-border: #151516;
--paper-hover: #52366f;
--paper-border-hover: #653497;
--info: #451376;
--info-border: #210b36;
--dialog-border-color: black;
--dialog-border-width: 2px;
--dialog-radius: 6px;
}
</style>
@code {
private bool isLoaded;
protected override async Task OnInitializedAsync()
{
await StorageService.Load();
isLoaded = true;
StateHasChanged();
}
}
@@ -0,0 +1,116 @@
@implements IDisposable;
@inject IMyDialogService MyDialogService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@if (MyDialogService.IsVisible)
{
<div class="confirmDialogBackground" onclick="@CloseDialog">
<div class="confirmDialogContainer"
@onclick:preventDefault="true"
@onclick:stopPropagation="true">
<div class="confirmDialogHeader">
@MyDialogService.GetDialogContents().Title
</div>
<div class="confirmDialogBody">
@MyDialogService.GetDialogContents().Message
</div>
<div class="confirmDialogFooter">
<ButtonComponent MyButtonType="MyButtonType.Secondary"
OnClick="MyDialogService.GetDialogContents().OnCancel">
Cancel
</ButtonComponent>
<ButtonComponent MyButtonType="MyButtonType.Primary"
OnClick="MyDialogService.GetDialogContents().OnConfirm">
@MyDialogService.GetDialogContents().ConfirmButtonLabel
</ButtonComponent>
</div>
</div>
</div>
<style>
.pageContents * {
filter: blur(2px);
}
.confirmDialogBackground {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
}
.confirmDialogContainer {
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);
padding: 8px;
box-shadow: 1px 2px 2px black;
display: flex;
flex-direction: column;
}
.confirmDialogHeader {
font-size: 1.4em;
padding: 12px;
}
.confirmDialogBody {
padding: 12px;
flex-grow: 1;
}
.confirmDialogFooter {
display: flex;
gap: 12px;
justify-content: flex-end;
padding: 12px;
}
</style>
}
@code {
protected override void OnInitialized()
{
base.OnInitialized();
MyDialogService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
MyDialogService.Unsubscribe(StateHasChanged);
}
public void CloseDialog()
{
MyDialogService.Hide();
}
}
@@ -0,0 +1,171 @@
@implements IDisposable;
@inject IEntityDialogService entityDialogService
<div class="dialogBackground" onclick="@CloseDialog">
<div class="dialogContainer"
@onclick:preventDefault="true"
@onclick:stopPropagation="true">
@if (entity == null)
{
<div>Entity is null</div>
}
else
{
<div class="dialogHeader">
@if (entityDialogService.HasHistory())
{
<button class="backButton" @onclick="entityDialogService.BackDialog">
<div class="backButtonIcon"></div>
</button>
}
<div class="dialogTitle">
@entity.Info().Name
</div>
</div>
<div class="dialogContent">
<CascadingValue Value="@entity">
<EntityVanguardAddedComponent/>
<EntityInfoComponent/>
<EntityVanguardsComponent/>
<EntityProductionComponent/>
<EntityStatsComponent/>
<EntityMechanicsComponent/>
<EntityPassivesComponent/>
<EntityPyreSpellsComponent/>
<EntityUpgradesComponent/>
<EntityWeaponsComponent/>
<EntityAbilitiesComponent/>
</CascadingValue>
</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);
}
</style>
@code {
EntityModel entity = default!;
private int refresh;
protected override void OnInitialized()
{
base.OnInitialized();
entity = EntityData.Get()[entityDialogService.GetEntityId() ?? string.Empty];
entityDialogService.Subscribe(OnUpdate);
}
void IDisposable.Dispose()
{
entityDialogService.Unsubscribe(OnUpdate);
}
void OnUpdate()
{
entity = EntityData.Get()[entityDialogService.GetEntityId()];
refresh++;
StateHasChanged();
}
public void CloseDialog()
{
entityDialogService.CloseDialog();
}
}
@@ -0,0 +1 @@

@@ -0,0 +1,211 @@
@implements IDisposable;
@inject ISearchService searchService
@inject IJSRuntime jsRuntime
@inject NavigationManager navigationManager
@if (searchService.IsLoaded() && searchService.IsVisible)
{
<div id="searchBackground" class="searchBackground" onclick="@CloseDialog">
<div class="searchContainer"
@onclick:preventDefault="true"
@onclick:stopPropagation="true">
<FormLayoutComponent>
<FormTextComponent OnFocus="OnFocus" Id="searchInput" Placeholder="Search..."
OnInput="SearchChanged"></FormTextComponent>
</FormLayoutComponent>
<div class="searchBox">
@if (SearchText.Length > 0)
{
foreach (var searchSection in searchService.Searches)
{
var searchPoints = searchSection.Value.FindAll(x => x.Title.ToLower().Contains(SearchText.ToLower()));
@if (searchPoints.Count > 0)
{
<div>
<div class="searchSectionTitle">
@searchSection.Key
</div>
<div class="searchContents">
@foreach (var searchPoint in searchPoints)
{
<div>
<button class="searchLink @searchPoint.PointType.ToLower()"
label="@searchPoint.Title"
@onclick="() => OnSearch(searchPoint)">
@searchPoint.Title
</button>
@if (!searchPoint.Summary.Trim().Equals(""))
{
<i> - @searchPoint.Summary</i>
}
</div>
}
</div>
</div>
}
}
}
</div>
</div>
</div>
<style>
.pageContents * {
filter: blur(2px);
}
.searchBackground {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
}
.searchBox {
padding: 12px;
overflow-y: scroll;
overflow-x: hidden;
height: 530px;
border: 1px solid black;
border-radius: 2px;
}
.searchContents {
display: flex;
flex-direction: column;
gap: 6px;
align-items: flex-start;
padding: 12px;
}
.searchSectionTitle {
font-weight: bolder;
}
.searchContainer {
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);
padding: 8px;
box-shadow: 1px 2px 2px black;
}
.searchLink {
text-decoration: underline;
}
@@media only screen and (max-width: 1025px) {
.searchContainer {
height: 300px;
}
.searchBox {
height: 230px;
}
}
</style>
}
@code {
private ElementReference searchBox;
private string SearchText { get; set; } = "";
protected override void OnInitialized()
{
searchService.Subscribe(OnSearchChanged);
timer = new Timer(200);
timer.Elapsed += FocusTimer;
timer.Enabled = false;
}
private void FocusTimer(object? sender, ElapsedEventArgs e)
{
jsRuntime.InvokeVoidAsync("SetFocusToElement", "searchInput");
StateHasChanged();
}
private Timer timer = null!;
private void OnSearchChanged()
{
if (timer.Enabled != searchService.IsVisible)
{
timer.Enabled = searchService.IsVisible;
}
StateHasChanged();
}
public void Dispose()
{
searchService.Unsubscribe(OnSearchChanged);
timer.Elapsed -= FocusTimer;
}
public void CloseDialog()
{
searchService.Hide();
}
public void NavigateTo(string url)
{
if (url.Contains("#"))
{
navigationManager.NavigateTo(url,
navigationManager.Uri.Split("#").First().Contains(url.Split("#").First()));
}
else
{
navigationManager.NavigateTo(url);
}
}
private void SearchChanged(ChangeEventArgs obj)
{
SearchText = obj.Value!.ToString()!;
}
private void OnSearch(SearchPointModel searchPoint)
{
NavigateTo(searchPoint.Href);
searchService.Hide();
}
private void OnFocus(object obj)
{
timer.Enabled = false;
}
}
+60
View File
@@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<LangVersion>12</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE;NO_SQL</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
</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" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
<PackageReference Include="MudBlazor" Version="9.2.0" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Components\Components.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\generated" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localizations.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Localizations.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Localizations.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Example.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>
+49
View File
@@ -0,0 +1,49 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IGP", "IGP.csproj", "{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "..\Model\Model.csproj", "{77395F7A-BE93-470C-9F10-F48FFA445B63}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components", "..\Components\Components.csproj", "{0419E7CD-0971-4A56-A61F-C090DF60FAF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "..\Services\Services.csproj", "{621178C8-4E8B-478E-80E5-7478F0E7B67E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAutomation", "..\TestAutomation\TestAutomation.csproj", "{8B49D038-D013-460D-9C4F-817CAFFEB06F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.Build.0 = Release|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.Build.0 = Release|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.Build.0 = Release|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.Build.0 = Release|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {19100811-B909-4D20-9AE0-2EB579A55B3A}
EndGlobalSection
EndGlobal
+5
View File
@@ -0,0 +1,5 @@
@page "/"
@layout PageLayout
<HomePage/>
+114
View File
@@ -0,0 +1,114 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace IGP {
using System;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Localizations {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Localizations() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("IGP.Localizations", typeof(Localizations).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string Greeting {
get {
return ResourceManager.GetString("Greeting", resourceCulture);
}
}
internal static string Tooltip_Chart_Info {
get {
return ResourceManager.GetString("Tooltip Chart Info", resourceCulture);
}
}
internal static string Tooltip_Filter_Info {
get {
return ResourceManager.GetString("Tooltip Filter Info", resourceCulture);
}
}
internal static string Tooltip_Entity_Info {
get {
return ResourceManager.GetString("Tooltip Entity Info", resourceCulture);
}
}
internal static string Tooltip_Bank_Info {
get {
return ResourceManager.GetString("Tooltip Bank Info", resourceCulture);
}
}
internal static string Tooltip_Army_Info {
get {
return ResourceManager.GetString("Tooltip Army Info", resourceCulture);
}
}
internal static string Tooltip_Highlights_Info {
get {
return ResourceManager.GetString("Tooltip Highlights Info", resourceCulture);
}
}
internal static string Tooltip_BuildOrder_Info {
get {
return ResourceManager.GetString("Tooltip BuildOrder Info", resourceCulture);
}
}
internal static string Tooltip_Timing_Info {
get {
return ResourceManager.GetString("Tooltip Timing Info", resourceCulture);
}
}
internal static string Tooltip_Hotkey_Info {
get {
return ResourceManager.GetString("Tooltip Hotkey Info", resourceCulture);
}
}
internal static string Tooltip_Options_Info {
get {
return ResourceManager.GetString("Tooltip Options Info", resourceCulture);
}
}
}
}
View File
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="Greeting" xml:space="preserve">
<value>Hello</value>
</data>
<data name="Tooltip Chart Info" xml:space="preserve">
<value>Shows economy at each game interval. Use to determine if spending additional resources on harvesters will help or hinder overall timing attack.</value>
</data>
<data name="Tooltip Filter Info" xml:space="preserve">
<value>Select build details, such as Faction and Immortal.
Affects entities you can build.</value>
</data>
<data name="Tooltip Entity Info" xml:space="preserve">
<value>Summary of the entity you just selected.</value>
</data>
<data name="Tooltip Bank Info" xml:space="preserve">
<value>Bank at time of last requested action. Use this section to determine if your build is floating too much alloy or ether.</value>
</data>
<data name="Tooltip Army Info" xml:space="preserve">
<value>Overview of current army, and when it will be ready to begin an attack.</value>
</data>
<data name="Tooltip Highlights Info" xml:space="preserve">
<value>Timeline highlights of your build order. Shows when you start a new action and when the action is done.</value>
</data>
<data name="Tooltip BuildOrder Info" xml:space="preserve">
<value>Some raw JSON data to represent your build order.</value>
</data>
<data name="Tooltip Timing Info" xml:space="preserve">
<value>Enter build details.</value>
</data>
<data name="Tooltip Hotkey Info" xml:space="preserve">
<value>Click on the desired entity to build it. &lt;i&gt;You cannot build entities you cannot afford, construct an ether extractor before spending ether.&lt;/i&gt;
You can also use the default Immortal hotkeys, but the above hotkey UI must have focus for this to work. &lt;i&gt;I.e. click on it if hotkeys aren't working, and a white border should appear after key input to indicate focus.&lt;/i&gt;
Additionally, more entities will appear as you build the required technology. You can click or press ` to remove the last made entity. &lt;i&gt;But you cannot remove the starting entities at interval 0.&lt;/i&gt;</value>
</data>
<data name="Tooltip Options Info" xml:space="preserve">
<value>Misc calculator controls.</value>
</data>
</root>
+143
View File
@@ -0,0 +1,143 @@
@inherits LayoutComponentBase
@inject ISearchService SearchService
@inject IDataCollectionService DataCollectionService
@inject NavigationManager NavigationManager
@using Model.Website.Data
@using Services.Website
@implements IDisposable
<MudThemeProvider @ref="@_mudThemeProvider" @bind-IsDarkMode="@_isDarkMode"/>
<MudPopoverProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<MudLayout>
<MudAppBar Elevation="1">
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert="true">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start"
OnClick="@(e => DrawerToggle())"/>
</MudHidden>
<MudButton Class="ml-3 mr-8"
Href="/"
Target="_self"
Variant="Variant.Text"
Color="Color.Default">
<MudText Typo="Typo.h5"> IGP Fan Reference</MudText>
</MudButton>
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
@foreach (var page in WebsiteData.GetPages())
{
<MudButton Href="@(page.Href)"
Variant="Variant.Text"
Color="Color.Default"
Class="mr-4">
<MudIcon Icon="@(page.Icon)" Class="mr-2"/>
@(page.Name)
</MudButton>
}
</MudHidden>
<MudSpacer/>
<SearchButtonComponent Id="desktop-searchButton"/>
</MudAppBar>
<MudHidden Breakpoint="Breakpoint.SmAndDown" Invert="true">
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<MudPaper Width="250px" Class="d-inline-flex py-3" Elevation="0">
<MudNavMenu Class="mud-width-full flex-grow-1">
@foreach (var page in WebsiteData.GetPages())
{
<MudNavLink Href="@(page.Href)" Icon="@(page.Icon)">@(page.Name)</MudNavLink>
}
</MudNavMenu>
</MudPaper>
</MudDrawer>
</MudHidden>
<MudMainContent>
<MudContainer Class="px-8" MaxWidth="MaxWidth.False">
<div id="content" class="content">
@Body
</div>
</MudContainer>
</MudMainContent>
</MudLayout>
<FooterComponent></FooterComponent>
@code {
private bool _isDarkMode = true;
private MudThemeProvider _mudThemeProvider;
bool _drawerOpen = true;
void DrawerToggle()
{
_drawerOpen = !_drawerOpen;
}
protected override void OnInitialized()
{
base.OnInitialized();
CollectFirstPageLoaded();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//TODO: Support light mode
//_isDarkMode = await _mudThemeProvider.GetSystemPreference();
//StateHasChanged();
}
}
private void CollectFirstPageLoaded()
{
var skipBaseUri = NavigationManager.Uri.Substring(NavigationManager.BaseUri.Length,
NavigationManager.Uri.Length - NavigationManager.BaseUri.Length);
var rootUrl = skipBaseUri.Split("/").First();
if (rootUrl.Trim().Equals(""))
{
rootUrl = "home";
}
DataCollectionService.SendEvent(DataCollectionKeys.FirstPage,
new Dictionary<string, string> { { "page", rootUrl } });
}
protected override async Task OnInitializedAsync()
{
await Focus();
}
private async Task Focus()
{
// await jsRuntime.InvokeVoidAsync("SetFocusToElement", pageContents);
}
protected override async void OnAfterRender(bool firstRender)
{
// await jsRuntime.InvokeVoidAsync("SetFocusToElement", pageContents);
}
void IDisposable.Dispose()
{
}
void HasChanged()
{
StateHasChanged();
}
private void HandleKeyDown(KeyboardEventArgs keyboardEventArgs)
{
if ((keyboardEventArgs.CtrlKey || keyboardEventArgs.MetaKey) && keyboardEventArgs.Key.ToLower() == "k")
{
SearchService.Show();
}
}
}
+13
View File
@@ -0,0 +1,13 @@
.layoutContainer {
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
}
.content {
margin-bottom: 64px;
display: flex;
min-width: 100%;
min-height: 100%;
flex-direction: column;
}
+52
View File
@@ -0,0 +1,52 @@
@layout PageLayout
@inherits BasePage
@inject IDataCollectionService DataCollectionService
@page "/about"
<LayoutMediumContentComponent>
<WebsiteTitleComponent>About</WebsiteTitleComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this website for?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is just a "yet another third-party tool" website for a video game. If you played a game like Path
of Exile, you are probably already used to seeing a bunch of said tools.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
So what is <i>this</i> specific tool for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Ideally, this website will be a casual reference, for getting started with understanding the themes and
game patterns of IMMORTAL: Gates of Pyre. That said, this tool is currently not near to achieving said
goal. In the meantime, you can check out the simple calculator and database tools on this website.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
The game hasn't even been released. Isn't it a bit early for publishing community tools?
</InfoQuestionComponent>
<InfoAnswerComponent>
Maybe 🙂
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Any disclaimers?
</InfoQuestionComponent>
<InfoAnswerComponent>
This website has no association with "SunSpear Games." Beyond that, any game data displayed on this
website for "IMMORTAL: Gates of Pyre" may be inaccurate due to my own human error and time limitations.
Use with caution.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
+37
View File
@@ -0,0 +1,37 @@
@using Services.Website
@inject IDataCollectionService DataCollectionService
@inject NavigationManager NavigationManager
@code {
protected override void OnInitialized()
{
base.OnInitialized();
CollectLoadedPage();
}
private void CollectLoadedPage()
{
var skipBaseUri = NavigationManager.Uri.Substring(NavigationManager.BaseUri.Length,
NavigationManager.Uri.Length - NavigationManager.BaseUri.Length);
var splitData = skipBaseUri.Split("/");
var rootUrl = splitData.First();
if (rootUrl.Trim().Equals(""))
{
rootUrl = "home";
}
var eventData = new Dictionary<string, string> { { "page", rootUrl } };
if (splitData.Length > 1)
{
eventData["inner-page"] = splitData.Last();
}
DataCollectionService.SendEvent(DataCollectionKeys.PageInitialized, eventData);
}
}
@@ -0,0 +1,305 @@
@layout PageLayout
@inherits BasePage
@inject IStringLocalizer<Localizations> Locale
@inject IKeyService KeyService
@inject IImmortalSelectionService FilterService
@inject IBuildOrderService BuildOrderService
@inject IEconomyService EconomyService
@inject IToastService ToastService
@inject ITimingService TimingService
@inject IDataCollectionService DataCollectionService
@page "/build-calculator"
@using Services.Website
@implements IDisposable
<LayoutLargeContentComponent>
<WebsiteTitleComponent>Build Calculator</WebsiteTitleComponent>
<AlertComponent Type="@SeverityType.Warning">
<Title>Work In Progress and Not Fully Tested</Title>
<Message>
Build Calculator hasn't been thoroughly tested. Bugs and inaccurate results assumed.
<br/>
Currently not considering running out of alloy and ether to harvest.
<br/>
<br/>
Build Calculator was built based on a much older version of the game and was only quickly modified for the
June 2025 Playtest version, so the above disclaimer is only more true.
<br/>
Expect even more oddities and invalid data then the above warning implies.
</Message>
</AlertComponent>
<ContentDividerComponent></ContentDividerComponent>
<div class="calculatorGrid">
<div class="gridItem" style="grid-area: timing;">
<ButtonComponent MyButtonType="MyButtonType.Secondary" OnClick="OnResetClicked">Clear Build Order
</ButtonComponent>
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Timing Info"]">
<TimingComponent></TimingComponent>
</InfoTooltipComponent>
</PanelComponent>
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Filter Info"]">
<FilterComponent></FilterComponent>
</InfoTooltipComponent>
</PanelComponent>
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Options Info"]">
<OptionsComponent></OptionsComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: chart;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Chart Info"]">
<BuildChartComponent></BuildChartComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: view;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Entity Info"]">
<EntityClickViewComponent/>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: bank;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Bank Info"]">
<BankComponent></BankComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: army;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Army Info"]">
<ArmyComponent></ArmyComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem gridKeys">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Hotkey Info"]">
<HotkeyViewerComponent Size="80"></HotkeyViewerComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: highlights;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip Highlights Info"]">
<HighlightsComponent></HighlightsComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
<div class="gridItem" style="grid-area: buildorder;">
<PanelComponent>
<InfoTooltipComponent InfoText="@Locale["Tooltip BuildOrder Info"]">
<BuildOrderComponent></BuildOrderComponent>
</InfoTooltipComponent>
</PanelComponent>
</div>
</div>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<FormLayoutComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is a calculator to determine build timings. Mostly so someone can quickly try out a few build
orders to see if they somewhat make sense.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
How does it work?
</InfoQuestionComponent>
<InfoAnswerComponent>
The tool calculates every second of game time. So if you attempt to build a <b>Legion Hall</b> as
your first action, the tool will scan every second, until you get to one where the request can be
made. In this case, that is interval 58.
<br/>
<br/>
If you then build 2 <b>Apostle of Bindings</b> a <b>Soul Foundry</b> and a 3 <b>Absolvers</b> you
should see yourself roughly floating 500 alloy, with barely having any ether. Which means you could
of gotten an <b>Acropolis</b> and a <b>Zentari</b> without hurting your build.
<br/>
<br/>
Try building <b>Apostle of Bindings</b> before the <b>Legion Hall</b> and see how that changes the
timing of your 3 <b>Absolvers</b>. (Spoiler:
<SpoilerTextComponent> your <b>Absolvers</b> will be built much faster, and you won't be floating so
much alloy.
</SpoilerTextComponent>
)
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is CONTROL key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Economy and tech related upgrades for townhalls.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is SHIFT key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
Misc building related upgrades. (Omnivores)
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is 2 key for?
</InfoQuestionComponent>
<InfoAnswerComponent>
It will be for Pyre camps. Currently not implemented.
</InfoAnswerComponent>
</InfoBodyComponent>
</FormLayoutComponent>
</PaperComponent>
</LayoutLargeContentComponent>
<style>
.gridItem {
display: flex;
flex-direction: column;
gap: 8px;
}
.calculatorGrid {
display: grid;
gap: 8px;
max-width: 90vw;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 600px 400px 450px;
grid-template-areas:
'timing view view view'
'timing bank army army'
'keys keys highlights buildorder'
'chart chart chart chart';
}
.gridKeys {
grid-area: keys;
}
@@media only screen and (max-width: 1025px) {
.gridKeys {
background-color: #282A30;
}
.calculatorGrid {
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-template-areas:
'timing'
'view'
'keys'
'bank'
'army'
'highlights'
'buildorder'
'chart';
padding-left: 2px;
padding-right: 2px;
}
}
</style>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
EconomyService.Calculate(BuildOrderService, TimingService, 0);
KeyService.Subscribe(HandleClick);
DataCollectionService.SendEvent(
DataCollectionKeys.PageInitialized,
new Dictionary<string, string> { { "page", "build-calculator" } }
);
}
void IDisposable.Dispose()
{
KeyService.Unsubscribe(HandleClick);
}
private void OnResetClicked()
{
ToastService.AddToast(new ToastModel
{
SeverityType = SeverityType.Success,
Message = "Build order has been cleared.",
Title = "Reset"
});
BuildOrderService.Reset();
}
private void HandleClick()
{
var hotkey = KeyService.GetHotkey();
if (hotkey == "")
{
return;
}
if (hotkey == "`")
{
BuildOrderService.RemoveLast();
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
return;
}
var hotkeyGroup = KeyService.GetHotkeyGroup();
var isHoldSpace = KeyService.IsHoldingSpace();
var faction = FilterService.GetFaction();
var immortal = FilterService.GetImmortal();
var entity = EntityModel.GetFrom(hotkey!, hotkeyGroup, isHoldSpace, faction, immortal);
if (entity == null)
{
return;
}
if (BuildOrderService.Add(entity, EconomyService))
{
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
}
}
@@ -0,0 +1,156 @@
@inject IJSRuntime jsRuntime
@inject IBuildOrderService buildOrder
@inject ITimingService timingService
@implements IDisposable
<div class="armyView">
<FormLayoutComponent>
<div style="display: flex; gap: 24px;">
<FormDisplayComponent Label="Army Completed At">
<Display>@lastInterval | T @Interval.ToTime(lastInterval)</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Army Attacking At">
<Display>@(lastInterval + timingService.GetTravelTime()) |
T @Interval.ToTime(lastInterval + timingService.GetTravelTime())</Display>
</FormDisplayComponent>
</div>
<FormDisplayComponent Label="Army units built">
<Display>
<div class="armyCardsContainer">
@foreach (var unit in armyCount)
{
<div class="armyCard">
<div class="armyCountPosition">
<div class="armyCount">@unit.Value.ToString()x</div>
</div>
<div>@unit.Key</div>
</div>
}
</div>
</Display>
</FormDisplayComponent>
</FormLayoutComponent>
</div>
<style>
.armyView {
overflow-y: scroll;
width: 100%;
overflow-x: hidden;
height: 350px;
}
.armyCardsContainer {
display: flex;
width: 100%;
gap: 16px;
flex-wrap: wrap;
}
.armyCard {
width: 100px;
height: 80px;
padding: 16px;
}
.armyCountPosition {
height: 0;
top: -20px;
left: -16px;
position: relative;
}
.armyCount {
font-weight: bolder;
}
</style>
@code {
private int lastInterval;
readonly Dictionary<string, int> armyCount = new();
List<EntityModel> army = new();
protected override void OnInitialized()
{
base.OnInitialized();
buildOrder.Subscribe(OnBuildOrderChanged);
timingService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
buildOrder.Unsubscribe(OnBuildOrderChanged);
timingService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "ArmyComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "ArmyComponent");
#endif
}
void OnBuildOrderChanged()
{
var armyCountWas = 0;
foreach (var army in armyCount)
{
armyCountWas += army.Value;
}
armyCount.Clear();
lastInterval = 0;
var entitiesOverTime = buildOrder.GetOrders();
foreach (var entitiesAtTime in entitiesOverTime)
{
foreach (var entity in entitiesAtTime.Value)
{
if (entity.EntityType == EntityType.Army)
{
if (!armyCount.TryAdd(entity.Info().Name, 1))
{
armyCount[entity.Info().Name]++;
}
if (entity.Production() != null && entity.Production().BuildTime + entitiesAtTime.Key > lastInterval)
{
lastInterval = entity.Production().BuildTime + entitiesAtTime.Key;
}
}
}
}
//TODO Better
var armyCountIs = 0;
foreach (var army in armyCount)
{
armyCountIs += army.Value;
}
if (armyCountWas != armyCountIs)
{
StateHasChanged();
}
}
}
@@ -0,0 +1,134 @@
@inject IJSRuntime jsRuntime;
@inject IEconomyService economyService
@implements IDisposable
<div class="bankContainer">
<FormDisplayComponent Label="Time">
<Display>@(BuildOrderService.GetLastRequestInterval() + 1) |
T @Interval.ToTime(BuildOrderService.GetLastRequestInterval() + 1)</Display>
</FormDisplayComponent>
<div class="bankRow">
<FormDisplayComponent Label="Alloy">
<Display>@_economy.Alloy +@_economy.AlloyIncome</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Ether">
<Display>@Math.Round(_economy.Ether) +@Math.Round(_economy.EtherIncome)</Display>
</FormDisplayComponent>
</div>
<div class="bankRow">
<FormDisplayComponent Label="Pyre">
<Display>@_economy.Pyre</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Supply">
<Display>@_supplyTaken / @_supplyGranted (@(_supplyGranted / 16)@(_extraBuildings > 0 ? "+" + _extraBuildings : ""))</Display>
</FormDisplayComponent>
</div>
<div>
<div class="workerText">Workers</div>
<div class="bankRow">
<FormDisplayComponent Label="Current">
<Display>@_economy.WorkerCount</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Busy">
<Display>@_economy.BusyWorkerCount</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Creating">
<Display>@_economy.CreatingWorkerCount</Display>
</FormDisplayComponent>
</div>
</div>
</div>
<style>
.bankContainer {
display: flex;
flex-direction: column;
gap: 5px;
}
.workerText {
margin-bottom: -2px;
font-size: 0.8em;
}
.bankRow {
display: flex;
gap: 8px;
}
</style>
@code {
[Inject] IBuildOrderService BuildOrderService { get; set; } = default!;
[Inject] IEconomyService EconomyService { get; set; } = default!;
EconomyModel _economy = new();
int _supplyGranted;
int _supplyTaken;
int _extraBuildings;
protected override void OnInitialized()
{
base.OnInitialized();
BuildOrderService.Subscribe(OnBuildOrderChanged);
_economy = EconomyService.GetEconomy(BuildOrderService.GetLastRequestInterval() + 1);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "BankComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "BankComponent");
#endif
}
void IDisposable.Dispose()
{
BuildOrderService.Unsubscribe(OnBuildOrderChanged);
}
void OnBuildOrderChanged()
{
_economy = EconomyService.GetEconomy(BuildOrderService.GetLastRequestInterval() + 1);
var ordersOverTime = BuildOrderService.GetOrders();
_supplyTaken = (from ordersAtInterval in ordersOverTime
from order in ordersAtInterval.Value
where order.Supply() != null
where order.Supply().Takes > 0
select order.Supply().Takes).Sum();
_supplyGranted = (from ordersAtInterval in ordersOverTime
from order in ordersAtInterval.Value
where order.Supply() != null
where order.Supply().Grants > 0
select order.Supply().Grants).Sum();
_extraBuildings = 0;
if (_supplyGranted > 160)
{
_extraBuildings = (_supplyGranted - 160) / 16;
_supplyGranted = 160;
}
StateHasChanged();
}
}
@@ -0,0 +1,331 @@
@inject IEconomyService EconomyService
@inject IBuildOrderService BuildOrderService
@inject ITimingService TimingService
@inject IJSRuntime JsRuntime;
@implements IDisposable
@if (lastRequestedRefreshIndex != requestedRefreshIndex)
{
<LoadingComponent/>
}
else
{
<div class="chartsContainer">
@foreach (var chart in charts)
{
var takenPixels = new Dictionary<int, bool>();
<div style="width: @chart.IntervalDisplayMax.ToString()px; height: @chart.ValueDisplayMax.ToString()px">
<div
style="position: relative; border: 2px solid gray; border-radius:2px; width: @chart.IntervalDisplayMax.ToString()px; height: @chart.ValueDisplayMax.ToString()px">
@foreach (var point in chart.Points)
{
var x = int.Parse(point.GetInterval(chart.HighestIntervalPoint, chart.IntervalDisplayMax));
if (takenPixels.ContainsKey(x)) continue;
takenPixels.Add(x, true);
<div style="position: absolute;
bottom:@point.GetValue(chart.HighestValuePoint, chart.ValueDisplayMax)px;
left:@point.GetInterval(chart.HighestIntervalPoint, chart.IntervalDisplayMax)px;
width: 0px;
height: 0px;">
<div
style="width:1px; height: 1px; border-top-right-radius:10px; border-top-left-radius:10px; border: 2px solid @chart.ChartColor; background-color:@chart.ChartColor">
</div>
</div>
}
</div>
</div>
}
</div>
<style>
.chartsContainer {
position: relative;
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 20px;
}
</style>
<FormLayoutComponent>
<FormDisplayComponent Label="Highest Alloy">
<Display>@highestAlloyPoint</Display>
</FormDisplayComponent>
<FormDisplayComponent Label="Highest Ether">
<Display>@highestEtherPoint</Display>
</FormDisplayComponent>
<DevOnlyComponent>
<FormDisplayComponent Label="Highest Pyre">
<Display>@highestEtherPoint</Display>
</FormDisplayComponent>
</DevOnlyComponent>
<FormDisplayComponent Label="Highest Army">
<Display>@highestArmyPoint</Display>
</FormDisplayComponent>
</FormLayoutComponent>
}
@code {
private readonly int width = 250;
List<int> valueList = new();
readonly List<ChartModel> charts = new();
float highestAlloyPoint;
float highestEtherPoint;
float highestPyrePoint;
float highestArmyPoint;
private Timer ageTimer = null!;
protected override void OnInitialized()
{
base.OnInitialized();
BuildOrderService.Subscribe(OnBuilderOrderChanged);
TimingService.Subscribe(OnBuilderOrderChanged);
ageTimer = new Timer(1000);
ageTimer.Elapsed += OnAge!;
ageTimer.Enabled = true;
GenerateChart();
}
int lastRequestedRefreshIndex;
void OnAge(object? sender, ElapsedEventArgs elapsedEventArgs)
{
if (requestedRefreshIndex > 0)
{
if (requestedRefreshIndex == lastRequestedRefreshIndex)
{
GenerateChart();
requestedRefreshIndex = 0;
lastRequestedRefreshIndex = 0;
}
lastRequestedRefreshIndex = requestedRefreshIndex;
}
ageTimer.Enabled = true;
}
void IDisposable.Dispose()
{
BuildOrderService.Unsubscribe(OnBuilderOrderChanged);
TimingService.Unsubscribe(OnBuilderOrderChanged);
}
int requestedRefreshIndex;
void OnBuilderOrderChanged()
{
requestedRefreshIndex++;
StateHasChanged();
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "ChartComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "ChartComponent");
#endif
}
void GenerateChart()
{
var economyOverTime = EconomyService.GetOverTime();
charts.Clear();
var alloyChart = new ChartModel
{
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "Cyan"
};
var etherChart = new ChartModel
{
Offset = width,
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "LightGreen"
};
var pyreChart = new ChartModel
{
Offset = width * 2,
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "Red"
};
var armyChart = new ChartModel
{
Offset = width * 3,
IntervalDisplayMax = width,
ValueDisplayMax = 100,
ChartColor = "White"
};
highestAlloyPoint = 0;
highestEtherPoint = 0;
highestPyrePoint = 0;
highestArmyPoint = 0;
for (var interval = 0; interval < economyOverTime.Count(); interval++)
{
var army = from unit in BuildOrderService.GetCompletedBefore(interval)
where unit.EntityType == EntityType.Army
select unit;
var armyValue = 0;
foreach (var unit in army)
{
armyValue += unit.Production().Alloy + unit.Production().Ether;
}
highestArmyPoint = Math.Max(highestArmyPoint, armyValue);
armyChart.Points.Add(new PointModel { Interval = interval, Value = armyValue });
}
for (var interval = 0; interval < economyOverTime.Count(); interval++)
{
var alloyPoint = new PointModel { Interval = interval };
var etherPoint = new PointModel { Interval = interval };
var pyrePoint = new PointModel { Interval = interval };
var economyAtSecond = economyOverTime[interval];
var alloyWorkerHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker
where harvester.Harvest().Resource == ResourceType.Alloy
select harvester;
var alloyAutomaticHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker == false
where harvester.Harvest().Resource == ResourceType.Alloy
select harvester;
var etherAutomaticHarvesters = from harvester in economyAtSecond.HarvestPoints
where harvester.Harvest() != null
where harvester.Harvest().RequiresWorker == false
where harvester.Harvest().Resource == ResourceType.Ether
select harvester;
float autoAlloy = 0;
float workerSlots = 0;
float workerAlloy = 0;
float autoEther = 0;
float economySpending = 0;
foreach (var alloyAutoHarvester in alloyAutomaticHarvesters)
{
autoAlloy += alloyAutoHarvester.Harvest().Slots * alloyAutoHarvester.Harvest().HarvestedPerInterval;
var production = alloyAutoHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
foreach (var alloyWorkerHarvester in alloyWorkerHarvesters)
{
workerSlots += alloyWorkerHarvester.Harvest().Slots;
var production = alloyWorkerHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
foreach (var etherWorkerHarvester in etherAutomaticHarvesters)
{
autoEther += etherWorkerHarvester.Harvest().Slots * etherWorkerHarvester.Harvest().HarvestedPerInterval;
var production = etherWorkerHarvester.Production();
if (production != null)
{
economySpending += production.Alloy;
}
}
economySpending += (economyAtSecond.WorkerCount - 6) * 50;
workerAlloy = Math.Min(economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount, workerSlots);
alloyPoint.TempValue = workerAlloy + autoAlloy;
etherPoint.Value = autoEther;
if (interval > 0)
{
alloyPoint.TempValue += alloyChart.Points.Last().TempValue;
etherPoint.Value += etherChart.Points.Last().Value;
pyrePoint.Value = pyreChart.Points.Last().Value + 1;
}
alloyPoint.Value = alloyPoint.TempValue - economySpending;
highestAlloyPoint = Math.Max(highestAlloyPoint, alloyPoint.Value);
highestEtherPoint = Math.Max(highestEtherPoint, etherPoint.Value);
alloyChart.Points.Add(alloyPoint);
etherChart.Points.Add(etherPoint);
pyreChart.Points.Add(pyrePoint);
}
alloyChart.HighestValuePoint = (int)Math.Max(highestAlloyPoint, 5000.0f);
etherChart.HighestValuePoint = (int)Math.Max(highestEtherPoint, 2000.0f);
pyreChart.HighestValuePoint = (int)Math.Max(highestPyrePoint, 2000.0f);
alloyChart.HighestIntervalPoint = economyOverTime.Count();
etherChart.HighestIntervalPoint = economyOverTime.Count();
pyreChart.HighestIntervalPoint = economyOverTime.Count();
armyChart.HighestValuePoint = (int)Math.Max(highestArmyPoint, 2000.0f);
armyChart.HighestIntervalPoint = economyOverTime.Count();
charts.Add(alloyChart);
charts.Add(etherChart);
//TODO WIP
//charts.Add(pyreChart);
charts.Add(armyChart);
StateHasChanged();
}
}
@@ -0,0 +1,43 @@
@inject IJSRuntime jsRuntime;
@inject IBuildOrderService buildOrderService
@implements IDisposable
<FormLayoutComponent>
<FormTextAreaComponent Label="JSON Data"
Rows="14"
Value="@buildOrderService.AsJson()">
</FormTextAreaComponent>
</FormLayoutComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
buildOrderService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
buildOrderService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "BuildOrderComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "BuildOrderComponent");
#endif
}
}
@@ -0,0 +1,86 @@
@inject IJSRuntime JsRuntime
@inject IKeyService KeyService
@inject IImmortalSelectionService FilterService
@inject IBuildOrderService BuildOrderService
@inject IStorageService StorageService
@using Services.Website
@implements IDisposable
@if (_entity != null)
{
<div class="entityClickView">
<CascadingValue Value="_entity">
<CascadingValue Value="@_viewType">
<EntityViewComponent></EntityViewComponent>
</CascadingValue>
</CascadingValue>
</div>
}
<style>
.entityClickView {
overflow-y: scroll;
width: 100%;
overflow-x: hidden;
height: 550px;
}
</style>
@code {
private EntityModel? _entity;
private string _viewType = EntityViewType.Detailed;
protected override void OnInitialized()
{
base.OnInitialized();
KeyService.Subscribe(HandleClick);
StorageService.Subscribe(RefreshDefaults);
RefreshDefaults();
}
void IDisposable.Dispose()
{
KeyService.Unsubscribe(HandleClick);
StorageService.Unsubscribe(RefreshDefaults);
}
void RefreshDefaults()
{
_viewType = StorageService.GetValue<bool>(StorageKeys.IsPlainView) ? EntityViewType.Plain : EntityViewType.Detailed;
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "EntityClickViewComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "EntityClickViewComponent");
#endif
}
private void HandleClick()
{
var hotkey = KeyService.GetHotkey();
var hotkeyGroup = KeyService.GetHotkeyGroup();
var isHoldSpace = KeyService.IsHoldingSpace();
var faction = FilterService.GetFaction();
var immortal = FilterService.GetImmortal();
var foundEntity = EntityModel.GetFrom(hotkey!, hotkeyGroup, isHoldSpace, faction, immortal);
if (foundEntity != null && _entity != foundEntity)
{
_entity = foundEntity;
StateHasChanged();
}
}
}
@@ -0,0 +1,80 @@
@inject IJSRuntime JsRuntime;
@inject IImmortalSelectionService FilterService
<FormLayoutComponent>
<FormSelectComponent OnChange="@OnFactionChanged">
<FormLabelComponent>Faction</FormLabelComponent>
<ChildContent>
<option value="@DataType.FACTION_Aru"
selected="@(FilterService.GetFaction().Equals(DataType.FACTION_Aru))">
Aru
</option>
<option value="@DataType.FACTION_QRath"
selected="@(FilterService.GetFaction().Equals(DataType.FACTION_QRath))">
Q'Rath
</option>
</ChildContent>
</FormSelectComponent>
<FormSelectComponent OnChange="@OnImmortalChanged">
<FormLabelComponent>Immortal</FormLabelComponent>
<ChildContent>
@if (FilterService.GetFaction() == DataType.FACTION_QRath)
{
<option value="@DataType.IMMORTAL_Orzum"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Orzum))">
Orzum
</option>
<option value="@DataType.IMMORTAL_Ajari"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Ajari))">
Ajari
</option>
}
@if (FilterService.GetFaction() == DataType.FACTION_Aru)
{
<option value="@DataType.IMMORTAL_Atzlan"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Atzlan))">
Atzlan
</option>
<option value="@DataType.IMMORTAL_Mala"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Mala))">
Mala
</option>
<option value="@DataType.IMMORTAL_Xol"
selected="@(FilterService.GetImmortal().Equals(DataType.IMMORTAL_Xol))">
Xol
</option>
}
</ChildContent>
</FormSelectComponent>
</FormLayoutComponent>
@code {
void OnFactionChanged(ChangeEventArgs e)
{
FilterService.SelectFaction(e.Value!.ToString()!);
}
void OnImmortalChanged(ChangeEventArgs e)
{
FilterService.SelectImmortal(e.Value!.ToString()!);
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "FilterComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "FilterComponent");
#endif
}
}
@@ -0,0 +1,89 @@
@inject IJSRuntime jsRuntime;
@inject IEconomyService economyService
@inject IBuildOrderService buildOrderService
@inject ITimingService timingService
@implements IDisposable
<div class="highlightsContainer">
<div>
<div>Requested</div>
@foreach (var ordersAtTime in buildOrderService.StartedOrders.Reverse())
{
foreach (var order in ordersAtTime.Value)
{
<div>
@ordersAtTime.Key | T @Interval.ToTime(ordersAtTime.Key)
</div>
<div>
@order.Info().Name
</div>
<br/>
}
}
</div>
<div>
<div>Finished</div>
@foreach (var ordersAtTime in buildOrderService.CompletedOrders.Reverse())
{
foreach (var order in ordersAtTime.Value)
{
<div>
@ordersAtTime.Key | T @Interval.ToTime(ordersAtTime.Key)
</div>
<div>
@order.Info().Name
</div>
<br/>
}
}
</div>
</div>
<style>
.highlightsContainer {
overflow-y: scroll;
overflow-x: hidden;
height: 400px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
}
</style>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
economyService.Subscribe(StateHasChanged);
buildOrderService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
economyService.Unsubscribe(StateHasChanged);
buildOrderService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "HighlightsComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "HighlightsComponent");
#endif
}
}
@@ -0,0 +1,400 @@
@inject IJSRuntime JsRuntime;
@using Services.Website
@implements IDisposable
@inject IKeyService KeyService
@inject IBuildOrderService BuildOrderService
@inject IImmortalSelectionService FilterService
@inject IEconomyService EconomyService
@inject ITimingService TimingService
@inject IToastService ToastService
@inject IDataCollectionService DataCollectionService
<InputPanelComponent>
<div class="keyContainer">
@foreach (var hotkey in hotkeys)
{
if (hotkey.IsHidden)
{
continue;
}
var color = (hotkey.KeyText.Equals("SPACE") && KeyService.IsHoldingSpace()) || KeyService.GetAllPressedKeys().Contains(hotkey.KeyText)
? hotkey.GetColor()
: hotkey.GetColor();
var x = hotkey.PositionX * Size;
var y = hotkey.PositionY * Size + (hotkey.PositionY == 0 ? 5 : -50);
var width = Size * hotkey.Width;
var height = hotkey.PositionY == 0 ? 50 : Size;
var borderRadius = hotkey.PositionY == 0 ? 12 : 0;
var border = "1px solid black";
if (hotkey.KeyText.Equals(key))
{
border = "5px solid black";
}
if (hotkey.KeyText.Equals(controlGroup))
{
color = "#257525";
}
if (hotkey.KeyText.Equals("SPACE") && KeyService.IsHoldingSpace())
{
border = "5px solid green";
}
var keyText = hotkey.KeyText.Equals("CAPSLOCK") ? "Caps"
: hotkey.KeyText.Equals("CONTROL") ? "Ctrl"
: hotkey.KeyText.Equals("SHIFT") ? "Shift"
: hotkey.KeyText.Equals("X") ? "X"
: hotkey.KeyText.Equals("SPACE") ? "Space" : hotkey.KeyText;
var controlStyle = $"background-color:{color}; " +
$"width: {width}px; " +
"border-top: 1px solid black; " +
"border-left: 1px solid black; " +
"border-right: 1px solid black; " +
$"border-top-left-radius: {borderRadius}px; " +
$"border-top-right-radius: {borderRadius}px; " +
"overflow: hidden; " +
"text-align: center;";
var keyStyle = $"background-color:{color}; " +
$"border: {border}; " +
$"width: {width}px; " +
$"height: {height}px; " +
"overflow: hidden; " +
"padding: 4px;";
var usedStyle = hotkey.PositionY == 0 ? controlStyle : keyStyle;
<div style="position:relative;
cursor:pointer;
top:@y.ToString()px;
left:@x.ToString()px;
width: 0px;
height: 0px;">
<div @onclick="e => ButtonClicked(e, hotkey)" style="@usedStyle">
@keyText
@foreach (var entity in data.Values)
{
if (InvalidKey(entity, hotkey) || InvalidKeyGroup(entity, hotkey) || InvalidHoldSpace(entity))
{
continue;
}
if (InvalidFaction(entity))
{
continue;
}
if (InvalidVanguard(entity) || InvalidNonVanguard(entity))
{
continue;
}
var isVanguard = entity.VanguardAdded() != null;
var style = isVanguard ? "font-weight: bold;" : "";
if (BuildOrderService.WillMeetRequirements(entity) == null)
{
style += "color:gray; font-style: italic;";
}
<div style="@style">@entity.Info()?.Name</div>
}
</div>
</div>
}
</div>
</InputPanelComponent>
<style>
.keyContainer {
width: 400px;
max-width: 95vw;
height: 350px;
outline: 3px solid black;
border-radius: 8px;
background-color: #282A30;
margin: auto;
}
@@media only screen and (max-width: 1025px) {
.keyContainer {
transform: scale(0.85) translateX(-20px);
background-color: transparent;
outline: none;
}
}
</style>
@code {
[Parameter] public int Size { get; set; } = 100;
readonly Dictionary<string, EntityModel> data = EntityModel.GetDictionary();
readonly List<HotkeyModel> hotkeys = HotkeyModel.GetAll();
private string controlGroup = "C";
private string key = "";
protected override void OnInitialized()
{
base.OnInitialized();
KeyService.Subscribe(OnKeyPressed);
FilterService.Subscribe(StateHasChanged);
BuildOrderService.Subscribe(OnBuilderOrderChanged);
}
void IDisposable.Dispose()
{
KeyService.Unsubscribe(OnKeyPressed);
FilterService.Unsubscribe(StateHasChanged);
BuildOrderService.Unsubscribe(OnBuilderOrderChanged);
}
int completedTimeCount;
void OnBuilderOrderChanged()
{
if (BuildOrderService.UniqueCompletedTimes.Count != completedTimeCount)
{
completedTimeCount = BuildOrderService.UniqueCompletedTimes.Count;
StateHasChanged();
}
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "HotKeyViewerComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "HotKeyViewerComponent");
#endif
}
// Move to Filter Service
bool InvalidFaction(EntityModel entity)
{
if (entity.Faction() != null && entity.Faction()?.Faction != FilterService.GetFaction() && FilterService.GetFaction() != DataType.Any)
{
return true;
}
return false;
}
// Move to Filter Service
bool InvalidVanguard(EntityModel entity)
{
if (entity.VanguardAdded() != null
&& entity.VanguardAdded()?.ImmortalId != FilterService.GetImmortal()
&& FilterService.GetImmortal() != DataType.Any)
{
return true;
}
return false;
}
// Move to Filter Service
bool InvalidNonVanguard(EntityModel entity)
{
if (entity.Replaceds().Count > 0)
{
foreach (var replaced in entity.Replaceds())
{
if (FilterService.GetImmortal() == replaced.ImmortalId)
{
return true;
}
}
}
return false;
}
bool InvalidKey(EntityModel entity, HotkeyModel key)
{
if (entity.Hotkey()?.Hotkey == key.KeyText)
{
return false;
}
return true;
}
bool InvalidKeyGroup(EntityModel entity, HotkeyModel key)
{
if (entity.Hotkey()?.HotkeyGroup == controlGroup)
{
return false;
}
return true;
}
bool InvalidKey(EntityModel entity)
{
if (entity.Hotkey()?.Hotkey == key)
{
return false;
}
return true;
}
bool InvalidKeyGroup(EntityModel entity)
{
if (entity.Hotkey()?.HotkeyGroup == controlGroup)
{
return false;
}
return true;
}
bool InvalidHoldSpace(EntityModel entity)
{
if (entity.Hotkey()?.HoldSpace == KeyService.IsHoldingSpace())
{
return false;
}
return true;
}
void OnKeyPressed()
{
var controlGroupWas = controlGroup;
var keyWas = key;
if (KeyService.GetAllPressedKeys().Contains("Z"))
{
controlGroup = "Z";
}
if (KeyService.GetAllPressedKeys().Contains("X"))
{
controlGroup = "X";
}
if (KeyService.GetAllPressedKeys().Contains("C"))
{
controlGroup = "C";
}
if (KeyService.GetAllPressedKeys().Contains("D"))
{
controlGroup = "D";
}
if (KeyService.GetAllPressedKeys().Contains("V"))
{
controlGroup = "V";
}
if (KeyService.GetAllPressedKeys().Contains("ALT"))
{
controlGroup = "ALT";
}
if (KeyService.GetAllPressedKeys().Contains("SHIFT"))
{
controlGroup = "SHIFT";
}
if (KeyService.GetAllPressedKeys().Contains("CONTROL"))
{
controlGroup = "CONTROL";
}
if (KeyService.GetAllPressedKeys().Count > 0)
{
key = KeyService.GetAllPressedKeys().First();
}
if (controlGroupWas != controlGroup || keyWas != key)
{
StateHasChanged();
}
}
private void HandleClick()
{
var hotkey = KeyService.GetHotkey();
if (hotkey is "`")
HandleCancelEntity();
if (EntityFromKey(hotkey, out var entity))
return;
if (BuildOrderService.Add(entity!, EconomyService))
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
private void HandleCancelEntity()
{
BuildOrderService.RemoveLast();
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
private bool EntityFromKey(string? hotkey, out EntityModel? entity)
{
var hotkeyGroup = KeyService.GetHotkeyGroup();
var isHoldSpace = KeyService.IsHoldingSpace();
var faction = FilterService.GetFaction();
var immortal = FilterService.GetImmortal();
entity = EntityModel.GetFrom(hotkey!, hotkeyGroup, isHoldSpace, faction, immortal);
return entity == null;
}
private void ButtonClicked(MouseEventArgs mouseEventArgs, HotkeyModel hotkey)
{
DataCollectionService.SendEvent(
DataCollectionKeys.BuildCalcInput,
new Dictionary<string, string> { { "key", hotkey.KeyText.ToLower() }, { "input-source", "mouse" } }
);
if (hotkey.KeyText.Equals(HotKeyType.SPACE.ToString()))
{
if (KeyService.IsHoldingSpace())
{
KeyService.RemovePressedKey(hotkey.KeyText);
}
else
{
KeyService.AddPressedKey(hotkey.KeyText);
}
}
else
{
KeyService.AddPressedKey(hotkey.KeyText);
KeyService.RemovePressedKey(hotkey.KeyText);
}
}
}
@@ -0,0 +1,52 @@
@using Services.Website
@inject IKeyService KeyService
@inject IDataCollectionService DataCollectionService
@inject IJSRuntime JsRuntime
<div tabindex="0"
style="margin: auto;"
@onkeydown="HandleKeyDown"
@onkeyup="HandleKeyUp"
@onkeydown:preventDefault="true"
@onkeydown:stopPropagation="true">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
private void HandleKeyDown(KeyboardEventArgs e)
{
DataCollectionService.SendEvent(
DataCollectionKeys.BuildCalcInput,
new Dictionary<string, string> { { "key", e.Key.ToLower() }, { "input-source", "keyboard" } }
);
KeyService.AddPressedKey(e.Key);
}
private void HandleKeyUp(KeyboardEventArgs e)
{
KeyService.RemovePressedKey(e.Key);
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "InputPanelComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "InputPanelComponent");
#endif
}
}
@@ -0,0 +1,124 @@
@inject IJSRuntime JsRuntime;
@inject IBuildOrderService BuildOrderService
@inject IEconomyService EconomyService
@inject IToastService ToastService
@inject ITimingService TimingService
@implements IDisposable
<FormLayoutComponent>
<FormNumberComponent Max="600"
Min="0"
Value="BuildDelay"
OnChange="@OnBuildingInputDelayChanged">
<FormLabelComponent>Building Input Delay</FormLabelComponent>
<FormInfoComponent>Add a input delay to constructing buildings for simulating worker movement and player
micro.
</FormInfoComponent>
</FormNumberComponent>
<div class="optionRow">
<FormLayoutComponent>
<FormNumberComponent Max="600"
Min="1"
Value="WaitTime"
OnChange="@OnWaitTimeChanged">
<FormLabelComponent>Wait Time</FormLabelComponent>
</FormNumberComponent>
<ButtonComponent OnClick="OnWaitClicked">Add Wait</ButtonComponent>
</FormLayoutComponent>
<FormLayoutComponent>
<FormNumberComponent Max="2048"
Min="1"
Value="WaitTo"
OnChange="@OnWaitToChanged">
<FormLabelComponent>Wait To</FormLabelComponent>
</FormNumberComponent>
<ButtonComponent OnClick="OnWaitToClicked">Add Wait</ButtonComponent>
</FormLayoutComponent>
</div>
</FormLayoutComponent>
<style>
.optionRow {
display: flex;
gap: 12px;
}
</style>
@code {
private int BuildDelay { get; set; } = 2;
private int WaitTime { get; set; } = 30;
private int WaitTo { get; set; } = 30;
protected override void OnInitialized()
{
base.OnInitialized();
TimingService.Subscribe(RefreshDefaults);
RefreshDefaults();
}
void IDisposable.Dispose()
{
TimingService.Unsubscribe(RefreshDefaults);
}
void RefreshDefaults()
{
BuildDelay = TimingService.BuildingInputDelay;
WaitTime = TimingService.WaitTime;
WaitTo = TimingService.WaitTo;
StateHasChanged();
}
void OnBuildingInputDelayChanged(ChangeEventArgs changeEventArgs)
{
TimingService.BuildingInputDelay = int.Parse(changeEventArgs.Value!.ToString()!);
}
void OnWaitTimeChanged(ChangeEventArgs changeEventArgs)
{
TimingService.WaitTime = (int)changeEventArgs.Value!;
WaitTime = (int)changeEventArgs.Value!;
}
void OnWaitToChanged(ChangeEventArgs changeEventArgs)
{
TimingService.WaitTo = (int)changeEventArgs.Value!;
WaitTo = (int)changeEventArgs.Value!;
}
private void OnWaitClicked()
{
if (BuildOrderService.AddWait(WaitTime))
{
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
}
private void OnWaitToClicked()
{
if (BuildOrderService.AddWaitTo(WaitTo))
{
EconomyService.Calculate(BuildOrderService, TimingService, BuildOrderService.GetLastRequestInterval());
}
}
protected override bool ShouldRender()
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.time", "TimingComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
JsRuntime.InvokeVoidAsync("console.timeEnd", "TimingComponent");
#endif
}
}
@@ -0,0 +1,88 @@
@inject IJSRuntime jsRuntime
@inject IEconomyService economyService
@inject IBuildOrderService buildOrderService
@implements IDisposable
<Virtualize Items="@economyService.GetOverTime()" Context="economyAtSecond" ItemSize="400" OverscanCount="4">
<div style="display: grid; gap: 8px; grid-template-columns: 1fr 1fr;">
<div>
<div>
@economyAtSecond.Interval
</div>
<div>
T @Interval.ToTime(economyAtSecond.Interval) | A @economyAtSecond.Alloy | E @economyAtSecond.Ether
</div>
<div>
Worker Count: @(economyAtSecond.WorkerCount)
</div>
<div>
Free Worker Count: @(economyAtSecond.WorkerCount - economyAtSecond.BusyWorkerCount)
</div>
<div>
Busy Worker Count: @economyAtSecond.BusyWorkerCount
</div>
<div>
Creating Worker Count: @economyAtSecond.CreatingWorkerCount
</div>
<br/>
</div>
<div>
@if (buildOrderService.StartedOrders.TryGetValue(economyAtSecond.Interval, out var ordersAtTime))
{
@foreach (var order in ordersAtTime)
{
<div>
Requested: @order.Info().Name
</div>
}
}
@if (buildOrderService.CompletedOrders.TryGetValue(economyAtSecond.Interval, out var ordersCompletedAtTime))
{
@foreach (var order in ordersCompletedAtTime)
{
<div>
New: @order.Info().Name
</div>
}
}
</div>
</div>
</Virtualize>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
economyService.Subscribe(StateHasChanged);
buildOrderService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
economyService.Unsubscribe(StateHasChanged);
buildOrderService.Unsubscribe(StateHasChanged);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "TimelineComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "TimelineComponent");
#endif
}
}
@@ -0,0 +1,105 @@
@inject IJSRuntime jsRuntime;
@inject IBuildOrderService buildOrderService
@inject IEconomyService economyService
@inject IToastService toastService
@inject ITimingService timingService
@implements IDisposable
<FormLayoutComponent>
<FormNumberComponent Max="2048"
Min="0"
Value="@timingService.GetAttackTime()"
OnChange="@OnAttackTimeChanged">
<FormLabelComponent>Attack Time</FormLabelComponent>
<FormInfoComponent>
<i>&emsp; T @Interval.ToTime(timingService.GetAttackTime())</i>
</FormInfoComponent>
</FormNumberComponent>
<FormNumberComponent Max="2048"
Min="0"
Value="@timingService.GetTravelTime()"
OnChange="@OnTravelTimeChanged">
<FormLabelComponent>Travel Time</FormLabelComponent>
<FormInfoComponent>
<i>&emsp; T @Interval.ToTime(timingService.GetTravelTime())</i>
</FormInfoComponent>
</FormNumberComponent>
</FormLayoutComponent>
@code {
protected override void OnInitialized()
{
base.OnInitialized();
timingService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
timingService.Unsubscribe(StateHasChanged);
}
void OnAttackTimeChanged(ChangeEventArgs changeEventArgs)
{
timingService.SetAttackTime(int.Parse(changeEventArgs.Value!.ToString()!));
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
toastService.AddToast(new ToastModel
{
Title = "Attack Time",
Message = "Attack Time has changed.",
SeverityType = SeverityType.Success
});
StateHasChanged();
}
void OnTravelTimeChanged(ChangeEventArgs changeEventArgs)
{
timingService.SetTravelTime(int.Parse(changeEventArgs.Value!.ToString()!));
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
toastService.AddToast(new ToastModel
{
Title = "Travel Time",
Message = "Travel Time has changed.",
SeverityType = SeverityType.Success
});
StateHasChanged();
}
void OnNameChanged(ChangeEventArgs changeEventArgs)
{
buildOrderService.SetName(changeEventArgs.Value!.ToString()!);
}
void OnColorChanged(ChangeEventArgs changeEventArgs)
{
buildOrderService.DeprecatedSetColor(changeEventArgs.Value!.ToString()!);
}
void OnNotesChanged(ChangeEventArgs changeEventArgs)
{
buildOrderService.SetNotes(changeEventArgs.Value!.ToString()!);
}
protected override bool ShouldRender()
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.time", "TimingComponent");
#endif
return true;
}
protected override void OnAfterRender(bool firstRender)
{
#if DEBUG
jsRuntime.InvokeVoidAsync("console.timeEnd", "TimingComponent");
#endif
}
}
+22
View File
@@ -0,0 +1,22 @@
@layout PageLayout
@inject IDataCollectionService DataCollectionService
@inherits BasePage
@page "/contact"
<LayoutMediumContentComponent>
<WebsiteTitleComponent>Contact</WebsiteTitleComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
How do I contact you for feature requests and bug reports?
</InfoQuestionComponent>
<InfoAnswerComponent>
You can message me at <b>JonathanMcCaffrey#3544</b> on my <a href="https://discord.gg/uMq8bMGeeN"
target="_blank">Discord</a> channel.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutMediumContentComponent>
+24
View File
@@ -0,0 +1,24 @@
@page "/data-collection"
@inject IDataCollectionService DataCollectionService
@inherits BasePage
@layout PageLayout
<LayoutMediumContentComponent>
<AlertComponent>
<Title>Not Implemented</Title>
<Message></Message>
</AlertComponent>
<PaperComponent>
TODO
</PaperComponent>
<ContentDividerComponent/>
<PaperComponent>
</PaperComponent>
</LayoutMediumContentComponent>
@@ -0,0 +1,59 @@
@layout PageLayout
@using IGP.Pages.DataTables.Parts
@inherits BasePage
@page "/data-tables"
<LayoutLargeContentComponent>
<WebsiteTitleComponent>Data Tables</WebsiteTitleComponent>
<AlertComponent Type="@SeverityType.Warning">
<Title>Errors Present</Title>
<Message>
Incomplete feature for easily comparing unit stats.
</Message>
</AlertComponent>
<MudTabs Elevation="2">
<MudTabPanel Text="Attacks">
<WeaponTable/>
</MudTabPanel>
<MudTabPanel Text="Production">
<ProductionTable/>
</MudTabPanel>
<MudTabPanel Text="Health">
<VitalityTable/>
</MudTabPanel>
<MudTabPanel Text="Movement">
<MovementTable/>
</MudTabPanel>
</MudTabs>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This tool is a data table of all information belonging to a type of data. Such as viewing all weapons,
so one can easily sort and see what weapon attack has the highest range, and what faction and unit that
attack belongs to.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutLargeContentComponent>
<style>
</style>
@code {
}
@@ -0,0 +1,21 @@
<MudDataGrid T="EntityMovementModel" Items="@_data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Movement"/>
<PropertyColumn Property="x => x.Speed"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
</MudDataGrid>
@code {
readonly IEnumerable<EntityMovementModel> _data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityMovementModel>()
.ToList();
}
@@ -0,0 +1,22 @@
<MudDataGrid T="EntityProductionModel" Items="@data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Alloy"/>
<PropertyColumn Property="x => x.BuildTime"/>
<PropertyColumn Property="x => x.Ether"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
</MudDataGrid>
@code {
readonly IEnumerable<EntityProductionModel> data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityProductionModel>()
.ToList();
}
@@ -0,0 +1,25 @@
<MudDataGrid T="EntityVitalityModel" Items="@_data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Health"/>
<PropertyColumn Property="x => x.Armor"/>
<PropertyColumn Property="x => x.Defense"/>
<PropertyColumn Property="x => x.DefenseLayer"/>
<PropertyColumn Property="x => x.IsStructure"/>
<PropertyColumn Property="x => x.IsEtheric"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
</MudDataGrid>
@code {
readonly IEnumerable<EntityVitalityModel> _data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityVitalityModel>()
.ToList();
}
@@ -0,0 +1,31 @@
<MudDataGrid T="EntityWeaponModel" Items="@_data"
SortMode="SortMode.Multiple"
Filterable="true"
Hideable="true">
<Columns>
<PropertyColumn Property="x => x.Parent.GetName()" Title="Entity"/>
<PropertyColumn Property="x => x.Range" Title="Range"/>
<PropertyColumn Property="x => x.Targets" Title="Targets"/>
<PropertyColumn Property="x => x.Damage" Title="Damage"/>
<PropertyColumn Property="x => x.AttacksPerSecond" Title="Attacks Per Second"/>
<PropertyColumn Property="x => x.DamagePerSecond()" Title="DPS"/>
<PropertyColumn Property="x => x.HasSplash" Title="Has Splash"/>
<PropertyColumn Property="x => x.DamagePerSecondLight()" Title="DPS (Light)"/>
<PropertyColumn Property="x => x.DamagePerSecondMedium()" Title="DPS (Medium)"/>
<PropertyColumn Property="x => x.DamagePerSecondHeavy()" Title="DPS (Heavy)"/>
<PropertyColumn Property="x => x.Parent.GetFaction()" Title="Faction"/>
<PropertyColumn Property="x => x.Parent.GetImmortal()" Title="Immortal"/>
</Columns>
<PagerContent>
<MudDataGridPager T="EntityWeaponModel"/>
</PagerContent>
</MudDataGrid>
@code {
readonly IEnumerable<EntityWeaponModel> _data = EntityData.Get()
.SelectMany(e => e.Value.EntityParts)
.OfType<EntityWeaponModel>()
.ToList();
}
@@ -0,0 +1,261 @@
@layout PageLayout
@inherits BasePage
@page "/database"
@using Model
@implements IDisposable
@inject IEntityDisplayService EntityDisplayService
<LayoutLargeContentComponent>
<WebsiteTitleComponent>Database</WebsiteTitleComponent>
<PaperComponent>
<FormDisplayComponent Label="Patch">
<Display>
Game Patch: @Variables.GamePatch
</Display>
</FormDisplayComponent>
</PaperComponent>
<div style="margin-left: 8px">
<ButtonGroupComponent OnClick="choice => { EntityDisplayService.SetDisplayType(choice); }"
Choice="@EntityDisplayService.GetDisplayType()"
Choices="@EntityDisplayService.DefaultChoices()"></ButtonGroupComponent>
</div>
<PaperComponent>
<EntityFilterComponent></EntityFilterComponent>
@if (searches != null)
{
<div class="databaseItems">
@foreach (var entity in searches)
{
<CascadingValue Value="entity">
<CascadingValue Value="@EntityDisplayService.GetDisplayType()">
<EntityViewComponent></EntityViewComponent>
</CascadingValue>
</CascadingValue>
}
</div>
}
</PaperComponent>
<ContentDividerComponent></ContentDividerComponent>
<PaperComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
What is this tool?
</InfoQuestionComponent>
<InfoAnswerComponent>
This is a reference database. Mostly so unit stats can be reviewed outside of the game, IMMORTAL: Gates
of Pyre.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Is this database complete?
</InfoQuestionComponent>
<InfoAnswerComponent>
No. A lot of content is missing, that needs to be manually transfered from screenshots of IMMORTAL:
Gates of Pyre. This will happen slowly over-time.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
Is this database updated to the latest version?
</InfoQuestionComponent>
<InfoAnswerComponent>
Maybe. Check this <b>@Variables.GamePatch</b> version number, and compare it to the
number on discord, in the <b>#game-updates</b> channel. That should give a general sense of how out of
date the data is.
</InfoAnswerComponent>
</InfoBodyComponent>
<InfoBodyComponent>
<InfoQuestionComponent>
This database has some errors in it.
</InfoQuestionComponent>
<InfoAnswerComponent>
Yup. The content is being transferred by hand, so some mistakes are expected.
</InfoAnswerComponent>
</InfoBodyComponent>
</PaperComponent>
</LayoutLargeContentComponent>
<style>
.databaseItems {
height: 900px;
overflow-x: hidden;
overflow-y: auto;
background-color: var(--background);
gap: 4px;
border-top: 4px solid var(--accent);
border-bottom: 4px solid var(--accent);
padding: 4px;
}
.databaseInfoContainer {
display: flex;
gap: 24px;
}
</style>
@code {
[Inject] public IEntityFilterService EntityFilterService { get; set; } = default!;
readonly List<EntityModel> defaults = (from entity in EntityModel.GetList()
where entity.IsSpeculative == false
select entity).ToList();
List<EntityModel> factions = default!;
List<EntityModel> immortals = default!;
List<EntityModel> entities = default!;
List<EntityModel> searches = default!;
string selectedFactionType = DataType.Any;
string selectedImmortalType = DataType.Any;
string selectedEntityType = EntityType.Army;
string searchText = "";
protected override void OnInitialized()
{
base.OnInitialized();
RefreshFactionSearch();
EntityFilterService.Subscribe(OnChange);
EntityDisplayService.Subscribe(StateHasChanged);
}
void IDisposable.Dispose()
{
EntityFilterService.Unsubscribe(OnChange);
EntityDisplayService.Unsubscribe(StateHasChanged);
}
void OnChange(EntityFilterEvent filterEntityEvent)
{
if (filterEntityEvent == EntityFilterEvent.OnRefreshFaction)
{
RefreshFactionSearch();
}
if (filterEntityEvent == EntityFilterEvent.OnRefreshImmortal)
{
RefreshImmortalSearch();
}
if (filterEntityEvent == EntityFilterEvent.OnRefreshEntity)
{
RefreshEntitySearch();
}
if (filterEntityEvent == EntityFilterEvent.OnRefreshSearch)
{
RefreshTextSearch();
}
}
void RefreshFactionSearch()
{
selectedFactionType = EntityFilterService.GetFactionType();
if (selectedFactionType == DataType.Any)
{
factions = defaults.ToList();
}
else
{
factions = (from entity in defaults
where entity.Faction() != null && entity.Faction().Faction == selectedFactionType
select entity).ToList();
}
RefreshImmortalSearch();
}
void OnImmortalChanged(ChangeEventArgs e)
{
selectedImmortalType = e.Value!.ToString()!;
RefreshImmortalSearch();
}
void RefreshImmortalSearch()
{
selectedImmortalType = EntityFilterService.GetImmortalType();
if (selectedImmortalType == DataType.Any)
{
immortals = factions.ToList();
}
else
{
immortals = (from entity in factions
where entity.VanguardAdded() == null || entity.VanguardAdded().ImmortalId == selectedImmortalType
select entity).ToList();
}
RefreshEntitySearch();
}
void OnEntityChanged(ChangeEventArgs e)
{
selectedEntityType = e.Value!.ToString()!;
RefreshEntitySearch();
}
void RefreshEntitySearch()
{
selectedEntityType = EntityFilterService.GetEntityType();
if (selectedEntityType == EntityType.Any)
{
entities = immortals.ToList();
}
else
{
entities = (from entity in immortals
where entity.EntityType == selectedEntityType
select entity).ToList();
}
RefreshTextSearch();
}
void OnSearchTextChanged(ChangeEventArgs e)
{
searchText = e.Value!.ToString()!;
RefreshTextSearch();
}
void RefreshTextSearch()
{
searchText = EntityFilterService.GetSearchText();
if (searchText.Trim() == "")
{
searches = entities.ToList();
}
else
{
searches = (from entity in entities
where entity.Info().Name.ToLower().Contains(searchText.ToLower())
select entity).ToList();
}
StateHasChanged();
}
}
@@ -0,0 +1,100 @@
@layout PageLayout
@inherits BasePage
@page "/database/{text}"
@inject IEntityDisplayService EntityDisplayService
@using Model
@implements IDisposable
<LayoutLargeContentComponent>
<PaperComponent>
<FormDisplayComponent Label="Patch">
<Display>
Game Patch: @Variables.GamePatch
</Display>
</FormDisplayComponent>
</PaperComponent>
<div style="margin-left: 8px">
<ButtonGroupComponent OnClick="choice => { EntityDisplayService.SetDisplayType(choice); }"
Choice="@EntityDisplayService.GetDisplayType()"
Choices="@EntityDisplayService.DefaultChoices()"></ButtonGroupComponent>
</div>
@if (Text!.Trim().ToLower().Equals("walter"))
{
<PaperComponent>
<CodeComponent>
Unhandled Exception: EXCEPTION_MEMORY_SIZE_VIOLATION
UNIT_WALTER too powerful to be displayed.
This SHOULD NEVER HAPPEN!
</CodeComponent>
</PaperComponent>
}
else if (_entity == null)
{
<PaperComponent>
<div>Invalid entity name entered: <span id="invalidSearch">@Text</span></div>
<div>No such entity. Did you mean <b>"<span id="validSearch">Throne</span>"</b>?</div>
</PaperComponent>
}
else
{
<PaperComponent>
<CascadingValue Value="_entity">
<CascadingValue Value="@EntityDisplayService.GetDisplayType()">
<EntityViewComponent></EntityViewComponent>
</CascadingValue>
</CascadingValue>
</PaperComponent>
}
</LayoutLargeContentComponent>
@code {
[Parameter] public string? Text { get; set; }
private EntityModel? _entity;
protected override void OnInitialized()
{
EntityDisplayService.Subscribe(StateHasChanged);
}
protected override void OnParametersSet()
{
base.OnParametersSet();
base.OnInitialized();
FocusEntity();
}
private void FocusEntity()
{
foreach (var e in EntityData.Get().Values)
{
if (e.Info().Name.ToLower().Equals(Text!.ToLower()))
{
_entity = e;
return;
}
}
}
void IDisposable.Dispose()
{
EntityDisplayService.Unsubscribe(StateHasChanged);
}
}
@@ -0,0 +1,56 @@
@if (Entity != null)
{
var isVanguard = Entity.VanguardAdded() != null ? " vanguard" : "";
<div id="@Entity.EntityType.ToLower()-@Entity.Info().Name.ToLower()" class="entitiesContainer @isVanguard">
<EntityHeaderComponent></EntityHeaderComponent>
<div class="entityPartsContainer">
<EntityVanguardAddedComponent></EntityVanguardAddedComponent>
<EntityInfoComponent></EntityInfoComponent>
<EntityVanguardsComponent></EntityVanguardsComponent>
<EntityProductionComponent></EntityProductionComponent>
<EntityStatsComponent></EntityStatsComponent>
<EntityMechanicsComponent></EntityMechanicsComponent>
<EntityPassivesComponent></EntityPassivesComponent>
<EntityPyreSpellsComponent></EntityPyreSpellsComponent>
<EntityUpgradesComponent></EntityUpgradesComponent>
<EntityWeaponsComponent></EntityWeaponsComponent>
<EntityAbilitiesComponent></EntityAbilitiesComponent>
</div>
</div>
}
<style>
.entitiesContainer {
margin-bottom: 12px;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
padding: 30px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
.entityPartsContainer {
display: flex;
flex-direction: column;
flex-wrap: wrap;
width: 100%;
}
@@media only screen and (max-width: 1025px) {
.entitiesContainer {
padding: 0px;
}
}
</style>
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string? StyleType { get; set; }
}
@@ -0,0 +1,134 @@
@if (Entity!.IdAbilities().Count > 0)
{
@if (StyleType.Equals("Plain"))
{
@foreach (var idAbility in Entity.IdAbilities())
{
var spell = EntityModel.Get(idAbility.Id);
var info = spell.Info();
var production = spell.Production();
<div>
<div>
<b>Ability Name:</b> @spell.Info().Name
</div>
<div>
<b>- Description:</b> @((MarkupString)info.Description)
</div>
@if (!info.Notes.Trim().Equals(""))
{
<div>
<b>- Notes:</b> @((MarkupString)info.Notes)
</div>
}
<div>
@if (production != null)
{
if (production.Energy != 0)
{
<div>
<b>- Energy: </b> @production.Energy
</div>
}
@if (!production.DefensiveLayer.Equals(0))
{
<div>
<b>- Shields:</b> @production.DefensiveLayer
</div>
}
if (production.BuildTime != 0)
{
<div>
<b>- BuildTime: </b> @production.BuildTime
</div>
}
if (production.Cooldown != 0)
{
<div>
<b>- Cooldown: </b> @production.Cooldown
</div>
}
}
</div>
</div>
}
}
else
{
<EntityDisplayComponent Title="Abilities">
@foreach (var idAbility in Entity.IdAbilities())
{
var spell = EntityModel.Get(idAbility.Id);
var info = spell.Info();
var production = spell.Production();
<div>
<div>
<b>Name:</b>
<EntityLabelComponent EntityId="@spell.DataType"/>
</div>
<div>
<b>Description:</b> @((MarkupString)info.Description)
</div>
@if (!info.Notes.Trim().Equals(""))
{
<div>
<b>Notes:</b> @((MarkupString)info.Notes)
</div>
}
<div>
@if (production != null)
{
if (production.Energy != 0)
{
<div>
<b> Energy: </b> @production.Energy
</div>
}
@if (!production.DefensiveLayer.Equals(0))
{
<div>
<b>Shields:</b> @production.DefensiveLayer
</div>
}
if (production.BuildTime != 0)
{
<div>
<b> BuildTime: </b> @production.BuildTime
</div>
}
if (production.Cooldown != 0)
{
<div>
<b> Cooldown: </b> @production.Cooldown
</div>
}
}
</div>
</div>
}
</EntityDisplayComponent>
}
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}
@@ -0,0 +1,72 @@
@if (StyleType.Equals("Plain"))
{
<div>
<b id="entityName">@Entity?.Info().Name</b>
@if (Entity?.Info().Descriptive != DescriptiveType.None)
{
<span>, @Entity?.Info().Descriptive.Replace("_", " ")</span>
}
</div>
}
else
{
<div class="entityHeader">
<div id="entityName" class="entityHeaderText">
@Entity?.Info().Name
</div>
<div style="font-size:1.4rem;">
<b>@Entity?.EntityType.Replace("_", " ")</b>
@if (Entity?.Info().Descriptive != DescriptiveType.None)
{
<span>
<b>:</b> @Entity!.Info().Descriptive.Replace("_", " ")
</span>
}
</div>
<div>
@if (Entity.Info().FlavorText != "")
{
<div>
<i> @((MarkupString)Entity.Info().FlavorText)</i>
</div>
}
</div>
</div>
<style>
.entityHeader {
display: flex;
flex-direction: row;
gap: 4px;
justify-content: space-between;
margin-bottom: 22px;
width: 100%;
margin-left: -6px;
}
.entityHeaderText {
font-size: 2rem;
font-weight: 900;
}
@@media only screen and (max-width: 1025px) {
.entityHeader {
flex-direction: column;
justify-content: normal;
margin-left: 4px;
}
}
</style>
}
@code {
[CascadingParameter] public EntityModel? Entity { get; set; }
[CascadingParameter] public string StyleType { get; set; } = "Detailed";
}

Some files were not shown because too many files have changed in this diff Show More