From 5dd42e91dbe1f6a68b5a2a415221de9e75d6ae9a Mon Sep 17 00:00:00 2001 From: Saleh Yusefnejad Date: Wed, 25 Dec 2024 09:37:50 +0330 Subject: [PATCH 01/28] feat(blazorui): add BitProLayout component #9502 (#9509) --- .../Components/ProLayout/BitCascadingValue.cs | 44 ++++++ .../ProLayout/BitCascadingValueProvider.cs | 69 +++++++++ .../Components/ProLayout/BitProLayout.razor | 23 +++ .../ProLayout/BitProLayout.razor.cs | 38 +++++ .../Components/ProLayout/BitProLayout.scss | 54 +++++++ .../ProLayout/BitProLayoutClassStyles.cs | 39 +++++ .../Bit.BlazorUI.Extras/Scripts/general.ts | 4 + .../Styles/bit.blazorui.extras.scss | 3 +- .../Styles/components.scss | 1 + .../Styles/extra-variables.scss | 17 +++ .../Bit.BlazorUI.Extras/Styles/general.scss | 21 +++ .../Components/ComponentExampleBox.razor | 51 ++++--- .../Extras/ProLayout/BitProLayoutDemo.razor | 24 ++++ .../ProLayout/BitProLayoutDemo.razor.cs | 133 ++++++++++++++++++ .../ProLayout/BitProLayoutDemo.razor.scss | 0 .../Pages/Home/ComponentsSection.razor | 3 + .../Shared/NavMenu.razor.cs | 1 + 17 files changed, 502 insertions(+), 23 deletions(-) create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValue.cs create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValueProvider.cs create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor.cs create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.scss create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayoutClassStyles.cs create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Scripts/general.ts create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-variables.scss create mode 100644 src/BlazorUI/Bit.BlazorUI.Extras/Styles/general.scss create mode 100644 src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor create mode 100644 src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.cs create mode 100644 src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.scss diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValue.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValue.cs new file mode 100644 index 0000000000..16f09199d0 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValue.cs @@ -0,0 +1,44 @@ +namespace Bit.BlazorUI; + +/// +/// The cascading value to be provided using the component. +/// +public class BitCascadingValue +{ + /// + /// The optional name of the cascading value. + /// + public string? Name { get; set; } + + /// + /// The value to be provided. + /// + public object? Value { get; set; } + + /// + /// If true, indicates that will not change. + /// + public bool IsFixed { get; set; } + + + + public BitCascadingValue() { } + public BitCascadingValue(object? value, string? name = null) : this(value, name, false) { } + public BitCascadingValue(object? value, bool isFixed) : this(value, null, isFixed) { } + public BitCascadingValue(object? value, string? name, bool isFixed) + { + Value = value; + Name = name; + IsFixed = isFixed; + } + + + + public static implicit operator BitCascadingValue(int value) => new(value); + public static implicit operator BitCascadingValue(int? value) => new(value); + public static implicit operator BitCascadingValue(bool value) => new(value); + public static implicit operator BitCascadingValue(bool? value) => new(value); + public static implicit operator BitCascadingValue(string value) => new(value); + public static implicit operator BitCascadingValue(BitDir? value) => new(value); + public static implicit operator BitCascadingValue(RouteData value) => new(value); +} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValueProvider.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValueProvider.cs new file mode 100644 index 0000000000..4dc929cf99 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitCascadingValueProvider.cs @@ -0,0 +1,69 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Bit.BlazorUI; + +/// +/// A component that provides a list of cascading values to all descendant components. +/// +public class BitCascadingValueProvider : ComponentBase +{ + /// + /// The content to which the values should be provided. + /// + [Parameter] public RenderFragment? ChildContent { get; set; } + + /// + /// The cascading values to be provided for the children. + /// + [Parameter] public IEnumerable? Values { get; set; } + + + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + private Type _cascadingValueType = typeof(CascadingValue<>); + + + + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BitCascadingValue))] + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var seq = 0; + RenderFragment? rf = ChildContent; + + foreach (var value in Values ?? []) + { + if (value.Value is null) continue; + + var r = rf; + var s = seq; + var v = value; + + rf = b => { CreateCascadingValue(b, s, v.Name, v.Value, v.IsFixed, r); }; + + seq += v.Name.HasValue() ? 5 : 4; + } + + builder.AddContent(seq, rf); + } + + + private void CreateCascadingValue(RenderTreeBuilder builder, + int seq, + string? name, + object value, + bool isFixed, + RenderFragment? childContent) + { +#pragma warning disable IL2055 // Either the type on which the MakeGenericType is called can't be statically determined, or the type parameters to be used for generic arguments can't be statically determined. + builder.OpenComponent(seq, _cascadingValueType.MakeGenericType(value.GetType())); +#pragma warning restore IL2055 // Either the type on which the MakeGenericType is called can't be statically determined, or the type parameters to be used for generic arguments can't be statically determined. + if (string.IsNullOrEmpty(name) is false) + { + builder.AddComponentParameter(++seq, "Name", name); + } + builder.AddComponentParameter(++seq, "Value", value); + builder.AddComponentParameter(++seq, "IsFixed", isFixed); + builder.AddComponentParameter(++seq, "ChildContent", childContent); + builder.CloseComponent(); + } +} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor new file mode 100644 index 0000000000..0b25c15ac0 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor @@ -0,0 +1,23 @@ +@namespace Bit.BlazorUI +@inherits BitComponentBase + +
+
+
+
+
+ + @ChildContent + +
+
+
+
+
\ No newline at end of file diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor.cs new file mode 100644 index 0000000000..af75a24d4a --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.razor.cs @@ -0,0 +1,38 @@ +namespace Bit.BlazorUI; + +public partial class BitProLayout : BitComponentBase +{ + /// + /// The cascading values to be provided for the children of the layout. + /// + [Parameter] public IEnumerable? CascadingValues { get; set; } + + /// + /// The content of the layout. + /// + [Parameter] public RenderFragment? ChildContent { get; set; } + + /// + /// Custom CSS classes for different parts of the layout. + /// + [Parameter] public BitProLayoutClassStyles? Classes { get; set; } + + /// + /// Custom CSS styles for different parts of the layout. + /// + [Parameter] public BitProLayoutClassStyles? Styles { get; set; } + + + + protected override string RootElementClass => "bit-ply"; + + protected override void RegisterCssClasses() + { + ClassBuilder.Register(() => Classes?.Root); + } + + protected override void RegisterCssStyles() + { + StyleBuilder.Register(() => Styles?.Root); + } +} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.scss new file mode 100644 index 0000000000..81b2fa70d4 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayout.scss @@ -0,0 +1,54 @@ +@import '../../Styles/extra-variables.scss'; +@import '../../../Bit.BlazorUI/Styles/functions.scss'; + +.bit-ply { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: $clr-bg-pri; +} + +.bit-ply-top { + width: 100%; + z-index: 999999; + height: $bit-env-inset-top; + background-color: $clr-bg-pri; +} + +.bit-ply-bottom { + width: 100%; + z-index: 999999; + height: $bit-env-inset-bottom; + background-color: $clr-bg-pri; +} + +.bit-ply-center { + width: 100%; + display: flex; + height: calc(100% - $bit-env-inset-top - $bit-env-inset-bottom); +} + +.bit-ply-main { + height: 100%; + display: flex; + overflow: auto; + position: relative; + scroll-behavior: smooth; + overscroll-behavior: none; + width: calc(100% - $bit-env-inset-left - $bit-env-inset-right); +} + +.bit-ply-left { + height: 100%; + z-index: 999999; + width: $bit-env-inset-left; + background-color: $clr-bg-pri; +} + +.bit-ply-right { + height: 100%; + z-index: 999999; + width: $bit-env-inset-right; + background-color: $clr-bg-pri; +} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayoutClassStyles.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayoutClassStyles.cs new file mode 100644 index 0000000000..6793afc5ca --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/ProLayout/BitProLayoutClassStyles.cs @@ -0,0 +1,39 @@ +namespace Bit.BlazorUI; + +public class BitProLayoutClassStyles +{ + /// + /// Custom CSS classes/styles for the root of the BitProLayout. + /// + public string? Root { get; set; } + + /// + /// Custom CSS classes/styles for the top area of the BitProLayout. + /// + public string? Top { get; set; } + + /// + /// Custom CSS classes/styles for the center area of the BitProLayout. + /// + public string? Center { get; set; } + + /// + /// Custom CSS classes/styles for the left area of the BitProLayout. + /// + public string? Left { get; set; } + + /// + /// Custom CSS classes/styles for the main area of the BitProLayout. + /// + public string? Main { get; set; } + + /// + /// Custom CSS classes/styles for the right area of the BitProLayout. + /// + public string? Right { get; set; } + + /// + /// Custom CSS classes/styles for the bottom area of the BitProLayout. + /// + public string? Bottom { get; set; } +} diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/general.ts b/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/general.ts new file mode 100644 index 0000000000..d19e62953b --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Scripts/general.ts @@ -0,0 +1,4 @@ +window.addEventListener('load', () => { + document.documentElement.style.setProperty('--bit-env-win-width', `${window.innerWidth}px`); + document.documentElement.style.setProperty('--bit-env-win-height', `${window.innerHeight}px`); +}); \ No newline at end of file diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/bit.blazorui.extras.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/bit.blazorui.extras.scss index 4d58f1782c..eb02b8b603 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/bit.blazorui.extras.scss +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/bit.blazorui.extras.scss @@ -1,2 +1,3 @@ -@import "components.scss"; +@import "general.scss"; +@import "components.scss"; @import "fabric.mdl2.bit.blazoui.extras.scss"; diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/components.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/components.scss index ca46bd9aef..7dcba98b06 100644 --- a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/components.scss +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/components.scss @@ -2,3 +2,4 @@ @import "../Components/DataGrid/Pagination/BitDataGridPaginator.scss"; @import "../Components/PdfReader/BitPdfReader.scss"; @import "../Components/ProPanel/BitProPanel.scss"; +@import "../Components/ProLayout/BitProLayout.scss"; diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-variables.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-variables.scss new file mode 100644 index 0000000000..8f09e8a4e9 --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-variables.scss @@ -0,0 +1,17 @@ +$bit-env-inset-top: var(--bit-env-inset-top); +$bit-env-inset-left: var(--bit-env-inset-left); +$bit-env-inset-right: var(--bit-env-inset-right); +$bit-env-inset-bottom: var(--bit-env-inset-bottom); +//-- +$bit-env-width-vw: var(--bit-env-width-vw); +$bit-env-height-vh: var(--bit-env-height-vh); +$bit-env-width-percent: var(--bit-env-width-per); +$bit-env-height-percent: var(--bit-env-height-per); +$bit-env-width-available: var(--bit-env-width-avl); +$bit-env-height-available: var(--bit-env-height-avl); +//-- +$bit-env-inset-inline-start: var(--bit-env-inset-inline-start); +$bit-env-inset-inline-end: var(--bit-env-inset-inline-end); +//-- +$bit-env-window-width: var(--bit-env-win-width); +$bit-env-window-height: var(--bit-env-win-height); diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Styles/general.scss b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/general.scss new file mode 100644 index 0000000000..d77e2b499d --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI.Extras/Styles/general.scss @@ -0,0 +1,21 @@ +:root { + --bit-env-inset-top: env(safe-area-inset-top, 0px); + --bit-env-inset-left: env(safe-area-inset-left, 0px); + --bit-env-inset-right: env(safe-area-inset-right, 0px); + --bit-env-inset-bottom: env(safe-area-inset-bottom, 0px); + //-- + --bit-env-width-vw: calc(100vw - var(--bit-env-inset-left) - var(--bit-env-inset-right)); + --bit-env-height-vh: calc(100vh - var(--bit-env-inset-top) - var(--bit-env-inset-bottom)); + --bit-env-width-per: calc(100% - var(--bit-env-inset-left) - var(--bit-env-inset-right)); + --bit-env-height-per: calc(100% - var(--bit-env-inset-top) - var(--bit-env-inset-bottom)); + --bit-env-width-avl: calc(var(--bit-env-win-width) - var(--bit-env-inset-left) - var(--bit-env-inset-right)); + --bit-env-height-avl: calc(var(--bit-env-win-height) - var(--bit-env-inset-top) - var(--bit-env-inset-bottom)); + //-- + --bit-env-inset-inline-start: var(--bit-env-inset-left); + --bit-env-inset-inline-end: var(--bit-env-inset-right); + + [dir="rtl"] { + --bit-env-inset-inline-start: var(--bit-env-inset-right); + --bit-env-inset-inline-end: var(--bit-env-inset-left); + } +} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentExampleBox.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentExampleBox.razor index dd473865ca..f8a69fc59a 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentExampleBox.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Components/ComponentExampleBox.razor @@ -3,25 +3,28 @@
@Title -
- - @(showCode ? "Hide code" : "Show code") - - - @if (isLinkCopied) - { - @copyLinkMessage - } - -
+ @if (RazorCode is not null || CsharpCode is not null) + { +
+ + @(showCode ? "Hide code" : "Show code") + + + @if (isLinkCopied) + { + @copyLinkMessage + } + +
+ }
@@ -39,14 +42,18 @@
-                @RazorCode.Trim()
+                
+                
+                    @RazorCode?.Trim()
+                
 
                 @if (CsharpCode.HasValue())
                 {
                     
 @code {
-     @CsharpCode.Trim().Replace("\n", "\n     ")
-}
+    @CsharpCode?.Trim().Replace("\n", "\n     ")
+}
+                    
                 }
             
} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor new file mode 100644 index 0000000000..c8f12f1d69 --- /dev/null +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor @@ -0,0 +1,24 @@ +@page "/components/prolayout" + +@inherits AppComponentBase + + + + + + +
Since this component is a base layout container, it is not possible to show its capabilities in a demo sample here.
+
+ You can always check our Boilerplate project template samples + (AdminPanel & Todo) + to see the BitProLayout in action. +
+
+
+
\ No newline at end of file diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.cs new file mode 100644 index 0000000000..714dbf8819 --- /dev/null +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.cs @@ -0,0 +1,133 @@ +namespace Bit.BlazorUI.Demo.Client.Core.Pages.Components.Extras.ProLayout; + +public partial class BitProLayoutDemo +{ + private readonly List componentParameters = + [ + new() + { + Name = "CascadingValues", + Type = "IEnumerable?", + DefaultValue = "null", + Description = "The cascading values to be provided for the children of the layout.", + LinkType = LinkType.Link, + Href = "#cascading-value" + }, + new() + { + Name = "ChildContent", + Type = "RenderFragment?", + DefaultValue = "null", + Description = "The content of the layout.", + }, + new() + { + Name = "Classes", + Type = "BitProLayoutClassStyles?", + DefaultValue = "null", + Description = "Custom CSS classes for different parts of the layout.", + LinkType = LinkType.Link, + Href = "#class-styles" + }, + new() + { + Name = "Styles", + Type = "BitProLayoutClassStyles?", + DefaultValue = "null", + Description = "Custom CSS styles for different parts of the layout.", + LinkType = LinkType.Link, + Href = "#class-styles" + }, + ]; + + private readonly List componentSubClasses = + [new() + { + Id = "cascading-value", + Title = "BitCascadingValue", + Description = "The cascading value to be provided using the BitCascadingValueProvider component.", + Parameters = + [ + new() + { + Name = "Name", + Type = "string?", + DefaultValue = "null", + Description = "The optional name of the cascading value.", + }, + new() + { + Name = "Value", + Type = "object?", + DefaultValue = "null", + Description = "The value to be provided.", + }, + new() + { + Name = "IsFixed", + Type = "bool", + DefaultValue = "null", + Description = "If true, indicates that Value will not change.", + } + ] + }, + new() + { + Id = "class-styles", + Title = "BitProLayoutClassStyles", + Parameters = + [ + new() + { + Name = "Root", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the root of the BitProLayout.", + }, + new() + { + Name = "Top", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the top area of the BitProLayout.", + }, + new() + { + Name = "Center", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the top center of the BitProLayout.", + }, + new() + { + Name = "Left", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the top left of the BitProLayout.", + }, + new() + { + Name = "Main", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the main area of the BitProLayout.", + }, + new() + { + Name = "Right", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the right area of the BitProLayout.", + }, + new() + { + Name = "Bottom", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the bottom area of the BitProLayout.", + }, + ] + } + ]; + +} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProLayout/BitProLayoutDemo.razor.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor index b3e28e6a15..79f9ec0e34 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor @@ -253,6 +253,9 @@ PdfReader + + ProLayout + ProPanel diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/NavMenu.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/NavMenu.razor.cs index 84b6f10f89..88e59091ab 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/NavMenu.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/NavMenu.razor.cs @@ -156,6 +156,7 @@ public partial class NavMenu : IDisposable new() { Text = "DataGrid", Url = "/components/datagrid", AdditionalUrls = ["/components/data-grid"] }, new() { Text = "Chart", Url = "/components/chart" }, new() { Text = "PdfReader", Url = "/components/pdfreader" }, + new() { Text = "ProLayout", Url = "/components/prolayout" }, new() { Text = "ProPanel", Url = "/components/propanel" }, new() { From 44afd49c4454fb82a3b0aa760820f6af35c389a8 Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Wed, 25 Dec 2024 16:38:47 +0100 Subject: [PATCH 02/28] feat(websites): add offline todo web app demo #9542 (#9543) --- .github/workflows/admin-sample.cd.yml | 2 +- .github/workflows/todo-sample.cd.yml | 100 +++++++++++++++++++++++++- README.md | 14 ++-- 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/.github/workflows/admin-sample.cd.yml b/.github/workflows/admin-sample.cd.yml index 20c90a0b1e..cb4d62d3d5 100644 --- a/.github/workflows/admin-sample.cd.yml +++ b/.github/workflows/admin-sample.cd.yml @@ -186,7 +186,7 @@ jobs: cd src\Templates\Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ..\..\..\ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --windows --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --captcha reCaptcha --signalR --offlineDb --framework net9.0 + cd ..\..\..\ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --windows --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --captcha reCaptcha --signalR --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 diff --git a/.github/workflows/todo-sample.cd.yml b/.github/workflows/todo-sample.cd.yml index b668a00726..4b1cc935cb 100644 --- a/.github/workflows/todo-sample.cd.yml +++ b/.github/workflows/todo-sample.cd.yml @@ -121,8 +121,8 @@ jobs: CLOUDFLARE_ZONE: ${{ secrets.BITPLATFORM_DEV_CLOUDFLARE_ZONE }} CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} - deploy_blazor_wasm_standalone: - name: build blazor wasm standalone + deploy_blazor_wasm_standalone_aot: + name: build blazor wasm standalone (AOT) runs-on: ubuntu-24.04 steps: @@ -166,7 +166,101 @@ jobs: - name: Upload to asw run: | npm install -g @azure/static-web-apps-cli - swa deploy --deployment-token ${{ secrets.TODO_ASW_TOKEN }} --env production --app-location ${{env.DOTNET_ROOT}}/client/wwwroot + swa deploy --deployment-token ${{ secrets.TODO_AOT_ASW_TOKEN }} --env production --app-location ${{env.DOTNET_ROOT}}/client/wwwroot + + deploy_blazor_wasm_standalone_offlineDb: + name: build blazor wasm standalone (Offline database) + runs-on: ubuntu-24.04 + + steps: + + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: src/global.json + + - name: Create project from Boilerplate + run: | + cd src/Templates/Boilerplate && dotnet build -c Release + dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 + dotnet new install Bit.Boilerplate.0.0.0.nupkg + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --offlineDb --signalR --framework net9.0 + + - name: Update core appsettings.json + uses: devops-actions/variable-substitution@v1.2 + with: + files: 'TodoSample/src/Shared/appsettings.json, TodoSample/src/Client/TodoSample.Client.Core/appsettings.json, TodoSample/src/Client/TodoSample.Client.Web/appsettings.json, TodoSample/src/Client/TodoSample.Client.Web/appsettings.Production.json' + env: + ServerAddress: ${{ env.SERVER_ADDRESS }} + GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install wasm + run: cd src && dotnet workload install wasm-tools + + - name: Generate CSS/JS files + run: dotnet build TodoSample/src/Client/TodoSample.Client.Core/TodoSample.Client.Core.csproj -t:BeforeBuildTasks -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" --no-restore -c Release + + - name: Publish + run: dotnet publish TodoSample/src/Client/TodoSample.Client.Web/TodoSample.Client.Web.csproj -c Release -p:PwaEnabled=true -o ${{env.DOTNET_ROOT}}/client -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" + + - name: Upload to asw + run: | + npm install -g @azure/static-web-apps-cli + swa deploy --deployment-token ${{ secrets.TODO_OFFLINE_ASW_TOKEN }} --env production --app-location ${{env.DOTNET_ROOT}}/client/wwwroot + + deploy_blazor_wasm_standalone_small: + name: build blazor wasm standalone (small) + runs-on: ubuntu-24.04 + + steps: + + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: src/global.json + + - name: Create project from Boilerplate + run: | + cd src/Templates/Boilerplate && dotnet build -c Release + dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 + dotnet new install Bit.Boilerplate.0.0.0.nupkg + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --framework net9.0 + + - name: Update core appsettings.json + uses: devops-actions/variable-substitution@v1.2 + with: + files: 'TodoSample/src/Shared/appsettings.json, TodoSample/src/Client/TodoSample.Client.Core/appsettings.json, TodoSample/src/Client/TodoSample.Client.Web/appsettings.json, TodoSample/src/Client/TodoSample.Client.Web/appsettings.Production.json' + env: + ServerAddress: ${{ env.SERVER_ADDRESS }} + GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install wasm + run: cd src && dotnet workload install wasm-tools + + - name: Generate CSS/JS files + run: dotnet build TodoSample/src/Client/TodoSample.Client.Core/TodoSample.Client.Core.csproj -t:BeforeBuildTasks -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" --no-restore -c Release + + - name: Publish + run: dotnet publish TodoSample/src/Client/TodoSample.Client.Web/TodoSample.Client.Web.csproj -c Release -p:PwaEnabled=true -o ${{env.DOTNET_ROOT}}/client -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" -p:MultilingualEnabled=false + + - name: Upload to asw + run: | + npm install -g @azure/static-web-apps-cli + swa deploy --deployment-token ${{ secrets.TODO_SMALL_ASW_TOKEN }} --env production --app-location ${{env.DOTNET_ROOT}}/client/wwwroot build_blazor_hybrid_windows: name: build blazor hybrid (windows) diff --git a/README.md b/README.md index e6111238e8..f57456a326 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,14 @@ The following apps are our open-source projects powered by the bit platform show | AdminPanel | [![Prerendered PWA](https://github-production-user-asset-6210df.s3.amazonaws.com/6169846/251381583-8b8eb895-80c9-4811-9641-57a5a08db163.png)](https://adminpanel.bitplatform.dev) | [![iOS app](https://github-production-user-asset-6210df.s3.amazonaws.com/6169846/251381842-e72976ce-fd20-431d-a677-ca1ed625b83b.png)](https://apps.apple.com/us/app/bit-adminpanel/id6450611349) | [![Android app](https://github-production-user-asset-6210df.s3.amazonaws.com/6169846/251381958-24931682-87f6-44fc-a1c7-eecf46387005.png)](https://play.google.com/store/apps/details?id=com.bitplatform.AdminPanel.Template) | [![Windows app](https://github-production-user-asset-6210df.s3.amazonaws.com/6169846/251382080-9ae97fea-934c-4097-aca4-124a2aed1595.png)](https://windows-admin.bitplatform.dev/AdminPanel.Client.Windows-win-Setup.exe) | [![macOS app](https://github-production-user-asset-6210df.s3.amazonaws.com/6169846/251382211-0d58f9ba-1a1f-4481-a0ca-b23a393cca9f.png)](https://apps.apple.com/nl/app/bit-adminpanel/id6450611349) | | bitplatform | [![SPA](https://github-production-user-asset-6210df.s3.amazonaws.com/6169846/251395129-71a5a79c-af74-4d4e-a0f7-ed9a15cf2e46.png)](https://bitplatform.dev)| -1. [bitplatform.dev](https://bitplatform.dev): .NET 9 Pre-rendered SPA with Blazor WebAssembly -2. [blazorui.bitplatform.dev](https://blazorui.bitplatform.dev): .NET 9 Pre-rendered PWA with Blazor WebAssembly -3. [todo.bitplatform.dev](https://todo.bitplatform.dev): .NET 8 Pre-rendered PWA with Blazor WebAssembly -5. [adminpanel.bitplatform.dev](https://adminpanel.bitplatform.dev): .NET 9 PWA with Blazor WebAssembly -6. [adminpanel.bitplatform.cc](https://adminpanel.bitplatform.cc): .NET 9 PWA with Blazor WebAssembly Standalone (Free Azure static web app) -7. [todo.bitplatform.cc](https://todo.bitplatform.cc): AOT Compiled .NET 9 PWA with Blazor WebAssembly Standalone (Free Azure static web app) +1. [bitplatform.dev](https://bitplatform.dev): .NET 9 Pre-rendered SPA with Blazor WebAssembly (Azure Web App + Cloudflare CDN) +2. [blazorui.bitplatform.dev](https://blazorui.bitplatform.dev): .NET 9 Pre-rendered PWA with Blazor WebAssembly (Azure Web App + Cloudflare CDN) +3. [todo.bitplatform.dev](https://todo.bitplatform.dev): .NET 8 Pre-rendered PWA with Blazor WebAssembly (Azure Web App + Cloudflare CDN) +5. [adminpanel.bitplatform.dev](https://adminpanel.bitplatform.dev): .NET 9 PWA with Blazor WebAssembly (Azure Web App + Cloudflare CDN) +6. [adminpanel.bitplatform.cc](https://adminpanel.bitplatform.cc): .NET 9 PWA with Blazor WebAssembly Standalone (Azure static web app) +7. [todo-aot.bitplatform.cc](https://todo-aot.bitplatform.cc): .NET 9 AOT Compiled PWA with Blazor WebAssembly Standalone (Azure static web app) +8. [todo-small.bitplatform.cc](https://todo-small.bitplatform.cc): .NET 9 Todo demo app with smaller download footprint (Azure static web app) +9. [todo-offline.bitplatform.cc](https://todo-offline.bitplatform.cc): .NET 9 Todo demo app with ef-core & sqlite (Azure static web app) [Todo](https://todo.bitplatform.dev) & [Adminpanel](https://adminpanel.bitplatform.dev) web apps will launch their respective Android and iOS applications if you have already installed them, mirroring the behavior of apps like YouTube and Instagram. From 3a3bfaf20d3df7bc6e14c8dd9e33269797872ae4 Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Thu, 26 Dec 2024 08:39:44 +0100 Subject: [PATCH 03/28] feat(templates): pass logger to velopack app #9544 (#9545) --- .../src/Client/Boilerplate.Client.Windows/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.cs index 4a5405502e..5f994c7865 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.cs @@ -3,6 +3,7 @@ using Microsoft.Web.WebView2.Core; using Boilerplate.Client.Core.Components; using Boilerplate.Client.Windows.Services; +using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Components.WebView.WindowsForms; namespace Boilerplate.Client.Windows; @@ -42,7 +43,7 @@ public static void Main(string[] args) }); // https://github.com/velopack/velopack - VelopackApp.Build().Run(); + VelopackApp.Build().Run(Services.GetRequiredService>()); _ = Task.Run(async () => { try From 0d4f11662beae0fc0b670084a1072ccee6e69192 Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Thu, 26 Dec 2024 20:56:14 +0100 Subject: [PATCH 04/28] feat(deps): update project dependencies #9547 (#9548) --- .../Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj | 4 ++-- .../Bit.BlazorUI.Demo.Server.csproj | 8 ++++---- .../Bit.BlazorUI.Demo.Client.Windows.csproj | 2 +- .../Bit.Boilerplate/src/Directory.Packages.props | 12 ++++++------ .../Bit.Boilerplate/src/Directory.Packages8.props | 12 ++++++------ .../Bit.Websites.Careers.Server.csproj | 8 ++++---- .../Bit.Websites.Platform.Server.csproj | 8 ++++---- .../Bit.Websites.Sales.Server.csproj | 8 ++++---- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj b/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj index cb16dd5cbb..fb70e9cf9e 100644 --- a/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj +++ b/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj @@ -15,8 +15,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj index a0b7a01a85..80815d7c14 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj @@ -16,10 +16,10 @@ - - - - + + + + diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Windows/Bit.BlazorUI.Demo.Client.Windows.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Windows/Bit.BlazorUI.Demo.Client.Windows.csproj index bb00b11acf..0964aff69a 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Windows/Bit.BlazorUI.Demo.Client.Windows.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Windows/Bit.BlazorUI.Demo.Client.Windows.csproj @@ -41,7 +41,7 @@ - + PreserveNewest diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props index 4ba12eda15..31ed6df3e0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props @@ -22,16 +22,16 @@ - + - - - + + + @@ -92,7 +92,7 @@ - - + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props index c3684fd67c..226b07c7ca 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages8.props @@ -22,16 +22,16 @@ - + - - - + + + @@ -92,7 +92,7 @@ - - + + \ No newline at end of file diff --git a/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj b/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj index ab68d34fb2..639d2f8331 100644 --- a/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj +++ b/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj @@ -19,10 +19,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj b/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj index 85b4fb2147..bdca6c4f9f 100644 --- a/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj +++ b/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj @@ -19,10 +19,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj b/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj index 6c400b121f..1234b62265 100644 --- a/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj +++ b/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj @@ -19,10 +19,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + From 03829a54a9b1a217f49a4fa0745db46b4269b6c9 Mon Sep 17 00:00:00 2001 From: Mohammad Aminsafaei Date: Fri, 27 Dec 2024 16:53:43 +0330 Subject: [PATCH 05/28] feat(blazorui): add Classes/Styles parameters to BitButtonGroup #9514 (#9546) --- .../BitButtonGroup/BitButtonGroup.razor | 6 +- .../BitButtonGroup/BitButtonGroup.razor.cs | 50 ++ .../BitButtonGroupClassStyles.cs | 29 + .../ButtonGroup/BitButtonGroupDemo.razor.cs | 61 ++ .../ButtonGroup/BitButtonGroupDemo.razor.scss | 6 + .../_BitButtonGroupCustomDemo.razor | 315 ++++++----- ..._BitButtonGroupCustomDemo.razor.samples.cs | 290 +++++----- .../ButtonGroup/_BitButtonGroupItemDemo.razor | 305 +++++----- .../_BitButtonGroupItemDemo.razor.samples.cs | 238 ++++---- .../_BitButtonGroupOptionDemo.razor | 527 +++++++++--------- ..._BitButtonGroupOptionDemo.razor.samples.cs | 332 +++++------ 11 files changed, 1196 insertions(+), 963 deletions(-) create mode 100644 src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupClassStyles.cs diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor index 34a0df0ad6..790fc14f3f 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor @@ -22,7 +22,7 @@ disabled="@(isEnabled is false)" aria-disabled="@(isEnabled is false)" title="@GetItemTitle(item)" - style="@GetStyle(item)" + style="@GetItemStyle(item)" class="@GetItemClass(item)"> @if (template is not null) { @@ -37,13 +37,13 @@ var iconName = GetItemIconName(item); @if (iconName.HasValue()) { - + } var text = GetItemText(item); if (text.HasValue()) { - @text + @text } } diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs index 73b9ff5894..f51e18e0c9 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs @@ -21,6 +21,11 @@ public partial class BitButtonGroup : BitComponentBase where TItem : clas /// [Parameter] public RenderFragment? ChildContent { get; set; } + /// + /// Custom CSS classes for different parts of the ButtonGroup. + /// + [Parameter] public BitButtonGroupClassStyles? Classes { get; set; } + /// /// Defines the general colors available in the bit BlazorUI. /// @@ -63,6 +68,11 @@ public partial class BitButtonGroup : BitComponentBase where TItem : clas [Parameter, ResetClassBuilder] public BitSize? Size { get; set; } + /// + /// Custom CSS styles for different parts of the ButtonGroup. + /// + [Parameter] public BitButtonGroupClassStyles? Styles { get; set; } + /// /// Display ButtonGroup with toggle mode enabled for each button. /// @@ -103,6 +113,8 @@ internal void UnregisterOption(BitButtonGroupOption option) protected override void RegisterCssClasses() { + ClassBuilder.Register(() => Classes?.Root); + ClassBuilder.Register(() => Variant switch { BitVariant.Fill => "bit-btg-fil", @@ -144,6 +156,11 @@ protected override void RegisterCssClasses() ClassBuilder.Register(() => Vertical ? "bit-btg-vrt" : string.Empty); } + protected override void RegisterCssStyles() + { + StyleBuilder.Register(() => Styles?.Root); + } + protected override void OnParametersSet() { base.OnParametersSet(); @@ -211,6 +228,11 @@ private async Task HandleOnItemClick(TItem item) if (_toggleItem == item) { classes.Add("bit-btg-chk"); + + if (Classes?.ToggledButton.HasValue() ?? false) + { + classes.Add(Classes.ToggledButton!); + } } var classItem = GetClass(item); @@ -219,9 +241,37 @@ private async Task HandleOnItemClick(TItem item) classes.Add(classItem!); } + if (Classes?.Button.HasValue() ?? false) + { + classes.Add(Classes.Button!); + } + return string.Join(' ', classes); } + private string? GetItemStyle(TItem? item) + { + List styles = new(); + + var style = GetStyle(item); + if (style.HasValue()) + { + styles.Add(style!.Trim(';')); + } + + if (Styles?.Button.HasValue() ?? false) + { + styles.Add(Styles.Button!.Trim(';')); + } + + if (_toggleItem == item && (Styles?.ToggledButton.HasValue() ?? false)) + { + styles.Add(Styles.ToggledButton!); + } + + return string.Join(';', styles); + } + private string? GetItemText(TItem? item) { if (IconOnly) return null; diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupClassStyles.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupClassStyles.cs new file mode 100644 index 0000000000..66dc6fb58e --- /dev/null +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupClassStyles.cs @@ -0,0 +1,29 @@ +namespace Bit.BlazorUI; + +public class BitButtonGroupClassStyles +{ + /// + /// Custom CSS classes/styles for the root element of the BitButtonGroup. + /// + public string? Root { get; set; } + + /// + /// Custom CSS classes/styles for the internal button of the BitButtonGroup. + /// + public string? Button { get; set; } + + /// + /// Custom CSS classes/styles for the icon of the BitButtonGroup. + /// + public string? Icon { get; set; } + + /// + /// Custom CSS classes/styles for the text of the BitButtonGroup. + /// + public string? Text { get; set; } + + /// + /// Custom CSS classes/styles for the button when in toggle mode of the BitButtonGroup. + /// + public string? ToggledButton { get; set; } +} diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs index c4b9b4d2d7..2de44d898a 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs @@ -19,6 +19,15 @@ public partial class BitButtonGroupDemo Description = "Determines that only the icon should be rendered.", }, new() + { + Name = "Classes", + Type = "BitButtonGroupClassStyles?", + DefaultValue = "null", + Description = "Custom CSS classes for different parts of the ButtonGroup.", + LinkType = LinkType.Link, + Href = "#class-styles", + }, + new() { Name = "Color", Type = "BitColor?", @@ -82,6 +91,15 @@ public partial class BitButtonGroupDemo Href = "#button-size-enum", }, new() + { + Name = "Styles", + Type = "BitButtonGroupClassStyles?", + DefaultValue = "null", + Description = "Custom CSS styles for different parts of the ButtonGroup.", + LinkType = LinkType.Link, + Href = "#class-styles", + }, + new() { Name = "Variant", Type = "BitVariant?", @@ -342,6 +360,49 @@ public partial class BitButtonGroupDemo ] }, new() + { + Id = "class-styles", + Title = "BitButtonGroupClassStyles", + Parameters = + [ + new() + { + Name = "Root", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the root element of the BitButtonGroup.", + }, + new() + { + Name = "Button", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the internal button of the BitButtonGroup.", + }, + new() + { + Name = "Icon", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the icon of the BitButtonGroup." + }, + new() + { + Name = "Text", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the text of the BitButtonGroup." + }, + new() + { + Name = "ToggledButton", + Type = "string?", + DefaultValue = "null", + Description = "Custom CSS classes/styles for the button when in toggle mode of the BitButtonGroup.", + }, + ], + }, + new() { Id = "name-selectors", Title = "BitButtonGroupNameSelectors", diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss index d477618eb1..daf5d6992e 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.scss @@ -26,4 +26,10 @@ color: peachpuff; background-color: tomato; } + + .custom-btn { + color: aliceblue; + border-color: aliceblue; + background-color: crimson; + } } diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor index 8552384e10..0d5e331a08 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor @@ -33,134 +33,7 @@ - - -
Offering a range of specialized colors, providing visual cues for specific states within your application.
-

-
-
Primary
- - - -
-

-
-
Secondary
- - - -
-

-
-
Tertiary
- - - -
-

-
-
Info
- - - -
-

-
-
Success
- - - -
-

-
-
Warning
- - - -
-

-
-
SevereWarning
- - - -
-

-
-
Error
- - - -
-

-
-
-
PrimaryBackground
- - - -
-

-
-
SecondaryBackground
- - - -
-

-
-
TertiaryBackground
- - - -
-
-

-
-
PrimaryForeground
- - - -
-

-
-
SecondaryForeground
- - - -
-

-
-
TertiaryForeground
- - - -
-

-
-
PrimaryBorder
- - - -
-

-
-
SecondaryBorder
- - - -
-

-
-
TertiaryBorder
- - - -
-
-
- - +
Each item in the ButtonGroup can have an icon.


@@ -187,7 +60,7 @@
- +
The IconOnly allows buttons to display only icons without any text, ideal for minimalistic designs or limited space.


@@ -237,7 +110,7 @@
- +
Reverses the positions of the icon and the main content of the button.


@@ -267,7 +140,7 @@
- +
The Toggled in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.


@@ -309,7 +182,7 @@
- +
By default the BitButtonGroup component is horizontal, but can be turned vertical by adding the Vertical property.


@@ -332,6 +205,29 @@
+ + +
Managing button click events.
+

+
+
Component's ItemClick event:
+ +
Clicked item: @clickedCustom
+
+

+
+
Item's Click event:
+ +
Click count: @clickCounter
+
+
+
+
Different sizes for buttons to meet design needs, ensuring flexibility within your application.
@@ -359,7 +255,134 @@
- + + +
Offering a range of specialized colors, providing visual cues for specific states within your application.
+

+
+
Primary
+ + + +
+

+
+
Secondary
+ + + +
+

+
+
Tertiary
+ + + +
+

+
+
Info
+ + + +
+

+
+
Success
+ + + +
+

+
+
Warning
+ + + +
+

+
+
SevereWarning
+ + + +
+

+
+
Error
+ + + +
+

+
+
+
PrimaryBackground
+ + + +
+

+
+
SecondaryBackground
+ + + +
+

+
+
TertiaryBackground
+ + + +
+
+

+
+
PrimaryForeground
+ + + +
+

+
+
SecondaryForeground
+ + + +
+

+
+
TertiaryForeground
+ + + +
+

+
+
PrimaryBorder
+ + + +
+

+
+
SecondaryBorder
+ + + +
+

+
+
TertiaryBorder
+ + + +
+
+
+ +
Empower customization by overriding default styles and classes, allowing tailored design modifications to suit specific UI requirements.


@@ -376,28 +399,18 @@ NameSelectors="@(new() { Text = { Selector = i => i.Name }, IconName = { Selector = i => i.Icon } })" />
- - - - - -
Managing button click events.


-
Component's ItemClick event:
+
Styles & Classes:
-
Clicked item: @clickedCustom
-
-

-
-
Item's Click event:
- -
Click count: @clickCounter
+ Styles="@(new() { Button = "color: darkcyan; border-color: deepskyblue; background-color: azure;" })" /> + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs index 76886dd07e..f84fb7e28e 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs @@ -49,90 +49,6 @@ public class Operation ];"; private readonly string example3RazorCode = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - private readonly string example3CsharpCode = @" -private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; - -public class Operation -{ - public string? Name { get; set; } -} - -private List basicCustoms = -[ - new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } -];"; - - private readonly string example4RazorCode = @" i.Name }, IconName = { Selector = i => i.Icon } })"" /> @@ -144,7 +60,7 @@ public class Operation i.Name }, IconName = { Selector = i => i.Icon } })"" />"; - private readonly string example4CsharpCode = @" + private readonly string example3CsharpCode = @" public class Operation { public string? Name { get; set; } @@ -158,7 +74,7 @@ public class Operation new() { Name = ""Delete"", Icon = BitIconName.Delete } ];"; - private readonly string example5RazorCode = @" + private readonly string example4RazorCode = @" i.Name }, IconName = { Selector = i => i.Icon } })"" IconOnly /> @@ -183,7 +99,7 @@ public class Operation i.Name }, IconName = { Selector = i => i.Icon } })"" />"; - private readonly string example5CsharpCode = @" + private readonly string example4CsharpCode = @" public class Operation { public string? Name { get; set; } @@ -204,7 +120,7 @@ public class Operation new() { Name = ""Delete"", Icon = BitIconName.Delete } ];"; - private readonly string example6RazorCode = @" + private readonly string example5RazorCode = @" i.Name }, IconName = { Selector = i => i.Icon }, @@ -219,7 +135,7 @@ public class Operation NameSelectors=""@(new() { Text = { Selector = i => i.Name }, IconName = { Selector = i => i.Icon }, ReversedIcon = { Selector = i => i.ReversedIcon } })"" />"; - private readonly string example6CsharpCode = @" + private readonly string example5CsharpCode = @" public class Operation { public string? Name { get; set; } @@ -234,7 +150,7 @@ public class Operation new() { Name = ""Delete"", Icon = BitIconName.Delete, ReversedIcon = true } ];"; - private readonly string example7RazorCode = @" + private readonly string example6RazorCode = @" i.OnName }, OffText = { Selector = i => i.OffName }, @@ -261,7 +177,7 @@ public class Operation OnIconName = { Selector = i => i.OnIcon }, OffIconName = { Selector = i => i.OffIcon }, ReversedIcon = { Selector = i => i.ReversedIcon } })"" Toggled />"; - private readonly string example7CsharpCode = @" + private readonly string example6CsharpCode = @" public class Operation { public string? OnIcon { get; set; } @@ -280,11 +196,11 @@ public class Operation new() { OnName = ""Forward (2X)"", OffName = ""Forward"", OnIcon = BitIconName.FastForwardTwoX, OffIcon = BitIconName.FastForward, ReversedIcon = true } ];"; - private readonly string example8RazorCode = @" + private readonly string example7RazorCode = @" "; - private readonly string example8CsharpCode = @" + private readonly string example7CsharpCode = @" private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; public class Operation @@ -297,6 +213,48 @@ public class Operation new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } ];"; + private readonly string example8RazorCode = @" + clickedCustom = item.Name"" /> +
Clicked item: @clickedCustom
+ + i.Name }, + IconName = { Selector = i => i.Icon }, + OnClick = { Selector = i => i.Clicked } })"" /> +
Click count: @clickCounter
"; + private readonly string example8CsharpCode = @" +private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; + +public class Operation +{ + public string? Name { get; set; } + public string? Icon { get; set; } + public Action? Clicked { get; set; } +} + +private int clickCounter; + +private List basicCustoms = +[ + new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } +]; + +private List eventsCustoms = +[ + new() { Name = ""Increase"", Icon = BitIconName.Add }, + new() { Name = ""Reset"", Icon = BitIconName.Reset }, + new() { Name = ""Decrease"", Icon = BitIconName.Remove } +]; + +protected override void OnInitialized() +{ + eventsCustoms[0].Clicked = _ => { clickCounter++; StateHasChanged(); }; + eventsCustoms[1].Clicked = _ => { clickCounter = 0; StateHasChanged(); }; + eventsCustoms[2].Clicked = _ => { clickCounter--; StateHasChanged(); }; +}"; + private readonly string example9RazorCode = @" @@ -323,6 +281,90 @@ public class Operation ];"; private readonly string example10RazorCode = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + private readonly string example10CsharpCode = @" +private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; + +public class Operation +{ + public string? Name { get; set; } +} + +private List basicCustoms = +[ + new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } +];"; + + private readonly string example11RazorCode = @" @@ -354,8 +402,18 @@ public class Operation i.Name }, - IconName = { Selector = i => i.Icon } })"" />"; - private readonly string example10CsharpCode = @" + IconName = { Selector = i => i.Icon } })"" /> + + + +"; + private readonly string example11CsharpCode = @" private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; public class Operation @@ -387,48 +445,6 @@ public class Operation } ];"; - private readonly string example11RazorCode = @" - clickedCustom = item.Name"" /> -
Clicked item: @clickedCustom
- - i.Name }, - IconName = { Selector = i => i.Icon }, - OnClick = { Selector = i => i.Clicked } })"" /> -
Click count: @clickCounter
"; - private readonly string example11CsharpCode = @" -private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; - -public class Operation -{ - public string? Name { get; set; } - public string? Icon { get; set; } - public Action? Clicked { get; set; } -} - -private int clickCounter; - -private List basicCustoms = -[ - new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } -]; - -private List eventsCustoms = -[ - new() { Name = ""Increase"", Icon = BitIconName.Add }, - new() { Name = ""Reset"", Icon = BitIconName.Reset }, - new() { Name = ""Decrease"", Icon = BitIconName.Remove } -]; - -protected override void OnInitialized() -{ - eventsCustoms[0].Clicked = _ => { clickCounter++; StateHasChanged(); }; - eventsCustoms[1].Clicked = _ => { clickCounter = 0; StateHasChanged(); }; - eventsCustoms[2].Clicked = _ => { clickCounter--; StateHasChanged(); }; -}"; - private readonly string example12RazorCode = @" - - -
Offering a range of specialized colors, providing visual cues for specific states within your application.
-

