diff --git a/samples/Blazor.ExampleConsumer/App.razor b/samples/Blazor.ExampleConsumer/App.razor index 623580d..6fd3ed1 100644 --- a/samples/Blazor.ExampleConsumer/App.razor +++ b/samples/Blazor.ExampleConsumer/App.razor @@ -1,12 +1,12 @@ - - - - - - - Not found - -

Sorry, there's nothing at this address.

-
-
-
+ + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/samples/Blazor.ExampleConsumer/Components/BingMap.razor b/samples/Blazor.ExampleConsumer/Components/BingMap.razor index 86c486a..6f6065e 100644 --- a/samples/Blazor.ExampleConsumer/Components/BingMap.razor +++ b/samples/Blazor.ExampleConsumer/Components/BingMap.razor @@ -1,19 +1,19 @@ -@inject IJSInProcessRuntime JavaScript - -
- -@code { +@inject IJSInProcessRuntime JavaScript + +
+ +@code { [Parameter, EditorRequired] - public GeolocationPosition Position { get; set; } = null!; - - protected override void OnParametersSet() - { - if (Position is null or { Coords: null }) - { - return; - } - - JavaScript.InvokeVoid( - "app.loadMap", "map", Position.Coords.Latitude, Position.Coords.Longitude); - } -} + public GeolocationPosition Position { get; set; } = null!; + + protected override void OnParametersSet() + { + if (Position is null or { Coords: null }) + { + return; + } + + JavaScript.InvokeVoid( + "app.loadMap", "map", Position.Coords.Latitude, Position.Coords.Longitude); + } +} diff --git a/samples/Blazor.ExampleConsumer/Components/Code.razor b/samples/Blazor.ExampleConsumer/Components/Code.razor index 2978cf8..94234c2 100644 --- a/samples/Blazor.ExampleConsumer/Components/Code.razor +++ b/samples/Blazor.ExampleConsumer/Components/Code.razor @@ -1,28 +1,28 @@ -@typeparam T where T : class - -@if (Value is not null) -{ -
-        
-            @{
-                var opts = new JsonSerializerOptions()
-                {
-                    WriteIndented = true,
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-                    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
-                };
-            }
-            @Value?.ToJson(opts)
-        
-    
-} - -@code { - [Parameter, EditorRequired] - public T? Value { get; set; } = default!; - - [Parameter] - public bool IsError { get; set; } - - string _textClass => IsError ? "text-warning" : "text-info"; -} +@typeparam T where T : class + +@if (Value is not null) +{ +
+        
+            @{
+                var opts = new JsonSerializerOptions()
+                {
+                    WriteIndented = true,
+                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+                    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+                };
+            }
+            @Value?.ToJson(opts)
+        
+    
+} + +@code { + [Parameter, EditorRequired] + public T? Value { get; set; } = default!; + + [Parameter] + public bool IsError { get; set; } + + string _textClass => IsError ? "text-warning" : "text-info"; +} diff --git a/samples/Blazor.ExampleConsumer/Components/StorageCheckbox.razor b/samples/Blazor.ExampleConsumer/Components/StorageCheckbox.razor index 5d13e0d..9240807 100644 --- a/samples/Blazor.ExampleConsumer/Components/StorageCheckbox.razor +++ b/samples/Blazor.ExampleConsumer/Components/StorageCheckbox.razor @@ -1,41 +1,41 @@ -@inject ILocalStorageService LocalStorage - - -@if (EndContent is not null) -{ - @EndContent -} - -@code { +@inject ILocalStorageService LocalStorage + + +@if (EndContent is not null) +{ + @EndContent +} + +@code { [Parameter, EditorRequired] public TodoItem Item { get; set; } = null!; - [Parameter] + [Parameter] public RenderFragment EndContent { get; set; } = null!; [Parameter] public EventCallback ItemChanged { get; set; } - Task OnIsCompletedChanged(ChangeEventArgs args) - { - if (bool.TryParse(args?.Value?.ToString(), out var isCompleted)) - { - LocalStorage.SetItem( - Item.Id, - Item = Item with { IsCompleted = isCompleted }); - - if (ItemChanged.HasDelegate) - { - return ItemChanged.InvokeAsync(Item); - } - } - - return Task.CompletedTask; - } + Task OnIsCompletedChanged(ChangeEventArgs args) + { + if (bool.TryParse(args?.Value?.ToString(), out var isCompleted)) + { + LocalStorage.SetItem( + Item.Id, + Item = Item with { IsCompleted = isCompleted }); + + if (ItemChanged.HasDelegate) + { + return ItemChanged.InvokeAsync(Item); + } + } + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/samples/Blazor.ExampleConsumer/GlobalUsings.cs b/samples/Blazor.ExampleConsumer/GlobalUsings.cs index d945059..7799891 100644 --- a/samples/Blazor.ExampleConsumer/GlobalUsings.cs +++ b/samples/Blazor.ExampleConsumer/GlobalUsings.cs @@ -1,8 +1,8 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -global using System.Text.Json; -global using System.Text.Json.Serialization; -global using Microsoft.JSInterop; -global using Microsoft.AspNetCore.Components; -global using static System.Globalization.CultureInfo; +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using Microsoft.JSInterop; +global using Microsoft.AspNetCore.Components; +global using static System.Globalization.CultureInfo; diff --git a/samples/Blazor.ExampleConsumer/Models/TodoItem.cs b/samples/Blazor.ExampleConsumer/Models/TodoItem.cs index 11728a9..d7cffdc 100644 --- a/samples/Blazor.ExampleConsumer/Models/TodoItem.cs +++ b/samples/Blazor.ExampleConsumer/Models/TodoItem.cs @@ -1,20 +1,20 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -using System.Text.RegularExpressions; - -namespace Blazor.ExampleConsumer.Models; - -public partial record class TodoItem( - string Task, - bool IsCompleted) -{ - internal const string IdPrefix = "todo"; - - [JsonIgnore] - public string Id => - $"{IdPrefix}{AlphabetOrDigitRegex().Replace(Task, "")}"; - - [GeneratedRegex("[^a-zA-Z0-9]")] - private static partial Regex AlphabetOrDigitRegex(); -} +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using System.Text.RegularExpressions; + +namespace Blazor.ExampleConsumer.Models; + +public partial record class TodoItem( + string Task, + bool IsCompleted) +{ + internal const string IdPrefix = "todo"; + + [JsonIgnore] + public string Id => + $"{IdPrefix}{AlphabetOrDigitRegex().Replace(Task, "")}"; + + [GeneratedRegex("[^a-zA-Z0-9]")] + private static partial Regex AlphabetOrDigitRegex(); +} diff --git a/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor b/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor index 83f27a6..4d89d35 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor +++ b/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor @@ -1,38 +1,38 @@ -@page "/geolocation" - -@inject IGeolocationService Geolocation - -Geolocation -

- Geolocation - @if (_isLoading) - { - - Loading... - - } - @if (_position is not null) - { - : - - @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) - - - - - - } -

- -@if (_isLoading) -{ -

This page demonstrates the source generated Blazor.Geolocation.WebAssembly package.

-} - -@{ - - - - +@page "/geolocation" + +@inject IGeolocationService Geolocation + +Geolocation +

+ Geolocation + @if (_isLoading) + { + + Loading... + + } + @if (_position is not null) + { + : + + @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) + + + + + + } +

+ +@if (_isLoading) +{ +

This page demonstrates the source generated Blazor.Geolocation.WebAssembly package.

+} + +@{ + + + + } \ No newline at end of file diff --git a/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor.cs b/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor.cs index 302bdf2..19733f2 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor.cs +++ b/samples/Blazor.ExampleConsumer/Pages/ClientPosition.razor.cs @@ -1,47 +1,47 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace Blazor.ExampleConsumer.Pages; - -public sealed partial class ClientPosition -{ - readonly JsonSerializerOptions _opts = new() - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - readonly PositionOptions _options = new() - { - EnableHighAccuracy = true, - MaximumAge = null, - Timeout = 15_000 - }; - - GeolocationPosition? _position; - GeolocationPositionError? _positionError; - bool _isLoading = true; - - protected override void OnInitialized() => - Geolocation.GetCurrentPosition( - component: this, - onSuccessCallbackMethodName: nameof(OnPositionRecieved), - onErrorCallbackMethodName: nameof(OnPositionError), - options: _options); - - [JSInvokable] - public void OnPositionRecieved(GeolocationPosition position) - { - _isLoading = false; - _position = position; - StateHasChanged(); - } - - [JSInvokable] - public void OnPositionError(GeolocationPositionError positionError) - { - _isLoading = false; - _positionError = positionError; - StateHasChanged(); - } -} +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Blazor.ExampleConsumer.Pages; + +public sealed partial class ClientPosition +{ + readonly JsonSerializerOptions _opts = new() + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + readonly PositionOptions _options = new() + { + EnableHighAccuracy = true, + MaximumAge = null, + Timeout = 15_000 + }; + + GeolocationPosition? _position; + GeolocationPositionError? _positionError; + bool _isLoading = true; + + protected override void OnInitialized() => + Geolocation.GetCurrentPosition( + component: this, + onSuccessCallbackMethodName: nameof(OnPositionRecieved), + onErrorCallbackMethodName: nameof(OnPositionError), + options: _options); + + [JSInvokable] + public void OnPositionRecieved(GeolocationPosition position) + { + _isLoading = false; + _position = position; + StateHasChanged(); + } + + [JSInvokable] + public void OnPositionError(GeolocationPositionError positionError) + { + _isLoading = false; + _positionError = positionError; + StateHasChanged(); + } +} diff --git a/samples/Blazor.ExampleConsumer/Pages/Index.razor b/samples/Blazor.ExampleConsumer/Pages/Index.razor index e97347f..014471e 100644 --- a/samples/Blazor.ExampleConsumer/Pages/Index.razor +++ b/samples/Blazor.ExampleConsumer/Pages/Index.razor @@ -1,9 +1,9 @@ -@page "/" - -Index - -

Hi friends! 🤓

- -

This sample app shows how to consume the source-generated projects of #blazorators.

- - +@page "/" + +Index + +

Hi friends! 🤓

+ +

This sample app shows how to consume the source-generated projects of #blazorators.

+ + diff --git a/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor b/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor index 2f42d46..67761f3 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor +++ b/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor @@ -1,29 +1,29 @@ -@page "/listen" - -Speech-to-text - -

This page demonstrates the source generated Blazor.SpeechRecongition.WebAssembly package.

- -
- - -
- - - \ No newline at end of file diff --git a/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor.cs b/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor.cs index 76cecda..4d22a12 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor.cs +++ b/samples/Blazor.ExampleConsumer/Pages/ListenToMe.razor.cs @@ -1,91 +1,91 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace Blazor.ExampleConsumer.Pages; - -public sealed partial class ListenToMe : IAsyncDisposable -{ - const string TranscriptKey = "listen-to-me-page-transcript"; - - IDisposable? _recognitionSubscription; - bool _isRecognizingSpeech = false; - SpeechRecognitionErrorEvent? _errorEvent; - string? _transcript; - - [Inject] - public ISpeechRecognitionService SpeechRecognition { get; set; } = null!; - - [Inject] - public ISessionStorageService SessionStorage { get; set; } = null!; - - protected override void OnInitialized() => - _transcript = SessionStorage.GetItem(TranscriptKey); - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await SpeechRecognition.InitializeModuleAsync(); - } - } - - void OnRecognizeSpeechClick() - { - if (_isRecognizingSpeech) - { - SpeechRecognition.CancelSpeechRecognition(false); - } - else - { - var bcp47Tag = CurrentUICulture.Name; - - _recognitionSubscription?.Dispose(); - _recognitionSubscription = SpeechRecognition.RecognizeSpeech( - bcp47Tag, - OnRecognized, - OnError, - OnStarted, - OnEnded); - } - } - - void OnStarted() - { - _isRecognizingSpeech = true; - StateHasChanged(); - } - - void OnEnded() - { - _isRecognizingSpeech = false; - StateHasChanged(); - } - - void OnError(SpeechRecognitionErrorEvent errorEvent) - { - _errorEvent = errorEvent; - StateHasChanged(); - } - - void OnRecognized(string transcript) - { - _transcript = _transcript switch - { - null => transcript, - _ => $"{_transcript.Trim()} {transcript}".Trim() - }; - - SessionStorage.SetItem(TranscriptKey, _transcript); - StateHasChanged(); - } - - public async ValueTask DisposeAsync() - { - if (SpeechRecognition is not null) - { - await SpeechRecognition.DisposeAsync(); - } - - _recognitionSubscription?.Dispose(); - } -} +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Blazor.ExampleConsumer.Pages; + +public sealed partial class ListenToMe : IAsyncDisposable +{ + const string TranscriptKey = "listen-to-me-page-transcript"; + + IDisposable? _recognitionSubscription; + bool _isRecognizingSpeech = false; + SpeechRecognitionErrorEvent? _errorEvent; + string? _transcript; + + [Inject] + public ISpeechRecognitionService SpeechRecognition { get; set; } = null!; + + [Inject] + public ISessionStorageService SessionStorage { get; set; } = null!; + + protected override void OnInitialized() => + _transcript = SessionStorage.GetItem(TranscriptKey); + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await SpeechRecognition.InitializeModuleAsync(); + } + } + + void OnRecognizeSpeechClick() + { + if (_isRecognizingSpeech) + { + SpeechRecognition.CancelSpeechRecognition(false); + } + else + { + var bcp47Tag = CurrentUICulture.Name; + + _recognitionSubscription?.Dispose(); + _recognitionSubscription = SpeechRecognition.RecognizeSpeech( + bcp47Tag, + OnRecognized, + OnError, + OnStarted, + OnEnded); + } + } + + void OnStarted() + { + _isRecognizingSpeech = true; + StateHasChanged(); + } + + void OnEnded() + { + _isRecognizingSpeech = false; + StateHasChanged(); + } + + void OnError(SpeechRecognitionErrorEvent errorEvent) + { + _errorEvent = errorEvent; + StateHasChanged(); + } + + void OnRecognized(string transcript) + { + _transcript = _transcript switch + { + null => transcript, + _ => $"{_transcript.Trim()} {transcript}".Trim() + }; + + SessionStorage.SetItem(TranscriptKey, _transcript); + StateHasChanged(); + } + + public async ValueTask DisposeAsync() + { + if (SpeechRecognition is not null) + { + await SpeechRecognition.DisposeAsync(); + } + + _recognitionSubscription?.Dispose(); + } +} diff --git a/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor b/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor index e1c5160..b96d0ae 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor +++ b/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor @@ -1,58 +1,58 @@ -@page "/speak" - -Text-to-speech - -

