diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor index 6abaf8a1bb..36023bf53e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor @@ -1,32 +1,13 @@ @inherits AppComponentBase -
- -
- - - - @title - - - - - - - - - @body - - - - - @Localizer[AppStrings.Ok] - - - -
-
-
\ No newline at end of file +
+ + @Body + +
+ + + + @Localizer[AppStrings.Ok] + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.cs index 6b38366c85..8ef16e20c7 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.cs @@ -2,63 +2,12 @@ public partial class MessageBox { - private bool isOpen; - private string? body; - private string? title; - private Action? unsubscribe; - private bool disposed = false; + [Parameter] public string? Body { get; set; } + [Parameter] public Action? OnOk { get; set; } - private TaskCompletionSource? tcs; - protected override Task OnInitAsync() + private void OnOkClick() { - unsubscribe = PubSubService.Subscribe(ClientPubSubMessages.SHOW_MESSAGE, async args => - { - var data = (MessageBoxData)args!; - - tcs = data.TaskCompletionSource; - - await ShowMessageBox(data.Message, data.Title); - }); - - return base.OnInitAsync(); - } - - private async Task ShowMessageBox(string message, string title = "") - { - await InvokeAsync(() => - { - isOpen = true; - this.title = title; - body = message; - - StateHasChanged(); - }); - } - - private async Task OnCloseClick() - { - isOpen = false; - tcs?.SetResult(false); - } - - private async Task OnOkClick() - { - isOpen = false; - tcs?.SetResult(true); - } - - protected override async ValueTask DisposeAsync(bool disposing) - { - await base.DisposeAsync(true); - - if (disposed || disposing is false) return; - - tcs?.TrySetResult(false); - tcs = null; - - unsubscribe?.Invoke(); - - disposed = true; + OnOk?.Invoke(); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.scss index 663287364a..ac94c637e7 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.scss +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MessageBox.razor.scss @@ -1,41 +1,7 @@ -@import '../../Styles/abstracts/_media-queries.scss'; - -section { - padding: 1rem; - min-width: 20rem; - max-height: var(--app-height); - - @include lt-md { - min-width: unset; - } -} - -::deep { - .root { - width: var(--app-width); - height: var(--app-height); - top: var(--app-inset-top); - left: var(--app-inset-left); - right: var(--app-inset-right); - bottom: var(--app-inset-bottom); - } - - .content { - @include lt-md { - min-width: 95%; - max-width: 95%; - } - } - - .stack { - max-height: calc(var(--app-height) - 3rem); - } - - .body { - width: 100%; - flex-grow: 1; - display: flex; - overflow: auto; - white-space: pre; - } +.body { + width: 100%; + flex-grow: 1; + display: flex; + overflow: auto; + white-space: pre; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor new file mode 100644 index 0000000000..ae47327841 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor @@ -0,0 +1,27 @@ +@inherits AppComponentBase + +
+ +
+ + + + @title + + + + + + + + @if (componentType is not null) + { + + } + +
+
+
\ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor.cs new file mode 100644 index 0000000000..a9f8f6c42b --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor.cs @@ -0,0 +1,63 @@ +namespace Boilerplate.Client.Core.Components.Layout; + +public partial class Modal +{ + private bool isOpen; + private string? title; + private Type? componentType; + private bool disposed = false; + private Action? unsubscribeShow; + private Action? unsubscribeClose; + private IDictionary? componentParameters; + + private TaskCompletionSource? tcs; + + protected override Task OnInitAsync() + { + unsubscribeShow = PubSubService.Subscribe(ClientPubSubMessages.SHOW_MODAL, async args => + { + var data = (ModalData)args!; + + tcs = data.TaskCompletionSource; + + await InvokeAsync(() => + { + isOpen = true; + title = data.Title; + componentType = data.ComponentType; + componentParameters = data.Parameters; + + StateHasChanged(); + }); + }); + + unsubscribeClose = PubSubService.Subscribe(ClientPubSubMessages.CLOSE_MODAL, async _ => + { + await CloseModal(); + await InvokeAsync(StateHasChanged); + }); + + return base.OnInitAsync(); + } + + private async Task CloseModal() + { + isOpen = false; + tcs?.SetResult(); + } + + protected override async ValueTask DisposeAsync(bool disposing) + { + await base.DisposeAsync(disposing); + + if (disposed || disposing is false) return; + + tcs?.TrySetResult(); + tcs = null; + + unsubscribeShow?.Invoke(); + unsubscribeClose?.Invoke(); + + disposed = true; + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor.scss new file mode 100644 index 0000000000..d412824f7a --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Modal.razor.scss @@ -0,0 +1,26 @@ +@import '../../Styles/abstracts/_media-queries.scss'; + +section { + padding: 1rem; + min-width: 20rem; + max-height: var(--app-height); + + @include lt-md { + min-width: unset; + } +} + +::deep { + .root { + width: var(--app-width); + height: var(--app-height); + top: var(--app-inset-top); + left: var(--app-inset-left); + right: var(--app-inset-right); + bottom: var(--app-inset-bottom); + } + + .stack { + max-height: calc(var(--app-height) - 3rem); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor new file mode 100644 index 0000000000..1584c38964 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor @@ -0,0 +1,16 @@ +@inherits AppComponentBase + +
+ + + @Body + + + +
+ + + + @Localizer[AppStrings.Ok] + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor.cs new file mode 100644 index 0000000000..3e85e73657 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Boilerplate.Client.Core.Components.Layout; + +public partial class Prompt +{ + private string? value; + + [Parameter] public string? Body { get; set; } + [Parameter] public Action? OnOk { get; set; } + + private void OnOkClick() + { + OnOk?.Invoke(value); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor.scss new file mode 100644 index 0000000000..ac94c637e7 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Prompt.razor.scss @@ -0,0 +1,7 @@ +.body { + width: 100%; + flex-grow: 1; + display: flex; + overflow: auto; + white-space: pre; +} 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 7b0a8a96eb..0577dae99b 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 @@ -44,8 +44,8 @@ + - \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs index adc05da2fb..2d2a3d0f98 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IClientCoreServiceCollectionExtensions.cs @@ -39,7 +39,9 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle // Defining them as singletons would result in them being shared across all users in Blazor Server and during pre-rendering. // To address this, we use the AddSessioned extension method. // AddSessioned applies AddSingleton in BlazorHybrid and AddScoped in Blazor WebAssembly and Blazor Server, ensuring correct service lifetimes for each environment. + services.AddSessioned(); services.AddSessioned(); + services.AddSessioned(); services.AddSessioned(); services.AddSessioned(); services.AddSessioned(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs index 1fd6a767c5..3a7e05b86b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ClientPubSubMessages.cs @@ -6,7 +6,8 @@ namespace Boilerplate.Client.Core.Services; public static partial class ClientPubSubMessages { public const string SHOW_SNACK = nameof(SHOW_SNACK); - public const string SHOW_MESSAGE = nameof(SHOW_MESSAGE); + public const string SHOW_MODAL = nameof(SHOW_MODAL); + public const string CLOSE_MODAL = nameof(CLOSE_MODAL); public const string THEME_CHANGED = nameof(THEME_CHANGED); public const string OPEN_NAV_PANEL = nameof(OPEN_NAV_PANEL); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxData.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxData.cs deleted file mode 100644 index fb1af21ec2..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxData.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Boilerplate.Client.Core.Services; - -public partial class MessageBoxData(string message, string title, TaskCompletionSource taskCompletionSource) -{ - public string Message { get; set; } = message; - - public string Title { get; set; } = title; - - public TaskCompletionSource TaskCompletionSource { get; set; } = taskCompletionSource; -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxService.cs index 7f9add12eb..15ba511267 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/MessageBoxService.cs @@ -2,43 +2,21 @@ public partial class MessageBoxService { - private bool isRunning = false; - private readonly ConcurrentQueue queue = new(); - - - [AutoInject] private readonly PubSubService pubSubService = default!; - + [AutoInject] private ModalService modalService = default!; public Task Show(string message, string title = "") { TaskCompletionSource tcs = new(); - - queue.Enqueue(new(message, title, tcs)); - - if (isRunning is false) + Dictionary parameters = new() { - isRunning = true; - _ = ProcessQueue(); - } - - return tcs.Task; - } - - private async Task ProcessQueue() - { - if (queue.IsEmpty) - { - isRunning = false; - return; - } - - if (queue.TryDequeue(out var data)) + { nameof(MessageBox.Body), message }, + { nameof(MessageBox.OnOk), () => { tcs.SetResult(true); modalService.Close(); } } + }; + modalService.Show(parameters, title).ContinueWith(async task => { - pubSubService.Publish(ClientPubSubMessages.SHOW_MESSAGE, data, persistent: true); - - await data.TaskCompletionSource.Task; - } - - _ = ProcessQueue(); + await task; + tcs.SetResult(false); + }); + return tcs.Task; } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ModalData.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ModalData.cs new file mode 100644 index 0000000000..0c33cba671 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ModalData.cs @@ -0,0 +1,12 @@ +namespace Boilerplate.Client.Core.Services; + +public partial class ModalData(Type type, IDictionary? parameters, string? title, TaskCompletionSource taskCompletionSource) +{ + public Type ComponentType { get; set; } = type; + + public IDictionary? Parameters { get; set; } = parameters; + + public string? Title { get; set; } = title; + + public TaskCompletionSource TaskCompletionSource { get; set; } = taskCompletionSource; +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ModalService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ModalService.cs new file mode 100644 index 0000000000..695eae7950 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ModalService.cs @@ -0,0 +1,49 @@ +namespace Boilerplate.Client.Core.Services; + +public partial class ModalService +{ + private bool isRunning = false; + private readonly ConcurrentQueue queue = new(); + + + [AutoInject] private readonly PubSubService pubSubService = default!; + + + public void Close() + { + pubSubService.Publish(ClientPubSubMessages.CLOSE_MODAL); + } + + public Task Show(IDictionary? parameters = null, string title = "") + { + TaskCompletionSource tcs = new(); + + queue.Enqueue(new(typeof(T), parameters, title, tcs)); + + if (isRunning is false) + { + isRunning = true; + _ = ProcessQueue(); + } + + return tcs.Task; + } + + private async Task ProcessQueue() + { + if (queue.IsEmpty) + { + isRunning = false; + return; + } + + if (queue.TryDequeue(out var data)) + { + pubSubService.Publish(ClientPubSubMessages.SHOW_MODAL, data, persistent: true); + + await data.TaskCompletionSource.Task; + } + + _ = ProcessQueue(); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PromptService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PromptService.cs new file mode 100644 index 0000000000..b3237fc9ff --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PromptService.cs @@ -0,0 +1,22 @@ +namespace Boilerplate.Client.Core.Services; + +public partial class PromptService +{ + [AutoInject] private ModalService modalService = default!; + + public Task Show(string message, string title = "") + { + TaskCompletionSource tcs = new(); + Dictionary parameters = new() + { + { nameof(Prompt.Body), message }, + { nameof(Prompt.OnOk), (string value) => { tcs.SetResult(value); modalService.Close(); } } + }; + modalService.Show(parameters, title).ContinueWith(async task => + { + await task; + tcs.SetResult(null); + }); + return tcs.Task; + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json index b836243a4e..3b4ebe9f7d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json @@ -36,6 +36,12 @@ "minify": { "enabled": false }, "options": { "sourceMap": false } }, + { + "outputFile": "Components/Layout/Modal.razor.css", + "inputFile": "Components/Layout/Modal.razor.scss", + "minify": { "enabled": false }, + "options": { "sourceMap": false } + }, { "outputFile": "Components/Layout/NavBar.razor.css", "inputFile": "Components/Layout/NavBar.razor.scss", @@ -48,6 +54,12 @@ "minify": { "enabled": false }, "options": { "sourceMap": false } }, + { + "outputFile": "Components/Layout/Prompt.razor.css", + "inputFile": "Components/Layout/Prompt.razor.scss", + "minify": { "enabled": false }, + "options": { "sourceMap": false } + }, { "outputFile": "Components/Layout/RootContainer.razor.css", "inputFile": "Components/Layout/RootContainer.razor.scss",