-
-
Primary
- - - -
-

-
-
Secondary
- - - -
-

-
-
Tertiary
- - - -
-

-
-
Info
- - - -
-

-
-
Success
- - - -
-

-
-
Warning
- - - -
-

-
-
SevereWarning
- - - -
-

-
-
Error
- - - -
-

-
-
-
PrimaryBackground
- - - -
-

-
-
SecondaryBackground
- - - -
-

-
-
TertiaryBackground
- - - -
-
-

-
-
PrimaryForeground
- - - -
-

-
-
SecondaryForeground
- - - -
-

-
-
TertiaryForeground
- - - -
-

-
-
PrimaryBorder
- - - -
-

-
-
SecondaryBorder
- - - -
-

-
-
TertiaryBorder
- - - -
-
-
- - +
Each item in the ButtonGroup can have an icon.


@@ -181,7 +54,7 @@
- +
The IconOnly allows buttons to display only icons without any text, ideal for minimalistic designs or limited space.


@@ -219,7 +92,7 @@
- +
Reverses the positions of the icon and the main content of the button.


@@ -240,7 +113,7 @@
- +
The Toggled in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.


@@ -261,7 +134,7 @@
- +
By default the BitButtonGroup component is horizontal, but can be turned vertical by adding the Vertical property.


@@ -284,6 +157,24 @@
+ + +
Managing button click events.
+