This page demonstrates the source generated Blazor.SpeechSynthesis.WebAssembly package.

- -
- - -
- -
- - - - @foreach (var speed in _voiceSpeeds) - { - - } - -
- -
- - -
- -
- - - - +@page "/speak" + +Text-to-speech + +

This page demonstrates the source generated Blazor.SpeechSynthesis.WebAssembly package.

+ +
+ + +
+ +
+ + + + @foreach (var speed in _voiceSpeeds) + { + + } + +
+ +
+ + +
+ +
+ + + +
\ No newline at end of file diff --git a/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor.cs b/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor.cs index 2d55ae0..09de22b 100644 --- a/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor.cs +++ b/samples/Blazor.ExampleConsumer/Pages/ReadToMe.razor.cs @@ -1,99 +1,99 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -using Humanizer; - -namespace Blazor.ExampleConsumer.Pages; - -public sealed partial class ReadToMe : IDisposable -{ - const string PreferredVoiceKey = "preferred-voice"; - const string PreferredSpeedKey = "preferred-speed"; - const string TextKey = "read-to-me-text"; - - string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types."; - SpeechSynthesisVoice[] _voices = Array.Empty(); - readonly IList _voiceSpeeds = - Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); - double _voiceSpeed = 1.5; - string? _selectedVoice; - string? _elapsedTimeMessage = null; - - SpeechSynthesisUtterance Utterance => new() - { - Text = _text ?? "You forgot to try uttering some text.", - Rate = _voiceSpeed, - Volume = 1, - Voice = _selectedVoice is { Length: > 0 } - ? _voices?.FirstOrDefault(voice => voice.Name == _selectedVoice) - : null - }; - - [Inject] - public ISpeechSynthesisService SpeechSynthesis { get; set; } = null!; - - [Inject] - public ILocalStorageService LocalStorage { get; set; } = null!; - - [Inject] - public ISessionStorageService SessionStorage { get; set; } = null!; - - [Inject] - public ILogger Logger { get; set; } = null!; - - protected override async Task OnInitializedAsync() - { - await GetVoicesAsync(); - SpeechSynthesis.OnVoicesChanged(() => GetVoicesAsync(true)); - - if (LocalStorage.GetItem(PreferredVoiceKey) - is { Length: > 0 } voice) - { - _selectedVoice = voice; - } - if (LocalStorage.GetItem(PreferredSpeedKey) - is double speed && speed > 0) - { - _voiceSpeed = speed; - } - if (SessionStorage.GetItem(TextKey) - is { Length: > 0 } text) - { - _text = text; - } - } - - async Task GetVoicesAsync(bool isFromCallback = false) - { - _voices = await SpeechSynthesis.GetVoicesAsync(); - if (_voices is { } && isFromCallback) - { - StateHasChanged(); - } - } - - void OnTextChanged(ChangeEventArgs args) => _text = args.Value?.ToString(); - - void OnVoiceSpeedChange(ChangeEventArgs args) => - _voiceSpeed = double.TryParse(args.Value?.ToString() ?? "1.5", out var speed) - ? speed : 1.5; - - void Speak() => SpeechSynthesis.Speak( - Utterance, - elapsedTime => - { - _elapsedTimeMessage = - $"Read aloud in {TimeSpan.FromMilliseconds(elapsedTime).Humanize()}."; - - StateHasChanged(); - }); - - void IDisposable.Dispose() - { - LocalStorage.SetItem(PreferredVoiceKey, _selectedVoice); - LocalStorage.SetItem(PreferredSpeedKey, _voiceSpeed); - SessionStorage.SetItem(TextKey, _text); - - SpeechSynthesis.UnsubscribeFromVoicesChanged(); - } -} +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using Humanizer; + +namespace Blazor.ExampleConsumer.Pages; + +public sealed partial class ReadToMe : IDisposable +{ + const string PreferredVoiceKey = "preferred-voice"; + const string PreferredSpeedKey = "preferred-speed"; + const string TextKey = "read-to-me-text"; + + string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types."; + SpeechSynthesisVoice[] _voices = Array.Empty(); + readonly IList _voiceSpeeds = + Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); + double _voiceSpeed = 1.5; + string? _selectedVoice; + string? _elapsedTimeMessage = null; + + SpeechSynthesisUtterance Utterance => new() + { + Text = _text ?? "You forgot to try uttering some text.", + Rate = _voiceSpeed, + Volume = 1, + Voice = _selectedVoice is { Length: > 0 } + ? _voices?.FirstOrDefault(voice => voice.Name == _selectedVoice) + : null + }; + + [Inject] + public ISpeechSynthesisService SpeechSynthesis { get; set; } = null!; + + [Inject] + public ILocalStorageService LocalStorage { get; set; } = null!; + + [Inject] + public ISessionStorageService SessionStorage { get; set; } = null!; + + [Inject] + public ILogger Logger { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + await GetVoicesAsync(); + SpeechSynthesis.OnVoicesChanged(() => GetVoicesAsync(true)); + + if (LocalStorage.GetItem(PreferredVoiceKey) + is { Length: > 0 } voice) + { + _selectedVoice = voice; + } + if (LocalStorage.GetItem(PreferredSpeedKey) + is double speed && speed > 0) + { + _voiceSpeed = speed; + } + if (SessionStorage.GetItem(TextKey) + is { Length: > 0 } text) + { + _text = text; + } + } + + async Task GetVoicesAsync(bool isFromCallback = false) + { + _voices = await SpeechSynthesis.GetVoicesAsync(); + if (_voices is { } && isFromCallback) + { + StateHasChanged(); + } + } + + void OnTextChanged(ChangeEventArgs args) => _text = args.Value?.ToString(); + + void OnVoiceSpeedChange(ChangeEventArgs args) => + _voiceSpeed = double.TryParse(args.Value?.ToString() ?? "1.5", out var speed) + ? speed : 1.5; + + void Speak() => SpeechSynthesis.Speak( + Utterance, + elapsedTime => + { + _elapsedTimeMessage = + $"Read aloud in {TimeSpan.FromMilliseconds(elapsedTime).Humanize()}."; + + StateHasChanged(); + }); + + void IDisposable.Dispose() + { + LocalStorage.SetItem(PreferredVoiceKey, _selectedVoice); + LocalStorage.SetItem(PreferredSpeedKey, _voiceSpeed); + SessionStorage.SetItem(TextKey, _text); + + SpeechSynthesis.UnsubscribeFromVoicesChanged(); + } +} diff --git a/samples/Blazor.ExampleConsumer/Pages/Sandbox.razor b/samples/Blazor.ExampleConsumer/Pages/Sandbox.razor index 511b653..c1dd653 100644 --- a/samples/Blazor.ExampleConsumer/Pages/Sandbox.razor +++ b/samples/Blazor.ExampleConsumer/Pages/Sandbox.razor @@ -1,49 +1,49 @@ -@page "/sandbox" -@inject IGeolocationService GeolocationService - -

