| File: Model\DashboardDialogService.cs | Web Access |
| Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using Aspire.Dashboard.Resources; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Localization; using Microsoft.FluentUI.AspNetCore.Components; using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons; namespace Aspire.Dashboard.Model; /// <summary> /// A service for showing dialogs in the dashboard with automatic localization of common UI elements. /// </summary> public sealed class DashboardDialogService( IDialogService dialogService, IStringLocalizer<Dialogs> dialogsLoc, DimensionManager dimensionManager) { private string CloseButtonText => dialogsLoc[nameof(Dialogs.DialogCloseButtonText)]; /// <summary> /// Gets the current viewport information from the dimension manager. /// </summary> public ViewportInformation ViewportInformation => dimensionManager.ViewportInformation; /// <summary> /// Gets a value indicating whether the viewport is in desktop mode. /// </summary> public bool IsDesktop => dimensionManager.ViewportInformation.IsDesktop; /// <summary> /// Shows a dialog with the specified content and parameters. /// Automatically sets the dismiss title to the localized close button text if not specified. /// </summary> /// <typeparam name="TDialog">The type of dialog component to show.</typeparam> /// <param name="content">The content to pass to the dialog.</param> /// <param name="parameters">The dialog parameters.</param> /// <returns>A reference to the opened dialog.</returns> public Task<DashboardDialogReference> ShowDialogAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TDialog>(object content, DialogParameters parameters) where TDialog : ComponentBase { return ShowAsync<TDialog>(content, parameters, drawer: false); } /// <summary> /// Shows a dialog with the specified parameters. /// Automatically sets the dismiss title to the localized close button text if not specified. /// </summary> /// <typeparam name="TDialog">The type of dialog component to show.</typeparam> /// <param name="parameters">The dialog parameters.</param> /// <returns>A reference to the opened dialog.</returns> public Task<DashboardDialogReference> ShowDialogAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TDialog>(DialogParameters parameters) where TDialog : ComponentBase { return ShowAsync<TDialog>(content: null, parameters, drawer: false); } /// <summary> /// Shows a panel dialog with the specified content and parameters. /// Automatically sets the dismiss title to the localized close button text if not specified. /// </summary> /// <typeparam name="TDialog">The type of dialog component to show.</typeparam> /// <param name="content">The content to pass to the dialog.</param> /// <param name="parameters">The dialog parameters.</param> /// <returns>A reference to the opened dialog.</returns> public Task<DashboardDialogReference> ShowPanelAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TDialog>(object content, DialogParameters parameters) where TDialog : ComponentBase { return ShowAsync<TDialog>(content, parameters, drawer: true); } /// <summary> /// Shows a panel dialog with the specified parameters. /// Automatically sets the dismiss title to the localized close button text if not specified. /// </summary> /// <typeparam name="TDialog">The type of dialog component to show.</typeparam> /// <param name="parameters">The dialog parameters.</param> /// <returns>A reference to the opened dialog.</returns> public Task<DashboardDialogReference> ShowPanelAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TDialog>(DialogParameters parameters) where TDialog : ComponentBase { return ShowAsync<TDialog>(content: null, parameters, drawer: true); } /// <summary> /// Shows a confirmation dialog with the specified message. /// </summary> /// <param name="message">The confirmation message to display.</param> /// <returns>The dialog result.</returns> public async Task<DialogResult> ShowConfirmationAsync(string message) { return await dialogService.ShowConfirmationAsync(message).ConfigureAwait(false); } /// <summary> /// Creates a dialog callback for handling dialog results. /// </summary> /// <param name="receiver">The component that will receive the callback.</param> /// <param name="callback">The callback function to execute when the dialog closes.</param> /// <returns>An event callback for the dialog result.</returns> public EventCallback<DialogResult> CreateDialogCallback(object receiver, Func<DialogResult, Task> callback) { return EventCallback.Factory.Create(receiver, callback); } private async Task<DashboardDialogReference> ShowAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TDialog>(object? content, DialogParameters parameters, bool drawer) where TDialog : ComponentBase { var opened = new TaskCompletionSource<IDialogInstance>(TaskCreationOptions.RunContinuationsAsynchronously); var completion = new TaskCompletionSource<DialogResult>(TaskCreationOptions.RunContinuationsAsynchronously); var options = CreateOptions(content, parameters, opened, drawer); var reference = new DashboardDialogReference(parameters.Id, completion.Task); var resultTask = drawer ? dialogService.ShowDrawerAsync<TDialog>(options) : dialogService.ShowDialogAsync<TDialog>(options); _ = CompleteAsync(resultTask, parameters, reference, completion); var firstCompleted = await Task.WhenAny(opened.Task, resultTask).ConfigureAwait(false); if (firstCompleted == opened.Task) { reference.SetInstance(await opened.Task.ConfigureAwait(false)); } else { await resultTask.ConfigureAwait(false); } return reference; } private DialogOptions CreateOptions(object? content, DialogParameters parameters, TaskCompletionSource<IDialogInstance> opened, bool drawer) { var options = new DialogOptions { Id = parameters.Id, Width = parameters.Width, Height = parameters.Height, Data = parameters, // Fluent UI v5 uses "modal" for light-dismiss dialogs and "alert" for dialogs that // ignore overlay clicks. Drawers retain the conventional modal/non-modal behavior. Modal = drawer ? parameters.Modal : parameters.PreventDismissOnOverlayClick, Alignment = parameters.Alignment switch { HorizontalAlignment.Left => DialogAlignment.Start, HorizontalAlignment.Right => DialogAlignment.End, _ => DialogAlignment.Default }, OnStateChange = args => { if (args.Instance is { } instance && args.State is DialogState.Opening or DialogState.Open) { opened.TrySetResult(instance); } } }; if (content is not null) { options.Parameters["Content"] = content; } options.Header.Title = parameters.Title; options.Header.CloseAction.Visible = parameters.ShowDismiss; options.Header.CloseAction.Title = parameters.DismissTitle ?? CloseButtonText; options.Header.CloseAction.Tooltip = parameters.DismissTitle ?? CloseButtonText; options.Header.CloseAction.Icon = new Icons.Regular.Size20.Dismiss(); options.Header.CloseAction.Label = null; options.Footer.PrimaryAction.Label = parameters.PrimaryAction; options.Footer.PrimaryAction.Visible = !parameters.UseCustomFooter && !string.IsNullOrEmpty(parameters.PrimaryAction); options.Footer.PrimaryAction.Disabled = !parameters.PrimaryActionEnabled; options.Footer.PrimaryAction.OnClickAsync = instance => instance.CloseAsync(DialogResult.Ok()); options.Footer.SecondaryAction.Label = parameters.SecondaryAction; options.Footer.SecondaryAction.Visible = !parameters.UseCustomFooter && !string.IsNullOrEmpty(parameters.SecondaryAction); options.Footer.SecondaryAction.OnClickAsync = instance => instance.CloseAsync(DialogResult.Cancel(true)); return options; } private static async Task CompleteAsync( Task<DialogResult> resultTask, DialogParameters parameters, DashboardDialogReference reference, TaskCompletionSource<DialogResult> completion) { try { var result = await resultTask.ConfigureAwait(true); if (parameters.OnDialogClosing.HasDelegate) { var instance = await GetInstanceAsync(reference).ConfigureAwait(true); if (instance is not null) { await parameters.OnDialogClosing.InvokeAsync(instance).ConfigureAwait(true); } } if (parameters.OnDialogResult.HasDelegate) { await parameters.OnDialogResult.InvokeAsync(result).ConfigureAwait(true); } completion.TrySetResult(result); } catch (Exception ex) { completion.TrySetException(ex); } static Task<IDialogInstance?> GetInstanceAsync(DashboardDialogReference reference) { return Task.FromResult(reference.Instance); } } }