+
+
Component's ItemClick event:
+ +
Clicked item: @clickedItem
+
+

+
+
Item's Click event:
+ +
Click count: @clickCounter
+
+
+
+
Different sizes for buttons to meet design needs, ensuring flexibility within your application.
@@ -311,37 +202,157 @@
- + -
Empower customization by overriding default styles and classes, allowing tailored design modifications to suit specific UI requirements.
+
Offering a range of specialized colors, providing visual cues for specific states within your application.


-
Component's style & class:
- - +
Primary
+ + +


-
Item's style & class:
- +
Secondary
+ + + +
+

+
+
Tertiary
+ + + +
+

+
+
Info
+ + + +
+

+
+
Success
+ + + +
+

+
+
Warning
+ + + +
+

+
+
SevereWarning
+ + + +
+

+
+
Error
+ + + +
+

+
+
+
PrimaryBackground
+ + + +
+

+
+
SecondaryBackground
+ + + +
+

+
+
TertiaryBackground
+ + + +
+
+

+
+
PrimaryForeground
+ + + +
+

+
+
SecondaryForeground
+ + + +
+

+
+
TertiaryForeground
+ + + +
+

+
+
PrimaryBorder
+ + + +
+

+
+
SecondaryBorder
+ + + +
+

+
+
TertiaryBorder
+ + +
- + -
Managing button click events.
+
Empower customization by overriding default styles and classes, allowing tailored design modifications to suit specific UI requirements.


-
Component's ItemClick event:
- -
Clicked item: @clickedItem
+
Component's style & class:
+ +


-
Item's Click event:
- -
Click count: @clickCounter
+
Item's style & class:
+ +
+

+
+
Styles & Classes:
+ + +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs index e41be97a4f..5431abfd76 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs @@ -34,87 +34,10 @@ public partial class _BitButtonGroupItemDemo ];"; private readonly string example3RazorCode = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - private readonly string example3CsharpCode = @" -private List basicItems = -[ - new() { Text = ""Add"" }, new() { Text = ""Edit"" }, new() { Text = ""Delete"" } -];"; - - private readonly string example4RazorCode = @" "; - private readonly string example4CsharpCode = @" + private readonly string example3CsharpCode = @" private List iconItems = [ new() { Text = ""Add"", IconName = BitIconName.Add }, @@ -122,7 +45,7 @@ public partial class _BitButtonGroupItemDemo new() { Text = ""Delete"", IconName = BitIconName.Delete } ];"; - private readonly string example5RazorCode = @" + private readonly string example4RazorCode = @" @@ -130,7 +53,7 @@ public partial class _BitButtonGroupItemDemo "; - private readonly string example5CsharpCode = @" + private readonly string example4CsharpCode = @" private List iconItems = [ new() { Text = ""Add"", IconName = BitIconName.Add }, @@ -145,11 +68,11 @@ public partial class _BitButtonGroupItemDemo new() { Text = ""Delete"", IconName = BitIconName.Delete } ];"; - private readonly string example6RazorCode = @" + private readonly string example5RazorCode = @" "; - private readonly string example6CsharpCode = @" + private readonly string example5CsharpCode = @" private List reversedIconItems = [ new() { Text = ""Add"", IconName = BitIconName.Add, ReversedIcon = true }, @@ -157,11 +80,11 @@ public partial class _BitButtonGroupItemDemo new() { Text = ""Delete"", IconName = BitIconName.Delete, ReversedIcon = true } ];"; - private readonly string example7RazorCode = @" + private readonly string example6RazorCode = @" "; - private readonly string example7CsharpCode = @" + private readonly string example6CsharpCode = @" private List toggledItems = [ new() { OnText = ""Back (2X)"", OffText = ""Back"", OnIconName = BitIconName.RewindTwoX, OffIconName = BitIconName.Rewind }, @@ -169,16 +92,40 @@ public partial class _BitButtonGroupItemDemo new() { OnText = ""Forward (2X)"", OffText = ""Forward"", OnIconName = BitIconName.FastForwardTwoX, OffIconName = BitIconName.FastForward, ReversedIcon = true } ];"; - private readonly string example8RazorCode = @" + private readonly string example7RazorCode = @" "; - private readonly string example8CsharpCode = @" + private readonly string example7CsharpCode = @" private List basicItems = [ new() { Text = ""Add"" }, new() { Text = ""Edit"" }, new() { Text = ""Delete"" } ];"; + private readonly string example8RazorCode = @" + clickedItem = item.Text"" /> +
Clicked item: @clickedItem
+ + +
Click count: @clickCounter
"; + private readonly string example8CsharpCode = @" +private int clickCounter; +private string? clickedItem; + +private List eventsItems = +[ + new() { Text = ""Increase"", IconName = BitIconName.Add }, + new() { Text = ""Reset"", IconName = BitIconName.Reset }, + new() { Text = ""Decrease"", IconName = BitIconName.Remove } +]; + +protected override void OnInitialized() +{ + eventsItems[0].OnClick = _ => { clickCounter++; StateHasChanged(); }; + eventsItems[1].OnClick = _ => { clickCounter = 0; StateHasChanged(); }; + eventsItems[2].OnClick = _ => { clickCounter--; StateHasChanged(); }; +}"; + private readonly string example9RazorCode = @" @@ -198,6 +145,83 @@ public partial class _BitButtonGroupItemDemo ];"; private readonly string example10RazorCode = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + private readonly string example10CsharpCode = @" +private List basicItems = +[ + new() { Text = ""Add"" }, new() { Text = ""Edit"" }, new() { Text = ""Delete"" } +];"; + + private readonly string example11RazorCode = @" -"; - private readonly string example10CsharpCode = @" + + + + +"; + private readonly string example11CsharpCode = @" private List basicItems = [ new() { Text = ""Add"" }, new() { Text = ""Edit"" }, new() { Text = ""Delete"" } @@ -249,30 +287,6 @@ public partial class _BitButtonGroupItemDemo } ];"; - private readonly string example11RazorCode = @" - clickedItem = item.Text"" /> -
Clicked item: @clickedItem
- - -
Click count: @clickCounter
"; - private readonly string example11CsharpCode = @" -private int clickCounter; -private string? clickedItem; - -private List eventsItems = -[ - new() { Text = ""Increase"", IconName = BitIconName.Add }, - new() { Text = ""Reset"", IconName = BitIconName.Reset }, - new() { Text = ""Decrease"", IconName = BitIconName.Remove } -]; - -protected override void OnInitialized() -{ - eventsItems[0].OnClick = _ => { clickCounter++; StateHasChanged(); }; - eventsItems[1].OnClick = _ => { clickCounter = 0; StateHasChanged(); }; - eventsItems[2].OnClick = _ => { clickCounter--; StateHasChanged(); }; -}"; - private readonly string example12RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor index 6828f69be3..f34499d397 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor @@ -55,236 +55,7 @@
- - -
Offering a range of specialized colors, providing visual cues for specific states within your application.
-

-
-
Primary
- - - - - - - - - -
-

-
-
Secondary
- - - - - - - - - -
-

-
-
Tertiary
- - - - - - - - - -
-

-
-
Info
- - - - - - - - - -
-

-
-
Success
- - - - - - - - - -
-

-
-
Warning
- - - - - - - - - -
-

-
-
SevereWarning
- - - - - - - - - -
-

-
-
Error
- - - - - - - - - -
-

-
-
-
PrimaryBackground
- - - - - - - - - -
-

-
-
SecondaryBackground
- - - - - - - - - -
-

-
-
TertiaryBackground
- - - - - - - - - -
-
-

-
-
PrimaryForeground
- - - - - - - - - -
-

-
-
SecondaryForeground
- - - - - - - - - -
-

-
-
TertiaryForeground
- - - - - - - - - -
-

-
-
PrimaryBorder
- - - - - - - - - -
-

-
-
SecondaryBorder
- - - - - - - - - -
-

-
-
TertiaryBorder
- - - - - - - - - -
-
-
- - +
Each item in the ButtonGroup can have an icon.


@@ -317,7 +88,7 @@
- +
The IconOnly allows buttons to display only icons without any text, ideal for minimalistic designs or limited space.


@@ -379,7 +150,7 @@
- +
Reverses the positions of the icon and the main content of the button.


@@ -412,7 +183,7 @@
- +
The Toggled in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.


@@ -445,7 +216,7 @@
- +
By default the BitButtonGroup component is horizontal, but can be turned vertical by adding the Vertical property.


@@ -474,6 +245,32 @@
+ + +
Managing button click events.
+

+
+
Component's ItemClick event:
+ + + + + +
Clicked item: @clickedOption
+
+

+
+
Item's Click event:
+ + + + + +
Click count: @clickCounter
+
+
+
+
Different sizes for buttons to meet design needs, ensuring flexibility within your application.
@@ -519,7 +316,236 @@
- + + +
Offering a range of specialized colors, providing visual cues for specific states within your application.
+

+
+
Primary
+ + + + + + + + + +
+

+
+
Secondary
+ + + + + + + + + +
+

+
+
Tertiary
+ + + + + + + + + +
+

+
+
Info
+ + + + + + + + + +
+

+
+
Success
+ + + + + + + + + +
+

+
+
Warning
+ + + + + + + + + +
+

+
+
SevereWarning
+ + + + + + + + + +
+

+
+
Error
+ + + + + + + + + +
+

+
+
+
PrimaryBackground
+ + + + + + + + + +
+

+
+
SecondaryBackground
+ + + + + + + + + +
+

+
+
TertiaryBackground
+ + + + + + + + + +
+
+

+
+
PrimaryForeground
+ + + + + + + + + +
+