- 🎭 Sandbox - @if (_isLoading) - { - - Loading... - - } - @if (_position is not null) - { - : - - @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) - - - - - - } -

- - - - - - -@code { - bool _isLoading => _position is null && _positionError is null; - +@page "/sandbox" +@inject IGeolocationService GeolocationService + +

+ 🎭 Sandbox + @if (_isLoading) + { + + Loading... + + } + @if (_position is not null) + { + : + + @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) + + + + + + } +

+ + + + + + +@code { + bool _isLoading => _position is null && _positionError is null; + GeolocationPosition? _position; GeolocationPositionError? _positionError; - protected override void OnInitialized() => - GeolocationService.GetCurrentPosition( - position => - { - _position = position; - StateHasChanged(); - }, - positionError => - { - _positionError = positionError; - StateHasChanged(); - }); -} + protected override void OnInitialized() => + GeolocationService.GetCurrentPosition( + position => + { + _position = position; + StateHasChanged(); + }, + positionError => + { + _positionError = positionError; + StateHasChanged(); + }); +} diff --git a/samples/Blazor.ExampleConsumer/Pages/TodoList.razor b/samples/Blazor.ExampleConsumer/Pages/TodoList.razor index 8b09a90..ce7eb7c 100644 --- a/samples/Blazor.ExampleConsumer/Pages/TodoList.razor +++ b/samples/Blazor.ExampleConsumer/Pages/TodoList.razor @@ -1,83 +1,83 @@ -@page "/todos" - -Tasks - -

