diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..da3d9e6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +.gitattributes text working-tree-encoding=UTF-8 eol=CRLF +.gitignore text working-tree-encoding=UTF-8 eol=CRLF + +*.cs text working-tree-encoding=UTF-8 eol=CRLF +*.razor text working-tree-encoding=UTF-8 eol=CRLF +*.css text working-tree-encoding=UTF-8 eol=CRLF +*.json text working-tree-encoding=UTF-8 eol=CRLF +*.js text working-tree-encoding=UTF-8 eol=CRLF +*.sln text working-tree-encoding=UTF-8 eol=CRLF +*.csproj text working-tree-encoding=UTF-8 eol=CRLF +*.ps1 text working-tree-encoding=UTF-16LE eol=CRLF diff --git a/Dockerfile b/Dockerfile index 95b4bb8..1dccfa4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base WORKDIR /app EXPOSE 5000 -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build WORKDIR /src COPY . . WORKDIR "/src/PlanningPoker.Web" diff --git a/PlanningPoker.Web/Game/GameInstance.cs b/PlanningPoker.Web/Game/GameInstance.cs index a97b9e4..318cb08 100644 --- a/PlanningPoker.Web/Game/GameInstance.cs +++ b/PlanningPoker.Web/Game/GameInstance.cs @@ -8,6 +8,7 @@ namespace PlanningPoker.Web.Game public class GameInstance { public event EventHandler Changed; + public event EventHandler PlaySound; private static readonly ConcurrentDictionary instances = new ConcurrentDictionary(); @@ -79,6 +80,7 @@ internal void Vote(Player player, string card) this.CurrentRound.Cards[player] = card; this.RaiseChanged(); + this.RaisePlaySound("place-card"); } internal void StartNewRound() @@ -91,6 +93,7 @@ internal void RevealCards() { this.CurrentRound.IsRevealed = true; this.RaiseChanged(); + this.RaisePlaySound("turn-cards"); } internal void RemovePlayer(Player player) @@ -104,5 +107,15 @@ internal void RemovePlayer(Player player) this.RaiseChanged(); } + + internal void PlayBingSound() + { + this.RaisePlaySound("bing-sound"); + } + + private void RaisePlaySound(string sound) + { + this.PlaySound?.Invoke(this, new PlaySoundEventArgs(sound)); + } } } diff --git a/PlanningPoker.Web/Game/PlaySoundEventArgs.cs b/PlanningPoker.Web/Game/PlaySoundEventArgs.cs new file mode 100644 index 0000000..01b066f --- /dev/null +++ b/PlanningPoker.Web/Game/PlaySoundEventArgs.cs @@ -0,0 +1,12 @@ +namespace PlanningPoker.Web.Game +{ + public class PlaySoundEventArgs + { + public string SoundName { get; } + + public PlaySoundEventArgs(string soundName) + { + this.SoundName = soundName; + } + } +} diff --git a/PlanningPoker.Web/Game/Player.cs b/PlanningPoker.Web/Game/Player.cs index afe02dc..99fd13f 100644 --- a/PlanningPoker.Web/Game/Player.cs +++ b/PlanningPoker.Web/Game/Player.cs @@ -5,9 +5,16 @@ namespace PlanningPoker.Web.Game { public class Player : IEquatable { + public event EventHandler PlaySound; + public string Name { get; set; } public Guid Secret { get; set; } + internal void NotifyPlayer() + { + this.PlaySound?.Invoke(this, new PlaySoundEventArgs("bing-sound")); + } + public bool Equals([AllowNull] Player other) { if (other is null) diff --git a/PlanningPoker.Web/Pages/Game.razor b/PlanningPoker.Web/Pages/Game.razor index bb67e05..b84a3b9 100644 --- a/PlanningPoker.Web/Pages/Game.razor +++ b/PlanningPoker.Web/Pages/Game.razor @@ -24,6 +24,9 @@
+
+ +
@@ -37,6 +40,10 @@ @foreach (var player in this.Game.Players) {
+
+ 🔊 +
+
@player.Name
@@ -59,6 +66,7 @@ { @:❌ } +
} @@ -88,4 +96,4 @@ } - \ No newline at end of file + diff --git a/PlanningPoker.Web/Pages/_Host.cshtml b/PlanningPoker.Web/Pages/_Host.cshtml index a2d25c9..e769c7c 100644 --- a/PlanningPoker.Web/Pages/_Host.cshtml +++ b/PlanningPoker.Web/Pages/_Host.cshtml @@ -23,7 +23,7 @@ - +
@@ -38,5 +38,6 @@
+ diff --git a/PlanningPoker.Web/PlanningPoker.Web.csproj b/PlanningPoker.Web/PlanningPoker.Web.csproj index e208bd1..fd88f8c 100644 --- a/PlanningPoker.Web/PlanningPoker.Web.csproj +++ b/PlanningPoker.Web/PlanningPoker.Web.csproj @@ -1,13 +1,18 @@ - - net8.0 - - - - - - - + + net9.0 + + + + + + + + + + + + diff --git a/PlanningPoker.Web/Shared/MainLayout.razor b/PlanningPoker.Web/Shared/MainLayout.razor index 00c3f1b..135cc54 100644 --- a/PlanningPoker.Web/Shared/MainLayout.razor +++ b/PlanningPoker.Web/Shared/MainLayout.razor @@ -6,7 +6,7 @@
diff --git a/PlanningPoker.Web/ViewModels/GameViewModel.cs b/PlanningPoker.Web/ViewModels/GameViewModel.cs index b1b9292..4d1d4a3 100644 --- a/PlanningPoker.Web/ViewModels/GameViewModel.cs +++ b/PlanningPoker.Web/ViewModels/GameViewModel.cs @@ -4,6 +4,7 @@ using Blazored.LocalStorage; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; using PlanningPoker.Web.Game; namespace PlanningPoker.Web.ViewModels @@ -16,6 +17,9 @@ public class GameViewModel : ComponentBase, IDisposable [Inject] public ILocalStorageService LocalStorage { get; set; } + [Inject] + public IJSRuntime JsRuntime { get; set; } + [Inject] public UserState UserState { get; set; } @@ -42,20 +46,15 @@ public string CardChoosenInCurrentRound } } - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + this.Game = GameInstance.Find(this.Hash); this.Game.Changed += this.Game_Changed; + this.Game.PlaySound += this.Game_PlaySound; this.UserState.NavigateToGameHash(this.Hash); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender == false) - { - return; - } Player player = null; if (await this.LocalStorage.ContainKeyAsync(nameof(Player)) == true) @@ -81,10 +80,36 @@ protected override async Task OnAfterRenderAsync(bool firstRender) this.Player = player; this.PlayerName = player.Name; + this.Player.PlaySound += this.Game_PlaySound; this.Game.Join(this.Player); + } + + private void Game_PlaySound(object sender, PlaySoundEventArgs e) + { + _ = this.InvokeAsync(async () => + { + try + { + await this.JsRuntime.InvokeVoidAsync("MediaPlayer.PlayAudio", e.SoundName); + } + catch { } + }); + } - this.StateHasChanged(); + protected void PlayBingSound() + { + this.Game.PlayBingSound(); + } + + protected void PlayBingSoundForPlayer(Player player) + { + player.NotifyPlayer(); + + if (player != this.Player) + { + this.Player.NotifyPlayer(); + } } protected void OnKeyDown(KeyboardEventArgs eventArgs) @@ -125,6 +150,8 @@ private void Game_Changed(object sender, EventArgs e) public void Dispose() { this.Game.Changed -= this.Game_Changed; + this.Game.PlaySound -= this.Game_PlaySound; + this.Player.PlaySound -= this.Game_PlaySound; this.Game.RemovePlayer(this.Player); } } diff --git a/PlanningPoker.Web/wwwroot/audio/bing-sound.mp3 b/PlanningPoker.Web/wwwroot/audio/bing-sound.mp3 new file mode 100644 index 0000000..59fec1e Binary files /dev/null and b/PlanningPoker.Web/wwwroot/audio/bing-sound.mp3 differ diff --git a/PlanningPoker.Web/wwwroot/audio/bing-sound.wav b/PlanningPoker.Web/wwwroot/audio/bing-sound.wav new file mode 100644 index 0000000..415d765 Binary files /dev/null and b/PlanningPoker.Web/wwwroot/audio/bing-sound.wav differ diff --git a/PlanningPoker.Web/wwwroot/audio/place-card.mp3 b/PlanningPoker.Web/wwwroot/audio/place-card.mp3 new file mode 100644 index 0000000..7fe51d5 Binary files /dev/null and b/PlanningPoker.Web/wwwroot/audio/place-card.mp3 differ diff --git a/PlanningPoker.Web/wwwroot/audio/place-card.wav b/PlanningPoker.Web/wwwroot/audio/place-card.wav new file mode 100644 index 0000000..cf1e0d1 Binary files /dev/null and b/PlanningPoker.Web/wwwroot/audio/place-card.wav differ diff --git a/PlanningPoker.Web/wwwroot/audio/turn-cards.mp3 b/PlanningPoker.Web/wwwroot/audio/turn-cards.mp3 new file mode 100644 index 0000000..ce2e115 Binary files /dev/null and b/PlanningPoker.Web/wwwroot/audio/turn-cards.mp3 differ diff --git a/PlanningPoker.Web/wwwroot/audio/turn-cards.wav b/PlanningPoker.Web/wwwroot/audio/turn-cards.wav new file mode 100644 index 0000000..9765639 Binary files /dev/null and b/PlanningPoker.Web/wwwroot/audio/turn-cards.wav differ diff --git a/PlanningPoker.Web/wwwroot/css/game.css b/PlanningPoker.Web/wwwroot/css/game.css index f73a499..7a0be87 100644 --- a/PlanningPoker.Web/wwwroot/css/game.css +++ b/PlanningPoker.Web/wwwroot/css/game.css @@ -14,6 +14,15 @@ margin-left: 10px; } +.player-notify { + padding: 5px 5px; + background-color: #334; + display: inline-block; + border-radius: 5px 0px 0px 5px; + padding-right: 10px; + padding-left: 10px; +} + .player-card { padding: 5px 5px; background-color: #334; diff --git a/PlanningPoker.Web/wwwroot/js/audio.js b/PlanningPoker.Web/wwwroot/js/audio.js new file mode 100644 index 0000000..b2a3353 --- /dev/null +++ b/PlanningPoker.Web/wwwroot/js/audio.js @@ -0,0 +1,10 @@ +var MediaPlayer = { + PlayAudio: function (filename) { + const mp3 = '/audio/' + filename + '.mp3'; + const wav = '/audio/' + filename + '.wav'; + + let audio = new Audio(); + audio.src = audio.canPlayType('audio/mpeg') ? mp3 : wav; + audio.play(); + } +}; diff --git a/README.md b/README.md index ff94a98..52caa77 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ You can check out a live demo [over here](https://www.planningpoker.party/demo). ## Prerequisites -- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) - [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/Download). ## How to in run in Visual Studio