squash a bunch of commits
This commit is contained in:
51
Shogi.UI/Shared/LocalStorage.cs
Normal file
51
Shogi.UI/Shared/LocalStorage.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Microsoft.JSInterop;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Shogi.UI.Shared
|
||||
{
|
||||
public class LocalStorage : ILocalStorage
|
||||
{
|
||||
private readonly JsonSerializerOptions jsonOptions;
|
||||
private readonly IJSRuntime jSRuntime;
|
||||
|
||||
public LocalStorage(IJSRuntime jSRuntime)
|
||||
{
|
||||
jsonOptions = new JsonSerializerOptions();
|
||||
jsonOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
this.jSRuntime = jSRuntime;
|
||||
}
|
||||
|
||||
public ValueTask Set<T>(string key, T value)
|
||||
{
|
||||
var serialized = JsonSerializer.Serialize(value);
|
||||
return jSRuntime.InvokeVoidAsync("localStorage.setItem", key, serialized);
|
||||
}
|
||||
|
||||
public async ValueTask<T?> Get<T>(string key) where T : struct
|
||||
{
|
||||
|
||||
var value = await jSRuntime.InvokeAsync<string>("localStorage.getItem", key);
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(value, jsonOptions);
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask Delete(string key)
|
||||
{
|
||||
return jSRuntime.InvokeVoidAsync("localStorage.removeItem", key);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILocalStorage
|
||||
{
|
||||
ValueTask Delete(string key);
|
||||
ValueTask<T?> Get<T>(string key) where T : struct;
|
||||
ValueTask Set<T>(string key, T value);
|
||||
}
|
||||
}
|
||||
24
Shogi.UI/Shared/LoginDisplay.razor
Normal file
24
Shogi.UI/Shared/LoginDisplay.razor
Normal file
@@ -0,0 +1,24 @@
|
||||
@*@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication*@
|
||||
|
||||
@*@inject NavigationManager Navigation
|
||||
@inject SignOutSessionStateManager SignOutManager*@
|
||||
|
||||
@*<AuthorizeView>
|
||||
<Authorized>
|
||||
Hello, @context.User.Identity?.Name!
|
||||
<button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<a href="authentication/login">Log in</a>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>*@
|
||||
|
||||
@code{
|
||||
// https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-6.0#customize-the-authentication-user-interface
|
||||
//private async Task BeginSignOut(MouseEventArgs args)
|
||||
//{
|
||||
// await SignOutManager.SetSignOutState();
|
||||
// Navigation.NavigateTo("authentication/logout");
|
||||
//}
|
||||
}
|
||||
4
Shogi.UI/Shared/MainLayout.razor
Normal file
4
Shogi.UI/Shared/MainLayout.razor
Normal file
@@ -0,0 +1,4 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@Body
|
||||
|
||||
3
Shogi.UI/Shared/MainLayout.razor.css
Normal file
3
Shogi.UI/Shared/MainLayout.razor.css
Normal file
@@ -0,0 +1,3 @@
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
54
Shogi.UI/Shared/Modal/ModalService.cs
Normal file
54
Shogi.UI/Shared/Modal/ModalService.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace Shogi.UI.Shared.Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// An injectible service which can be invoked to display preset modals via the <Modals /> razor component.
|
||||
/// Only one modal me be visible at a time.
|
||||
/// </summary>
|
||||
public class ModalService
|
||||
{
|
||||
public event EventHandler<ModalVisibilityChangedEventArgs>? ModalVisibilityChangedEvent;
|
||||
|
||||
public ModalService()
|
||||
{
|
||||
}
|
||||
|
||||
public bool LoginModalIsVisible { get; private set; }
|
||||
public bool GuestAccountDescriptionIsVisible { get; private set; }
|
||||
|
||||
public void ShowLoginModal()
|
||||
{
|
||||
SetAllVisibilitiesToFalse();
|
||||
LoginModalIsVisible = true;
|
||||
EmitCurrentState();
|
||||
}
|
||||
|
||||
public void ShowGuestAccountDescriptionModal()
|
||||
{
|
||||
SetAllVisibilitiesToFalse();
|
||||
GuestAccountDescriptionIsVisible = true;
|
||||
EmitCurrentState();
|
||||
}
|
||||
|
||||
public void HideAllModals()
|
||||
{
|
||||
SetAllVisibilitiesToFalse();
|
||||
EmitCurrentState();
|
||||
}
|
||||
|
||||
private void EmitCurrentState()
|
||||
{
|
||||
ModalVisibilityChangedEvent?.Invoke(this, new()
|
||||
{
|
||||
LoginModalIsVisible = LoginModalIsVisible,
|
||||
GuestAccountDescriptionIsVisible = GuestAccountDescriptionIsVisible
|
||||
});
|
||||
}
|
||||
|
||||
private void SetAllVisibilitiesToFalse()
|
||||
{
|
||||
LoginModalIsVisible = false;
|
||||
GuestAccountDescriptionIsVisible = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
9
Shogi.UI/Shared/Modal/ModalVisibilityChangedEventArgs.cs
Normal file
9
Shogi.UI/Shared/Modal/ModalVisibilityChangedEventArgs.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Shogi.UI.Shared.Modal
|
||||
{
|
||||
public class ModalVisibilityChangedEventArgs : EventArgs
|
||||
{
|
||||
public bool LoginModalIsVisible { get; set; }
|
||||
public bool GuestAccountDescriptionIsVisible { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
39
Shogi.UI/Shared/Modal/Modals.razor
Normal file
39
Shogi.UI/Shared/Modal/Modals.razor
Normal file
@@ -0,0 +1,39 @@
|
||||
@inject ModalService modalService
|
||||
@inject AccountManager Account
|
||||
@inject NavigationManager NavManager
|
||||
@inject ILocalStorage localStorage
|
||||
|
||||
@if (shouldShow)
|
||||
{
|
||||
<div class="my-modal-background">
|
||||
<div class="my-modal">
|
||||
@if (modalService.LoginModalIsVisible)
|
||||
{
|
||||
|
||||
}
|
||||
else if (modalService.GuestAccountDescriptionIsVisible)
|
||||
{
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
bool shouldShow = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
modalService.ModalVisibilityChangedEvent += OnModalChange;
|
||||
}
|
||||
|
||||
void OnModalChange(object? sender, ModalVisibilityChangedEventArgs args)
|
||||
{
|
||||
Console.WriteLine("Modal Change");
|
||||
if (args != null)
|
||||
{
|
||||
shouldShow = args.LoginModalIsVisible || args.GuestAccountDescriptionIsVisible;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Shogi.UI/Shared/Modal/Modals.razor.css
Normal file
21
Shogi.UI/Shared/Modal/Modals.razor.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.my-modal-background {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
position: fixed;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
inset: 0;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.my-modal {
|
||||
text-align: center;
|
||||
background-color: var(--contrast-color);
|
||||
padding: 1rem;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
.account-description {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content max-content;
|
||||
column-gap: 1.5rem;
|
||||
}
|
||||
5
Shogi.UI/Shared/MyNotAuthorized.razor
Normal file
5
Shogi.UI/Shared/MyNotAuthorized.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
<h3>MyNotAuthorized</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
14
Shogi.UI/Shared/RedirectToLogin.razor
Normal file
14
Shogi.UI/Shared/RedirectToLogin.razor
Normal file
@@ -0,0 +1,14 @@
|
||||
@inject NavigationManager Navigation
|
||||
@inject ModalService ModalService
|
||||
@inject AccountManager ShogiService
|
||||
|
||||
@*<button @onclick="() => ShogiService.ConnectAsync(asGuest: true)">Guest Login</button>*@
|
||||
<div>Not implemented!</div>
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
//ModalService.ShowLoginModal();
|
||||
//Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}");
|
||||
}
|
||||
}
|
||||
28
Shogi.UI/Shared/RotatingCogsSvg.razor
Normal file
28
Shogi.UI/Shared/RotatingCogsSvg.razor
Normal file
@@ -0,0 +1,28 @@
|
||||
<div className="rotating-cogs">
|
||||
|
||||
<svg className="cog" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<path fill="#444" d="M94.1 58.8c.6-2.8.9-5.8.9-8.8s-.3-6-.9-8.8l-11.7-.4c-.7-2.6-1.7-5-3-7.3l8-8.5c-3.3-4.9-7.5-9.2-12.5-12.5l-8.5 8c-2.3-1.3-4.7-2.3-7.3-3l-.3-11.6C56 5.3 53 5 50 5s-6 .3-8.8.9l-.4 11.7c-2.6.7-5 1.7-7.3 3l-8.5-8c-4.9 3.3-9.2 7.5-12.5 12.5l8 8.5c-1.3 2.3-2.3 4.7-3 7.3l-11.6.3C5.3 44 5 47 5 50s.3 6 .9 8.8l11.7.4c.7 2.6 1.7 5 3 7.3l-8 8.5c3.3 4.9 7.5 9.2 12.5 12.5l8.5-8c2.3 1.3 4.7 2.3 7.3 3l.4 11.7c2.7.5 5.7.8 8.7.8s6-.3 8.8-.9l.4-11.7c2.6-.7 5-1.7 7.3-3l8.5 8c4.9-3.3 9.2-7.5 12.5-12.5l-8-8.5c1.3-2.3 2.3-4.7 3-7.3l11.6-.3zM50 66.9c-9.3 0-16.9-7.6-16.9-16.9S40.7 33.1 50 33.1 66.9 40.7 66.9 50 59.3 66.9 50 66.9z" />
|
||||
<animateTransform attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="20 0 0"
|
||||
to="380 0 0"
|
||||
dur="12s"
|
||||
repeatCount="indefinite" />
|
||||
</svg>
|
||||
|
||||
<svg className="cog" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<path fill="#444" d="M94.1 58.8c.6-2.8.9-5.8.9-8.8s-.3-6-.9-8.8l-11.7-.4c-.7-2.6-1.7-5-3-7.3l8-8.5c-3.3-4.9-7.5-9.2-12.5-12.5l-8.5 8c-2.3-1.3-4.7-2.3-7.3-3l-.3-11.6C56 5.3 53 5 50 5s-6 .3-8.8.9l-.4 11.7c-2.6.7-5 1.7-7.3 3l-8.5-8c-4.9 3.3-9.2 7.5-12.5 12.5l8 8.5c-1.3 2.3-2.3 4.7-3 7.3l-11.6.3C5.3 44 5 47 5 50s.3 6 .9 8.8l11.7.4c.7 2.6 1.7 5 3 7.3l-8 8.5c3.3 4.9 7.5 9.2 12.5 12.5l8.5-8c2.3 1.3 4.7 2.3 7.3 3l.4 11.7c2.7.5 5.7.8 8.7.8s6-.3 8.8-.9l.4-11.7c2.6-.7 5-1.7 7.3-3l8.5 8c4.9-3.3 9.2-7.5 12.5-12.5l-8-8.5c1.3-2.3 2.3-4.7 3-7.3l11.6-.3zM50 66.9c-9.3 0-16.9-7.6-16.9-16.9S40.7 33.1 50 33.1 66.9 40.7 66.9 50 59.3 66.9 50 66.9z" />
|
||||
<animateTransform attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="360 0 0"
|
||||
to="0 0 0"
|
||||
dur="12s"
|
||||
repeatCount="indefinite" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
15
Shogi.UI/Shared/RotatingCogsSvg.razor.css
Normal file
15
Shogi.UI/Shared/RotatingCogsSvg.razor.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.rotating-cogs {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left, right {
|
||||
position: relative;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.left {
|
||||
left: 2px;
|
||||
}
|
||||
.right {
|
||||
left: -2px;
|
||||
}
|
||||
89
Shogi.UI/Shared/ShogiSocket.cs
Normal file
89
Shogi.UI/Shared/ShogiSocket.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Shogi.Contracts.Socket;
|
||||
using Shogi.Contracts.Types;
|
||||
using System.Buffers;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Shogi.UI.Shared;
|
||||
|
||||
public class ShogiSocket : IDisposable
|
||||
{
|
||||
public event EventHandler<SessionCreatedSocketMessage>? OnCreateGameMessage;
|
||||
|
||||
private readonly ClientWebSocket socket;
|
||||
private readonly JsonSerializerOptions serializerOptions;
|
||||
private readonly UriBuilder uriBuilder;
|
||||
private readonly CancellationTokenSource cancelToken;
|
||||
private readonly IMemoryOwner<byte> memoryOwner;
|
||||
private bool disposedValue;
|
||||
|
||||
public ShogiSocket(IConfiguration configuration, ClientWebSocket socket, JsonSerializerOptions serializerOptions)
|
||||
{
|
||||
this.socket = socket;
|
||||
this.serializerOptions = serializerOptions;
|
||||
this.uriBuilder = new UriBuilder(configuration["SocketUrl"]);
|
||||
this.cancelToken = new CancellationTokenSource();
|
||||
this.memoryOwner = MemoryPool<byte>.Shared.Rent(1024 * 2);
|
||||
}
|
||||
|
||||
public async Task OpenAsync(string token)
|
||||
{
|
||||
uriBuilder.Query = new QueryBuilder
|
||||
{
|
||||
{ "token", token }
|
||||
}.ToQueryString().Value;
|
||||
|
||||
await socket.ConnectAsync(this.uriBuilder.Uri, cancelToken.Token);
|
||||
Console.WriteLine("CONNECTED");
|
||||
Listen();
|
||||
}
|
||||
|
||||
private async void Listen()
|
||||
{
|
||||
while (socket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested)
|
||||
{
|
||||
var result = await socket.ReceiveAsync(this.memoryOwner.Memory, cancelToken.Token);
|
||||
var memory = this.memoryOwner.Memory[..result.Count].ToArray();
|
||||
var action = JsonDocument
|
||||
.Parse(memory[..result.Count])
|
||||
.RootElement
|
||||
.GetProperty(nameof(ISocketResponse.Action))
|
||||
.Deserialize<SocketAction>();
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case SocketAction.SessionCreated:
|
||||
this.OnCreateGameMessage?.Invoke(this, JsonSerializer.Deserialize<SessionCreatedSocketMessage>(memory, this.serializerOptions)!);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cancelToken.IsCancellationRequested)
|
||||
{
|
||||
throw new InvalidOperationException("Stopped socket listening without cancelling.");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
cancelToken.Cancel();
|
||||
socket.Dispose();
|
||||
memoryOwner.Dispose();
|
||||
}
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user