- Local Storage - - - -

-

This page demonstrates the source generated Blazor.LocalStorage.WebAssembly package.

- -
-
-
-
-
- - - -
-
-
- @if (_todos is { Count: > 0 }) - { -
    - @foreach (var todo in _todos) - { -
  • - - - - - -
  • - } -
- } -
-
-@if (_localStorageItems is { Count: > 0 }) -{ -
-
-
- Raw window.localStorage values - - - - - - - - - - @foreach (var (i, key, value) in _localStorageItems.Select((kvp, i) => (i, kvp.Key, kvp.Value))) - { - - - - - - } - -
#KeyValue
@i.ToString()@key.ToString()@value.ToString()
-
-
-
+@page "/todos" + +Tasks + +

+ Local Storage + + + +

+

This page demonstrates the source generated Blazor.LocalStorage.WebAssembly package.

+ +
+
+
+
+
+ + + +
+
+
+ @if (_todos is { Count: > 0 }) + { +
    + @foreach (var todo in _todos) + { +
  • + + + + + +
  • + } +
+ } +
+
+@if (_localStorageItems is { Count: > 0 }) +{ +
+
+
+ Raw window.localStorage values + + + + + + + + + + @foreach (var (i, key, value) in _localStorageItems.Select((kvp, i) => (i, kvp.Key, kvp.Value))) + { + + + + + + } + +
#KeyValue
@i.ToString()@key.ToString()@value.ToString()
+
+
+
} \ No newline at end of file diff --git a/samples/Blazor.ExampleConsumer/Pages/TodoList.razor.cs b/samples/Blazor.ExampleConsumer/Pages/TodoList.razor.cs index 588db28..8f65297 100644 --- a/samples/Blazor.ExampleConsumer/Pages/TodoList.razor.cs +++ b/samples/Blazor.ExampleConsumer/Pages/TodoList.razor.cs @@ -1,121 +1,121 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -using System.Diagnostics.CodeAnalysis; -using Blazor.ExampleConsumer.Models; -using Microsoft.AspNetCore.Components.Web; - -namespace Blazor.ExampleConsumer.Pages; - -public sealed partial class TodoList -{ - readonly Dictionary _localStorageItems = new(); - HashSet _todos = new(); - string? _todoValue; - - [Inject] - public ILocalStorageService LocalStorage { get; set; } = null!; - - protected override void OnInitialized() => UpdateTodoItems(); - - void UpdateTodoItems() - { - var todos = GetLocalStorageKeys() - .Where(key => key.StartsWith(TodoItem.IdPrefix)) - .Select(key => LocalStorage.GetItem(key)) - .Where(todo => todo is not null) - .ToHashSet() ?? new(); - - _todos = todos!; - - foreach (var key in GetLocalStorageKeys()) - { - if (TryGet(key, out TodoItem? todo)) - { - _localStorageItems[key] = todo.ToString(); - continue; - } - if (TryGet(key, out string? @string)) - { - _localStorageItems[key] = @string; - continue; - } - if (TryGet(key, out decimal num)) - { - _localStorageItems[key] = num.ToString(); - continue; - } - if (TryGet(key, out bool @bool)) - { - _localStorageItems[key] = @bool.ToString(); - continue; - } - } - - bool TryGet(string key, [NotNullWhen(true)] out T? value) - { - try - { - value = LocalStorage.GetItem(key); - return value is not null; - } - catch - { - value = default; - return false; - } - } - } - - IEnumerable GetLocalStorageKeys() - { - var length = LocalStorage.Length; - for (var i = 0; i < length; ++i) - { - if (LocalStorage.Key(i) is { Length: > 0 } key) - { - yield return key; - } - } - } - - void AddNewTodo() - { - if (_todoValue is not null) - { - var todo = new TodoItem(_todoValue, false); - LocalStorage.SetItem(todo.Id, todo); - UpdateTodoItems(); - _todoValue = null; - } - } - - void OnKeyUp(KeyboardEventArgs args) - { - if (args is { Key: "Enter" }) - { - AddNewTodo(); - } - } - - void Delete(TodoItem todo) - { - LocalStorage.RemoveItem(todo.Id); - _todos.RemoveWhere(t => t.Id == todo.Id); - _localStorageItems.Remove(todo.Id); - } - - void ClearAll() - { - LocalStorage.Clear(); - _todos.Clear(); - _localStorageItems.Clear(); - } - - async Task OnItemChanged(TodoItem _) - { - await Task.CompletedTask; - - UpdateTodoItems(); - } -} +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Blazor.ExampleConsumer.Models; +using Microsoft.AspNetCore.Components.Web; + +namespace Blazor.ExampleConsumer.Pages; + +public sealed partial class TodoList +{ + readonly Dictionary _localStorageItems = new(); + HashSet _todos = new(); + string? _todoValue; + + [Inject] + public ILocalStorageService LocalStorage { get; set; } = null!; + + protected override void OnInitialized() => UpdateTodoItems(); + + void UpdateTodoItems() + { + var todos = GetLocalStorageKeys() + .Where(key => key.StartsWith(TodoItem.IdPrefix)) + .Select(key => LocalStorage.GetItem(key)) + .Where(todo => todo is not null) + .ToHashSet() ?? new(); + + _todos = todos!; + + foreach (var key in GetLocalStorageKeys()) + { + if (TryGet(key, out TodoItem? todo)) + { + _localStorageItems[key] = todo.ToString(); + continue; + } + if (TryGet(key, out string? @string)) + { + _localStorageItems[key] = @string; + continue; + } + if (TryGet(key, out decimal num)) + { + _localStorageItems[key] = num.ToString(); + continue; + } + if (TryGet(key, out bool @bool)) + { + _localStorageItems[key] = @bool.ToString(); + continue; + } + } + + bool TryGet(string key, [NotNullWhen(true)] out T? value) + { + try + { + value = LocalStorage.GetItem(key); + return value is not null; + } + catch + { + value = default; + return false; + } + } + } + + IEnumerable GetLocalStorageKeys() + { + var length = LocalStorage.Length; + for (var i = 0; i < length; ++i) + { + if (LocalStorage.Key(i) is { Length: > 0 } key) + { + yield return key; + } + } + } + + void AddNewTodo() + { + if (_todoValue is not null) + { + var todo = new TodoItem(_todoValue, false); + LocalStorage.SetItem(todo.Id, todo); + UpdateTodoItems(); + _todoValue = null; + } + } + + void OnKeyUp(KeyboardEventArgs args) + { + if (args is { Key: "Enter" }) + { + AddNewTodo(); + } + } + + void Delete(TodoItem todo) + { + LocalStorage.RemoveItem(todo.Id); + _todos.RemoveWhere(t => t.Id == todo.Id); + _localStorageItems.Remove(todo.Id); + } + + void ClearAll() + { + LocalStorage.Clear(); + _todos.Clear(); + _localStorageItems.Clear(); + } + + async Task OnItemChanged(TodoItem _) + { + await Task.CompletedTask; + + UpdateTodoItems(); + } +} diff --git a/samples/Blazor.ExampleConsumer/Program.cs b/samples/Blazor.ExampleConsumer/Program.cs index 47885fc..18822f0 100644 --- a/samples/Blazor.ExampleConsumer/Program.cs +++ b/samples/Blazor.ExampleConsumer/Program.cs @@ -1,23 +1,23 @@ -using Blazor.ExampleConsumer; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; - -var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); - -builder.Services.AddScoped(_ => new HttpClient -{ - BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) -}); - -// Use source-generated DI bits... -builder.Services.AddLocalStorageServices(); -builder.Services.AddSessionStorageServices(); -builder.Services.AddGeolocationServices(); -builder.Services.AddSpeechSynthesisServices(); - -// Custom library bits... -builder.Services.AddSpeechRecognitionServices(); - -await builder.Build().RunAsync(); +using Blazor.ExampleConsumer; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(_ => new HttpClient +{ + BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) +}); + +// Use source-generated DI bits... +builder.Services.AddLocalStorageServices(); +builder.Services.AddSessionStorageServices(); +builder.Services.AddGeolocationServices(); +builder.Services.AddSpeechSynthesisServices(); + +// Custom library bits... +builder.Services.AddSpeechRecognitionServices(); + +await builder.Build().RunAsync(); diff --git a/samples/Blazor.ExampleConsumer/Shared/MainLayout.razor b/samples/Blazor.ExampleConsumer/Shared/MainLayout.razor index be4d594..042aba3 100644 --- a/samples/Blazor.ExampleConsumer/Shared/MainLayout.razor +++ b/samples/Blazor.ExampleConsumer/Shared/MainLayout.razor @@ -1,14 +1,14 @@ -@inherits LayoutComponentBase - -
- - -
-
-
- @Body -
-
-
+@inherits LayoutComponentBase + +
+ + +
+
+
+ @Body +
+
+
diff --git a/samples/Blazor.ExampleConsumer/Shared/NavMenu.razor b/samples/Blazor.ExampleConsumer/Shared/NavMenu.razor index c60ba28..3542a7e 100644 --- a/samples/Blazor.ExampleConsumer/Shared/NavMenu.razor +++ b/samples/Blazor.ExampleConsumer/Shared/NavMenu.razor @@ -1,63 +1,63 @@ - - -
- -
- -@code { - bool _collapseNavMenu = true; - string? NavMenuCssClass => _collapseNavMenu ? "collapse" : null; - void ToggleNavMenu() => _collapseNavMenu = !_collapseNavMenu; -} + + +
+ +
+ +@code { + bool _collapseNavMenu = true; + string? NavMenuCssClass => _collapseNavMenu ? "collapse" : null; + void ToggleNavMenu() => _collapseNavMenu = !_collapseNavMenu; +} diff --git a/samples/Blazor.ExampleConsumer/Shared/RepositoryPrompt.razor b/samples/Blazor.ExampleConsumer/Shared/RepositoryPrompt.razor index f21d5a6..8c99c4a 100644 --- a/samples/Blazor.ExampleConsumer/Shared/RepositoryPrompt.razor +++ b/samples/Blazor.ExampleConsumer/Shared/RepositoryPrompt.razor @@ -1,12 +1,12 @@ -
- - @Title - - Want to see the source code? Check it out - on GitHub. - -
- -@code { - [Parameter] public string? Title { get; set; } -} +
+ + @Title + + Want to see the source code? Check it out + on GitHub. + +
+ +@code { + [Parameter] public string? Title { get; set; } +} diff --git a/samples/Blazor.ExampleConsumer/_Imports.razor b/samples/Blazor.ExampleConsumer/_Imports.razor index dff9278..c2f28d7 100644 --- a/samples/Blazor.ExampleConsumer/_Imports.razor +++ b/samples/Blazor.ExampleConsumer/_Imports.razor @@ -1,15 +1,15 @@ -@using System.Net.Http -@using System.Net.Http.Json -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http -@using Microsoft.JSInterop -@using System.Text.Json -@using System.Text.Json.Serialization -@using Blazor.ExampleConsumer -@using Blazor.ExampleConsumer.Models -@using Blazor.ExampleConsumer.Shared -@using Blazor.ExampleConsumer.Components +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using System.Text.Json +@using System.Text.Json.Serialization +@using Blazor.ExampleConsumer +@using Blazor.ExampleConsumer.Models +@using Blazor.ExampleConsumer.Shared +@using Blazor.ExampleConsumer.Components @using Blazor.Serialization.Extensions \ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/BlazorServer.ExampleConsumer.csproj b/samples/BlazorServer.ExampleConsumer/BlazorServer.ExampleConsumer.csproj index 890eb49..60ed375 100644 --- a/samples/BlazorServer.ExampleConsumer/BlazorServer.ExampleConsumer.csproj +++ b/samples/BlazorServer.ExampleConsumer/BlazorServer.ExampleConsumer.csproj @@ -6,7 +6,11 @@ enable - + + + + + diff --git a/samples/BlazorServer.ExampleConsumer/Components/BingMap.razor b/samples/BlazorServer.ExampleConsumer/Components/BingMap.razor new file mode 100644 index 0000000..3f1562b --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Components/BingMap.razor @@ -0,0 +1,19 @@ +@inject IJSRuntime JavaScript + +
+ +@code { + [Parameter, EditorRequired] + public GeolocationPosition Position { get; set; } = null!; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (Position is null or { Coords: null }) + { + return; + } + + await JavaScript.InvokeVoidAsync( + "app.loadMap", "map", Position.Coords.Latitude, Position.Coords.Longitude); + } +} diff --git a/samples/BlazorServer.ExampleConsumer/Components/Code.razor b/samples/BlazorServer.ExampleConsumer/Components/Code.razor new file mode 100644 index 0000000..94234c2 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Components/Code.razor @@ -0,0 +1,28 @@ +@typeparam T where T : class + +@if (Value is not null) +{ +
+        
+            @{
+                var opts = new JsonSerializerOptions()
+                {
+                    WriteIndented = true,
+                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+                    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+                };
+            }
+            @Value?.ToJson(opts)
+        
+    
+} + +@code { + [Parameter, EditorRequired] + public T? Value { get; set; } = default!; + + [Parameter] + public bool IsError { get; set; } + + string _textClass => IsError ? "text-warning" : "text-info"; +} diff --git a/samples/BlazorServer.ExampleConsumer/Components/StorageCheckbox.razor b/samples/BlazorServer.ExampleConsumer/Components/StorageCheckbox.razor new file mode 100644 index 0000000..2370e58 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Components/StorageCheckbox.razor @@ -0,0 +1,39 @@ +@inject ILocalStorageService LocalStorage + + +@if (EndContent is not null) +{ + @EndContent +} + +@code { + [Parameter, EditorRequired] + public TodoItem Item { get; set; } = null!; + + [Parameter] + public RenderFragment EndContent { get; set; } = null!; + + [Parameter] + public EventCallback ItemChanged { get; set; } + + async Task OnIsCompletedChanged(ChangeEventArgs args) + { + if (bool.TryParse(args?.Value?.ToString(), out var isCompleted)) + { + await LocalStorage.SetItemAsync( + Item.Id, + (Item = Item with { IsCompleted = isCompleted }).ToJson()); + + if (ItemChanged.HasDelegate) + { + await ItemChanged.InvokeAsync(Item); + } + } + } +} \ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/Data/WeatherForecast.cs b/samples/BlazorServer.ExampleConsumer/Data/WeatherForecast.cs deleted file mode 100644 index 7b3b0ba..0000000 --- a/samples/BlazorServer.ExampleConsumer/Data/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace BlazorServer.ExampleConsumer.Data; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} diff --git a/samples/BlazorServer.ExampleConsumer/Data/WeatherForecastService.cs b/samples/BlazorServer.ExampleConsumer/Data/WeatherForecastService.cs deleted file mode 100644 index 497ad8a..0000000 --- a/samples/BlazorServer.ExampleConsumer/Data/WeatherForecastService.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace BlazorServer.ExampleConsumer.Data; - -public class WeatherForecastService -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - public Task GetForecastAsync(DateOnly startDate) - { - return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }).ToArray()); - } -} diff --git a/samples/BlazorServer.ExampleConsumer/GlobalUsings.cs b/samples/BlazorServer.ExampleConsumer/GlobalUsings.cs new file mode 100644 index 0000000..05bd19d --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/GlobalUsings.cs @@ -0,0 +1,13 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using Microsoft.JSInterop; +global using Microsoft.AspNetCore.Components; +global using BlazorServer.ExampleConsumer; +global using BlazorServer.ExampleConsumer.Components; +global using BlazorServer.ExampleConsumer.Models; +global using BlazorServer.ExampleConsumer.Pages; +global using BlazorServer.ExampleConsumer.Shared; +global using static System.Globalization.CultureInfo; diff --git a/samples/BlazorServer.ExampleConsumer/Models/TodoItem.cs b/samples/BlazorServer.ExampleConsumer/Models/TodoItem.cs new file mode 100644 index 0000000..cb7f5c3 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Models/TodoItem.cs @@ -0,0 +1,20 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using System.Text.RegularExpressions; + +namespace BlazorServer.ExampleConsumer.Models; + +public partial record class TodoItem( + string Task, + bool IsCompleted) +{ + internal const string IdPrefix = "todo"; + + [JsonIgnore] + public string Id => + $"{IdPrefix}{AlphabetOrDigitRegex().Replace(Task, "")}"; + + [GeneratedRegex("[^a-zA-Z0-9]")] + private static partial Regex AlphabetOrDigitRegex(); +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ClientPosition.razor b/samples/BlazorServer.ExampleConsumer/Pages/ClientPosition.razor new file mode 100644 index 0000000..25c9db7 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/ClientPosition.razor @@ -0,0 +1,38 @@ +@page "/geolocation" + +@inject IGeolocationService Geolocation + +Geolocation +

+ Geolocation + @if (_isLoading) + { + + Loading... + + } + @if (_position is not null) + { + : + + @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) + + + + + + } +