+
+
SecondaryForeground
+ + + + + + + + + +
+

+
+
TertiaryForeground
+ + + + + + + + + +
+

+
+
PrimaryBorder
+ + + + + + + + + +
+

+
+
SecondaryBorder
+ + + + + + + + + +
+

+
+
TertiaryBorder
+ + + + + + + + + +
+
+
+ +
Empower customization by overriding default styles and classes, allowing tailored design modifications to suit specific UI requirements.


@@ -541,31 +567,20 @@
- - - - - -
Managing button click events.


-
Component's ItemClick event:
- - - - +
Styles & Classes:
+ + -
Clicked item: @clickedOption
-
-

-
-
Item's Click event:
- - - - + + + -
Click count: @clickCounter
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs index 796c28abae..8c30e6d21c 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs @@ -41,6 +41,164 @@ public partial class _BitButtonGroupOptionDemo "; private readonly string example3RazorCode = @" + + + + + + + + + + + + + + + + +"; + + private readonly string example4RazorCode = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + private readonly string example5RazorCode = @" + + + + + + + + + + + + + + + + +"; + + private readonly string example6RazorCode = @" + + + + + + + + + + + + + + + + +"; + + private readonly string example7RazorCode = @" + + + + + + + + + + +"; + + private readonly string example8RazorCode = @" + clickedOption = item.Text"" TItem=""BitButtonGroupOption""> + + + + +
Clicked item: @clickedOption
+ + + { clickCounter++; StateHasChanged(); }"" /> + { clickCounter=0; StateHasChanged(); }"" /> + { clickCounter--; StateHasChanged(); }"" /> + +
Click count: @clickCounter
"; + private readonly string example8CsharpCode = @" +private int clickCounter; +private string? clickedOption;"; + + private readonly string example9RazorCode = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + private readonly string example10RazorCode = @" @@ -214,147 +372,7 @@ public partial class _BitButtonGroupOptionDemo "; - private readonly string example4RazorCode = @" - - - - - - - - - - - - - - - - -"; - - private readonly string example5RazorCode = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - - private readonly string example6RazorCode = @" - - - - - - - - - - - - - - - - -"; - - private readonly string example7RazorCode = @" - - - - - - - - - - - - - - - - -"; - - private readonly string example8RazorCode = @" - - - - - - - - - - -"; - - private readonly string example9RazorCode = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - - private readonly string example10RazorCode = @" + private readonly string example11RazorCode = @" @@ -392,25 +416,19 @@ public partial class _BitButtonGroupOptionDemo -"; - - private readonly string example11RazorCode = @" - clickedOption = item.Text"" TItem=""BitButtonGroupOption""> - - - -
Clicked item: @clickedOption
- - { clickCounter++; StateHasChanged(); }"" /> - { clickCounter=0; StateHasChanged(); }"" /> - { clickCounter--; StateHasChanged(); }"" /> + + -
Click count: @clickCounter
"; - private readonly string example11CsharpCode = @" -private int clickCounter; -private string? clickedOption;"; + + + +"; private readonly string example12RazorCode = @" From 4a66fe2a4053778e586329b01d6eff369508a085 Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Fri, 27 Dec 2024 21:15:50 +0100 Subject: [PATCH 06/28] feat(templates): improve boilerplate maui project #9549 (#9550) --- .github/workflows/bit.full.ci.yml | 12 +-- .../Extensions/IConfigurationExtensions.cs | 2 +- .../Boilerplate.Client.Maui/App.xaml.cs | 88 ++++++++++++++++--- .../Boilerplate.Client.Maui.csproj | 8 ++ .../Components/Pages/AboutPage.razor.cs | 23 +++-- .../Boilerplate.Client.Maui/MauiProgram.cs | 15 +++- .../Services/WindowsLocalHttpServer.cs | 2 +- .../src/Directory.Packages.props | 3 + .../Program.Services.cs | 2 +- .../Server/Boilerplate.Server.Api/Program.cs | 2 +- .../Server/Boilerplate.Server.Web/Program.cs | 2 +- .../src/Shared/Resources/AppStrings.fa.resx | 4 +- .../src/Shared/Resources/AppStrings.nl.resx | 4 +- .../src/Shared/Resources/AppStrings.resx | 4 +- .../Services/AppPlatform.cs | 2 +- .../src/Shared/Services/CultureInfoManager.cs | 2 +- 16 files changed, 135 insertions(+), 40 deletions(-) rename src/Templates/Boilerplate/Bit.Boilerplate/src/{Client/Boilerplate.Client.Core => Shared}/Services/AppPlatform.cs (95%) diff --git a/.github/workflows/bit.full.ci.yml b/.github/workflows/bit.full.ci.yml index 003f326ba4..bb30fe241c 100644 --- a/.github/workflows/bit.full.ci.yml +++ b/.github/workflows/bit.full.ci.yml @@ -146,33 +146,33 @@ jobs: continue-on-error: true run: | dotnet new bit-bp --name TestPostgreSQL --database PostgreSQL --framework net8.0 --signalR - cd TestPostgreSQL/src/Server/TestPostgreSQL.Server.Api/ + cd TestPostgreSQL/src/Server/TestPostgreSQL.Server.Web/ dotnet build cd ../../../../ dotnet new bit-bp --name TestMySql --database MySql --framework net8.0 --sample Admin --offlineDb - cd TestMySql/src/Server/TestMySql.Server.Api/ + cd TestMySql/src/Server/TestMySql.Server.Web/ dotnet build cd ../../../../ dotnet new bit-bp --name TestOther --database Other --framework net9.0 --sample Todo --sentry - cd TestOther/src/Server/TestOther.Server.Api/ + cd TestOther/src/Server/TestOther.Server.Web/ dotnet build - name: Test file storage options continue-on-error: true run: | dotnet new bit-bp --name TestLocal --filesStorage Local --framework net8.0 --appInsights - cd TestLocal/src/Server/TestLocal.Server.Api/ + cd TestLocal/src/Server/TestLocal.Server.Web/ dotnet build cd ../../../../ dotnet new bit-bp --name TestAzureBlobStorage --filesStorage AzureBlobStorage --framework net9.0 --captcha reCaptcha --notification - cd TestAzureBlobStorage/src/Server/TestAzureBlobStorage.Server.Api/ + cd TestAzureBlobStorage/src/Server/TestAzureBlobStorage.Server.Web/ dotnet build - name: Test backend setup options continue-on-error: true run: | dotnet new bit-bp --name TestStandalone --api Standalone --framework net8.0 - cd TestStandalone/src/Server/TestStandalone.Server.Api/ + cd TestStandalone/src/Server/TestStandalone.Server.Web/ dotnet build cd ../ cd TestStandalone.Server.Web/ diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs index b4ec8c4355..ba80cbb651 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs @@ -13,7 +13,7 @@ public static string GetServerAddress(this IConfiguration configuration) if (AppEnvironment.IsDev() && serverAddress.Contains("localhost", StringComparison.InvariantCultureIgnoreCase) && - OperatingSystem.IsAndroid()) + AppPlatform.IsAndroid) { const string androidEmulatorDevMachineIP = "10.0.2.2"; // Special alias to your host loopback interface in Android Emulators (127.0.0.1 on your development machine) serverAddress = serverAddress.Replace("localhost", androidEmulatorDevMachineIP, StringComparison.InvariantCultureIgnoreCase); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs index 0e8c2635b4..cf447680d7 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs @@ -1,24 +1,55 @@ -[assembly: XamlCompilation(XamlCompilationOptions.Compile)] +//+:cnd:noEmit +//#if (framework == 'net9.0') +using Maui.AppStores; +using Maui.InAppReviews; +using System.Runtime.InteropServices; +//#endif + +[assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace Boilerplate.Client.Maui; public partial class App { private readonly Page mainPage; + //#if (framework == 'net9.0') + private readonly IStorageService storageService; + //#endif private readonly IExceptionHandler exceptionHandler; private readonly IBitDeviceCoordinator deviceCoordinator; private readonly IStringLocalizer localizer; public App(MainPage mainPage, + //#if (framework == 'net9.0') + PubSubService pubSubService, + IStorageService storageService, + //#endif IExceptionHandler exceptionHandler, IBitDeviceCoordinator deviceCoordinator, - IStorageService storageService, IStringLocalizer localizer) { + this.localizer = localizer; + //#if (framework == 'net9.0') + this.storageService = storageService; + //#endif this.exceptionHandler = exceptionHandler; this.deviceCoordinator = deviceCoordinator; this.mainPage = new NavigationPage(mainPage); - this.localizer = localizer; + + //#if (framework == 'net9.0') + pubSubService.Subscribe(ClientPubSubMessages.PROFILE_UPDATED, async _ => + { + // It's an opportune moment to request a store review. (: + await Dispatcher.DispatchAsync(async () => + { + if ((await storageService.GetItem("StoreReviewRequested")) is not "true") + { + await storageService.SetItem("StoreReviewRequested", "true"); + ReviewStatus status = await InAppReview.Current.RequestAsync(); + } + }); + }); + //#endif InitializeComponent(); } @@ -28,7 +59,7 @@ protected override Window CreateWindow(IActivationState? activationState) return new Window(mainPage) { }; } - protected async override void OnStart() + protected override async void OnStart() { try { @@ -36,9 +67,8 @@ protected async override void OnStart() await deviceCoordinator.ApplyTheme(AppInfo.Current.RequestedTheme is AppTheme.Dark); -//-:cnd:noEmit + //-:cnd:noEmit #if Android - //+:cnd:noEmit //#if (framework == 'net9.0') const int minimumSupportedWebViewVersion = 94; @@ -51,20 +81,58 @@ protected async override void OnStart() */ //#endif //#endif - //-:cnd:noEmit if (Version.TryParse(Android.Webkit.WebView.CurrentWebViewPackage?.VersionName, out var webViewVersion) && - webViewVersion.Major < minimumSupportedWebViewVersion) + webViewVersion.Major < minimumSupportedWebViewVersion) { - await App.Current!.Windows.First().Page!.DisplayAlert("Boilerplate", localizer[nameof(AppStrings.UpdateWebViewThroughGooglePlay)], localizer[nameof(AppStrings.Ok)]); + await App.Current!.Windows[0].Page!.DisplayAlert("Boilerplate", localizer[nameof(AppStrings.UpdateWebViewThroughGooglePlay)], localizer[nameof(AppStrings.Ok)]); await Launcher.OpenAsync($"https://play.google.com/store/apps/details?id={Android.Webkit.WebView.CurrentWebViewPackage.PackageName}"); } -//-:cnd:noEmit + //-:cnd:noEmit #endif + //+:cnd:noEmit + + //#if (framework == 'net9.0') + await CheckForUpdates(); + //#endif } catch (Exception exp) { exceptionHandler.Handle(exp); } } + + //#if (framework == 'net9.0') + private async Task CheckForUpdates() + { + if (AppPlatform.IsAndroid) + return; // Android has in-app update feature. + + await Task.Delay(TimeSpan.FromSeconds(3)); // No rush to check for updates. + + try + { + if (await AppStoreInfo.Current.IsUsingLatestVersionAsync() is false) + { + var newVersion = await AppStoreInfo.Current.GetLatestVersionAsync(); + var releaseNotes = (await AppStoreInfo.Current.GetInformationAsync()).ReleaseNotes; + + if (await storageService.GetItem($"{newVersion}_UpdateFromVersionIsRequested") is not "true") + { + await storageService.SetItem($"{newVersion}_UpdateFromVersionIsRequested", "true"); + + // It's an opportune moment to request an update. (: + // https://github.com/oscoreio/Maui.AppStoreInfo + if (await App.Current!.Windows[0].Page!.DisplayAlert(localizer[nameof(AppStrings.NewVersionIsAvailable), newVersion], localizer[nameof(AppStrings.UpdateToNewVersion), releaseNotes], localizer[nameof(AppStrings.Yes)], localizer[nameof(AppStrings.No)]) is true) + { + await AppStoreInfo.Current.OpenApplicationInStoreAsync(); + } + } + } + } + catch (InvalidOperationException) when ((AppPlatform.IsIOS || AppPlatform.IsMacOS) && AppEnvironment.IsDev()) { } + catch (FileNotFoundException) { } + catch (COMException) { } + } + //#endif } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj index 725fdf6be1..7fc1cf70dc 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj @@ -167,6 +167,14 @@ + + + + + + + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs index 699fbf5340..0115021d51 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs @@ -1,4 +1,8 @@ -//-:cnd:noEmit +//+:cnd:noEmit + +//#if (framework == 'net9.0') +using Maui.AppStores; +//#endif namespace Boilerplate.Client.Maui.Components.Pages; @@ -10,12 +14,12 @@ public partial class AboutPage protected override string? Subtitle => string.Empty; + private string oem = default!; private string appName = default!; - private string appVersion = default!; - private string processId = default!; - private string platform = default!; private string webView = default!; - private string oem = default!; + private string platform = default!; + private string processId = default!; + private string appVersion = default!; protected async override Task OnInitAsync() { @@ -23,11 +27,14 @@ protected async override Task OnInitAsync() // call third-party Java, Kotlin, Swift, and Objective-C libraries. // https://stackoverflow.com/a/2941199/2720104 appName = AppInfo.Name; - appVersion = telemetryContext.AppVersion!; - platform = telemetryContext.Platform!; webView = telemetryContext.WebView!; - processId = Environment.ProcessId.ToString(); + platform = telemetryContext.Platform!; oem = DeviceInfo.Current.Manufacturer; + appVersion = telemetryContext.AppVersion!; + processId = Environment.ProcessId.ToString(); + //#if (framework == 'net9.0') + appVersion += $" / {await AppStoreInfo.Current.GetLatestVersionAsync(CurrentCancellationToken)}"; + //#endif await base.OnInitAsync(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.cs index 6259f4c1dc..ff88c32bbf 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.cs @@ -1,13 +1,17 @@ -//-:cnd:noEmit +//+:cnd:noEmit using Microsoft.Maui.Platform; using Microsoft.Maui.LifecycleEvents; -//+:cnd:noEmit //#if (notification == true) using Plugin.LocalNotification; //#endif -//-:cnd:noEmit using Boilerplate.Client.Core.Styles; using Boilerplate.Client.Maui.Services; +//#if (framework == 'net9.0') +using Maui.AppStores; +using Maui.InAppReviews; +using Maui.Android.InAppUpdates; +//#endif +//-:cnd:noEmit #if iOS || Mac using UIKit; using WebKit; @@ -38,6 +42,11 @@ public static MauiApp CreateMauiApp() //+:cnd:noEmit builder .UseMauiApp() + //#if (framework == 'net9.0') + .UseInAppReviews() + .UseAppStoreInfo() + .UseAndroidInAppUpdates() + //#endif //#if (sentry == true) .UseSentry(options => { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs index c670e1748f..2e4e7772f3 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs @@ -32,7 +32,7 @@ public int Start(CancellationToken cancellationToken) ctx.Redirect(url); - Application.OpenForms[0]!.Activate(); + await Application.OpenForms[0]!.InvokeAsync(() => Application.OpenForms[0]!.Activate(), cancellationToken); await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props index 31ed6df3e0..236556000f 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Packages.props @@ -29,6 +29,9 @@ + + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs index b777853cb8..fd91fa54cd 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs @@ -288,7 +288,7 @@ private static void AddIdentity(WebApplicationBuilder builder) var identityOptions = appSettings.Identity; var certificatePath = Path.Combine(AppContext.BaseDirectory, "DataProtectionCertificate.pfx"); - var certificate = new X509Certificate2(certificatePath, appSettings.DataProtectionCertificatePassword, OperatingSystem.IsWindows() ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet); + var certificate = new X509Certificate2(certificatePath, appSettings.DataProtectionCertificatePassword, AppPlatform.IsWindows ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet); services.AddDataProtection() .PersistKeysToDbContext() diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.cs index 5f051f2b91..0c58fc6b62 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.cs @@ -16,7 +16,7 @@ public static async Task Main(string[] args) //#endif // The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address. - if (builder.Environment.IsDevelopment() && OperatingSystem.IsWindows()) + if (builder.Environment.IsDevelopment() && AppPlatform.IsWindows) { builder.WebHost.UseUrls("http://localhost:5031", "http://*:5031"); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.cs index 554278a1bf..3e6f3cc193 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.cs @@ -27,7 +27,7 @@ public static async Task Main(string[] args) //#endif // The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address. - if (builder.Environment.IsDevelopment() && OperatingSystem.IsWindows()) + if (builder.Environment.IsDevelopment() && AppPlatform.IsWindows) { builder.WebHost.UseUrls("http://localhost:5030", "http://*:5030"); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx index 42ca3a8258..fc3341aaee 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.fa.resx @@ -1012,10 +1012,10 @@ بروز رسانی - نسخه جدید در دسترس است + نسخه جدید {0} در دسترس است - آیا می خواهید به نسخه جدید بروید؟ + آیا می خواهید به نسخه جدید بروید؟ {0} درباره diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx index 2c766e5494..7e788854da 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx @@ -1012,10 +1012,10 @@ Update - Nieuwe versie is beschikbaar + Nieuwe versie {0} is beschikbaar - Wilt u updaten naar de nieuwe versie? + Wilt u updaten naar de nieuwe versie? {0} Over diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx index 6b47a6a849..2073430021 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.resx @@ -1012,10 +1012,10 @@ Update - New version is available + New version {0} is available - Would you like to update to the new version? + Would you like to update to the new version? {0} About diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppPlatform.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/AppPlatform.cs similarity index 95% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppPlatform.cs rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/AppPlatform.cs index 4c451e99bc..fa1fe8f882 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppPlatform.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/AppPlatform.cs @@ -1,6 +1,6 @@ using System.Runtime.Versioning; -namespace Boilerplate.Client.Core.Services; +namespace Boilerplate.Shared.Services; public static partial class AppPlatform { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs index 982ae71684..14e0b9f3f2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs @@ -27,7 +27,7 @@ public static (string DisplayName, CultureInfo Culture)[] SupportedCultures => public static CultureInfo CreateCultureInfo(string name) { - var cultureInfo = OperatingSystem.IsBrowser() ? CultureInfo.CreateSpecificCulture(name) : new CultureInfo(name); + var cultureInfo = AppPlatform.IsBrowser ? CultureInfo.CreateSpecificCulture(name) : new CultureInfo(name); if (name == "fa-IR") { From baf0aa52465cb0164260430ea9d4083607e271c1 Mon Sep 17 00:00:00 2001 From: Saleh Yusefnejad Date: Sat, 28 Dec 2024 18:05:32 +0330 Subject: [PATCH 07/28] feat(templates): make social sign-in buttons clearer in Boilerplate #9553 (#9554) --- .../Components/Pages/Identity/Components/SocialButton.razor | 2 +- .../Components/Pages/Identity/Components/SocialRow.razor | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialButton.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialButton.razor index ef24d9ac7c..833c437c9e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialButton.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialButton.razor @@ -9,7 +9,7 @@ OnClick="OnClick" Size="BitSize.Small" Class="social-button" - Variant="BitVariant.Text" + Variant="BitVariant.Fill" ButtonType="BitButtonType.Button" Color="BitColor.SecondaryBackground"> @ChildContent diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialRow.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialRow.razor index 36ef1224fd..3e17bd8b71 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialRow.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Identity/Components/SocialRow.razor @@ -1,7 +1,7 @@ @inherits AppComponentBase
- + From 7a0b43dd2823728ab6ee08d80157c9912a1c9f83 Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Sat, 28 Dec 2024 21:40:37 +0100 Subject: [PATCH 08/28] feat(templates): add support for Boilerplate multiple web apps on different domains with one backend #9551 (#9552) --- .github/workflows/admin-sample.cd.yml | 13 +++++--- .github/workflows/todo-sample.cd.yml | 17 ++++++---- .../.azure-devops/workflows/cd.yml | 4 +-- .../Bit.Boilerplate/.github/workflows/cd.yml | 4 +-- .../.template.config/ide.host.json | 8 +++-- .../.template.config/template.json | 32 ++++++++++++++++--- .../Components/AppDataAnnotationsValidator.cs | 2 +- .../Components/Layout/NavBar.razor.cs | 6 ---- .../Components/Layout/RootContainer.razor | 28 ++++++++-------- .../Components/Layout/RootContainer.razor.cs | 1 - .../Components/Layout/RootLayout.razor | 1 - .../Components/Layout/RootLayout.razor.cs | 25 ++++----------- .../Dashboard/DashboardPage.razor.cs | 2 +- .../Pages/Authorized/Todo/TodoPage.razor.cs | 2 +- .../Components/Pages/TermsPage.razor | 2 +- .../Components/Parameters.cs | 1 - .../Extensions/IConfigurationExtensions.cs | 4 +-- .../Services/AuthManager.cs | 5 +-- .../Services/Contracts/ITelemetryContext.cs | 2 +- .../ExceptionDelegatingHandler.cs | 2 +- .../Services/ThemeService.cs | 1 - .../Boilerplate.Client.Maui/App.xaml.cs | 3 -- .../ClientMauiSettings.cs | 6 +++- .../Components/Pages/AboutPage.razor.cs | 4 +-- .../MauiProgram.Services.cs | 15 +++++++-- .../Platforms/Android/MainActivity.cs | 2 +- .../Platforms/Android/MainApplication.cs | 8 ++--- .../AndroidPushNotificationService.cs | 2 +- .../MacCatalystPushNotificationService.cs | 2 +- .../iOS/Entitlements.Development.plist | 4 +-- .../iOS/Entitlements.Production.plist | 4 +-- .../Services/iOSPushNotificationService.cs | 2 +- .../Services/MauiLocalHttpServer.cs | 9 +++++- .../appsettings.Production.json | 2 +- .../Boilerplate.Client.Maui/appsettings.json | 15 ++++++++- .../wwwroot/index.html | 2 +- .../Program.Services.cs | 9 +++++- .../Services/WebPushNotificationService.cs | 2 +- .../Boilerplate.Client.Web/wwwroot/index.html | 2 +- .../Boilerplate.Client.Web/wwwroot/robots.txt | 4 +-- .../.config/dotnet-tools.json | 2 +- .../ClientWindowsSettings.cs | 6 ++++ .../Program.Services.cs | 7 ++-- .../Services/WindowsLocalHttpServer.cs | 12 +++++-- .../appsettings.Production.json | 2 +- .../appsettings.json | 15 ++++++++- .../ElevatedAccessTokenTemplate.razor | 4 +-- .../Components/EmailTokenTemplate.razor | 4 +-- .../Components/OtpTemplate.razor | 6 ++-- .../ResetPasswordTokenTemplate.razor | 6 ++-- .../Components/TwoFactorTokenTemplate.razor | 6 ++-- .../Controllers/AttachmentController.cs | 5 +-- .../IdentityController.EmailConfirmation.cs | 2 +- .../IdentityController.ResetPassword.cs | 2 +- .../IdentityController.SocialSignIn.cs | 13 ++++---- .../Identity/IdentityController.cs | 2 +- .../Controllers/Identity/UserController.cs | 2 +- .../Controllers/Products/ProductController.cs | 6 ++-- .../Controllers/Todo/TodoItemController.cs | 12 +++---- .../Extensions/HttpRequestExtensions.cs | 29 ++++++++++------- .../Program.Services.cs | 18 +---------- .../ServerApiSettings.cs | 29 +++++++++++++---- .../Services/EmailService.cs | 6 ++-- .../Services/ODataOperationFilter.cs | 4 +-- .../Services/PushNotificationService.cs | 2 +- .../Boilerplate.Server.Api/appsettings.json | 2 ++ .../Components/App.razor | 2 +- .../Components/App.razor.cs | 4 ++- .../Program.Services.cs | 2 ++ .../IConfigurationBuilderExtensions.cs | 2 +- .../Extensions/JsonSeralizerExtensions.cs | 3 +- .../src/Shared/Extensions/TupleExtensions.cs | 6 ++-- .../src/Shared/Services/CultureInfoManager.cs | 2 +- .../src/Shared/SharedSettings.cs | 6 ---- .../src/Shared/appsettings.json | 2 -- 75 files changed, 277 insertions(+), 215 deletions(-) diff --git a/.github/workflows/admin-sample.cd.yml b/.github/workflows/admin-sample.cd.yml index cb4d62d3d5..60dbe0e9d2 100644 --- a/.github/workflows/admin-sample.cd.yml +++ b/.github/workflows/admin-sample.cd.yml @@ -35,7 +35,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 + cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 @@ -88,7 +88,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 + cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 @@ -186,13 +186,14 @@ jobs: cd src\Templates\Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ..\..\..\ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --windows --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --captcha reCaptcha --signalR --framework net9.0 + cd ..\..\..\ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --windows --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --captcha reCaptcha --signalR --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 with: files: 'AdminPanel\src\Shared\appsettings.json, AdminPanel\src\Client\AdminPanel.Client.Core\appsettings.json, AdminPanel\src\Client\AdminPanel.Client.Windows\appsettings.json' env: + WebAppUrl: ${{ env.SERVER_ADDRESS }} ServerAddress: ${{ env.SERVER_ADDRESS }} Logging.Sentry.Dsn: ${{ secrets.ADMINPANEL_SENTRY_DSN }} GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} @@ -234,7 +235,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 + cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 - uses: actions/setup-node@v4 with: @@ -259,6 +260,7 @@ jobs: with: files: 'AdminPanel/src/Shared/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Core/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Maui/appsettings.json' env: + WebAppUrl: ${{ env.SERVER_ADDRESS }} ServerAddress: ${{ env.SERVER_ADDRESS }} Logging.Sentry.Dsn: ${{ secrets.ADMINPANEL_SENTRY_DSN }} GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} @@ -311,13 +313,14 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 + cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 with: files: 'AdminPanel/src/Shared/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Core/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Maui/appsettings.json' env: + WebAppUrl: ${{ env.SERVER_ADDRESS }} ServerAddress: ${{ env.SERVER_ADDRESS }} Logging.Sentry.Dsn: ${{ secrets.ADMINPANEL_SENTRY_DSN }} GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} diff --git a/.github/workflows/todo-sample.cd.yml b/.github/workflows/todo-sample.cd.yml index 4b1cc935cb..ee7043d191 100644 --- a/.github/workflows/todo-sample.cd.yml +++ b/.github/workflows/todo-sample.cd.yml @@ -42,7 +42,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net8.0 + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net8.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 @@ -140,7 +140,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --framework net9.0 + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 @@ -187,7 +187,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --offlineDb --signalR --framework net9.0 + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --offlineDb --signalR --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 @@ -234,7 +234,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --framework net9.0 + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --framework net9.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 @@ -289,13 +289,14 @@ jobs: cd src\Templates\Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ..\..\..\ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --windows --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --captcha reCaptcha --signalR --framework net8.0 + cd ..\..\..\ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --windows --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --captcha reCaptcha --signalR --framework net8.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 with: files: 'TodoSample\src\Shared\appsettings.json, TodoSample\src\Client\TodoSample.Client.Core\appsettings.json, TodoSample\src\Client\TodoSample.Client.Windows\appsettings.json' env: + WebAppUrl: ${{ env.SERVER_ADDRESS }} ServerAddress: ${{ env.SERVER_ADDRESS }} Logging.Sentry.Dsn: ${{ secrets.TODO_SENTRY_DSN }} WindowsUpdate.FilesUrl: https://windows-todo.bitplatform.dev @@ -345,7 +346,7 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net8.0 + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net8.0 - name: Extract Android signing key from env uses: timheuer/base64-to-file@v1.2 @@ -366,6 +367,7 @@ jobs: with: files: 'TodoSample/src/Shared/appsettings.json, TodoSample/src/Client/TodoSample.Client.Core/appsettings.json, TodoSample/src/Client/TodoSample.Client.Maui/appsettings.json' env: + WebAppUrl: ${{ env.SERVER_ADDRESS }} ServerAddress: ${{ env.SERVER_ADDRESS }} Logging.Sentry.Dsn: ${{ secrets.TODO_SENTRY_DSN }} GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} @@ -442,13 +444,14 @@ jobs: cd src/Templates/Boilerplate && dotnet build -c Release dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0 dotnet new install Bit.Boilerplate.0.0.0.nupkg - cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net8.0 + cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --appInsights --sentry --apiServerUrl ${{ env.SERVER_ADDRESS }} --webAppUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net8.0 - name: Update core appsettings.json uses: devops-actions/variable-substitution@v1.2 with: files: 'TodoSample/src/Shared/appsettings.json, TodoSample/src/Client/TodoSample.Client.Core/appsettings.json, TodoSample/src/Client/TodoSample.Client.Maui/appsettings.json' env: + WebAppUrl: ${{ env.SERVER_ADDRESS }} ServerAddress: ${{ env.SERVER_ADDRESS }} Logging.Sentry.Dsn: ${{ secrets.TODO_SENTRY_DSN }} GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.azure-devops/workflows/cd.yml b/src/Templates/Boilerplate/Bit.Boilerplate/.azure-devops/workflows/cd.yml index 0a8be9b251..62e7aa5a21 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.azure-devops/workflows/cd.yml +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.azure-devops/workflows/cd.yml @@ -8,8 +8,8 @@ variables: AZURE_SUBSCRIPTION: 'bp-test-service-connection' # https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#azure-resource-manager-service-connection ConnectionStrings.SqlServerConnectionString: $(DB_CONNECTION_STRING) DataProtectionCertificatePassword: $(API_DATA_PROTECTION_CERTIFICATE_PASSWORD) - ServerAddress: 'https://use-your-server-url-here.com/' - WindowsUpdate.FilesUrl: 'https://use-your-server-url-here.com/windows' # Deploy the published Windows application files to your desired hosting location and use the host url here. + ServerAddress: 'https://use-your-api-server-url-here.com/' + WindowsUpdate.FilesUrl: 'https://use-your-api-server-url-here.com/windows' # Deploy the published Windows application files to your desired hosting location and use the host url here. jobs: diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.github/workflows/cd.yml b/src/Templates/Boilerplate/Bit.Boilerplate/.github/workflows/cd.yml index 4210c9210c..2ad78fa234 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.github/workflows/cd.yml +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.github/workflows/cd.yml @@ -3,7 +3,7 @@ # https://bitplatform.dev/templates/dev-ops env: - SERVER_ADDRESS: 'https://use-your-server-url-here.com/' + SERVER_ADDRESS: 'https://use-your-api-server-url-here.com/' APP_SERVICE_NAME: 'app-service-bp-test' IOS_CODE_SIGN_PROVISION: 'Boilerplate' @@ -150,7 +150,7 @@ jobs: files: 'src\Shared\appsettings.json, src\Client\Boilerplate.Client.Core\appsettings.json, src\Client\Boilerplate.Client.Windows\appsettings.json' env: ServerAddress: ${{ env.SERVER_ADDRESS }} - WindowsUpdate.FilesUrl: 'https://use-your-server-url-here.com/windows' # Deploy the published Windows application files to your desired hosting location and use the host url here. + WindowsUpdate.FilesUrl: 'https://use-your-api-server-url-here.com/windows' # Deploy the published Windows application files to your desired hosting location and use the host url here. - name: Generate CSS/JS files run: dotnet build src\Client\Boilerplate.Client.Core\Boilerplate.Client.Core.csproj -t:BeforeBuildTasks --no-restore -c Release diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/ide.host.json b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/ide.host.json index 2a8b8763ec..08820af6a2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/ide.host.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/ide.host.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "http://json.schemastore.org/vs-2017.3.host", "requiredComponents": [ { @@ -51,7 +51,11 @@ "id": "notification" }, { - "id": "serverUrl", + "id": "apiServerUrl", + "isVisible": false + }, + { + "id": "webAppUrl", "isVisible": false }, { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json index 32f395e039..7289c1cfc1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json @@ -285,19 +285,19 @@ }, "replaces": "4030" }, - "serverUrl": { + "apiServerUrl": { "displayName": "Server url", "type": "parameter", "datatype": "string", - "defaultValue": "use-your-server-url-here.com", - "description": "The backend server url. You can change its value later if needed." + "defaultValue": "use-your-api-server-url-here.com", + "description": "The backend api server url. You can change its value later if needed." }, "normalizedServerUrl": { "type": "generated", "generator": "regex", "dataType": "string", "parameters": { - "source": "serverUrl", + "source": "apiServerUrl", "steps": [ { "regex": "^(https|http):\/\/", @@ -305,7 +305,29 @@ } ] }, - "replaces": "use-your-server-url-here.com" + "replaces": "use-your-api-server-url-here.com" + }, + "webAppUrl": { + "displayName": "web app url", + "type": "parameter", + "datatype": "string", + "defaultValue": "use-your-web-app-url-here.com", + "description": "The web app url. You can change its value later if needed." + }, + "normalizedWebAppUrl": { + "type": "generated", + "generator": "regex", + "dataType": "string", + "parameters": { + "source": "webAppUrl", + "steps": [ + { + "regex": "^(https|http):\/\/", + "replacement": "" + } + ] + }, + "replaces": "use-your-web-app-url-here.com" }, "advancedTests": { "displayName": "Include advanced automated tests?", diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppDataAnnotationsValidator.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppDataAnnotationsValidator.cs index 9b1044f72d..1a795f0981 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppDataAnnotationsValidator.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppDataAnnotationsValidator.cs @@ -15,7 +15,7 @@ namespace Boilerplate.Client.Core.Components; public partial class AppDataAnnotationsValidator : AppComponentBase { [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_OtherPropertyDisplayName")] - extern static void SetOtherPropertyDisplayName(CompareAttribute valAttribute, string name); + static extern void SetOtherPropertyDisplayName(CompareAttribute valAttribute, string name); private bool disposed; private ValidationMessageStore validationMessageStore = default!; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavBar.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavBar.razor.cs index c9d1de54e6..22c43408b0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavBar.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavBar.razor.cs @@ -2,10 +2,4 @@ public partial class NavBar { - [CascadingParameter(Name = Parameters.CurrentUrl)] private string? currentUrl { get; set; } - - private bool IsActive(string url) - { - return currentUrl == url; - } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor index bc74c51314..5469518310 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor @@ -1,22 +1,20 @@  - - - - - -
-
-
-
-
- @ChildContent -
-
+ + + + +
+
+
+
+
+ @ChildContent
-
+
- +
+
diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor.cs index b4fffa2398..08bf0365c0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootContainer.razor.cs @@ -9,7 +9,6 @@ public partial class RootContainer /// [Parameter] public bool? IsOnline { get; set; } [Parameter] public BitDir? CurrentDir { get; set; } - [Parameter] public string? CurrentUrl { get; set; } [Parameter] public bool? IsAuthenticated { get; set; } /// /// diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor index 7016fe3615..d06dc35b58 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor @@ -2,7 +2,6 @@ @@ -70,7 +69,6 @@ protected override async Task OnInitializedAsync() isAuthenticated = await prerenderStateService.GetValue(async () => (await AuthenticationStateTask).User.IsAuthenticated()); SetCurrentDir(); - SetCurrentUrl(); currentTheme = await themeService.GetCurrentTheme(); await base.OnInitializedAsync(); @@ -119,7 +117,6 @@ private async void AuthenticationStateChanged(Task task) private void NavigationManagerLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) { - SetCurrentUrl(); StateHasChanged(); } @@ -129,18 +126,6 @@ private void SetCurrentDir() currentDir = CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? BitDir.Rtl : null; } - private void SetCurrentUrl() - { - var path = navigationManager.GetUriPath(); - - currentUrl = Urls.All.SingleOrDefault(pageUrl => - { - return pageUrl == Urls.HomePage - ? pageUrl == path - : path.StartsWith(pageUrl); - }); - } - /// /// /// @@ -186,8 +171,7 @@ private string GetMainCssClass() return authClass + crossClass; } - - public void Dispose() + public async ValueTask DisposeAsync() { navigationManager.LocationChanged -= NavigationManagerLocationChanged; @@ -195,6 +179,9 @@ public void Dispose() unsubscribers.ForEach(d => d.Invoke()); - _ = keyboard?.DisposeAsync(); + if (keyboard is not null) + { + await keyboard.DisposeAsync(); + } } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Dashboard/DashboardPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Dashboard/DashboardPage.razor.cs index 6db19b111d..46dc3bb7b4 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Dashboard/DashboardPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Dashboard/DashboardPage.razor.cs @@ -15,7 +15,7 @@ public partial class DashboardPage private Action? unsubscribe; //#endif - protected async override Task OnInitAsync() + protected override async Task OnInitAsync() { //#if (signalR == true) unsubscribe = PubSubService.Subscribe(SharedPubSubMessages.DASHBOARD_DATA_CHANGED, async _ => diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor.cs index 2c0c1e748f..a1807280e2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor.cs @@ -28,7 +28,7 @@ public partial class TodoPage protected override async Task OnInitAsync() { - _ = keyboard.Add(ButilKeyCodes.KeyF, () => _ = searchBox.FocusAsync(), ButilModifiers.Ctrl); + _ = keyboard.Add(ButilKeyCodes.KeyF, () => searchBox.FocusAsync(), ButilModifiers.Ctrl); selectedFilter = nameof(AppStrings.All); selectedSort = nameof(AppStrings.Alphabetical); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TermsPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TermsPage.razor index a10436fc2b..9ab8bf424b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TermsPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/TermsPage.razor @@ -9,7 +9,7 @@ Boilerplate license This EULA and Privacy Policy are written for individuals who use the Boilerplate Demo Version through the - https://use-your-server-url-here.com website, or the + https://use-your-web-app-url-here.com website, or the published version of this app on the bit platform's Google Play, Apple Store, and Microsoft Store accounts.
If you want to build your own project based on this Project Template, please refer to the diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Parameters.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Parameters.cs index 9aabbca204..ebf5f6fedc 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Parameters.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Parameters.cs @@ -5,7 +5,6 @@ namespace Boilerplate.Client.Core.Components; public class Parameters { public const string CurrentDir = nameof(CurrentDir); - public const string CurrentUrl = nameof(CurrentUrl); public const string CurrentTheme = nameof(CurrentTheme); public const string IsAuthenticated = nameof(IsAuthenticated); public const string CurrentRouteData = nameof(CurrentRouteData); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs index ba80cbb651..afe160601d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IConfigurationExtensions.cs @@ -12,8 +12,8 @@ public static string GetServerAddress(this IConfiguration configuration) var serverAddress = settings.ServerAddress; if (AppEnvironment.IsDev() && - serverAddress.Contains("localhost", StringComparison.InvariantCultureIgnoreCase) && - AppPlatform.IsAndroid) + AppPlatform.IsAndroid && + serverAddress.Contains("localhost", StringComparison.InvariantCultureIgnoreCase)) { const string androidEmulatorDevMachineIP = "10.0.2.2"; // Special alias to your host loopback interface in Android Emulators (127.0.0.1 on your development machine) serverAddress = serverAddress.Replace("localhost", androidEmulatorDevMachineIP, StringComparison.InvariantCultureIgnoreCase); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs index 8f8a0ac3c9..26fb1b29c5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AuthManager.cs @@ -48,10 +48,7 @@ public async Task SignIn(SignInRequestDto request, CancellationToken cance public async Task StoreTokens(TokenResponseDto response, bool? rememberMe = null) { - if (rememberMe is null) - { - rememberMe = await storageService.IsPersistent("refresh_token"); - } + rememberMe ??= await storageService.IsPersistent("refresh_token"); await storageService.SetItem("access_token", response!.AccessToken, rememberMe is true); await storageService.SetItem("refresh_token", response!.RefreshToken, rememberMe is true); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs index cc58092e7a..25ff971cbb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs @@ -55,7 +55,7 @@ public static ITelemetryContext? Current public Dictionary ToDictionary(Dictionary? additionalParameters = null) { - var data = new Dictionary(additionalParameters ??= []) + var data = new Dictionary(additionalParameters ?? []) { { nameof(UserId), UserId }, { nameof(UserSessionId), UserSessionId }, diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs index 44c34d4cf9..1ba1de0d26 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/HttpMessageHandlers/ExceptionDelegatingHandler.cs @@ -35,7 +35,7 @@ response.IsSuccessStatusCode is false && Type exceptionType = typeof(RestErrorInfo).Assembly.GetType(restError.ExceptionType!) ?? typeof(UnknownException); - var args = new List { typeof(KnownException).IsAssignableFrom(exceptionType) ? new LocalizedString(restError.Key!, restError.Message!) : restError.Message! }; + var args = new List { typeof(KnownException).IsAssignableFrom(exceptionType) ? new LocalizedString(restError.Key!, restError.Message!) : (object?)restError.Message! }; if (exceptionType == typeof(ResourceValidationException)) { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs index ae3e266f15..a299d4f798 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ThemeService.cs @@ -2,7 +2,6 @@ public partial class ThemeService { - [AutoInject] private Cookie cookie = default!; [AutoInject] private PubSubService pubSubService = default!; [AutoInject] private BitThemeManager bitThemeManager = default!; [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs index cf447680d7..b2ca0d9828 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/App.xaml.cs @@ -105,9 +105,6 @@ protected override async void OnStart() //#if (framework == 'net9.0') private async Task CheckForUpdates() { - if (AppPlatform.IsAndroid) - return; // Android has in-app update feature. - await Task.Delay(TimeSpan.FromSeconds(3)); // No rush to check for updates. try diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/ClientMauiSettings.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/ClientMauiSettings.cs index 2baad82fd2..2be490d484 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/ClientMauiSettings.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/ClientMauiSettings.cs @@ -5,5 +5,9 @@ namespace Boilerplate.Client.Maui; public class ClientMauiSettings : ClientCoreSettings { - + /// + /// When the maui app sends a request to the API server, and the API server and web app are hosted on different URLs, + /// the origin of the generated links (e.g., email confirmation links) will depend on `WebAppUrl` value. + /// + public Uri? WebAppUrl { get; set; } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs index 0115021d51..8cb158ac5e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Components/Pages/AboutPage.razor.cs @@ -21,7 +21,7 @@ public partial class AboutPage private string processId = default!; private string appVersion = default!; - protected async override Task OnInitAsync() + protected override async Task OnInitAsync() { // You have direct access to the Android, iOS, macOS, and Windows SDK features along with the ability to // call third-party Java, Kotlin, Swift, and Objective-C libraries. @@ -33,7 +33,7 @@ protected async override Task OnInitAsync() appVersion = telemetryContext.AppVersion!; processId = Environment.ProcessId.ToString(); //#if (framework == 'net9.0') - appVersion += $" / {await AppStoreInfo.Current.GetLatestVersionAsync(CurrentCancellationToken)}"; + appVersion += $" / {(AppStoreInfo.Current.CachedInformation?.LatestVersion?.ToString() ?? "?")}"; //#endif await base.OnInitAsync(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs index a0b61cae98..efb7f47489 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs @@ -20,10 +20,14 @@ public static void ConfigureServices(this MauiAppBuilder builder) services.AddScoped(sp => { var handler = sp.GetRequiredService(); - HttpClient httpClient = new(handler) + var httpClient = new HttpClient(handler) { BaseAddress = new Uri(configuration.GetServerAddress(), UriKind.Absolute) }; + if (sp.GetRequiredService().WebAppUrl is Uri origin) + { + httpClient.DefaultRequestHeaders.Add("X-Origin", origin.ToString()); + } return httpClient; }); @@ -35,8 +39,15 @@ public static void ConfigureServices(this MauiAppBuilder builder) return settings; }); services.AddSingleton(ITelemetryContext.Current!); - if (AppPlatform.IsWindows || AppPlatform.IsMacOS) + if (AppPlatform.IsWindows + || AppPlatform.IsMacOS + || AppEnvironment.IsDev()) { + // About AppEnvironment.IsDev: + // In the development environment, universal links are not configured. Universal links are required to provide the + // same user experience (UX) that you can test in the production app (available on Google Play at https://play.google.com/store/apps/details?id=com.bitplatform.AdminPanel.Template). + // As a workaround, we will fallback to a local HTTP server. This will provide a slightly degraded UX, but it will allow you to test the app in the development environment. + services.AddSingleton(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs index 7a4ec535d7..cd1cc96a6a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs @@ -14,7 +14,7 @@ namespace Boilerplate.Client.Maui.Platforms.Android; [IntentFilter([Intent.ActionView], DataSchemes = ["https", "http"], - DataHosts = ["use-your-server-url-here.com"], + DataHosts = ["use-your-web-app-url-here.com"], // the following app links will be opened in app instead of browser if the app is installed on Android device. DataPaths = [Urls.HomePage], DataPathPrefixes = [ diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainApplication.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainApplication.cs index 77c9433661..c877c1a5d7 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainApplication.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainApplication.cs @@ -24,13 +24,9 @@ namespace Boilerplate.Client.Maui.Platforms.Android; AllowBackup = true, SupportsRtl = true )] -public partial class MainApplication : MauiApplication +public partial class MainApplication(IntPtr handle, JniHandleOwnership ownership) + : MauiApplication(handle, ownership) { - public MainApplication(IntPtr handle, JniHandleOwnership ownership) - : base(handle, ownership) - { - } - protected override MauiApp CreateMauiApp() => MauiProgram .CreateMauiApp(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs index 5eadb473b9..f97a20355a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs @@ -7,7 +7,7 @@ namespace Boilerplate.Client.Maui.Platforms.Android.Services; public partial class AndroidPushNotificationService : PushNotificationServiceBase { - public async override Task IsPushNotificationSupported(CancellationToken cancellationToken) + public override async Task IsPushNotificationSupported(CancellationToken cancellationToken) { return await MainThread.InvokeOnMainThreadAsync(async () => { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs index 9051249542..6e53aae359 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs @@ -6,7 +6,7 @@ namespace Boilerplate.Client.Maui.Platforms.MacCatalyst.Services; public partial class MacCatalystPushNotificationService : PushNotificationServiceBase { - public async override Task IsPushNotificationSupported(CancellationToken cancellationToken) + public override async Task IsPushNotificationSupported(CancellationToken cancellationToken) { return await MainThread.InvokeOnMainThreadAsync(async () => { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Development.plist b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Development.plist index ee53d1ab52..98b2e40517 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Development.plist +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Development.plist @@ -1,10 +1,10 @@ - + com.apple.developer.associated-domains - applinks:use-your-server-url-here.com + applinks:use-your-web-app-url-here.com aps-environment diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Production.plist b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Production.plist index e6eda9d2f3..230e7b179b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Production.plist +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Production.plist @@ -1,10 +1,10 @@ - + com.apple.developer.associated-domains - applinks:use-your-server-url-here.com + applinks:use-your-web-app-url-here.com aps-environment diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs index ab3aa8a942..93620f1b94 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs @@ -6,7 +6,7 @@ namespace Boilerplate.Client.Maui.Platforms.iOS.Services; public partial class iOSPushNotificationService : PushNotificationServiceBase { - public async override Task IsPushNotificationSupported(CancellationToken cancellationToken) + public override async Task IsPushNotificationSupported(CancellationToken cancellationToken) { return await MainThread.InvokeOnMainThreadAsync(async () => { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs index 59c00117e2..2ac3f07c71 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs @@ -31,7 +31,14 @@ public int Start(CancellationToken cancellationToken) ctx.Redirect(url); - await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); + _ = Task.Delay(1) + .ContinueWith(async _ => + { + await MainThread.InvokeOnMainThreadAsync(async () => + { + await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); + }); + }); } catch (Exception exp) { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.Production.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.Production.json index 2b7a979354..63e9267114 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.Production.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.Production.json @@ -1,3 +1,3 @@ -{ +{ "$schema": "https://json.schemastore.org/appsettings.json" } \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.json index d81b30bff0..98223403a1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/appsettings.json @@ -1,3 +1,16 @@ -{ +{ + //#if (api == "Integrated") + "WebAppUrl": null, + //#endif + //#if (IsInsideProjectTemplate) + /* + //#endif + //#if (api == "Standalone") + "WebAppUrl": "http://localhost:5030/", + //#endif + //#if (IsInsideProjectTemplate) + */ + //#endif + "WebAppUrl_Comment": "When the maui app sends a request to the API server, and the API server and web app are hosted on different URLs, the origin of the generated links (e.g., email confirmation links) will depend on `WebAppUrl` value.", "$schema": "https://json.schemastore.org/appsettings.json" } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/index.html b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/index.html index d003f4814e..c177e1b373 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/index.html +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/wwwroot/index.html @@ -14,7 +14,7 @@ - + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.Services.cs index a23526a4a3..c32c8c8239 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.Services.cs @@ -23,7 +23,14 @@ public static void ConfigureServices(this WebAssemblyHostBuilder builder) serverAddress = new Uri(new Uri(builder.HostEnvironment.BaseAddress), serverAddress); } - services.AddScoped(sp => new HttpClient(sp.GetRequiredService()) { BaseAddress = serverAddress }); + services.AddScoped(sp => + { + var httpClient = new HttpClient(sp.GetRequiredService()) { BaseAddress = serverAddress }; + + httpClient.DefaultRequestHeaders.Add("X-Origin", builder.HostEnvironment.BaseAddress); + + return httpClient; + }); } public static void AddClientWebProjectServices(this IServiceCollection services, IConfiguration configuration) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebPushNotificationService.cs index b087f66beb..ded0f0e3dd 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Services/WebPushNotificationService.cs @@ -9,7 +9,7 @@ public partial class WebPushNotificationService : PushNotificationServiceBase [AutoInject] private readonly IJSRuntime jSRuntime = default!; [AutoInject] private readonly ClientWebSettings clientWebSettings = default!; - public async override Task GetSubscription(CancellationToken cancellationToken) + public override async Task GetSubscription(CancellationToken cancellationToken) { return await jSRuntime.GetPushNotificationSubscription(clientWebSettings.AdsPushVapid!.PublicKey); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/index.html b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/index.html index 32fcbcef0d..b967311a52 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/index.html +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/index.html @@ -22,7 +22,7 @@ - + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/robots.txt b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/robots.txt index b22132dba6..ca03c530bd 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/robots.txt +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/robots.txt @@ -1,4 +1,4 @@ -User-agent: * +User-agent: * Disallow: -Sitemap: https://use-your-server-url-here.com/sitemap.xml \ No newline at end of file +Sitemap: https://use-your-web-app-url-here.com/sitemap.xml \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/.config/dotnet-tools.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/.config/dotnet-tools.json index 461b027bf4..5d6ab1af3b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/.config/dotnet-tools.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "vpk": { - "version": "0.0.942", + "version": "0.0.1015", "commands": [ "vpk" ] diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/ClientWindowsSettings.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/ClientWindowsSettings.cs index 2ccd1cc977..191659fac6 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/ClientWindowsSettings.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/ClientWindowsSettings.cs @@ -5,6 +5,12 @@ namespace Boilerplate.Client.Windows; public class ClientWindowsSettings : ClientCoreSettings { + /// + /// When the Windows app sends a request to the API server, and the API server and web app are hosted on different URLs, + /// the origin of the generated links (e.g., email confirmation links) will depend on `WebAppUrl` value. + /// + public Uri? WebAppUrl { get; set; } + public WindowsUpdateOptions? WindowsUpdate { get; set; } public override IEnumerable Validate(ValidationContext validationContext) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.Services.cs index 0132bfcbde..ba1d6e24d2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Program.Services.cs @@ -1,5 +1,4 @@ //+:cnd:noEmit -using System.Net.Http; using Microsoft.Extensions.Logging; using Boilerplate.Client.Windows.Services; @@ -17,10 +16,14 @@ public static void AddClientWindowsProjectServices(this IServiceCollection servi services.AddScoped(sp => { var handler = sp.GetRequiredService(); - HttpClient httpClient = new(handler) + var httpClient = new HttpClient(handler) { BaseAddress = new Uri(configuration.GetServerAddress(), UriKind.Absolute) }; + if (sp.GetRequiredService().WebAppUrl is Uri origin) + { + httpClient.DefaultRequestHeaders.Add("X-Origin", origin.ToString()); + } return httpClient; }); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs index 2e4e7772f3..cf52aed8b4 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsLocalHttpServer.cs @@ -32,9 +32,15 @@ public int Start(CancellationToken cancellationToken) ctx.Redirect(url); - await Application.OpenForms[0]!.InvokeAsync(() => Application.OpenForms[0]!.Activate(), cancellationToken); - - await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); + _ = Task.Delay(1) + .ContinueWith(async _ => + { + Application.OpenForms[0]!.Invoke(() => + { + Application.OpenForms[0]!.Activate(); + }); + await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); + }); } catch (Exception exp) { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.Production.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.Production.json index 2b7a979354..63e9267114 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.Production.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.Production.json @@ -1,3 +1,3 @@ -{ +{ "$schema": "https://json.schemastore.org/appsettings.json" } \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.json index 610c1fa653..43e9a256a1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/appsettings.json @@ -1,4 +1,17 @@ -{ +{ + //#if (api == "Integrated") + "WebAppUrl": null, + //#endif + //#if (IsInsideProjectTemplate) + /* + //#endif + //#if (api == "Standalone") + "WebAppUrl": "http://localhost:5030/", + //#endif + //#if (IsInsideProjectTemplate) + */ + //#endif + "WebAppUrl_Comment": "When the Windows app sends a request to the API server, and the API server and web app are hosted on different URLs, the origin of the generated links (e.g., email confirmation links) will depend on `WebAppUrl` value.", "WindowsUpdate": { "FilesUrl": null, "AutoReload": true diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ElevatedAccessTokenTemplate.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ElevatedAccessTokenTemplate.razor index 8b91064c42..0426a729d1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ElevatedAccessTokenTemplate.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ElevatedAccessTokenTemplate.razor @@ -48,12 +48,12 @@ style="padding:20px; text-align:center;"> - Company Logo + Company Logo - + @EmailLocalizer[nameof(EmailStrings.AppName)] diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/EmailTokenTemplate.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/EmailTokenTemplate.razor index 738a4efd37..d521b17380 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/EmailTokenTemplate.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/EmailTokenTemplate.razor @@ -52,12 +52,12 @@ diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/OtpTemplate.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/OtpTemplate.razor index 1c06372f99..fd87b31b27 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/OtpTemplate.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/OtpTemplate.razor @@ -1,4 +1,4 @@ -@using Boilerplate.Server.Api.Models.Emailing +@using Boilerplate.Server.Api.Models.Emailing @code { [Parameter] public OtpTemplateModel Model { get; set; } = default!; @@ -58,12 +58,12 @@ style="padding:20px; text-align:center;"> diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ResetPasswordTokenTemplate.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ResetPasswordTokenTemplate.razor index d070632e9b..b9949784ae 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ResetPasswordTokenTemplate.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/ResetPasswordTokenTemplate.razor @@ -1,4 +1,4 @@ -@using Boilerplate.Server.Api.Models.Emailing +@using Boilerplate.Server.Api.Models.Emailing @code { [Parameter] public ResetPasswordTokenTemplateModel Model { get; set; } = default!; @@ -58,12 +58,12 @@ style="padding:20px; text-align:center;"> diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/TwoFactorTokenTemplate.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/TwoFactorTokenTemplate.razor index 18a9afab1a..3902f9c6d4 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/TwoFactorTokenTemplate.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/TwoFactorTokenTemplate.razor @@ -1,4 +1,4 @@ -@using Boilerplate.Server.Api.Models.Emailing +@using Boilerplate.Server.Api.Models.Emailing @code { [Parameter] public TwoFactorTokenTemplateModel Model { get; set; } = default!; @@ -48,12 +48,12 @@ style="padding:20px; text-align:center;"> diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/AttachmentController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/AttachmentController.cs index 5e128771ce..735fb5b897 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/AttachmentController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/AttachmentController.cs @@ -29,10 +29,7 @@ public async Task UploadProfileImage(IFormFile? file, CancellationToken cancella var userId = User.GetUserId(); - var user = await userManager.FindByIdAsync(userId.ToString()); - - if (user is null) - throw new ResourceNotFoundException(); + var user = await userManager.FindByIdAsync(userId.ToString()) ?? throw new ResourceNotFoundException(); var destFileName = $"{userId}_{file.FileName}"; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.EmailConfirmation.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.EmailConfirmation.cs index 15e8030ba9..1cb19f0140 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.EmailConfirmation.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.EmailConfirmation.cs @@ -78,7 +78,7 @@ private async Task SendConfirmEmailToken(User user, string? returnUrl, Cancellat var email = user.Email!; var token = await userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, FormattableString.Invariant($"VerifyEmail:{email},{user.EmailTokenRequestedOn?.ToUniversalTime()}")); - var link = new Uri(HttpContext.Request.GetWebClientUrl(), $"{Urls.ConfirmPage}?email={Uri.EscapeDataString(email)}&emailToken={Uri.EscapeDataString(token)}&culture={CultureInfo.CurrentUICulture.Name}&return-url={returnUrl}"); + var link = new Uri(HttpContext.Request.GetWebAppUrl(), $"{Urls.ConfirmPage}?email={Uri.EscapeDataString(email)}&emailToken={Uri.EscapeDataString(token)}&culture={CultureInfo.CurrentUICulture.Name}&return-url={returnUrl}"); await emailService.SendEmailToken(user, email, token, link, cancellationToken); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.ResetPassword.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.ResetPassword.cs index 5a295f4a32..0b3614af31 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.ResetPassword.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.ResetPassword.cs @@ -36,7 +36,7 @@ public async Task SendResetPasswordToken(SendResetPasswordTokenRequestDto reques var isEmail = string.IsNullOrEmpty(request.Email) is false; var qs = $"{(isEmail ? "email" : "phoneNumber")}={Uri.EscapeDataString(isEmail ? request.Email! : request.PhoneNumber!)}"; var url = $"{Urls.ResetPasswordPage}?token={Uri.EscapeDataString(token)}&{qs}&culture={CultureInfo.CurrentUICulture.Name}"; - var link = new Uri(HttpContext.Request.GetWebClientUrl(), url); + var link = new Uri(HttpContext.Request.GetWebAppUrl(), url); List sendMessagesTasks = []; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.SocialSignIn.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.SocialSignIn.cs index a66fd513ae..8eed768d58 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.SocialSignIn.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.SocialSignIn.cs @@ -11,14 +11,17 @@ public partial class IdentityController [HttpGet] public async Task GetSocialSignInUri(string provider, string? returnUrl = null, int? localHttpPort = null, CancellationToken cancellationToken = default) { - var uri = Url.Action(nameof(SocialSignIn), new { provider, returnUrl, localHttpPort })!; + var uri = Url.Action(nameof(SocialSignIn), new { provider, returnUrl, localHttpPort, origin = Request.GetWebAppUrl() })!; return new Uri(Request.GetBaseUrl(), uri).ToString(); } [HttpGet] - public async Task SocialSignIn(string provider, string? returnUrl = null, int? localHttpPort = null) + public async Task SocialSignIn(string provider, + string? returnUrl = null, /* Specifies the relative page address to navigate to after completion. */ + int? localHttpPort = null, /* Defines the local HTTP server port awaiting the social sign-in result on Windows/macOS versions of the app. */ + [FromQuery] string? origin = null /* Indicates the base address URL for redirection after the process completes. */ ) { - var redirectUrl = Url.Action(nameof(SocialSignInCallback), "Identity", new { returnUrl, localHttpPort }); + var redirectUrl = Url.Action(nameof(SocialSignInCallback), "Identity", new { returnUrl, localHttpPort, origin }); var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); return new ChallengeResult(provider, properties); } @@ -95,9 +98,7 @@ public async Task SocialSignInCallback(string? returnUrl = null, i } if (localHttpPort is not null) return Redirect(new Uri(new Uri($"http://localhost:{localHttpPort}"), url).ToString()); - var webClientUrl = Settings.WebClientUrl; - if (string.IsNullOrEmpty(webClientUrl) is false) return Redirect(new Uri(new Uri(webClientUrl), url).ToString()); - return LocalRedirect($"~{url}"); + return Redirect(new Uri(Request.HttpContext.Request.GetWebAppUrl(), url).ToString()); } [LoggerMessage(Level = LogLevel.Error, Message = "Failed to perform {loginProvider} social sign in for {principal}")] diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs index 314ce51e0c..d11bdb2eb0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/IdentityController.cs @@ -297,7 +297,7 @@ public async Task SendOtp(IdentityRequestDto request, string? returnUrl = null, var (magicLinkToken, url) = await GenerateAutomaticSignInLink(user, returnUrl, originalAuthenticationMethod: "Email"); - var link = new Uri(HttpContext.Request.GetWebClientUrl(), url); + var link = new Uri(HttpContext.Request.GetWebAppUrl(), url); List sendMessagesTasks = []; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs index fc6268d279..3db42bd079 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs @@ -170,7 +170,7 @@ public async Task SendChangeEmailToken(SendEmailTokenRequestDto request, Cancell FormattableString.Invariant($"ChangeEmail:{request.Email},{user.EmailTokenRequestedOn?.ToUniversalTime()}")); var link = new Uri( - HttpContext.Request.GetWebClientUrl(), + HttpContext.Request.GetWebAppUrl(), $"{Urls.SettingsPage}/{Urls.SettingsSections.Account}?email={Uri.EscapeDataString(request.Email!)}&emailToken={Uri.EscapeDataString(token)}&culture={CultureInfo.CurrentUICulture.Name}"); await emailService.SendEmailToken(user, request.Email!, token, link, cancellationToken); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs index 6cc30e787a..0e18f16793 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs @@ -38,10 +38,8 @@ public async Task> GetProducts(ODataQueryOptions Get(Guid id, CancellationToken cancellationToken) { - var dto = await Get().FirstOrDefaultAsync(t => t.Id == id, cancellationToken); - - if (dto is null) - throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ProductCouldNotBeFound)]); + var dto = await Get().FirstOrDefaultAsync(t => t.Id == id, cancellationToken) + ?? throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ProductCouldNotBeFound)]); return dto; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Todo/TodoItemController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Todo/TodoItemController.cs index 83f05cd53e..975be7800f 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Todo/TodoItemController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Todo/TodoItemController.cs @@ -32,10 +32,8 @@ public async Task> GetTodoItems(ODataQueryOptions Get(Guid id, CancellationToken cancellationToken) { - var dto = await Get().FirstOrDefaultAsync(t => t.Id == id, cancellationToken); - - if (dto is null) - throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ToDoItemCouldNotBeFound)]); + var dto = await Get().FirstOrDefaultAsync(t => t.Id == id, cancellationToken) + ?? throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ToDoItemCouldNotBeFound)]); return dto; } @@ -59,10 +57,8 @@ public async Task Create(TodoItemDto dto, CancellationToken cancell [HttpPut] public async Task Update(TodoItemDto dto, CancellationToken cancellationToken) { - var entityToUpdate = await DbContext.TodoItems.FirstOrDefaultAsync(t => t.Id == dto.Id, cancellationToken); - - if (entityToUpdate is null) - throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ToDoItemCouldNotBeFound)]); + var entityToUpdate = await DbContext.TodoItems.FirstOrDefaultAsync(t => t.Id == dto.Id, cancellationToken) + ?? throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ToDoItemCouldNotBeFound)]); dto.Patch(entityToUpdate); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Extensions/HttpRequestExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Extensions/HttpRequestExtensions.cs index 306f709ca9..000dc7e303 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Extensions/HttpRequestExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Extensions/HttpRequestExtensions.cs @@ -4,6 +4,23 @@ namespace Microsoft.AspNetCore.Http; public static partial class HttpRequestExtensions { + internal static Uri GetWebAppUrl(this HttpRequest req) + { + var settings = req.HttpContext.RequestServices.GetRequiredService(); + + var serverUrl = req.GetBaseUrl(); + + var origin = req.Query["origin"].Union(req.Headers["X-Origin"]).Select(o => new Uri(o)).SingleOrDefault(); + + if (origin is null) + return serverUrl; // Assume that web app and server are hosted in one place. + + if (origin == serverUrl || settings.IsAllowedOrigin(origin)) + return origin; + + throw new BadRequestException($"Invalid origin {origin}"); + } + /// /// https://blog.elmah.io/how-to-get-base-url-in-asp-net-core/ /// @@ -17,16 +34,4 @@ internal static Uri GetBaseUrl(this HttpRequest req) return uriBuilder.Uri; } - - internal static Uri GetWebClientUrl(this HttpRequest req) - { - var settings = req.HttpContext.RequestServices.GetRequiredService(); - - var webClientUrl = settings.WebClientUrl; - - if (string.IsNullOrEmpty(webClientUrl) is false) - return new Uri(webClientUrl); - - return req.GetBaseUrl(); - } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs index fd91fa54cd..34a6b89d56 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs @@ -2,7 +2,6 @@ using System.Net; using System.Net.Mail; using System.IO.Compression; -using System.Text.RegularExpressions; using System.Security.Cryptography.X509Certificates; using Microsoft.OpenApi.Models; using Microsoft.AspNetCore.OData; @@ -123,16 +122,7 @@ public static void AddServerApiProjectServices(this WebApplicationBuilder builde ServerApiSettings settings = new(); configuration.Bind(settings); - var webClientUrl = settings.WebClientUrl; - var allowedOrigins = settings.Cors?.AllowedOrigins?.ToList() ?? []; - - if (string.IsNullOrEmpty(webClientUrl) is false) - { - allowedOrigins.Add(webClientUrl); - } - - policy.SetIsOriginAllowed(origin => AllowedOriginsRegex().IsMatch(origin) - || allowedOrigins.Any(o => string.Equals(o, origin, StringComparison.InvariantCultureIgnoreCase))) + policy.SetIsOriginAllowed(origin => settings.IsAllowedOrigin(new Uri(origin))) .AllowAnyHeader() .AllowAnyMethod() .WithExposedHeaders(HeaderNames.RequestId); @@ -431,10 +421,4 @@ private static void AddSwaggerGen(WebApplicationBuilder builder) }); }); } - - /// - /// For either Blazor Hybrid web view, localhost, dev tunnels etc in dev environment. - /// - [GeneratedRegex(@"^(http|https|app):\/\/(localhost|0\.0\.0\.0|0\.0\.0\.1|127\.0\.0\.1|.*?devtunnels\.ms|.*?github\.dev)(:\d+)?(\/.*)?$")] - private static partial Regex AllowedOriginsRegex(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/ServerApiSettings.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/ServerApiSettings.cs index f0f6989863..afff79dec9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/ServerApiSettings.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/ServerApiSettings.cs @@ -2,6 +2,7 @@ //#if (notification == true) using AdsPush.Abstraction.Settings; //#endif +using System.Text.RegularExpressions; namespace Boilerplate.Server.Api; @@ -39,7 +40,10 @@ public partial class ServerApiSettings : SharedSettings public ForwardedHeadersOptions? ForwardedHeaders { get; set; } - public CorsOptions? Cors { get; set; } + /// + /// Defines the list of origins permitted for CORS access to the API. These origins are also valid for use as return URLs after social sign-ins and for generating URLs in emails. + /// + public Uri[] AllowedOrigins { get; set; } = []; public override IEnumerable Validate(ValidationContext validationContext) { @@ -92,6 +96,24 @@ public override IEnumerable Validate(ValidationContext validat return validationResults; } + + internal bool IsAllowedOrigin(Uri origin) + { + return AllowedOrigins.Any(allowedOrigin => allowedOrigin == origin) + || AllowedOriginsRegex().IsMatch(origin.ToString()); + } + + //-:cnd:noEmit + /// + /// Blazor Hybrid's webview, localhost, devtunnels, github codespaces. + /// +#if Development + [GeneratedRegex(@"^(http|https|app):\/\/(localhost|0\.0\.0\.0|0\.0\.0\.1|127\.0\.0\.1|.*?devtunnels\.ms|.*?github\.dev)(:\d+)?(\/.*)?$")] +#else + [GeneratedRegex(@"^(http|https|app):\/\/(localhost|0\.0\.0\.0|0\.0\.0\.1|127\.0\.0\.1)(:\d+)?(\/.*)?$")] +#endif + //+:cnd:noEmit + private partial Regex AllowedOriginsRegex(); } public partial class AppIdentityOptions : IdentityOptions @@ -159,8 +181,3 @@ public partial class SmsOptions string.IsNullOrEmpty(TwilioAccountSid) is false && string.IsNullOrEmpty(TwilioAutoToken) is false; } - -public class CorsOptions -{ - public string[] AllowedOrigins { get; set; } = []; -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/EmailService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/EmailService.cs index e510b613c4..78a4677d34 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/EmailService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/EmailService.cs @@ -22,7 +22,7 @@ public async Task SendResetPasswordToken(User user, string token, Uri link, Canc if (hostEnvironment.IsDevelopment()) { - LogSendEmail(logger, subject, user.Email!, "ResetPassword", Uri.UnescapeDataString(link.ToString())); + LogSendEmail(logger, subject, user.Email!, "ResetPassword", link.ToString()); } var body = await BuildBody(new Dictionary() @@ -45,7 +45,7 @@ public async Task SendOtp(User user, string token, Uri link, CancellationToken c if (hostEnvironment.IsDevelopment()) { - LogSendEmail(logger, subject, user.Email!, "Otp", Uri.UnescapeDataString(link.ToString())); + LogSendEmail(logger, subject, user.Email!, "Otp", link.ToString()); } var body = await BuildBody(new Dictionary() @@ -86,7 +86,7 @@ public async Task SendEmailToken(User user, string toEmailAddress, string token, if (hostEnvironment.IsDevelopment()) { - LogSendEmail(logger, subject, user.Email!, "EmailToken", Uri.UnescapeDataString(link.ToString())); + LogSendEmail(logger, subject, user.Email!, "EmailToken", link.ToString()); } var body = await BuildBody(new Dictionary() diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/ODataOperationFilter.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/ODataOperationFilter.cs index cc04f6ddff..b41405db8c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/ODataOperationFilter.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/ODataOperationFilter.cs @@ -11,7 +11,7 @@ public partial class ODataOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { - if (operation.Parameters is null) operation.Parameters = []; + operation.Parameters ??= []; if (context.ApiDescription.ActionDescriptor is not ControllerActionDescriptor descriptor) return; @@ -40,7 +40,7 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) { Type = "string", }, - Description = "Include only the selected objects. (ex. Childrens, Locations)", + Description = "Include only the selected objects. (ex. Orders, Locations)", Required = false }); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs index 211d9a102b..1b756a77eb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Services/PushNotificationService.cs @@ -116,7 +116,7 @@ public async Task RequestPush(string? title = null, string? message = null, stri } }); - if (exceptions.Any()) + if (exceptions.IsEmpty is false) { logger.LogError(new AggregateException(exceptions), "Failed to send push notifications"); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/appsettings.json index 670cbd3930..9d790c5e2d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/appsettings.json @@ -123,6 +123,8 @@ } }, "AllowedHosts": "*", + "AllowedOrigins": [], + "AllowedOrigins_Comment": "Defines the list of origins permitted for CORS access to the API. These origins are also valid for use as return URLs after social sign-ins and for generating URLs in emails.", "ForwardedHeaders": { "ForwardedHeaders": "All", "ForwardedHeaders_Comment": "These values apply only if your backend is hosted behind a CDN (such as `Cloudflare`).", diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor index 715ead4ecc..176648d642 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor @@ -36,7 +36,7 @@ @*#endif*@ - @* for PWA *@ + @* for PWA *@ diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor.cs index 9e5be58fdf..5d4577411d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Components/App.razor.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Rendering; +using Boilerplate.Client.Core.Services; namespace Boilerplate.Server.Web.Components; @@ -11,8 +12,9 @@ public partial class App [CascadingParameter] HttpContext HttpContext { get; set; } = default!; - [AutoInject] IStringLocalizer localizer = default!; [AutoInject] ServerWebSettings serverWebSettings = default!; + [AutoInject] IStringLocalizer localizer = default!; + [AutoInject] AbsoluteServerAddressProvider absoluteServerAddress = default!; protected override void OnInitialized() { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Services.cs index 6eda668616..1ac4e6cd98 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/Program.Services.cs @@ -118,6 +118,8 @@ private static void AddBlazor(WebApplicationBuilder builder) httpClient.DefaultRequestHeaders.Add(HeaderNames.Referer, string.Join(',', headerValues.AsEnumerable())); } + httpClient.DefaultRequestHeaders.Add("X-Origin", currentRequest.GetBaseUrl().ToString()); + return httpClient; }); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/IConfigurationBuilderExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/IConfigurationBuilderExtensions.cs index 4949ef4661..079361a0c5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/IConfigurationBuilderExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/IConfigurationBuilderExtensions.cs @@ -15,7 +15,7 @@ public static partial class IConfigurationBuilderExtensions /// public static void AddSharedConfigurations(this IConfigurationBuilder builder) { - IConfigurationBuilder configBuilder = new ConfigurationBuilder(); + var configBuilder = new ConfigurationBuilder(); var sharedAssembly = Assembly.Load("Boilerplate.Shared"); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/JsonSeralizerExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/JsonSeralizerExtensions.cs index 386d722246..3f5745420d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/JsonSeralizerExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/JsonSeralizerExtensions.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Text.Json.Serialization.Metadata; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/TupleExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/TupleExtensions.cs index e84fb45b37..7cb5d66b11 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/TupleExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/TupleExtensions.cs @@ -16,8 +16,8 @@ namespace System.Threading.Tasks; /// ProductDto[] products; /// protected override async Task OnInitAsync() /// { -/// categories = await HttpClient.GetJsonAsync("api/categories"); // Tooks 150ms -/// products = await HttpClient.GetJsonAsync("api/products"); // Tooks 150ms +/// categories = await HttpClient.GetJsonAsync("api/categories"); // Took 150ms +/// products = await HttpClient.GetJsonAsync("api/products"); // Took 150ms /// // The total time is 300ms 😖 /// } /// } @@ -29,7 +29,7 @@ namespace System.Threading.Tasks; /// ProductDto[] products; /// protected override async Task OnInitAsync() /// { -/// (categories, products) = await (HttpClient.GetJsonAsync("api/categories"), HttpClient.GetJsonAsync("api/products")); // Tooks 150ms +/// (categories, products) = await (HttpClient.GetJsonAsync("api/categories"), HttpClient.GetJsonAsync("api/products")); // Took 150ms /// // The total time is 150ms 👍 /// } /// } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs index 14e0b9f3f2..52c189578d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Services/CultureInfoManager.cs @@ -68,5 +68,5 @@ private static CultureInfo CustomizeCultureInfoForFaCulture(CultureInfo cultureI } [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_calendar")] - extern static ref Calendar Get_CalendarField(CultureInfo cultureInfo); + static extern ref Calendar Get_CalendarField(CultureInfo cultureInfo); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/SharedSettings.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/SharedSettings.cs index 25b5597677..278996e991 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/SharedSettings.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/SharedSettings.cs @@ -3,12 +3,6 @@ namespace Boilerplate.Shared; public partial class SharedSettings : IValidatableObject { - /// - /// If you are hosting the API and web client on different URLs (e.g., api.test.com and test.com), you must set `WebClientUrl` to your web client's address. - /// This ensures that the API server redirects to the correct URL after social sign-ins and other similar actions. - /// - public string? WebClientUrl { get; set; } - //#if (appInsights == true) public ApplicationInsightsOptions? ApplicationInsights { get; set; } //#endif diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json index d8281cfda8..2aeda8d50e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/appsettings.json @@ -1,6 +1,4 @@ { - "WebClientUrl": null, - "WebClientUrl_Comment": "If you are hosting the API and web client on different URLs (e.g., api.company.com and app.company.com), you must set `WebClientUrl` to your web client's address. This ensures that the API server redirects to the correct URL after social sign-ins and other similar actions.", //#if (appInsights == true) "ApplicationInsights": { "ConnectionString": null From ebf09ab92df978f2407ff7238247f10cf2908a29 Mon Sep 17 00:00:00 2001 From: Mohammad Aminsafaei Date: Sun, 29 Dec 2024 14:14:34 +0330 Subject: [PATCH 09/28] feat(blazorui): rename Toggled parameter in BitButtonGroup to Toggle #9555 (#9556) --- .../BitButtonGroup/BitButtonGroup.razor.cs | 10 ++++----- .../ButtonGroup/BitButtonGroupDemo.razor.cs | 2 +- .../_BitButtonGroupCustomDemo.razor | 22 ++++++++++++------- .../_BitButtonGroupCustomDemo.razor.cs | 4 ++-- ..._BitButtonGroupCustomDemo.razor.samples.cs | 22 ++++++++++++------- .../ButtonGroup/_BitButtonGroupItemDemo.razor | 10 ++++----- .../_BitButtonGroupItemDemo.razor.cs | 4 ++-- .../_BitButtonGroupItemDemo.razor.samples.cs | 10 ++++----- .../_BitButtonGroupOptionDemo.razor | 22 +++++++++---------- ..._BitButtonGroupOptionDemo.razor.samples.cs | 18 +++++++-------- 10 files changed, 68 insertions(+), 56 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs index f51e18e0c9..9bf05e3061 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs @@ -76,7 +76,7 @@ public partial class BitButtonGroup : BitComponentBase where TItem : clas /// /// Display ButtonGroup with toggle mode enabled for each button. /// - [Parameter] public bool Toggled { get; set; } + [Parameter] public bool Toggle { get; set; } /// /// The visual variant of the button group. @@ -203,7 +203,7 @@ private async Task HandleOnItemClick(TItem item) } } - if (Toggled) + if (Toggle) { if (_toggleItem == item) { @@ -276,7 +276,7 @@ private async Task HandleOnItemClick(TItem item) { if (IconOnly) return null; - if (Toggled) + if (Toggle) { if (_toggleItem == item) { @@ -301,7 +301,7 @@ private async Task HandleOnItemClick(TItem item) private string? GetItemTitle(TItem? item) { - if (Toggled) + if (Toggle) { if (_toggleItem == item) { @@ -326,7 +326,7 @@ private async Task HandleOnItemClick(TItem item) private string? GetItemIconName(TItem? item) { - if (Toggled) + if (Toggle) { if (_toggleItem == item) { diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs index 2de44d898a..cf0fc5c887 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs @@ -76,7 +76,7 @@ public partial class BitButtonGroupDemo }, new() { - Name = "Toggled", + Name = "Toggle", Type = "bool", DefaultValue = "false", Description = "Display ButtonGroup with toggle mode enabled for each button.", diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor index 0d5e331a08..e4db2fc337 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor @@ -140,44 +140,50 @@ - + -
The Toggled in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.
+
The Toggle in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.


Fill (default)
- + ReversedIcon = { Selector = i => i.ReversedIcon } })" />


