File: Components\Dialogs\HelpDialog.razor.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 Aspire.Dashboard.Components.CustomIcons;
using Aspire.Dashboard.Components.Layout;
using Aspire.Dashboard.Configuration;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Utils;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Microsoft.FluentUI.AspNetCore.Components;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
 
namespace Aspire.Dashboard.Components.Dialogs;
 
/// <summary>
/// The tabs of the help dialog.
/// </summary>
public enum HelpDialogTab
{
    Documentation,
    KeyboardShortcuts,
    AIAgents
}
 
/// <summary>
/// The dashboard's help: documentation links, keyboard shortcuts and how AI agents use the dashboard, each in a tab.
/// </summary>
public partial class HelpDialog
{
    private const string TabIdPrefix = "help-tab-";
    private const string AspireDocumentationUrl = "https://aspire.dev/";
    private const string ReportIssueUrl = "https://github.com/microsoft/aspire/issues/new/choose";
    // The invite that aspire.dev links from its community page.
    private const string DiscordUrl = "https://discord.com/invite/raNPcaaSj8";
 
    private HelpDialogTab _activeTab;
 
    /// <summary>
    /// The tab to open on.
    /// </summary>
    [Parameter]
    public HelpDialogTab Content { get; set; } = HelpDialogTab.KeyboardShortcuts;
 
    [Inject]
    public required IStringLocalizer<Resources.Dialogs> Loc { get; init; }
 
    [Inject]
    public required IStringLocalizer<Resources.Layout> LayoutLoc { get; init; }
 
    [Inject]
    public required IOptionsMonitor<DashboardOptions> Options { get; init; }
 
    [Inject]
    public required IDashboardClient DashboardClient { get; init; }
 
    [Inject]
    public required DashboardViewPreferences ViewPreferences { get; init; }
 
    [Inject]
    public required ISessionStorage SessionStorage { get; init; }
 
    [Inject]
    public required NavigationManager NavigationManager { get; init; }
 
    [CascadingParameter]
    public IDialogInstance? Dialog { get; set; }
 
    private bool IsAgentHelpEnabled => Options.CurrentValue.UI.DisableAgentHelp != true;
 
    // The game is played on the resource graph, which needs the resource service and can be disabled.
    private bool IsGameAvailable => DashboardClient.IsEnabled && ViewPreferences.IsGraphEnabled;
 
    protected override void OnInitialized()
    {
        // Agent help can be disabled, which removes its tab. Open on the documentation then, rather than on no tab.
        _activeTab = Content is HelpDialogTab.AIAgents && !IsAgentHelpEnabled ? HelpDialogTab.Documentation : Content;
    }
 
    private static string GetTabId(HelpDialogTab tab) => $"{TabIdPrefix}{tab}";
 
    private void OnTabChanged(FluentTab? tab)
    {
        if (tab?.Id is { } id
            && id.StartsWith(TabIdPrefix, StringComparison.Ordinal)
            && Enum.TryParse<HelpDialogTab>(id[TabIdPrefix.Length..], out var activeTab))
        {
            _activeTab = activeTab;
        }
    }
 
    private async Task OpenResourceGraphAsync()
    {
        await ViewPreferences.SetGroupOverviewAsync(GroupOverview.Graph);
 
        // Entering the resources view restores the last selection, which would show only those resources. Forget it
        // so the game starts with all of them, the same way clearing the selection in the resource list does.
        await SessionStorage.SetAsync(BrowserStorageKeys.LastSelectedResources, new List<string>());
 
        // Close first: the layout returns focus to the help button once the dialog is gone.
        if (Dialog is not null)
        {
            await Dialog.CloseAsync();
        }
 
        NavigationManager.NavigateTo(DashboardUrls.ResourceListUrl());
    }
 
    private List<HelpLinkSection> GetLinkSections() =>
    [
        new("learn", Loc[nameof(Resources.Dialogs.HelpDialogLearnHeading)],
        [
            new(LayoutLoc[nameof(Resources.Layout.MainLayoutAspireDashboardHelpLink)],
                Loc[nameof(Resources.Dialogs.HelpDialogDashboardDocumentationDescription)],
                MainLayout.DashboardDocumentationUrl,
                new Icons.Regular.Size24.Book()),
            new(Loc[nameof(Resources.Dialogs.HelpDialogAspireDocumentationTitle)],
                Loc[nameof(Resources.Dialogs.HelpDialogAspireDocumentationDescription)],
                AspireDocumentationUrl,
                new Icons.Regular.Size24.Globe())
        ]),
        new("community", Loc[nameof(Resources.Dialogs.HelpDialogCommunityHeading)],
        [
            new(Loc[nameof(Resources.Dialogs.HelpDialogDiscordTitle)],
                Loc[nameof(Resources.Dialogs.HelpDialogDiscordDescription)],
                DiscordUrl,
                new AspireIcons.Size24.Discord()),
            new(LayoutLoc[nameof(Resources.Layout.MainLayoutAspireRepoLink)],
                Loc[nameof(Resources.Dialogs.HelpDialogRepoDescription)],
                MainLayout.AspireRepoUrl,
                new AspireIcons.Size24.GitHub()),
            new(Loc[nameof(Resources.Dialogs.HelpDialogReportIssueTitle)],
                Loc[nameof(Resources.Dialogs.HelpDialogReportIssueDescription)],
                ReportIssueUrl,
                new Icons.Regular.Size24.Bug()),
            new(Loc[nameof(Resources.Dialogs.HelpDialogDashboardFeedbackTitle)],
                Loc[nameof(Resources.Dialogs.HelpDialogDashboardFeedbackDescription)],
                DashboardFeedbackDialog.DiscussionUrl,
                new Icons.Regular.Size24.ChatHelp()),
            .. IsGameAvailable
                ? new HelpLink[]
                {
                    new(Loc[nameof(Resources.Dialogs.HelpDialogGameTitle)],
                        Loc[nameof(Resources.Dialogs.HelpDialogGameDescription)],
                        DashboardUrls.GraphUrl(),
                        new Icons.Regular.Size24.Games(),
                        OpenResourceGraphAsync)
                }
                : []
        ])
    ];
 
    /// <summary>
    /// A link in the help dialog. It opens in a new tab, unless <paramref name="OnClick"/> handles it inside the dashboard.
    /// </summary>
    private sealed record HelpLink(string Title, string Description, string Url, Icon Icon, Func<Task>? OnClick = null);
 
    private sealed record HelpLinkSection(string Id, string Heading, List<HelpLink> Links);
}