Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Components/Controls/PropertyGrid.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="@GridTemplateColumns">
GridTemplateColumns="@GridTemplateColumns"
ShowHover="true">
<TemplateColumn Title="@(NameColumnTitle ?? Loc[nameof(ControlsStrings.NameColumnHeader)])" Class="nameColumn" SortBy="@NameSort" Sortable="@IsNameSortable">
<GridValue Value="@NameColumnValue(context)" HighlightText="@HighlightText" />
</TemplateColumn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 1.5fr">
GridTemplateColumns="1fr 1.5fr"
ShowHover="true">
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
<GridValue Value="@(context.KnownProperty?.DisplayName ?? context.Key)" ToolTip="@context.Key" />
</TemplateColumn>
Expand All @@ -66,7 +67,8 @@
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 1.5fr">
GridTemplateColumns="1fr 1.5fr"
ShowHover="true">
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
<GridValue Value="@context.Name" />
</TemplateColumn>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
@{
var gridTemplateColumns = HasResourcesWithCommands ? "1fr 1.5fr 1.25fr 1.5fr 2.5fr 2.5fr 1fr 1fr 1fr" : "1fr 1.5fr 1.25fr 1.5fr 2.5fr 2.5fr 1fr 1fr";
}
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" Items="@FilteredResources" ResizableColumns="true" GridTemplateColumns="@gridTemplateColumns" RowClass="GetRowClass" Loading="_isLoading">
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" Items="@FilteredResources" ResizableColumns="true" GridTemplateColumns="@gridTemplateColumns" RowClass="GetRowClass" Loading="_isLoading" ShowHover="true">
<ChildContent>
<PropertyColumn Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesTypeColumnHeader)]" Property="@(c => c.ResourceType)" Sortable="true" Tooltip="true" TooltipText="@(c => c.ResourceType)"/>
<TemplateColumn Title="@ControlsStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Sortable="true" SortBy="@_nameSort" Tooltip="true" TooltipText="@(c => GetResourceName(c))" >
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
<Summary>
<div class="logs-summary-layout">
<div class="logs-grid-container continuous-scroll-overflow">
<FluentDataGrid Virtualize="true" RowClass="@GetRowClass" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="OtlpLogEntry" GridTemplateColumns="1fr 1fr 1fr 5fr 0.8fr 0.8fr">
<FluentDataGrid Virtualize="true" RowClass="@GetRowClass" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="OtlpLogEntry" GridTemplateColumns="1fr 1fr 1fr 5fr 0.8fr 0.8fr" ShowHover="true">
<ChildContent>
<TemplateColumn Title="@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsResourceColumnHeader)]" Tooltip="true" TooltipText="@(e => GetResourceName(e.Application))">
<span style="padding-left:5px; border-left-width: 5px; border-left-style: solid; border-left-color: @(ColorGenerator.Instance.GetColorHexByKey(GetResourceName(context.Application)));">
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/TraceDetail.razor
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
</div>
</DetailsTitleTemplate>
<Summary>
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" Class="trace-view-grid" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="SpanWaterfallViewModel" RowClass="@GetRowClass" GridTemplateColumns="4fr 12fr 85px">
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" Class="trace-view-grid" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="SpanWaterfallViewModel" RowClass="@GetRowClass" GridTemplateColumns="4fr 12fr 85px" ShowHover="true">
<TemplateColumn Title="Name">
<div class="col-long-content" title="@context.GetTooltip(_applications)" @onclick="() => OnShowPropertiesAsync(context, null)">
@{
Expand Down
6 changes: 1 addition & 5 deletions src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@
display: inline !important;
}

::deep .trace-view-grid fluent-data-grid-row[row-type="default"]:hover {
background-color: var(--neutral-fill-hover);
}