Outline
- + ReversedIcon = { Selector = i => i.ReversedIcon } })" />


Text
- + ReversedIcon = { Selector = i => i.ReversedIcon } })" />
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.cs index 0369b8eb8c..b8528799dc 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.cs @@ -40,9 +40,9 @@ public partial class _BitButtonGroupCustomDemo private List toggledCustoms = [ - new() { OnName = "Back (2X)", OffName = "Back", OnIcon = BitIconName.RewindTwoX, OffIcon = BitIconName.Rewind }, + new() { OnName = "Back (2X)", OffName = "Back (1X)", OnIcon = BitIconName.RewindTwoX, OffIcon = BitIconName.Rewind }, new() { OnTitle = "Resume", OffTitle = "Play", OnIcon = BitIconName.PlayResume, OffIcon = BitIconName.Play }, - new() { OnName = "Forward (2X)", OffName = "Forward", OnIcon = BitIconName.FastForwardTwoX, OffIcon = BitIconName.FastForward, ReversedIcon = true } + new() { OnName = "Forward (2X)", OffName = "Forward (1X)", OnIcon = BitIconName.FastForwardTwoX, OffIcon = BitIconName.FastForward, ReversedIcon = true } ]; private List eventsCustoms = diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs index f84fb7e28e..25ff2cbc36 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs @@ -151,32 +151,38 @@ public class Operation ];"; private readonly string example6RazorCode = @" - i.OnName }, OffText = { Selector = i => i.OffName }, OnTitle = { Selector = i => i.OnTitle }, OffTitle = { Selector = i => i.OffTitle }, OnIconName = { Selector = i => i.OnIcon }, OffIconName = { Selector = i => i.OffIcon }, - ReversedIcon = { Selector = i => i.ReversedIcon } })"" Toggled /> + ReversedIcon = { Selector = i => i.ReversedIcon } })"" /> - i.OnName }, OffText = { Selector = i => i.OffName }, OnTitle = { Selector = i => i.OnTitle }, OffTitle = { Selector = i => i.OffTitle }, OnIconName = { Selector = i => i.OnIcon }, OffIconName = { Selector = i => i.OffIcon }, - ReversedIcon = { Selector = i => i.ReversedIcon } })"" Toggled /> + ReversedIcon = { Selector = i => i.ReversedIcon } })"" /> - i.OnName }, OffText = { Selector = i => i.OffName }, OnTitle = { Selector = i => i.OnTitle }, OffTitle = { Selector = i => i.OffTitle }, OnIconName = { Selector = i => i.OnIcon }, OffIconName = { Selector = i => i.OffIcon }, - ReversedIcon = { Selector = i => i.ReversedIcon } })"" Toggled />"; + ReversedIcon = { Selector = i => i.ReversedIcon } })"" />"; private readonly string example6CsharpCode = @" public class Operation { @@ -191,9 +197,9 @@ public class Operation private List toggledCustoms = [ - new() { OnName = ""Back (2X)"", OffName = ""Back"", OnIcon = BitIconName.RewindTwoX, OffIcon = BitIconName.Rewind }, + new() { OnName = ""Back (2X)"", OffName = ""Back (1X)"", OnIcon = BitIconName.RewindTwoX, OffIcon = BitIconName.Rewind }, new() { OnTitle = ""Resume"", OffTitle = ""Play"", OnIcon = BitIconName.PlayResume, OffIcon = BitIconName.Play }, - new() { OnName = ""Forward (2X)"", OffName = ""Forward"", OnIcon = BitIconName.FastForwardTwoX, OffIcon = BitIconName.FastForward, ReversedIcon = true } + new() { OnName = ""Forward (2X)"", OffName = ""Forward (1X)"", OnIcon = BitIconName.FastForwardTwoX, OffIcon = BitIconName.FastForward, ReversedIcon = true } ];"; private readonly string example7RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor index 1ea39f8981..b5486f1134 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor @@ -113,23 +113,23 @@
- + -
The Toggled in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.
+
The Toggle in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.


