Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/Aspire.Dashboard/Components/Controls/AspireMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@
<FluentMenu Class="aspire-menu-container" @ref="_menu" Anchor="@Anchor" Anchored="@Anchored" Open="@Open" OpenChanged="OnOpenChanged" Style="@Style" VerticalThreshold="@VerticalThreshold" HorizontalThreshold="200">
@foreach (var item in Items)
{
@if (item.IsDivider)
@RenderMenuItem(item)
}
</FluentMenu>

@code {
private RenderFragment RenderMenuItem(MenuButtonItem item)
{
if (item.IsDivider)
{
<FluentDivider />
return @<FluentDivider />;
}
else
{
Expand All @@ -17,15 +24,30 @@
{ "title", !string.IsNullOrEmpty(item.Tooltip) ? item.Tooltip : item.Text ?? string.Empty }
};

<FluentMenuItem Id="@item.Id" Class="@item.Class" OnClick="() => HandleItemClicked(item)" Disabled="@item.IsDisabled" AdditionalAttributes="@additionalMenuItemAttributes">
@item.Text
return @<FluentMenuItem Id="@item.Id" Class="@item.Class" OnClick="() => HandleItemClicked(item)" Disabled="@item.IsDisabled" AdditionalAttributes="@additionalMenuItemAttributes" Label="@item.Text" MenuItems="@RenderNestedItems(item)">
@if (item.Icon != null)
{
<span slot="start">
<FluentIcon Value="@item.Icon" Style="vertical-align: text-bottom;" Width="16px" />
</span>
}
</FluentMenuItem>
</FluentMenuItem>;
}
}
</FluentMenu>

private RenderFragment? RenderNestedItems(MenuButtonItem item)
{
if (item.NestedMenuItems is null || item.NestedMenuItems.Count == 0)
{
return null;
}

// div is required because a single element has to be returned.
return @<div>
@foreach (var nestedItem in item.NestedMenuItems)
{
@RenderMenuItem(nestedItem)
}
</div>;
}
}
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/Model/MenuButtonItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Aspire.Dashboard.Model;
public class MenuButtonItem
{
public bool IsDivider { get; set; }
public List<MenuButtonItem>? NestedMenuItems { get; set; }
public string? Text { get; set; }
public string? Tooltip { get; set; }
public Icon? Icon { get; set; }
Expand Down
121 changes: 86 additions & 35 deletions src/Aspire.Dashboard/Model/ResourceMenuItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static class ResourceMenuItems
private static readonly Icon s_tracesIcon = new Icons.Regular.Size16.GanttChart();
private static readonly Icon s_metricsIcon = new Icons.Regular.Size16.ChartMultiple();
private static readonly Icon s_linkIcon = new Icons.Regular.Size16.Link();
private static readonly Icon s_toolboxIcon = new Icons.Regular.Size16.Toolbox();

public static void AddMenuItems(
List<MenuButtonItem> menuItems,
Expand Down Expand Up @@ -56,6 +57,48 @@ public static void AddMenuItems(
});
}

AddTelemetryMenuItems(menuItems, resource, navigationManager, telemetryRepository, getResourceName, loc);

AddCommandMenuItems(menuItems, resource, loc, commandsLoc, commandSelected, isCommandExecuting);

if (showUrls)
{
AddUrlMenuItems(menuItems, resource);
}
}

private static void AddUrlMenuItems(List<MenuButtonItem> menuItems, ResourceViewModel resource)
{
var urls = ResourceUrlHelpers.GetUrls(resource, includeInternalUrls: false, includeNonEndpointUrls: true)
.Where(u => !string.IsNullOrEmpty(u.Url))
.ToList();

if (urls.Count > 0)
{
menuItems.Add(new MenuButtonItem { IsDivider = true });
}

foreach (var url in urls)
{
// Opens the URL in a new window when clicked.
// It's important that this is done in the onclick event so the browser popup allows it.
menuItems.Add(new MenuButtonItem
{
Text = url.Text,
Tooltip = url.Url,
Icon = s_linkIcon,
AdditionalAttributes = new Dictionary<string, object>
{
["data-openbutton"] = "true",
["data-url"] = url.Url!,
["data-target"] = "_blank"
}
});
}
}

private static void AddTelemetryMenuItems(List<MenuButtonItem> menuItems, ResourceViewModel resource, NavigationManager navigationManager, TelemetryRepository telemetryRepository, Func<ResourceViewModel, string> getResourceName, IStringLocalizer<Resources.Resources> loc)
{
// Show telemetry menu items if there is telemetry for the resource.
var telemetryApplication = telemetryRepository.GetApplicationByCompositeName(resource.Name);
if (telemetryApplication != null)
Expand Down Expand Up @@ -104,58 +147,66 @@ public static void AddMenuItems(
});
}
}
}

