Skip to content

Commit

Permalink
Added JmSpinningWheel component
Browse files Browse the repository at this point in the history
  • Loading branch information
Jimmys20 committed Mar 3, 2024
1 parent 07d223b commit ba6a577
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

<ItemGroup>
<PackageReference Include="BlazorPro.BlazorSize" Version="6.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.2" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions demo/Jimmys20.BlazorComponents.Demo/Layout/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="spinningwheel">
<span class="bi bi-arrow-repeat-nav-menu" aria-hidden="true"></span> Spinning Wheel
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
Expand Down
4 changes: 4 additions & 0 deletions demo/Jimmys20.BlazorComponents.Demo/Layout/NavMenu.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}

.bi-arrow-repeat-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-repeat' viewBox='0 0 16 16'%3E%3Cpath d='M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41m-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9'/%3E%3Cpath fill-rule='evenodd' d='M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5 5 0 0 0 8 3M3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9z'/%3E%3C/svg%3E");
}

.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
Expand Down
44 changes: 44 additions & 0 deletions demo/Jimmys20.BlazorComponents.Demo/Pages/SpinningWheel.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@page "/spinningwheel"
@inject IDialogService Dialog

<PageTitle>Spinning wheel</PageTitle>

<h1>Spinning wheel</h1>

<JmSpinningWheel @ref="_spinningWheelRef"
NumberOfSlots="_numberOfSlots"
SlotNames="_slotNames"
Size="600"
SpinStarted="OnSpinStarted"
SpinCompleted="OnSpinCompleted" />

<JmButton Color="Color.Primary" Clicked="Spin">Spin</JmButton>