Fill (default)
- +


Outline
- +


Text
- +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.cs index 15b29a29c7..32b455a4da 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.cs @@ -38,9 +38,9 @@ public partial class _BitButtonGroupItemDemo private List toggledItems = [ - new() { OnText = "Back (2X)", OffText = "Back", OnIconName = BitIconName.RewindTwoX, OffIconName = BitIconName.Rewind }, + new() { OnText = "Back (2X)", OffText = "Back (1X)", OnIconName = BitIconName.RewindTwoX, OffIconName = BitIconName.Rewind }, new() { OnTitle = "Resume", OffTitle = "Play", OnIconName = BitIconName.PlayResume, OffIconName = BitIconName.Play }, - new() { OnText = "Forward (2X)", OffText = "Forward", OnIconName = BitIconName.FastForwardTwoX, OffIconName = BitIconName.FastForward, ReversedIcon = true } + new() { OnText = "Forward (2X)", OffText = "Forward (1X)", OnIconName = BitIconName.FastForwardTwoX, OffIconName = BitIconName.FastForward, ReversedIcon = true } ]; private List eventsItems = diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs index 5431abfd76..6008d38cf6 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupItemDemo.razor.samples.cs @@ -81,15 +81,15 @@ public partial class _BitButtonGroupItemDemo ];"; private readonly string example6RazorCode = @" - - -"; + + +"; private readonly string example6CsharpCode = @" private List toggledItems = [ - new() { OnText = ""Back (2X)"", OffText = ""Back"", OnIconName = BitIconName.RewindTwoX, OffIconName = BitIconName.Rewind }, + new() { OnText = ""Back (2X)"", OffText = ""Back (1X)"", OnIconName = BitIconName.RewindTwoX, OffIconName = BitIconName.Rewind }, new() { OnTitle = ""Resume"", OffTitle = ""Play"", OnIconName = BitIconName.PlayResume, OffIconName = BitIconName.Play }, - new() { OnText = ""Forward (2X)"", OffText = ""Forward"", OnIconName = BitIconName.FastForwardTwoX, OffIconName = BitIconName.FastForward, ReversedIcon = true } + new() { OnText = ""Forward (2X)"", OffText = ""Forward (1X)"", OnIconName = BitIconName.FastForwardTwoX, OffIconName = BitIconName.FastForward, ReversedIcon = true } ];"; private readonly string example7RazorCode = @" diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor index f34499d397..8580fa574f 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor @@ -183,34 +183,34 @@
- + -
The Toggled in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.
+
The Toggle in BitButtonGroup allows you to control the active or inactive state of each button, providing clear visual feedback to users about which buttons are selected or currently in use.