private static void AddCommandMenuItems(List<MenuButtonItem> menuItems, ResourceViewModel resource, IStringLocalizer<Resources.Resources> loc, IStringLocalizer<Commands> commandsLoc, EventCallback<CommandViewModel> commandSelected, Func<ResourceViewModel, CommandViewModel, bool> isCommandExecuting)
{
var menuCommands = resource.Commands
.Where(c => c.State != CommandViewModelState.Hidden)
.OrderBy(c => !c.IsHighlighted)
.ToList();
.Where(c => c.State != CommandViewModelState.Hidden)
.ToList();
if (menuCommands.Count > 0)
{
var highlightedMenuCommands = menuCommands.Where(c => c.IsHighlighted).ToList();
var otherMenuCommands = menuCommands.Where(c => !c.IsHighlighted).ToList();

menuItems.Add(new MenuButtonItem { IsDivider = true });

foreach (var command in menuCommands)
// Always show the highlighted commands first and not in a sub-menu.
foreach (var highlightedCommand in highlightedMenuCommands)
{
menuItems.Add(CreateMenuItem(highlightedCommand));
}

// If there are more than 5 commands, we group them under a "Commands" menu item. This is done to avoid the menu going off the end of the screen.
// A scenario where this could happen is viewing the menu for a resource and the resource is in the middle of the screen.
if (highlightedMenuCommands.Count + otherMenuCommands.Count > 5)
{
var icon = (!string.IsNullOrEmpty(command.IconName) && IconResolver.ResolveIconName(command.IconName, IconSize.Size16, command.IconVariant) is { } i) ? i : null;
var commands = new List<MenuButtonItem>();

foreach (var command in otherMenuCommands)
{
commands.Add(CreateMenuItem(command));
}

menuItems.Add(new MenuButtonItem
{
Text = command.GetDisplayName(commandsLoc),
Tooltip = command.GetDisplayDescription(commandsLoc),
Icon = icon,
OnClick = () => commandSelected.InvokeAsync(command),
IsDisabled = command.State == CommandViewModelState.Disabled || isCommandExecuting(resource, command)
Text = loc[nameof(Resources.Resources.ResourcesCommands)],
Tooltip = "", // No tooltip for the commands menu item.
Icon = s_toolboxIcon,
NestedMenuItems = commands
});
}
else
{
foreach (var command in otherMenuCommands)
{
menuItems.Add(CreateMenuItem(command));
}
}
}

if (showUrls)
MenuButtonItem CreateMenuItem(CommandViewModel command)
{
var urls = ResourceUrlHelpers.GetUrls(resource, includeInternalUrls: false, includeNonEndpointUrls: true)
.Where(u => !string.IsNullOrEmpty(u.Url))
.ToList();

if (urls.Count > 0)
{
menuItems.Add(new MenuButtonItem { IsDivider = true });
}
var icon = (!string.IsNullOrEmpty(command.IconName) && IconResolver.ResolveIconName(command.IconName, IconSize.Size16, command.IconVariant) is { } i) ? i : null;

foreach (var url in urls)
return new MenuButtonItem
{
// Opens the URL in a new window when clicked.
// It's important that this is done in the onclick event so the browser popup allows it.
menuItems.Add(new MenuButtonItem
{
Text = url.Text,
Tooltip = url.Url,
Icon = s_linkIcon,
AdditionalAttributes = new Dictionary<string, object>
{
["data-openbutton"] = "true",
["data-url"] = url.Url!,
["data-target"] = "_blank"
}
});
}
Text = command.GetDisplayName(commandsLoc),
Tooltip = command.GetDisplayDescription(commandsLoc),
Icon = icon,
OnClick = () => commandSelected.InvokeAsync(command),
IsDisabled = command.State == CommandViewModelState.Disabled || isCommandExecuting(resource, command)
};
}
}
}
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Model/ResourceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,14 @@ public string GetDisplayName(IStringLocalizer<Commands> loc)
};
}

public string GetDisplayDescription(IStringLocalizer<Commands> loc)
public string? GetDisplayDescription(IStringLocalizer<Commands> loc)
{
return Name switch
{
KnownResourceCommands.StartCommand => loc[nameof(Commands.StartCommandDisplayDescription)],
KnownResourceCommands.StopCommand => loc[nameof(Commands.StopCommandDisplayDescription)],
KnownResourceCommands.RestartCommand => loc[nameof(Commands.RestartCommandDisplayDescription)],
_ => DisplayDescription
_ => DisplayDescription is { Length : > 0 } ? DisplayDescription : null
};
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/Aspire.Dashboard/Resources/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Resources/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,7 @@
<value>Showing &lt;strong&gt;{0} resources&lt;/strong&gt;</value>
<comment>{0} is a number. This is raw markup, so &lt;strong&gt; and &lt;/strong&gt; should not be modified</comment>
</data>
<data name="ResourcesCommands" xml:space="preserve">
<value>Commands</value>
</data>
</root>
5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading