Add frontend creation game (#13)

Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
2025-04-13 17:21:15 +02:00
parent 3ea96186e7
commit 225e8ba140
60 changed files with 913 additions and 231 deletions

View File

@@ -3,7 +3,7 @@
<div class="account-setting-container" tabindex="1000">
<div class="account-setting-content @(ContentVisile ? string.Empty : "invisible")">
@if (!AuthentificationService.IsLogin)
@if (!IsLogin)
{
<EditForm EditContext="EditContext" OnSubmit="HandleLoginSubmit">
<FluentValidationValidator />

View File

@@ -1,28 +1,21 @@
using GameIdeas.BlazorApp.Services;
using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components.Forms;
namespace GameIdeas.BlazorApp.Shared.Components.Account;
public partial class AccountSettings (
AuthentificationService AuthentificationService)
public partial class AccountSettings
{
private bool ContentVisile = false;
private EditContext? EditContext;
private LoginDto LoginDto = new();
private bool IsLoading = false;
private bool IsLogin = true;
protected override void OnInitialized()
{
EditContext = new EditContext(LoginDto);
}
public void Open()
{
ContentVisile = true;
StateHasChanged();
}
public void Close()
{
ContentVisile = false;
@@ -45,15 +38,10 @@ public partial class AccountSettings (
IsLoading = true;
await Task.Delay(TimeSpan.FromSeconds(5));
Close();
AuthentificationService.Login();
IsLoading = false;
LoginDto = new();
EditContext = new EditContext(LoginDto);
}
private void HandleLogoutClicked()
{
Close();
AuthentificationService.Logout();
}
}

View File

@@ -5,7 +5,7 @@
}
.backdrop-filter.overlay {
background-color: var(--grey-filter);
background-color: rgba(0, 0, 0, 0.2);
}
.backdrop-filter.transparent {

View File

@@ -1,3 +1,3 @@
window.setBodyOverflow = (overflow) => {
document.getElementsByTagName('html')[0].style.overflow = overflow;
document.getElementsByClassName('page')[0].style.overflow = overflow;
}

View File

@@ -0,0 +1,18 @@
@using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
@using GameIdeas.BlazorApp.Shared.Constants
<CascadingValue Value="this">
<div class="popup-wrapper" style="@GetDisplayStyle()">
<div class="popup-content">
@if (Closable)
{
<button @onclick="HandleBackdropFilterClicked">@Icons.Shared.Close</button>
}
@ChildContent
</div>
</div>
</CascadingValue>
<BackdropFilter @ref="BackdropFilter" OnClick="HandleBackdropFilterClicked" CloseOnClick="@Closable"
AllowBodyScroll="false" Color="BackdropFilterColor.Overlay" />

View File

@@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Popup;
public partial class Popup
{
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public bool IsDrawerOpen { get; set; }
[Parameter] public EventCallback BackdropFilterClicked { get; set; }
[Parameter] public bool Closable { get; set; } = true;
public bool IsOpen { get; set; }
public EventHandler<bool>? StateChanged { get; set; }
private BackdropFilter.BackdropFilter? BackdropFilter;
protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
if (BackdropFilter?.IsVisible == true && IsOpen)
{
await BackdropFilter.Hide();
}
if (BackdropFilter?.IsVisible == false && IsOpen)
{
await BackdropFilter.Show();
}
}
public async Task Open()
{
IsOpen = true;
await BackdropFilter?.Show()!;
StateChanged?.Invoke(null, IsOpen);
StateHasChanged();
}
public async Task Close()
{
IsOpen = false;
await BackdropFilter?.Hide()!;
StateChanged?.Invoke(null, IsOpen);
StateHasChanged();
}
private async Task HandleBackdropFilterClicked()
{
await BackdropFilterClicked.InvokeAsync();
}
private string GetDisplayStyle() => IsOpen ? "display: flex;" : "display: none;";
}

View File

@@ -0,0 +1,41 @@
.popup-wrapper {
display: none;
justify-content: center;
align-items: center;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: var(--index-popup);
}
.popup-content {
position: relative;
background-color: var(--dropdown-content);
padding: 10px;
border-radius: var(--big-radius);
box-shadow: var(--drop-shadow);
}
.popup-content button {
top: 4px;
right: 4px;
position: absolute;
background: transparent;
border: none;
outline: none;
padding: 0;
height: 20px;
width: 20px;
}
::deep .popup-content button svg {
height: 18px;
fill: var(--white);
}
.popup-content button:hover {
cursor: pointer;
background: var(--input-selected);
border-radius: var(--small-radius);
}

View File

@@ -1,4 +1,6 @@
@using GameIdeas.Shared.Constants
@using GameIdeas.BlazorApp.Shared.Constants
@using GameIdeas.Shared.Constants
<div class="search-container">
<input @ref=InputText
type="text"
@@ -13,7 +15,7 @@
@if (!string.IsNullOrEmpty(Text))
{
<div class="clear-icon" @onclick=HandleClearClicked>
@ClearIcon
@Icons.Shared.Close;
</div>
}

View File

@@ -1,3 +1,4 @@
using GameIdeas.BlazorApp.Shared.Constants;
using GameIdeas.Shared.Constants;
using Microsoft.AspNetCore.Components;
@@ -14,7 +15,6 @@ public partial class SearchInput
[Parameter] public SearchInputIcon Icon { get; set; }
private ElementReference InputText;
private readonly MarkupString ClearIcon = new(Icons.Search.Clear);
protected override void OnInitialized()
{
@@ -52,8 +52,8 @@ public partial class SearchInput
{
return Icon switch
{
SearchInputIcon.Dropdown => new MarkupString(Icons.Search.Triangle),
SearchInputIcon.Search => new MarkupString(Icons.Search.Glass),
SearchInputIcon.Dropdown => Icons.Search.Triangle,
SearchInputIcon.Search => Icons.Search.Glass,
_ => new MarkupString()
};
}

View File

@@ -24,6 +24,7 @@
background: none !important;
color: var(--white);
height: 100%;
width: 100%;
padding: 0;
min-width: 0;
}

View File

@@ -1,9 +1,9 @@
namespace GameIdeas.BlazorApp.Shared.Components.Select.Models;
public class SelectElement<TItem>
public class SelectElement<TItem>(TItem item, string? label)
{
public TItem? Item { get; set; }
public string? Label { get; set; }
public TItem Item { get; set; } = item;
public string? Label { get; set; } = label;
public bool IsSelected { get; set; } = false;
public bool IsNew { get; set; } = false;
}

View File

@@ -5,5 +5,6 @@ public enum SelectListTheme
Navigation,
Sort,
Filter,
AdvancedFilter
AdvancedFilter,
Creation
}

View File

@@ -30,14 +30,22 @@
margin: 2px 6px;
border-bottom: 2px solid var(--input-selected);
}
/* Advanced filter */
::deep .select-button.advancedfilter .search-container {
height: 24px;
background: var(--input-secondary);
}
::deep .select-button.advancedfilter .search-container input::placeholder {
color: #bbb;
::deep .select-button.advancedfilter .search-container input::placeholder {
color: #bbb;
}
/* Creation */
::deep .select-button.creation .search-container {
height: 24px;
background: var(--input-secondary);
border: solid 1px var(--input-selected);
box-sizing: border-box;
}
/* width */

View File

@@ -1,6 +1,7 @@
@using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
@using GameIdeas.BlazorApp.Shared.Components.Select.Components
@typeparam TItem
@typeparam TItem
@typeparam THeader
<div class="select-list">
<div class="select-button" @onclick=HandleButtonClicked>
@@ -11,10 +12,10 @@
@if (IsContentOpen)
{
<div class="select-content @(Enum.GetName(Theme)?.ToLower())">
@foreach (var item in Headers)
@foreach (var header in Headers)
{
<SelectListElement TItem="TItem"
Value="item"
<SelectListElement TItem="THeader"
Value="header"
ValueChanged="HandleHeaderClicked"
Theme="Theme" />
}

View File

@@ -3,24 +3,25 @@ using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select;
public partial class SelectList<TItem>
public partial class SelectList<TItem, THeader>
{
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public TItem? Value { get; set; }
[Parameter] public EventCallback<TItem?> ValueChanged { get; set; }
[Parameter] public TItem? Header { get; set; }
[Parameter] public EventCallback<TItem?> HeaderChanged { get; set; }
[Parameter] public THeader? Header { get; set; }
[Parameter] public EventCallback<THeader?> HeaderChanged { get; set; }
[Parameter] public IEnumerable<SelectElement<TItem>> Items { get; set; } = [];
[Parameter] public IEnumerable<SelectElement<TItem>> Headers { get; set; } = [];
[Parameter] public IEnumerable<SelectElement<THeader>> Headers { get; set; } = [];
[Parameter] public SelectListTheme Theme { get; set; }
[Parameter] public bool AlignRight { get; set; }
private bool IsContentOpen = false;
private void HandleButtonClicked()
{
public void Close() =>
IsContentOpen = false;
private void HandleButtonClicked() =>
IsContentOpen = !IsContentOpen;
}
private void HandleContentClosed()
{
@@ -40,7 +41,7 @@ public partial class SelectList<TItem>
await ValueChanged.InvokeAsync(Value);
}
private async Task HandleHeaderClicked(SelectElement<TItem> selectedValue)
private async Task HandleHeaderClicked(SelectElement<THeader> selectedValue)
{
foreach (var header in Headers)
{

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="slider-track"></div>
<input type="range" id="min-range" style="@StatusColor(Value)" min="@Params.Min" max="@Params.Max"
@bind="@Value" @bind:event="oninput" @bind:after=HandleSlideOnInput />
<div class="values">
<span class="value">@Params.Min</span>
<span class="value">@Params.Max</span>
</div>
</div>

View File

@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Slider;
public partial class Slider
{
[Parameter] public SliderParams Params { get; set; } = new();
[Parameter] public int Value { get; set; }
[Parameter] public EventCallback<int> ValueChanged { get; set; }
private async Task HandleSlideOnInput()
{
await ValueChanged.InvokeAsync(Value);
}
private string StatusColor(int value)
{
string str = "--thumb-color: var({0});";
int firstTier = (int)Math.Floor(0.33 * Params.Max);
int secondTier = (int)Math.Ceiling(0.66 * Params.Max);
return value switch
{
int x when x <= firstTier => string.Format(str, "--red"),
int x when x >= secondTier => string.Format(str, "--green"),
_ => string.Format(str, "--yellow"),
};
}
}

View File

@@ -0,0 +1,89 @@
.container {
position: relative;
width: 100%;
z-index: var(--index-component)
}
input[type="range"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
outline: none;
margin: auto;
position: absolute;
top: 0;
bottom: 0;
background-color: transparent;
pointer-events: none;
}
.slider-track {
width: 100%;
height: 2px;
margin: auto;
border-radius: 2px;
background: var(--input-primary);
}
input[type="range"]::-webkit-slider-runnable-track {
-webkit-appearance: none;
height: 2px;
}
input[type="range"]::-moz-range-track {
-moz-appearance: none;
height: 2px;
}
input[type="range"]::-ms-track {
appearance: none;
height: 2px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 1em;
width: 1em;
background-color: var(--thumb-color);
cursor: pointer;
margin-top: -6px;
pointer-events: auto;
border-radius: 50%;
}
input[type="range"]::-moz-range-thumb {
-webkit-appearance: none;
height: 1em;
width: 1em;
cursor: pointer;
border-radius: 50%;
background-color: var(--thumb-color);
pointer-events: auto;
border: none;
}
input[type="range"]::-ms-thumb {
appearance: none;
height: 1em;
width: 1em;
cursor: pointer;
border-radius: 50%;
background-color: var(--thumb-color);
pointer-events: auto;
}
.values {
display: flex;
position: absolute;
margin-top: 2px;
width: 100%;
font-weight: bold;
justify-content: space-between;
}
.value {
width: 1em;
text-align:center;
}

View File

@@ -0,0 +1,8 @@
namespace GameIdeas.BlazorApp.Shared.Components.Slider;
public class SliderParams
{
public int Min{ get; set; }
public int Max { get; set; }
public int Gap { get; set; } = 0;
}

View File

@@ -1,13 +1,13 @@
<div class="container">
<div class="slider-track" style="@FillColor()"></div>
<input type="range" id="min-range" style="@StatusColor(Params.ValueMin)" min="@Params.Min" max="@Params.Max"
@bind="@Params.ValueMin" @bind:event="oninput" @bind:after=HandleSlideOnInput />
<input type="range" id="max-range" style="@StatusColor(Params.ValueMax)" min="@Params.Min" max="@Params.Max"
@bind="@Params.ValueMax" @bind:event="oninput" @bind:after=HandleSlideTwoInput />
<input type="range" id="min-range" style="@StatusColor(Min)" min="@Params.Min" max="@Params.Max"
@bind="@Min" @bind:event="oninput" @bind:after=HandleSlideOnInput />
<input type="range" id="max-range" style="@StatusColor(Max)" min="@Params.Min" max="@Params.Max"
@bind="@Max" @bind:event="oninput" @bind:after=HandleSlideTwoInput />
<div class="values">
<span class="value">@Params.ValueMin</span>
<span class="value">@Params.ValueMax</span>
<span class="value">@Min</span>
<span class="value">@Max</span>
</div>
</div>

View File

@@ -13,30 +13,28 @@ public partial class SliderRange
private async Task HandleSlideTwoInput()
{
if (Params.ValueMax - Params.ValueMin <= Params.Gap)
if (Max - Min <= Params.Gap)
{
Params.ValueMin = Params.ValueMax - Params.Gap;
Min = Max - Params.Gap;
}
Max = Params.Max;
await MaxChanged.InvokeAsync(Params.Max);
await MaxChanged.InvokeAsync(Max);
}
private async Task HandleSlideOnInput()
{
if (Params.ValueMax - Params.ValueMin <= Params.Gap)
if (Max - Min <= Params.Gap)
{
Params.ValueMax = Params.ValueMin + Params.Gap;
Max = Min + Params.Gap;
}
Min = Params.Min;
await MinChanged.InvokeAsync(Params.Min);
await MinChanged.InvokeAsync(Min);
}
private string FillColor()
{
var percent1 = (double)(Params.ValueMin - Params.Min) / (Params.Max - Params.Min) * 100;
var percent2 = (double)(Params.ValueMax - Params.Min) / (Params.Max - Params.Min) * 100;
var percent1 = (double)(Min - Params.Min) / (Params.Max - Params.Min) * 100;
var percent2 = (double)(Max - Params.Min) / (Params.Max - Params.Min) * 100;
return $"background: linear-gradient(to right, var(--line) {percent1}% , var(--violet) {percent1}% , var(--violet) {percent2}%, var(--line) {percent2}%)";
}

View File

@@ -4,7 +4,5 @@ public class SliderRangeParams
{
public int Min{ get; set; }
public int Max { get; set; }
public int ValueMin { get; set; }
public int ValueMax { get; set; }
public int Gap { get; set; } = 0;
}

View File

@@ -0,0 +1,14 @@
namespace GameIdeas.BlazorApp.Shared.Constants;
public static class Endpoints
{
public static class Game
{
}
public static class Category
{
public static readonly string AllCategories = "api/Category/All";
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Constants;
public static class Icons
{
private const string OpenBraket = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">";
private const string CloseBraket = "</svg>";
public static class Search
{
public readonly static MarkupString Glass = new(OpenBraket +
"<path d=\"M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z\" />" +
CloseBraket);
public readonly static MarkupString Triangle = new(OpenBraket +
"<path d=\"M1 3H23L12 22\" />" +
CloseBraket);
}
public static class Shared
{
public readonly static MarkupString Close = new(OpenBraket +
"<path d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" />" +
CloseBraket);
}
}

View File

@@ -0,0 +1,3 @@
namespace GameIdeas.BlazorApp.Shared.Exceptions;
public class CategoryNotFoundException(string message) : Exception(message);