Fill (default)
- - + + - +


Outline
- - + + - +


Text
- - + + - +
diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs index 8c30e6d21c..985c5f08f4 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupOptionDemo.razor.samples.cs @@ -118,22 +118,22 @@ public partial class _BitButtonGroupOptionDemo "; private readonly string example6RazorCode = @" - - + + - + - - + + - + - - + + - + "; private readonly string example7RazorCode = @" From 0e166fbf28d6728218c8b68057108fc55466265e Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Sun, 29 Dec 2024 13:32:19 +0100 Subject: [PATCH 10/28] feat(templates): improve Boilerplate social sign-in in blazor hybrid #9557 (#9558) --- .../Services/Contracts/ILocalHttpServer.cs | 2 +- .../MauiProgram.Services.cs | 12 +-------- .../Services/MauiExternalNavigationService.cs | 2 +- .../Services/MauiLocalHttpServer.cs | 26 ++++++++++++++----- .../Components/SocialSignedInPage.razor | 3 +++ 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs index dfcf4ba11f..ef429f3504 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ILocalHttpServer.cs @@ -2,7 +2,7 @@ /// /// Social sign-in functions seamlessly on web browsers and on Android and iOS via universal app links. -/// However, for Windows and macOS, a local HTTP server is needed to ensure a smooth social sign-in experience. +/// However, for blazor hybrid, a local HTTP server is needed to ensure a smooth social sign-in experience. /// public interface ILocalHttpServer { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs index efb7f47489..78ba7c58a2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/MauiProgram.Services.cs @@ -39,17 +39,7 @@ public static void ConfigureServices(this MauiAppBuilder builder) return settings; }); services.AddSingleton(ITelemetryContext.Current!); - if (AppPlatform.IsWindows - || AppPlatform.IsMacOS - || AppEnvironment.IsDev()) - { - // About AppEnvironment.IsDev: - // In the development environment, universal links are not configured. Universal links are required to provide the - // same user experience (UX) that you can test in the production app (available on Google Play at https://play.google.com/store/apps/details?id=com.bitplatform.AdminPanel.Template). - // As a workaround, we will fallback to a local HTTP server. This will provide a slightly degraded UX, but it will allow you to test the app in the development environment. - - services.AddSingleton(); - } + services.AddSingleton(); services.AddMauiBlazorWebView(); services.AddBlazorWebViewDeveloperTools(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExternalNavigationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExternalNavigationService.cs index 471a604039..c629f6a373 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExternalNavigationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiExternalNavigationService.cs @@ -4,6 +4,6 @@ public partial class MauiExternalNavigationService : IExternalNavigationService { public async Task NavigateToAsync(string url) { - await Browser.OpenAsync(url, AppPlatform.IsAndroid ? BrowserLaunchMode.SystemPreferred : BrowserLaunchMode.External); + await Browser.OpenAsync(url, AppPlatform.IsWindows || AppPlatform.IsMacOS ? BrowserLaunchMode.External : BrowserLaunchMode.SystemPreferred /* in app browser */); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs index 2ac3f07c71..d4c134d38b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiLocalHttpServer.cs @@ -27,18 +27,32 @@ public int Start(CancellationToken cancellationToken) { try { + // Redirect to SocialSignedInPage.razor that will close the browser window. var url = new Uri(absoluteServerAddress, $"/api/Identity/SocialSignedIn?culture={CultureInfo.CurrentUICulture.Name}").ToString(); - ctx.Redirect(url); - _ = Task.Delay(1) - .ContinueWith(async _ => + if (AppPlatform.IsIOS) + { + // SocialSignedInPage.razor's `window.close()` does NOT work on iOS's in app browser. + await MainThread.InvokeOnMainThreadAsync(() => { - await MainThread.InvokeOnMainThreadAsync(async () => +#if iOS + if (UIKit.UIApplication.SharedApplication.KeyWindow?.RootViewController?.PresentedViewController is SafariServices.SFSafariViewController controller) { - await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); - }); + controller.DismissViewController(animated: true, completionHandler: null); + } +#endif + }); + } + + _ = Task.Delay(1) + .ContinueWith(async _ => + { + await MainThread.InvokeOnMainThreadAsync(async () => + { + await Routes.OpenUniversalLink(ctx.Request.Url.PathAndQuery, replace: true); }); + }); } catch (Exception exp) { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor index 2676399732..7d1fe4f328 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Components/SocialSignedInPage.razor @@ -46,6 +46,9 @@