+ +@if (_isLoading) +{ +

This page demonstrates the source generated Blazor.Geolocation package.

+} + +@{ + + + + +} \ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ClientPosition.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/ClientPosition.razor.cs new file mode 100644 index 0000000..f977521 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/ClientPosition.razor.cs @@ -0,0 +1,54 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace BlazorServer.ExampleConsumer.Pages; + +public sealed partial class ClientPosition +{ + readonly JsonSerializerOptions _opts = new() + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + readonly PositionOptions _options = new() + { + EnableHighAccuracy = true, + MaximumAge = null, + Timeout = 15_000 + }; + + GeolocationPosition? _position; + GeolocationPositionError? _positionError; + bool _isLoading = true; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender is false) + { + return; + } + + await Geolocation.GetCurrentPositionAsync( + component: this, + onSuccessCallbackMethodName: nameof(OnPositionRecieved), + onErrorCallbackMethodName: nameof(OnPositionError), + options: _options); + } + + [JSInvokable] + public void OnPositionRecieved(GeolocationPosition position) + { + _isLoading = false; + _position = position; + StateHasChanged(); + } + + [JSInvokable] + public void OnPositionError(GeolocationPositionError positionError) + { + _isLoading = false; + _positionError = positionError; + StateHasChanged(); + } +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Counter.razor b/samples/BlazorServer.ExampleConsumer/Pages/Counter.razor deleted file mode 100644 index ef23cb3..0000000 --- a/samples/BlazorServer.ExampleConsumer/Pages/Counter.razor +++ /dev/null @@ -1,18 +0,0 @@ -@page "/counter" - -Counter - -

