From 4afafacab01b640e49d9032489777edc1ecd0c8a Mon Sep 17 00:00:00 2001
From: Pat Hartl <pat@pathar.tl>
Date: Thu, 12 Jan 2023 01:27:32 -0600
Subject: [PATCH] Added Blazor support with two components for managing actions
 and multiplayer modes on a game

---
 LANCommander/Components/ActionEditor.razor    | 77 ++++++++++++++++++
 .../Components/MultiplayerModeEditor.razor    | 78 +++++++++++++++++++
 LANCommander/Components/_Imports.razor        |  7 ++
 LANCommander/Program.cs                       | 13 +++-
 LANCommander/Views/Games/Edit.cshtml          | 15 +++-
 LANCommander/Views/Shared/_Layout.cshtml      |  9 ++-
 6 files changed, 194 insertions(+), 5 deletions(-)
 create mode 100644 LANCommander/Components/ActionEditor.razor
 create mode 100644 LANCommander/Components/MultiplayerModeEditor.razor
 create mode 100644 LANCommander/Components/_Imports.razor

diff --git a/LANCommander/Components/ActionEditor.razor b/LANCommander/Components/ActionEditor.razor
new file mode 100644
index 0000000..f7b292a
--- /dev/null
+++ b/LANCommander/Components/ActionEditor.razor
@@ -0,0 +1,77 @@
+@using LANCommander.Data.Models
+
+@{
+    int i = 0;
+}
+<div class="table-responsive">
+    <table class="table mb-0">
+        <thead>
+            <tr>
+                <th>Name</th>
+                <th>Path</th>
+                <th>Arguments</th>
+                <th>Primary</th>
+                <th></th>
+            </tr>
+        </thead>
+
+        <tbody>
+            @if (Actions == null || Actions.Count == 0)
+            {
+                <tr><td colspan="5">Actions are used to start the game or launch other executables. It is recommended to have at least one action to launch the game.</td></tr>
+            }
+
+            @foreach (var action in Actions)
+            {
+                <tr>
+                    <td><input @bind="action.Name" name="Game.Actions[@i].Name" class="form-control" placeholder="Play" /></td>
+                    <td><input @bind="action.Path" name="Game.Actions[@i].Path" class="form-control" placeholder="Game.exe" /></td>
+                    <td><input @bind="action.Arguments" name="Game.Actions[@i].Arguments" class="form-control" placeholder="Launch Arguments" /></td>
+                    <td class="align-middle">
+                        <div class="form-check form-switch form-check-inline mb-0">
+                            <input @bind="action.PrimaryAction" name="Game.Actions[@i].PrimaryAction" class="form-check-input" type="checkbox" />
+                        </div>
+                    </td>
+                    <td>
+                        <div class="btn-list flex-nowrap justify-content-end">
+                            <button class="btn btn-ghost-danger btn-icon" @onclick="() => RemoveAction(i)" type="button">
+                                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                    <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
+                                    <line x1="18" y1="6" x2="6" y2="18"></line>
+                                    <line x1="6" y1="6" x2="18" y2="18"></line>
+                                </svg>
+                            </button>
+                        </div>
+                    </td>
+                </tr>
+            }
+            <tr>
+                <td colspan="5">
+                    <div class="btn-list flex-nowrap justify-content-end">
+                        <button class="btn btn-ghost-primary" @onclick="AddAction" type="button">Add Action</button>
+                    </div>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+</div>
+
+@code {
+    [Parameter] public ICollection<Data.Models.Action> Actions { get; set; }
+
+    private void AddAction()
+    {
+        if (Actions == null)
+            Actions = new List<Data.Models.Action>();
+
+        Actions.Add(new Data.Models.Action()
+        {
+            PrimaryAction = Actions.Count == 0
+        });
+    }
+
+    private void RemoveAction(int index)
+    {
+        Actions.Remove(Actions.ElementAt(index));
+    }
+}
diff --git a/LANCommander/Components/MultiplayerModeEditor.razor b/LANCommander/Components/MultiplayerModeEditor.razor
new file mode 100644
index 0000000..c7f11a4
--- /dev/null
+++ b/LANCommander/Components/MultiplayerModeEditor.razor
@@ -0,0 +1,78 @@
+@using LANCommander.Data.Enums
+@using LANCommander.Data.Models
+
+@{
+    int i = 0;
+}
+<div class="table-responsive">
+    <table class="table mb-0">
+        <thead>
+            <tr>
+                <th>Type</th>
+                <th>Min Players</th>
+                <th>Max Players</th>
+                <th>Description</th>
+                <th></th>
+            </tr>
+        </thead>
+
+        <tbody>
+            @if (MultiplayerModes.Count == 0)
+            {
+                <tr><td colspan="5">If the game has any multiplayer modes you can add them here to provide metadata to clients.</td></tr>
+            }
+
+            @foreach (var multiplayerMode in MultiplayerModes)
+            {
+                <tr>
+                    <td>
+                        <select @bind="multiplayerMode.Type" name="Game.MultiplayerModes[@i].Type" class="form-control">
+                            @foreach (var type in Enum.GetValues(typeof(MultiplayerType)))
+                            {
+                                <option value="@type">@type</option>
+                            }
+                        </select>
+                    </td>
+                    <td><input @bind="multiplayerMode.MinPlayers" name="Game.MultiplayerModes[@i].MinPlayers" class="form-control" /></td>
+                    <td><input @bind="multiplayerMode.MaxPlayers" name="Game.MultiplayerModes[@i].MaxPlayers" class="form-control" /></td>
+                    <td><input @bind="multiplayerMode.Description" name="Game.MultiplayerModes[@i].Description" class="form-control" /></td>
+                    <td>
+                        <div class="btn-list flex-nowrap justify-content-end">
+                            <button class="btn btn-ghost-danger btn-icon" @onclick="() => RemoveMode(i)" type="button">
+                                <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+                                    <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
+                                    <line x1="18" y1="6" x2="6" y2="18"></line>
+                                    <line x1="6" y1="6" x2="18" y2="18"></line>
+                                </svg>
+                            </button>
+                        </div>
+                    </td>
+                </tr>
+            }
+            <tr>
+                <td colspan="5">
+                    <div class="btn-list flex-nowrap justify-content-end">
+                        <button class="btn btn-ghost-primary" @onclick="AddMode" type="button">Add Mode</button>
+                    </div>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+</div>
+
+@code {
+    [Parameter] public ICollection<MultiplayerMode> MultiplayerModes { get; set; }
+
+    private void AddMode()
+    {
+        if (MultiplayerModes == null)
+            MultiplayerModes = new List<MultiplayerMode>();
+
+        MultiplayerModes.Add(new MultiplayerMode());
+    }
+
+    private void RemoveMode(int index)
+    {
+        MultiplayerModes.Remove(MultiplayerModes.ElementAt(index));
+    }
+}
diff --git a/LANCommander/Components/_Imports.razor b/LANCommander/Components/_Imports.razor
new file mode 100644
index 0000000..bdfbec1
--- /dev/null
+++ b/LANCommander/Components/_Imports.razor
@@ -0,0 +1,7 @@
+@using System.Net.Http
+@using Microsoft.AspNetCore.Authorization
+@using Microsoft.AspNetCore.Components.Authorization
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.JSInterop
\ No newline at end of file
diff --git a/LANCommander/Program.cs b/LANCommander/Program.cs
index a3f3d96..450b7dc 100644
--- a/LANCommander/Program.cs
+++ b/LANCommander/Program.cs
@@ -56,7 +56,9 @@ builder.Services.AddAuthentication(options =>
 builder.Services.AddControllersWithViews().AddJsonOptions(x =>
 {
     x.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
+    x.JsonSerializerOptions.MaxDepth = 3;
 });
+builder.Services.AddServerSideBlazor();
 
 builder.Services.AddScoped<SettingService>();
 builder.Services.AddScoped<ArchiveService>();
@@ -92,9 +94,14 @@ app.UseAuthorization();
 
 app.MapControllers();
 
-app.MapControllerRoute(
-    name: "default",
-    pattern: "{controller=Home}/{action=Index}/{id?}");
+app.UseEndpoints(endpoints =>
+{
+    endpoints.MapControllerRoute(
+        name: "default",
+        pattern: "{controller=Home}/{action=Index}/{id?}");
+    endpoints.MapBlazorHub();
+});
+
 app.MapRazorPages();
 
 if (!Directory.Exists("Upload"))
diff --git a/LANCommander/Views/Games/Edit.cshtml b/LANCommander/Views/Games/Edit.cshtml
index 22c5653..31e71fc 100644
--- a/LANCommander/Views/Games/Edit.cshtml
+++ b/LANCommander/Views/Games/Edit.cshtml
@@ -1,4 +1,5 @@
-@using LANCommander.Data.Models
+@using LANCommander.Components
+@using LANCommander.Data.Models
 @model LANCommander.Models.GameViewModel
 
 @{
@@ -79,6 +80,18 @@
                         </div>
                     </div>
 
+                    <div class="card-header">
+                        <h3 class="card-title">Actions</h3>
+                    </div>
+
+                    <component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions" />
+
+                    <div class="card-header">
+                        <h3 class="card-title">Multiplayer Modes</h3>
+                    </div>
+
+                    <component type="typeof(MultiplayerModeEditor)" render-mode="Server" param-MultiplayerModes="Model.Game.MultiplayerModes" />
+
                     <div class="card-footer">
                         <div class="d-flex">
                             <a asp-action="Index" class="btn btn-ghost-primary">Cancel</a>
diff --git a/LANCommander/Views/Shared/_Layout.cshtml b/LANCommander/Views/Shared/_Layout.cshtml
index 6da2064..7bcd38f 100644
--- a/LANCommander/Views/Shared/_Layout.cshtml
+++ b/LANCommander/Views/Shared/_Layout.cshtml
@@ -1,4 +1,9 @@
-<!DOCTYPE html>
+@inject Microsoft.AspNetCore.Http.IHttpContextAccessor _HttpContext
+@{
+    _HttpContext.HttpContext.Response.Headers["Cache-Control"] = "no-store";
+}
+
+<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="utf-8" />
@@ -6,6 +11,7 @@
     <title>@ViewData["Title"] - LANCommander</title>
     <link href="~/css/tabler.min.css" rel="stylesheet" />
     <link href="~/lib/selectize.js/css/selectize.bootstrap5.min.css" rel="stylesheet" />
+    <base href="~/"/>
 </head>
 <body>
     <header class="navbar navbar-expand-md navbar-light">
@@ -92,6 +98,7 @@
     <script src="~/lib/tabler/core/dist/js/tabler.min.js"></script>
     <script src="~/js/Modal.js"></script>
     <script src="~/js/Select.js"></script>
+    <script src="~/_framework/blazor.server.js"></script>
 
     @await RenderSectionAsync("Scripts", required: false)
 </body>