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",