::deep .trace-view-grid fluent-data-grid-row[row-type="default"] {
::deep fluent-data-grid.trace-view-grid fluent-data-grid-row[row-type="default"]:hover {
cursor: pointer;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Traces.razor
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</ToolbarSection>
<MainSection>
<div class="datagrid-overflow-area continuous-scroll-overflow" tabindex="-1">
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="OtlpTrace" GridTemplateColumns="0.8fr 2fr 3fr 0.8fr 0.5fr">
<FluentDataGrid Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" ResizableColumns="true" ItemsProvider="@GetData" TGridItem="OtlpTrace" GridTemplateColumns="0.8fr 2fr 3fr 0.8fr 0.5fr" ShowHover="true">
<ChildContent>
<TemplateColumn Title="@ControlsStringsLoc[nameof(ControlsStrings.TimestampColumnHeader)]" TooltipText="@(context => FormatHelpers.FormatDateTime(TimeProvider, context.FirstSpan.StartTime, MillisecondsDisplay.Full, CultureInfo.CurrentCulture))" Tooltip="true">
@FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, context.FirstSpan.StartTime, MillisecondsDisplay.Truncated)
Expand Down
50 changes: 45 additions & 5 deletions src/Aspire.Dashboard/wwwroot/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ fluent-toolbar[orientation=horizontal] {
--messagebar-info-border-color: #d1d1d1;

--layout-toolbar-padding: calc(var(--design-unit) * 1.5px);

/*
--neutral-layer-2 is the background for the body of most of the site so
we can default the datagrid hover color to that and just override it as
necessary elsewhere (e.g. in the details view)
*/
--datagrid-hover-color: var(--neutral-layer-2-hover);
}

[data-theme="dark"] {
Expand All @@ -77,6 +84,17 @@ fluent-toolbar[orientation=horizontal] {
color-scheme: dark;
}

/*
Override the `cursor: pointer` in the default styles from fluentui-blazor since
we don't have selection implemented and the pointer doesn't really make sense.
The `fluent-data-grid-row[role='row']` part added to the beginning is a bit of
a hack to get the specificity higher than the default style to avoid using
`!important` just in case there's a reason to to override further later.
*/
fluent-data-grid-row[role='row'].hover:not([row-type='header'],[row-type='sticky-header']):hover {
cursor: auto;
}

[data-theme="light"] {
color-scheme: light;
}
Expand Down Expand Up @@ -338,6 +356,24 @@ fluent-switch.table-switch::part(label) {
width: 160px;
}

/*
If ShowHover is enabled on a FluentDataGrid, we need to tweak the way anchors and buttons
are colored when a row is hovered
*/
fluent-data-grid-row[row-type=default].hover:hover fluent-anchor[appearance=lightweight]::part(control):not(:hover),
fluent-data-grid-row[row-type=default].hover:hover fluent-anchor[appearance=stealth]::part(control):not(:hover),
fluent-data-grid-row[row-type=default].hover:hover fluent-button[appearance=lightweight]::part(control):not(:hover),
fluent-data-grid-row[row-type=default].hover:hover fluent-button[appearance=stealth]::part(control):not(:hover) {
background-color: var(--datagrid-hover-color);
}

fluent-data-grid-row[row-type=default].hover:hover fluent-anchor[appearance=lightweight]::part(control):hover,
fluent-data-grid-row[row-type=default].hover:hover fluent-anchor[appearance=stealth]::part(control):hover,
fluent-data-grid-row[row-type=default].hover:hover fluent-button[appearance=lightweight]::part(control):hover,
fluent-data-grid-row[row-type=default].hover:hover fluent-button[appearance=stealth]::part(control):hover {
background-color: var(--fill-color);
}

.property-grid-container {
grid-area: main;
overflow: auto;
Expand All @@ -352,8 +388,12 @@ fluent-switch.table-switch::part(label) {
--fill-color: var(--neutral-layer-1);
}

.property-grid-container fluent-accordion fluent-data-grid {
--datagrid-hover-color: var(--neutral-layer-1-hover);
}

/* Under the current light theme, --neutral-layer-1 is too white, so we'll
use something that's halfway between --neutral-layer-2 and --neutral-layer1.
use something that's halfway between --neutral-layer-2 and --neutral-layer-1.
*/
[data-theme="light"] .property-grid-container fluent-accordion {
--fill-color: var(--neutral-fill-rest);
Expand Down Expand Up @@ -385,13 +425,13 @@ fluent-switch.table-switch::part(label) {
border-bottom: none;
}

.property-grid-container fluent-accordion fluent-data-grid fluent-button[appearance=stealth]:not(:hover)::part(control),
.property-grid-container fluent-accordion fluent-data-grid fluent-button[appearance=lightweight]:not(:hover)::part(control) {
.property-grid-container fluent-accordion fluent-data-grid-row fluent-button[appearance=stealth]::part(control):not(:hover),
.property-grid-container fluent-accordion fluent-data-grid-row fluent-button[appearance=lightweight]::part(control):not(:hover) {
background-color: var(--fill-color);
}

.property-grid-container fluent-accordion fluent-data-grid fluent-button[appearance=stealth]:hover::part(control),
.property-grid-container fluent-accordion fluent-data-grid fluent-button[appearance=lightweight]:hover::part(control) {
.property-grid-container fluent-accordion fluent-data-grid-row:not([row-type=default]) fluent-button[appearance=stealth]::part(control):hover,
.property-grid-container fluent-accordion fluent-data-grid-row:not([row-type=default]) fluent-button[appearance=lightweight]::part(control):hover {
background-color: var(--neutral-fill-stealth-hover-on-neutral-fill-layer-rest);
}

Expand Down
92 changes: 91 additions & 1 deletion src/Aspire.Dashboard/wwwroot/js/app-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import {
baseLayerLuminance,
SwatchRGB,
fillColor,
neutralLayerL2
neutralLayerL2,
neutralPalette,
DesignToken,
neutralFillLayerRestDelta
} from "/_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.lib.module.js";

const currentThemeCookieName = "currentTheme";
Expand All @@ -12,6 +15,7 @@ const themeSettingDark = "Dark";
const themeSettingLight = "Light";
const darkThemeLuminance = 0.19;
const lightThemeLuminance = 1.0;
const darknessLuminanceTarget = (-0.1 + Math.sqrt(0.21)) / 2;

/**
* Updates the current theme on the site based on the specified theme
Expand Down Expand Up @@ -160,11 +164,97 @@ function applyTheme(theme) {
setThemeOnDocument(theme);
}

/**
*
* @param {Palette} palette
* @param {number} baseLayerLuminance
* @returns {number}
*/
function neutralLayer1Index(palette, baseLayerLuminance) {
return palette.closestIndexOf(SwatchRGB.create(baseLayerLuminance, baseLayerLuminance, baseLayerLuminance));
}

/**
*
* @param {Palette} palette
* @param {Swatch} reference
* @param {number} baseLayerLuminance
* @param {number} layerDelta
* @param {number} hoverDeltaLight
* @param {number} hoverDeltaDark
* @returns {Swatch}
*/
function neutralLayerHoverAlgorithm(palette, reference, baseLayerLuminance, layerDelta, hoverDeltaLight, hoverDeltaDark) {
const baseIndex = neutralLayer1Index(palette, baseLayerLuminance);
// Determine both the size of the delta (from the value passed in) and the direction (if the current color is dark,
// the hover color will be a lower index (lighter); if the current color is light, the hover color will be a higher index (darker))
const hoverDelta = isDark(reference) ? hoverDeltaDark * -1 : hoverDeltaLight;
return palette.get(baseIndex + (layerDelta * -1) + hoverDelta);
}

/**
*
* @param {Swatch} color
* @returns {boolean}
*/
function isDark(color) {
return color.relativeLuminance <= darknessLuminanceTarget;
}

/**
* Creates additional design tokens that are used to define the hover colors for the neutral layers
* used in the site theme (neutral-layer-1 and neutral-layer-2, specifically). Unlike other -hover
* variants, these are not created by the design system by default so we need to create them ourselves.
* This is a lightly tweaked variant of other hover recipes used in the design system.
*/
function createAdditionalDesignTokens() {
const neutralLayer1HoverLightDelta = DesignToken.create({ name: 'neutral-layer-1-hover-light-delta', cssCustomPropertyName: null }).withDefault(3);
const neutralLayer1HoverDarkDelta = DesignToken.create({ name: 'neutral-layer-1-hover-dark-delta', cssCustomPropertyName: null }).withDefault(2);
const neutralLayer2HoverLightDelta = DesignToken.create({ name: 'neutral-layer-2-hover-light-delta', cssCustomPropertyName: null }).withDefault(2);
const neutralLayer2HoverDarkDelta = DesignToken.create({ name: 'neutral-layer-2-hover-dark-delta', cssCustomPropertyName: null }).withDefault(2);

const neutralLayer1HoverRecipe = DesignToken.create({ name: 'neutral-layer-1-hover-recipe', cssCustomPropertyName: null }).withDefault({
evaluate: (element, reference) =>
neutralLayerHoverAlgorithm(
neutralPalette.getValueFor(element),
reference || fillColor.getValueFor(element),
baseLayerLuminance.getValueFor(element),
0, // No layer delta since this is for neutral-layer-1
neutralLayer1HoverLightDelta.getValueFor(element),
neutralLayer1HoverDarkDelta.getValueFor(element)
),
});

const neutralLayer2HoverRecipe = DesignToken.create({ name: 'neutral-layer-2-hover-recipe', cssCustomPropertyName: null }).withDefault({
evaluate: (element, reference) =>
neutralLayerHoverAlgorithm(
neutralPalette.getValueFor(element),
reference || fillColor.getValueFor(element),
baseLayerLuminance.getValueFor(element),
// Use the same layer delta used by the base recipe to calculate layer 2
neutralFillLayerRestDelta.getValueFor(element),
neutralLayer2HoverLightDelta.getValueFor(element),
neutralLayer2HoverDarkDelta.getValueFor(element)
),
});

// Creates the --neutral-layer-1-hover custom CSS property
DesignToken.create('neutral-layer-1-hover').withDefault((element) =>
neutralLayer1HoverRecipe.getValueFor(element).evaluate(element),
);

// Creates the --neutral-layer-2-hover custom CSS property
DesignToken.create('neutral-layer-2-hover').withDefault((element) =>
neutralLayer2HoverRecipe.getValueFor(element).evaluate(element),
);
}

function initializeTheme() {
const themeCookieValue = getThemeCookieValue();
const effectiveTheme = getEffectiveTheme(themeCookieValue);

applyTheme(effectiveTheme);
}

createAdditionalDesignTokens();
initializeTheme();
2 changes: 1 addition & 1 deletion tests/Aspire.Workload.Tests/WorkloadTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ protected static async Task<ResourceRow[]> CheckDashboardHasResourcesAsync(IPage
await Task.Delay(500);

// _testOutput.WriteLine($"Checking for rows again");
ILocator rowsLocator = dashboardPage.Locator("//fluent-data-grid-row[@class='resource-row']");
ILocator rowsLocator = dashboardPage.Locator("//fluent-data-grid-row[@class='hover resource-row']");
Copy link
Member

@JamesNK JamesNK Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't like that this requires the exact right classes be specified in the query. It's very brittle. I wonder if there is something like the browser's getElementsByClassName?

Unrelated to this change though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it seems a little brittle. Maybe just switching on the row type instead might be better (to skip header rows)? Even that seems a little brittle. But I didn't even realize this existed until you pointed it out, so I wasn't trying to delve into it too far yet, just trying to unblock.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hit by it last week. I vented here: #4834

I'll add class name brittleness to the list

var allRows = await rowsLocator.AllAsync();
// _testOutput.WriteLine($"found rows#: {allRows.Count}");
if (allRows.Count == 0)
Expand Down