Skip to content

Commit

Permalink
feat(blazorui): add BitModalService #9322 (#9325)
Browse files Browse the repository at this point in the history
  • Loading branch information
msynk authored Dec 1, 2024
1 parent 75a8d7a commit f25d0de
Show file tree
Hide file tree
Showing 27 changed files with 695 additions and 112 deletions.
11 changes: 5 additions & 6 deletions src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

<PropertyGroup>
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Bit.BlazorUI</RootNamespace>
<IsTrimmable>true</IsTrimmable>
<WarningLevel>0</WarningLevel>
<ImplicitUsings>enable</ImplicitUsings>
<ResolveStaticWebAssetsInputsDependsOn Condition="'$(TargetFramework)' == 'net9.0'">
BeforeBuildTasks;
$(ResolveStaticWebAssetsInputsDependsOn)
Expand Down Expand Up @@ -35,6 +33,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Bit.BlazorUI\Bit.BlazorUI.csproj" />
<ProjectReference Include="..\Bit.BlazorUI.SourceGenerators\Bit.BlazorUI.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

Expand All @@ -57,9 +56,9 @@
</ItemGroup>

<Target Name="BeforeBuildTasks" AfterTargets="CoreCompile" Condition="'$(TargetFramework)' == 'net9.0'">
<CallTarget Targets="InstallNodejsDependencies"/>
<CallTarget Targets="BuildJavaScript"/>
<CallTarget Targets="BuildCss"/>
<CallTarget Targets="InstallNodejsDependencies" />
<CallTarget Targets="BuildJavaScript" />
<CallTarget Targets="BuildCss" />
</Target>

<Target Name="InstallNodejsDependencies" Inputs="package.json" Outputs="node_modules\.package-lock.json">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@namespace Bit.BlazorUI

@foreach (var modalReference in _modalRefs)
{
<CascadingValue Value="modalReference">
<CascadingValue Value="BitModalParameters.Merge(modalReference.Parameters, ModalParameters)">
@modalReference.Modal
</CascadingValue>
</CascadingValue>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace Bit.BlazorUI;

public partial class BitModalContainer : IDisposable
{
private readonly List<BitModalReference> _modalRefs = [];



[Parameter] public BitModalParameters ModalParameters { get; set; } = new();



[Inject] private BitModalService _modalService { get; set; } = default!;



internal void InjectPersistentModals(ConcurrentQueue<BitModalReference> queue)
{
while (queue.TryDequeue(out var modalRef))
{
_modalRefs.Add(modalRef);
}
}



protected override void OnInitialized()
{
base.OnInitialized();

_modalService.InitContainer(this);

_modalService.OnAddModal += OnModalAdd;
_modalService.OnCloseModal += OnCloseModal;
}



private Task OnModalAdd(BitModalReference modalRef)
{
if (_modalRefs.Contains(modalRef)) return Task.CompletedTask;

_modalRefs.Add(modalRef);
return InvokeAsync(StateHasChanged);
}

private Task OnCloseModal(BitModalReference modalRef)
{
_modalRefs.Remove(modalRef);
return InvokeAsync(StateHasChanged);
}



public void Dispose()
{
_modalService.OnAddModal -= OnModalAdd;
_modalService.OnCloseModal -= OnCloseModal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace Bit.BlazorUI;

public class BitModalReference
{
private readonly BitModalService _modalService;



public string Id { get; init; }

public bool Persistent { get; private set; }

public object? Content { get; private set; }

public RenderFragment? Modal { get; private set; }

public BitModalParameters? Parameters { get; private set; }




public BitModalReference(BitModalService modalService, bool persistent)
{
Id = BitShortId.NewId();
_modalService = modalService;
Persistent = persistent;
}



public void SetContent(object content)
{
Content = content;
}

public void SetModal(RenderFragment modal)
{
Modal = modal;
}

public void SetParameters(BitModalParameters? parameters)
{
Parameters = parameters;
}

public void Close()
{
_modalService.Close(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;

namespace Bit.BlazorUI;

public class BitModalService
{
private BitModalContainer? _container;
private readonly ConcurrentQueue<BitModalReference> _persistentModalsQueue = new();



/// <summary>
/// The event for when a new modal gets added through calling the Show method.
/// </summary>
public event Func<BitModalReference, Task>? OnAddModal;

/// <summary>
/// The event for when a modal gets removed through calling the Close method.
/// </summary>
public event Func<BitModalReference, Task>? OnCloseModal;



/// <summary>
/// Initializes the current modal container that is responsible for rendering the modals.
/// </summary>
public void InitContainer(BitModalContainer container)
{
_container = container;
_container.InjectPersistentModals(_persistentModalsQueue);
}

/// <summary>
/// Closes an already opened modal using its reference.
/// </summary>
public async Task Close(BitModalReference modalRef)
{
var modalClose = OnCloseModal;
if (modalClose is not null)
{
await modalClose(modalRef);
}
}

/// <summary>
/// Shows a new persistent BitModal that will persist through the lifecycle of the application until it gets shown.
/// </summary>
public Task<BitModalReference> Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
bool persistent = false)
{
return Show<T>(null, null, persistent);
}

/// <summary>
/// Shows a new BitModal with a custom component with parameters as its content.
/// </summary>
public Task<BitModalReference> Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
Dictionary<string, object>? parameters, bool persistent = false)
{
return Show<T>(parameters, null, persistent);
}

/// <summary>
/// Shows a new BitModal with a custom component with parameters as its content.
/// </summary>
public Task<BitModalReference> Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
Dictionary<string, object> parameters)
{
return Show<T>(parameters, null, false);
}

/// <summary>
/// Shows a new BitModal with a custom component as its content with custom parameters for the modal.
/// </summary>
public Task<BitModalReference> Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
BitModalParameters modalParameters)
{
return Show<T>(null, modalParameters, false);
}

/// <summary>
/// Shows a new BitModal with a custom component as its content with custom parameters for the modal.
/// </summary>
public Task<BitModalReference> Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
BitModalParameters? modalParameters, bool persistent = false)
{
return Show<T>(null, modalParameters, persistent);
}

/// <summary>
/// Shows a new BitModal with a custom component as its content with custom parameters for the custom component and the modal.
/// </summary>
public async Task<BitModalReference> Show<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(
Dictionary<string, object>? parameters,
BitModalParameters? modalParameters,
bool persistent = false)
{
var componentType = typeof(T);

if (typeof(IComponent).IsAssignableFrom(componentType) is false)
{
throw new ArgumentException($"Type {componentType.Name} must be a Blazor component");
}

var modalReference = new BitModalReference(this, persistent);
modalReference.SetParameters(modalParameters);

var content = new RenderFragment(builder =>
{
var i = 0;
builder.OpenComponent(i++, componentType);

if (parameters is not null)
{
foreach (var parameter in parameters)
{
builder.AddAttribute(i++, parameter.Key, parameter.Value);
}
}

builder.AddComponentReferenceCapture(i, c => { modalReference.SetContent((T)c); });
builder.CloseComponent();
});

var modal = new RenderFragment(builder =>
{
builder.OpenComponent<BitModal>(0);
builder.SetKey(modalReference.Id);
builder.AddComponentParameter(1, nameof(BitModal.IsOpen), true);
builder.AddComponentParameter(2, nameof(BitModal.OnOverlayClick), EventCallback.Factory.Create<MouseEventArgs>(modalReference, () => modalReference.Close()));
builder.AddComponentParameter(3, nameof(BitModal.ChildContent), content);
builder.CloseComponent();
});
modalReference.SetModal(modal);

var modalAdd = OnAddModal;
if (modalAdd is not null)
{
await modalAdd(modalReference);
}

if (persistent && _container is null)
{
_persistentModalsQueue.Enqueue(modalReference);
}

return modalReference;
}
}
21 changes: 21 additions & 0 deletions src/BlazorUI/Bit.BlazorUI.Extras/IServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Bit.BlazorUI;

public static class IServiceCollectionExtensions
{
public static IServiceCollection AddBitBlazorUIExtrasServices(this IServiceCollection services, bool singleton = false)
{
if (singleton)
{
services.TryAddSingleton<BitModalService>();
}
else
{
services.TryAddScoped<BitModalService>();
}

return services;
}
}
Loading

0 comments on commit f25d0de

Please sign in to comment.