Counter

- -

Current count: @currentCount

- - - -@code { - private int currentCount = 0; - - private void IncrementCount() - { - currentCount++; - } -} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/FetchData.razor b/samples/BlazorServer.ExampleConsumer/Pages/FetchData.razor deleted file mode 100644 index 7fb4641..0000000 --- a/samples/BlazorServer.ExampleConsumer/Pages/FetchData.razor +++ /dev/null @@ -1,47 +0,0 @@ -@page "/fetchdata" -@using BlazorServer.ExampleConsumer.Data -@inject WeatherForecastService ForecastService - -Weather forecast - -

Weather forecast

- -

This component demonstrates fetching data from a service.

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ - - - - - - - - - - - @foreach (var forecast in forecasts) - { - - - - - - - } - -
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
-} - -@code { - private WeatherForecast[]? forecasts; - - protected override async Task OnInitializedAsync() - { - forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now)); - } -} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Index.razor b/samples/BlazorServer.ExampleConsumer/Pages/Index.razor index 6085c4a..014471e 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/Index.razor +++ b/samples/BlazorServer.ExampleConsumer/Pages/Index.razor @@ -2,8 +2,8 @@ Index -

Hello, world!

+

Hi friends! 🤓

-Welcome to your new app. +

This sample app shows how to consume the source-generated projects of #blazorators.

- + diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor b/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor new file mode 100644 index 0000000..002da94 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor @@ -0,0 +1,29 @@ +@page "/listen" + +Speech-to-text + +

This page demonstrates the source generated Blazor.SpeechRecongition package.

+ +
+ + +
+ + + \ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs new file mode 100644 index 0000000..3ddce3c --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs @@ -0,0 +1,94 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace BlazorServer.ExampleConsumer.Pages; + +public sealed partial class ListenToMe : IAsyncDisposable +{ + const string TranscriptKey = "listen-to-me-page-transcript"; + + IDisposable? _recognitionSubscription; + bool _isRecognizingSpeech = false; + SpeechRecognitionErrorEvent? _errorEvent; + string? _transcript; + + [Inject] + public ISpeechRecognitionService SpeechRecognition { get; set; } = null!; + + [Inject] + public ISessionStorageService SessionStorage { get; set; } = null!; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender is false) + { + return; + } + + _transcript = await SessionStorage.GetItemAsync(TranscriptKey); + } + + async Task OnRecognizeSpeechClick() + { + if (_isRecognizingSpeech) + { + await SpeechRecognition.CancelSpeechRecognitionAsync(false); + } + else + { + var bcp47Tag = CurrentUICulture.Name; + + _recognitionSubscription?.Dispose(); + _recognitionSubscription = await SpeechRecognition.RecognizeSpeechAsync( + bcp47Tag, + OnRecognized, + OnError, + OnStarted, + OnEnded); + } + } + + Task OnStarted() => + InvokeAsync(() => + { + _isRecognizingSpeech = true; + StateHasChanged(); + }); + + Task OnEnded() => + InvokeAsync(() => + { + _isRecognizingSpeech = false; + StateHasChanged(); + }); + + Task OnError(SpeechRecognitionErrorEvent errorEvent) => + InvokeAsync(() => + { + _errorEvent = errorEvent; + StateHasChanged(); + }); + + Task OnRecognized(string transcript) => + InvokeAsync(async () => + { + _transcript = _transcript switch + { + null => transcript, + _ => $"{_transcript.Trim()} {transcript}".Trim() + }; + + await SessionStorage.SetItemAsync(TranscriptKey, _transcript); + StateHasChanged(); + }); + + public async ValueTask DisposeAsync() + { + if (SpeechRecognition is not null) + { + await SpeechRecognition.DisposeAsync(); + } + + _recognitionSubscription?.Dispose(); + } +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor new file mode 100644 index 0000000..be18f14 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor @@ -0,0 +1,49 @@ +@page "/speak" + +Text-to-speech + +

This page demonstrates the source generated Blazor.SpeechSynthesis package.

+ +
+ + +
+ +
+ + + + @foreach (var speed in _voiceSpeeds) + { + + } + +
+ +
+ + +
+ +
+ +
\ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs new file mode 100644 index 0000000..579aa62 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs @@ -0,0 +1,94 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using Humanizer; + +namespace BlazorServer.ExampleConsumer.Pages; + +public sealed partial class ReadToMe : IAsyncDisposable +{ + const string PreferredVoiceKey = "preferred-voice"; + const string PreferredSpeedKey = "preferred-speed"; + const string TextKey = "read-to-me-text"; + + string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types."; + SpeechSynthesisVoice[] _voices = Array.Empty(); + readonly IList _voiceSpeeds = + Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); + double _voiceSpeed = 1.5; + string? _selectedVoice; + string? _elapsedTimeMessage = null; + + SpeechSynthesisUtterance Utterance => new() + { + Text = _text ?? "You forgot to try uttering some text.", + Rate = _voiceSpeed, + Volume = 1, + Voice = _selectedVoice is { Length: > 0 } + ? _voices?.FirstOrDefault(voice => voice.Name == _selectedVoice) + : null + }; + + [Inject] + public ISpeechSynthesisService SpeechSynthesis { get; set; } = null!; + + [Inject] + public ILocalStorageService LocalStorage { get; set; } = null!; + + [Inject] + public ISessionStorageService SessionStorage { get; set; } = null!; + + [Inject] + public ILogger Logger { get; set; } = null!; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender is false) + { + return; + } + + await GetVoicesAsync(); + + if (await LocalStorage.GetItemAsync(PreferredVoiceKey) + is { Length: > 0 } voice) + { + _selectedVoice = voice; + } + if (await LocalStorage.GetItemAsync(PreferredSpeedKey) + is { Length: > 0 } s && + double.TryParse(s, out var speed) && speed > 0) + { + _voiceSpeed = speed; + } + if (await SessionStorage.GetItemAsync(TextKey) + is { Length: > 0 } text) + { + _text = text; + } + } + + async Task GetVoicesAsync(bool isFromCallback = false) + { + _voices = await SpeechSynthesis.GetVoicesAsync(); + if (_voices is { } && isFromCallback) + { + StateHasChanged(); + } + } + + void OnTextChanged(ChangeEventArgs args) => _text = args.Value?.ToString(); + + void OnVoiceSpeedChange(ChangeEventArgs args) => + _voiceSpeed = double.TryParse(args.Value?.ToString() ?? "1.5", out var speed) + ? speed : 1.5; + + ValueTask Speak() => SpeechSynthesis.SpeakAsync(Utterance); + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await LocalStorage.SetItemAsync(PreferredVoiceKey, _selectedVoice!); + await LocalStorage.SetItemAsync(PreferredSpeedKey, _voiceSpeed.ToString()); + await SessionStorage.SetItemAsync(TextKey, _text!); + } +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Sandbox.razor b/samples/BlazorServer.ExampleConsumer/Pages/Sandbox.razor new file mode 100644 index 0000000..3f547f5 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/Sandbox.razor @@ -0,0 +1,49 @@ +@page "/sandbox" +@inject IGeolocationService GeolocationService + +

+ 🎭 Sandbox + @if (_isLoading) + { + + Loading... + + } + @if (_position is not null) + { + : + + @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) + + + + + + } +