@code {
private JmSpinningWheel _spinningWheelRef;
private int _numberOfSlots = 18;
private string[] _slotNames =
[
"50", "100", "150", "200", "1000", "500", "550", "600", "650",
"700", "10", "800", "2000", "60", "995", "500", "10000", "900"
];

private int _numberOfTimes = 5;
private bool _shouldRandomizeNumberOfSpins = true;

private async Task Spin()
{
await _spinningWheelRef.Spin(_numberOfTimes, _shouldRandomizeNumberOfSpins);
}

private void OnSpinStarted()
{
Console.WriteLine("Spin started");
}

private async Task OnSpinCompleted(string nameOfSelectedSlot)
{
//Console.WriteLine($"Spin completed - nameOfSelectedSlot: {nameOfSelectedSlot}");
await Dialog.Alert($"Spin completed - nameOfSelectedSlot: {nameOfSelectedSlot}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<ItemGroup>
<PackageReference Include="BlazorComponentUtilities" Version="1.8.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.2" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions src/Jimmys20.BlazorComponents/Extensions/RandomExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Jimmys20.BlazorComponents.Extensions;

internal static class RandomExtensions
{
public static double NextDouble(this Random random, double minValue, double maxValue)
{
return random.NextDouble() * (maxValue - minValue) + minValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public partial class JmGridLayout<T>

protected override void OnParametersSet()
{
Items ??= Enumerable.Empty<T>();
Items ??= [];
}

internal void AddColumn(JmGridLayoutColumn<T> gridLayoutColumn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
<PackageReference Include="Excubo.Blazor.Canvas" Version="3.2.42" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.2" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Jimmys20.BlazorComponents.SpinningWheel.Internal;

internal class Sector
{
public string Color { get; init; }
public string Label { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@namespace Jimmys20.BlazorComponents

<canvas @ref="_canvasRef" id="wheel" width="@Size" height="@Size" @attributes="AdditionalAttributes"></canvas>
180 changes: 180 additions & 0 deletions src/Jimmys20.BlazorComponents/SpinningWheel/JmSpinningWheel.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using Excubo.Blazor.Canvas;
using Excubo.Blazor.Canvas.Contexts;
using Jimmys20.BlazorComponents.Extensions;
using Jimmys20.BlazorComponents.SpinningWheel.Internal;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Jimmys20.BlazorComponents;

public partial class JmSpinningWheel : IAsyncDisposable
{
//private static readonly string[] Colors = ["#f82", "#0bf", "#fb0", "#0fb", "#b0f", "#f0b", "#bf0"];
private static readonly string[] Colors =
[
"#ffcc01", "#675ca1", "#059dde", "#a7d02a", "#26cda2", "#903288", "#222025", "#e50305", "#eb7008"
];

private IJSObjectReference _module;
private DotNetObjectReference<JmSpinningWheel> _selfReference;
private ElementReference _canvasRef;
private Context2D _context = null;
private bool _isSpinning = false;
private double _angle = 0;
private int _selectedSlotIndex = -1;

/// <summary>
///
/// </summary>
[Parameter] public int NumberOfSlots { get; set; } = 18;

/// <summary>
///
/// </summary>
[Parameter] public IList<string> SlotNames { get; set; }

/// <summary>
///
/// </summary>
//[Parameter] public string SelectedSlot { get; set; }

/// <summary>
///
/// </summary>
[Parameter] public EventCallback SpinStarted { get; set; }

/// <summary>
///
/// </summary>
[Parameter] public EventCallback<string> SpinCompleted { get; set; }

/// <summary>
///
/// </summary>
[Parameter] public int Size { get; set; }

/// <summary>
/// Captures values that don't match any other parameter.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> AdditionalAttributes { get; set; }

[Inject] private IJSRuntime JS { get; set; }

private double Diameter => Size;
private double Radius => Diameter / 2;
private double Arc => Math.Tau / NumberOfSlots;

private static double Mod(double n, double m) => (n % m + m) % m;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_module = await JS.InvokeAsync<IJSObjectReference>("import",
"./_content/Jimmys20.BlazorComponents/js/spinning-wheel.js");
_selfReference = DotNetObjectReference.Create(this);

_context = await JS.GetContext2DAsync(_canvasRef);
await DrawWheel();
}
}

public async Task Redraw()
{
await _context.ClearRectAsync(0, 0, Size, Size);
await DrawWheel();
}

private async Task DrawWheel()
{
await using Batch2D batch = _context.CreateBatch();

for (var i = 0; i < NumberOfSlots; i++)
{
var sector = new Sector
{
Color = Colors[i % Colors.Length],
Label = SlotNames[i % SlotNames.Count],
};

await DrawSector(batch, sector, i);
}
}

private async Task DrawSector(Batch2D batch, Sector sector, int i)
{
double angle = Arc * i;

await batch.SaveAsync();

await batch.BeginPathAsync();
await batch.FillStyleAsync(sector.Color);
await batch.MoveToAsync(Radius, Radius);
await batch.ArcAsync(Radius, Radius, Radius, angle, angle + Arc);
await batch.LineToAsync(Radius, Radius);
await batch.FillAsync(FillRule.NonZero);

await batch.TranslateAsync(Radius, Radius);
await batch.RotateAsync(angle + Arc / 2);
await batch.TextAlignAsync(TextAlign.Right);
await batch.FillStyleAsync("#fff");
await batch.FontAsync("bold 30px sans-serif");
await batch.FillTextAsync(sector.Label, Radius - 10, 10);

await batch.RestoreAsync();
}

public async Task Spin(int numberOfTimes, bool shouldRandomizeNumberOfSpins = false)
{
if (_isSpinning)
{
return;
}

_isSpinning = true;

var angleAbsolute = Mod(_angle, Math.Tau);

_selectedSlotIndex = Random.Shared.Next(0, NumberOfSlots);
var angleNew = Math.Tau - Arc * _selectedSlotIndex;
angleNew -= Random.Shared.NextDouble(0, Arc);
angleNew = Mod(angleNew, Math.Tau);

var angleDifference = Mod(angleNew - angleAbsolute, Math.Tau);

var revolutions = Math.Tau * (shouldRandomizeNumberOfSpins ? Random.Shared.Next(1, numberOfTimes + 1) : numberOfTimes);

_angle += angleDifference + revolutions;

var duration = Random.Shared.Next(4000, 5000);
await _module.InvokeVoidAsync("spin", _canvasRef, _angle, duration, _selfReference);

await SpinStarted.InvokeAsync();
}

[JSInvokable]
public async Task HandleAnimationFinish()
{
_isSpinning = false;
//_selectedSlotIndex = -1;

var nameOfSelectedSlot = SlotNames[_selectedSlotIndex];
await SpinCompleted.InvokeAsync(nameOfSelectedSlot);
}

public async ValueTask DisposeAsync()
{
_selfReference?.Dispose();

if (_module != null)
{
await _module.InvokeVoidAsync("cancelAnimations", _canvasRef);
await _module.DisposeAsync();
}

if (_context != null)
{
await _context.DisposeAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#wheel {
display: block;
border-radius: 50%;
transform: rotate(-90deg);
}
15 changes: 15 additions & 0 deletions src/Jimmys20.BlazorComponents/wwwroot/js/spinning-wheel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function spin(canvas, angle, duration, componentInstance) {
const spinAnimation = canvas.animate([{ rotate: `${angle}rad` }], {
duration: duration,
easing: 'cubic-bezier(0.2, 0, 0.1, 1)',
fill: 'forwards'
});

spinAnimation.addEventListener('finish', () => {
componentInstance.invokeMethodAsync('HandleAnimationFinish');
});
}

export function cancelAnimations(canvas) {
canvas.getAnimations().forEach(animation => animation.cancel());
}

0 comments on commit ba6a577

Please sign in to comment.