@Localizer[nameof(AppStrings.SocialSignedInMessageBody)]

+ \ No newline at end of file From 0dc9ab8a10d1db65a1c323e771bf61ba28b85511 Mon Sep 17 00:00:00 2001 From: Mohammad Aminsafaei Date: Mon, 30 Dec 2024 17:19:05 +0330 Subject: [PATCH 11/28] feat(blazorui): add FullWidth parameter to BitGroupButton #9559 (#9560) --- .../BitButtonGroup/BitButtonGroup.razor.cs | 8 ++++++ .../BitButtonGroup/BitButtonGroup.scss | 7 ++++- .../ButtonGroup/BitButtonGroupDemo.razor.cs | 7 +++++ .../_BitButtonGroupCustomDemo.razor | 18 ++++++++++--- ..._BitButtonGroupCustomDemo.razor.samples.cs | 27 +++++++++++++++---- .../ButtonGroup/_BitButtonGroupItemDemo.razor | 18 ++++++++++--- .../_BitButtonGroupItemDemo.razor.samples.cs | 20 ++++++++++---- .../_BitButtonGroupOptionDemo.razor | 25 ++++++++++++++--- ..._BitButtonGroupOptionDemo.razor.samples.cs | 15 +++++++++-- 9 files changed, 123 insertions(+), 22 deletions(-) diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs index 9bf05e3061..ad846198c6 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs @@ -32,6 +32,12 @@ public partial class BitButtonGroup : BitComponentBase where TItem : clas [Parameter, ResetClassBuilder] public BitColor? Color { get; set; } + /// + /// Expand the ButtonGroup width to 100% of the available width. + /// + [Parameter, ResetClassBuilder] + public bool FullWidth { get; set; } + /// /// Determines that only the icon should be rendered. /// @@ -154,6 +160,8 @@ protected override void RegisterCssClasses() }); ClassBuilder.Register(() => Vertical ? "bit-btg-vrt" : string.Empty); + + ClassBuilder.Register(() => FullWidth ? "bit-btg-flw" : string.Empty); } protected override void RegisterCssStyles() diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss index 9a55b86981..3e6fb265db 100644 --- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss +++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss @@ -2,9 +2,9 @@ .bit-btg { overflow: hidden; + width: fit-content; flex-direction: row; display: inline-flex; - max-width: fit-content; border-width: $shp-border-width; border-style: $shp-border-style; border-radius: $shp-border-radius; @@ -24,7 +24,12 @@ } } +.bit-btg-flw { + width: 100%; +} + .bit-btg-itm { + flex-grow: 1; display: flex; gap: spacing(1); cursor: pointer; diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs index cf0fc5c887..698761d93b 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/BitButtonGroupDemo.razor.cs @@ -37,6 +37,13 @@ public partial class BitButtonGroupDemo Href = "#color-enum", }, new() + { + Name = "FullWidth", + Type = "bool", + DefaultValue = "false", + Description = "Expand the ButtonGroup width to 100% of the available width.", + }, + new() { Name = "Items", Type = "IEnumerable", diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor index e4db2fc337..f382d7a534 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor @@ -261,7 +261,19 @@
- + + +
Setting the FullWidth makes the button group occupy 100% of its container's width.
+

+
+ + + +
+
+
+ +
Offering a range of specialized colors, providing visual cues for specific states within your application.


@@ -388,7 +400,7 @@
- +
Empower customization by overriding default styles and classes, allowing tailored design modifications to suit specific UI requirements.


@@ -421,7 +433,7 @@
- +
Use BitButtonGroup in right-to-left (RTL).

diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs index 25ff2cbc36..ef5dcaa1e1 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Buttons/ButtonGroup/_BitButtonGroupCustomDemo.razor.samples.cs @@ -287,6 +287,23 @@ public class Operation ];"; private readonly string example10RazorCode = @" + + +"; + private readonly string example10CsharpCode = @" +private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; + +public class Operation +{ + public string? Name { get; set; } +} + +private List basicCustoms = +[ + new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } +];"; + + private readonly string example11RazorCode = @" @@ -357,7 +374,7 @@ public class Operation "; - private readonly string example10CsharpCode = @" + private readonly string example11CsharpCode = @" private BitButtonGroupNameSelectors nameSelector = new() { Text = { Selector = i => i.Name } }; public class Operation @@ -370,7 +387,7 @@ public class Operation new() { Name = ""Add"" }, new() { Name = ""Edit"" }, new() { Name = ""Delete"" } ];"; - private readonly string example11RazorCode = @" + private readonly string example12RazorCode = @" - + - +
Header
- -
NavMenu
-
+ +
NavPanel
+
Main
@@ -234,7 +218,7 @@ public partial class BitLayoutDemo
"; private readonly string example2CsharpCode = @" -private bool hideNavMenu;"; +private bool HideNavPanel;"; private readonly string example3RazorCode = @"
- Company Logo + Company Logo
- + @EmailLocalizer[nameof(EmailStrings.AppName)]
- Company Logo + Company Logo
- + @EmailLocalizer[nameof(EmailStrings.AppName)]
- Company Logo + Company Logo
- + @EmailLocalizer[nameof(EmailStrings.AppName)]
- Company Logo + Company Logo
- + @EmailLocalizer[nameof(EmailStrings.AppName)]