+ + + + + + +@code { + bool _isLoading => _position is null && _positionError is null; + + GeolocationPosition? _position; + GeolocationPositionError? _positionError; + + protected override async Task OnInitializedAsync() => + await GeolocationService.GetCurrentPositionAsync( + position => + { + _position = position; + StateHasChanged(); + }, + positionError => + { + _positionError = positionError; + StateHasChanged(); + }); +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor b/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor new file mode 100644 index 0000000..2ec59d1 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor @@ -0,0 +1,83 @@ +@page "/todos" + +Tasks + +

+ Local Storage + + + +

+

This page demonstrates the source generated Blazor.LocalStorage package.

+ +
+
+
+
+
+ + + +
+
+
+ @if (_todos is { Count: > 0 }) + { +
    + @foreach (var todo in _todos) + { +
  • + + + + + +
  • + } +
+ } +
+
+@if (_localStorageItems is { Count: > 0 }) +{ +
+
+
+ Raw window.localStorage values + + + + + + + + + + @foreach (var (i, key, value) in _localStorageItems.Select((kvp, i) => (i, kvp.Key, kvp.Value))) + { + + + + + + } + +
#KeyValue
@i.ToString()@key.ToString()@value.ToString()
+
+
+
+} \ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs new file mode 100644 index 0000000..bb32045 --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs @@ -0,0 +1,113 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using Blazor.Serialization.Extensions; +using BlazorServer.ExampleConsumer.Models; +using Microsoft.AspNetCore.Components.Web; + +namespace BlazorServer.ExampleConsumer.Pages; + +public sealed partial class TodoList +{ + readonly Dictionary _localStorageItems = new(); + HashSet _todos = new(); + string? _todoValue; + + [Inject] + public ILocalStorageService LocalStorage { get; set; } = null!; + + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender is false) + { + return Task.CompletedTask; + } + + return UpdateTodoItemsAsync(); + } + + async Task UpdateTodoItemsAsync() + { + HashSet todos = new(); + await foreach (var key in GetLocalStorageKeysAsync()) + { + if (key.StartsWith(TodoItem.IdPrefix)) + { + var rawValue = await LocalStorage.GetItemAsync(key); + if (rawValue.FromJson() is TodoItem todo) + { + todos.Add(todo); + _localStorageItems[key] = todo.ToString(); + continue; + } + if (rawValue is not null) + { + _localStorageItems[key] = rawValue; + continue; + } + if (bool.TryParse(rawValue, out var @bool)) + { + _localStorageItems[key] = @bool.ToString(); + continue; + } + if (decimal.TryParse(rawValue, out var num)) + { + _localStorageItems[key] = num.ToString(); + continue; + } + } + } + + _todos = todos!; + } + + async IAsyncEnumerable GetLocalStorageKeysAsync() + { + var length = await LocalStorage.Length; + for (var i = 0; i < length; ++i) + { + if (await LocalStorage.KeyAsync(i) is { Length: > 0 } key) + { + yield return key; + } + } + } + + async Task AddNewTodoAsync() + { + if (_todoValue is not null) + { + var todo = new TodoItem(_todoValue, false); + await LocalStorage.SetItemAsync(todo.Id, todo.ToJson()); + await UpdateTodoItemsAsync(); + _todoValue = null; + } + } + + Task OnKeyUp(KeyboardEventArgs args) => + args is { Key: "Enter" } + ? AddNewTodoAsync() + : Task.CompletedTask; + + async Task Delete(TodoItem todo) + { + await LocalStorage.RemoveItemAsync(todo.Id); + _todos.RemoveWhere(t => t.Id == todo.Id); + _localStorageItems.Remove(todo.Id); + } + + async Task ClearAll() + { + await LocalStorage.ClearAsync(); + _todos.Clear(); + _localStorageItems.Clear(); + } + + async Task OnItemChanged(TodoItem _) + { + await Task.CompletedTask; + await UpdateTodoItemsAsync(); + } +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Track.razor b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor new file mode 100644 index 0000000..920482d --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor @@ -0,0 +1,38 @@ +@page "/track" + +@inject IGeolocationService Geolocation + +Track +

+ Geolocation + @if (_isLoading) + { + + Loading... + + } + @if (_position is not null) + { + : + + @(_position.TimestampAsUtcDateTime.ToLocalTime().ToString()) + + + + + + } +

+ +@if (_isLoading) +{ +

This page demonstrates the source generated Blazor.Geolocation package.

+} + +@{ + + + + +} \ No newline at end of file diff --git a/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs new file mode 100644 index 0000000..91f921e --- /dev/null +++ b/samples/BlazorServer.ExampleConsumer/Pages/Track.razor.cs @@ -0,0 +1,58 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace BlazorServer.ExampleConsumer.Pages; + +public sealed partial class Track : IAsyncDisposable +{ + readonly JsonSerializerOptions _opts = new() + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + readonly PositionOptions _options = new() + { + EnableHighAccuracy = true, + MaximumAge = null, + Timeout = 15_000 + }; + + GeolocationPosition? _position; + GeolocationPositionError? _positionError; + double _watchId; + bool _isLoading = true; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender is false) + { + return; + } + + _watchId = await Geolocation.WatchPositionAsync( + component: this, + onSuccessCallbackMethodName: nameof(OnPositionRecieved), + onErrorCallbackMethodName: nameof(OnPositionError), + options: _options); + } + + [JSInvokable] + public void OnPositionRecieved(GeolocationPosition position) + { + _isLoading = false; + _position = position; + StateHasChanged(); + } + + [JSInvokable] + public void OnPositionError(GeolocationPositionError positionError) + { + _isLoading = false; + _positionError = positionError; + StateHasChanged(); + } + + public async ValueTask DisposeAsync() => + await Geolocation.ClearWatchAsync(_watchId); +} diff --git a/samples/BlazorServer.ExampleConsumer/Pages/_Host.cshtml b/samples/BlazorServer.ExampleConsumer/Pages/_Host.cshtml index fdc9dd9..d0b5896 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/_Host.cshtml +++ b/samples/BlazorServer.ExampleConsumer/Pages/_Host.cshtml @@ -16,6 +16,13 @@ + + Fork me on GitHub + +
@@ -30,5 +37,9 @@
+ + + + diff --git a/samples/BlazorServer.ExampleConsumer/Program.cs b/samples/BlazorServer.ExampleConsumer/Program.cs index 2eaf62a..f5f6ee7 100644 --- a/samples/BlazorServer.ExampleConsumer/Program.cs +++ b/samples/BlazorServer.ExampleConsumer/Program.cs @@ -1,14 +1,11 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. -using BlazorServer.ExampleConsumer.Data; - var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddSingleton(); // Use source-generated DI bits... builder.Services.AddLocalStorageServices(); diff --git a/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor b/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor index 65151ec..042aba3 100644 --- a/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor +++ b/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor @@ -1,18 +1,13 @@ @inherits LayoutComponentBase -BlazorServer.ExampleConsumer -
-
- About -
- -
+
+
@Body
diff --git a/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor.css b/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor.css index 551e4b2..c865427 100644 --- a/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor.css +++ b/samples/BlazorServer.ExampleConsumer/Shared/MainLayout.razor.css @@ -21,12 +21,17 @@ main { align-items: center; } - .top-row ::deep a, .top-row .btn-link { + .top-row ::deep a, .top-row ::deep .btn-link { white-space: nowrap; margin-left: 1.5rem; + text-decoration: none; } - .top-row a:first-child { + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { overflow: hidden; text-overflow: ellipsis; } @@ -40,7 +45,7 @@ main { justify-content: space-between; } - .top-row a, .top-row .btn-link { + .top-row ::deep a, .top-row ::deep .btn-link { margin-left: 0; } } @@ -63,6 +68,12 @@ main { z-index: 1; } + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + .top-row, article { padding-left: 2rem !important; padding-right: 1.5rem !important; diff --git a/samples/BlazorServer.ExampleConsumer/Shared/NavMenu.razor b/samples/BlazorServer.ExampleConsumer/Shared/NavMenu.razor index 63b3f34..3542a7e 100644 --- a/samples/BlazorServer.ExampleConsumer/Shared/NavMenu.razor +++ b/samples/BlazorServer.ExampleConsumer/Shared/NavMenu.razor @@ -1,39 +1,63 @@  -