From 74a661868b1684fa6133c147fa37c4afcbbdef38 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 7 Aug 2025 07:12:57 +0800 Subject: [PATCH 1/6] Switch grid display mode to fix scrolling --- .../Components/Pages/StructuredLogs.razor | 16 +++++------ .../Components/Pages/StructuredLogs.razor.cs | 27 ++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor index 044d9c6b9bd..955f4779318 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor @@ -127,27 +127,27 @@ ResizeColumnOnAllRows="false" ItemsProvider="@GetData" TGridItem="OtlpLogEntry" - GridTemplateColumns="@_manager.GetGridTemplateColumns()" ShowHover="true" ItemKey="@(r => r.InternalId)" OnRowClick="@(r => r.ExecuteOnDefault(d => OnShowPropertiesAsync(d, buttonId: null)))" - Class="main-grid enable-row-click"> + Class="main-grid enable-row-click" + DisplayMode="DataGridDisplayMode.Table"> - + @GetResourceName(context.ApplicationView) - + - + @FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, context.TimeStamp, MillisecondsDisplay.Truncated) - + - + @if (!string.IsNullOrEmpty(context.TraceId)) { @@ -159,7 +159,7 @@ } - + @{ var id = $"details-button-{context.InternalId}"; } diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs index 1d34d9453a3..7e70d7cf0cc 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs @@ -152,12 +152,12 @@ protected override void OnInitialized() (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ - new GridColumn(Name: ResourceColumn, DesktopWidth: "2fr", MobileWidth: "1fr"), - new GridColumn(Name: LogLevelColumn, DesktopWidth: "1fr"), - new GridColumn(Name: TimestampColumn, DesktopWidth: "1.5fr"), - new GridColumn(Name: MessageColumn, DesktopWidth: "5fr", "2.5fr"), - new GridColumn(Name: TraceColumn, DesktopWidth: "1fr"), - new GridColumn(Name: ActionsColumn, DesktopWidth: "1fr", MobileWidth: "0.8fr") + new GridColumn(Name: ResourceColumn, DesktopWidth: "20%", MobileWidth: "1fr"), + new GridColumn(Name: LogLevelColumn, DesktopWidth: "10%"), + new GridColumn(Name: TimestampColumn, DesktopWidth: "15%"), + new GridColumn(Name: MessageColumn, DesktopWidth: "50%", MobileWidth: "2.5fr"), + new GridColumn(Name: TraceColumn, DesktopWidth: "10%"), + new GridColumn(Name: ActionsColumn, DesktopWidth: "10%", MobileWidth: "0.8fr") ]; if (!string.IsNullOrEmpty(TraceId)) @@ -379,25 +379,26 @@ private List GetFilterMenuItems() protected override async Task OnAfterRenderAsync(bool firstRender) { + await Task.Yield(); if (_applicationChanged) { - await JS.InvokeVoidAsync("resetContinuousScrollPosition"); + //await JS.InvokeVoidAsync("resetContinuousScrollPosition"); _applicationChanged = false; } if (firstRender) { - await JS.InvokeVoidAsync("initializeContinuousScroll"); + //await JS.InvokeVoidAsync("initializeContinuousScroll"); DimensionManager.OnViewportInformationChanged += OnBrowserResize; } } private void OnBrowserResize(object? o, EventArgs args) { - InvokeAsync(async () => - { - await JS.InvokeVoidAsync("resetContinuousScrollPosition"); - await JS.InvokeVoidAsync("initializeContinuousScroll"); - }); + //InvokeAsync(async () => + //{ + // await JS.InvokeVoidAsync("resetContinuousScrollPosition"); + // await JS.InvokeVoidAsync("initializeContinuousScroll"); + //}); } private string? PauseText => PauseManager.AreStructuredLogsPaused(out var startTime) From d4fe032183ccae1e6778f3e5d1f54a40c13c5141 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 8 Aug 2025 13:21:39 +0800 Subject: [PATCH 2/6] More --- .../Controls/Grid/AspireTemplateColumn.cs | 5 ++ .../Components/Controls/GridValue.razor | 2 +- .../Components/Pages/Resources.razor | 4 +- .../Components/Pages/Resources.razor.cs | 16 ++--- .../Components/Pages/StructuredLogs.razor | 14 ++--- .../Components/Pages/StructuredLogs.razor.cs | 14 ++--- .../Components/Pages/TraceDetail.razor | 4 +- .../Components/Pages/TraceDetail.razor.cs | 8 +-- .../Components/Pages/Traces.razor | 4 +- .../Components/Pages/Traces.razor.cs | 12 ++-- .../Resize/GridColumnManager.razor.cs | 60 ++++++++++++------- src/Aspire.Dashboard/Model/GridColumn.cs | 18 +++++- 12 files changed, 98 insertions(+), 63 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Controls/Grid/AspireTemplateColumn.cs b/src/Aspire.Dashboard/Components/Controls/Grid/AspireTemplateColumn.cs index 95781857f0f..ec5adb9e82d 100644 --- a/src/Aspire.Dashboard/Components/Controls/Grid/AspireTemplateColumn.cs +++ b/src/Aspire.Dashboard/Components/Controls/Grid/AspireTemplateColumn.cs @@ -17,6 +17,11 @@ public class AspireTemplateColumn : TemplateColumn, IAspir protected override void OnInitialized() { Tooltip = true; + + if (ColumnManager is not null && ColumnId is not null) + { + Width = ColumnManager.GetColumnWidth(ColumnId); + } } protected override bool ShouldRender() diff --git a/src/Aspire.Dashboard/Components/Controls/GridValue.razor b/src/Aspire.Dashboard/Components/Controls/GridValue.razor index ebbaf904ed6..0663ed94b71 100644 --- a/src/Aspire.Dashboard/Components/Controls/GridValue.razor +++ b/src/Aspire.Dashboard/Components/Controls/GridValue.razor @@ -3,7 +3,7 @@ @inject IStringLocalizer Loc @inject IStringLocalizer DialogsLoc -
+
@* Value area *@ diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor b/src/Aspire.Dashboard/Components/Pages/Resources.razor index fa6c77155b6..758405398e7 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor @@ -131,14 +131,14 @@ ItemsProvider="@GetData" ResizableColumns="true" ResizeColumnOnAllRows="false" - GridTemplateColumns="@_manager.GetGridTemplateColumns()" RowClass="@(r => GetRowClass(r.Resource))" Loading="!_loadingTcs.Task.IsCompleted" ShowHover="true" TGridItem="ResourceGridViewModel" ItemKey="@(r => r.Resource.Name)" OnRowClick="@(r => r.ExecuteOnDefault(d => ShowResourceDetailsAsync(d.Resource, buttonId: null)))" - Class="main-grid enable-row-click"> + Class="main-grid enable-row-click" + DisplayMode="DataGridDisplayMode.Table"> @{ diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs index 5b3b0f24626..87b9e143a93 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs @@ -34,7 +34,7 @@ public partial class Resources : ComponentBase, IComponentWithTelemetry, IAsyncD private const string ActionsColumn = nameof(ActionsColumn); private Subscription? _logsSubscription; - private IList? _gridColumns; + private List? _gridColumns; private EventCallback _onToggleCollapseAllCallback; private EventCallback _onToggleResourceTypeCallback; private bool _hideResourceGraph; @@ -175,13 +175,13 @@ protected override async Task OnInitializedAsync() (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ - new GridColumn(Name: NameColumn, DesktopWidth: "1.5fr", MobileWidth: "1.5fr"), - new GridColumn(Name: StateColumn, DesktopWidth: "1.25fr", MobileWidth: "1.25fr"), - new GridColumn(Name: StartTimeColumn, DesktopWidth: "1fr"), - new GridColumn(Name: TypeColumn, DesktopWidth: "1fr", IsVisible: () => _showResourceTypeColumn), - new GridColumn(Name: SourceColumn, DesktopWidth: "2.25fr"), - new GridColumn(Name: UrlsColumn, DesktopWidth: "2.25fr", MobileWidth: "2fr"), - new GridColumn(Name: ActionsColumn, DesktopWidth: "minmax(150px, 1.5fr)", MobileWidth: "1fr") + new GridColumn(Name: NameColumn, DesktopWidth: Width.Fraction(1.5m), MobileWidth: Width.Fraction(1.5m)), + new GridColumn(Name: StateColumn, DesktopWidth: Width.Fraction(1.25m), MobileWidth: Width.Fraction(1.25m)), + new GridColumn(Name: StartTimeColumn, DesktopWidth: Width.Fraction(1)), + new GridColumn(Name: TypeColumn, DesktopWidth: Width.Fraction(1), IsVisible: () => _showResourceTypeColumn), + new GridColumn(Name: SourceColumn, DesktopWidth: Width.Fraction(2.25m)), + new GridColumn(Name: UrlsColumn, DesktopWidth: Width.Fraction(2.25m), MobileWidth: Width.Fraction(2)), + new GridColumn(Name: ActionsColumn, DesktopWidth: Width.Fraction(1.5m), MobileWidth: Width.Fraction(1)) ]; _onToggleCollapseAllCallback = EventCallback.Factory.Create(this, OnToggleCollapseAll); diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor index 955f4779318..5e93668935e 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor @@ -122,7 +122,7 @@ RowClass="@GetRowClass" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" - OverscanCount="100" + OverscanCount="20" ResizableColumns="true" ResizeColumnOnAllRows="false" ItemsProvider="@GetData" @@ -133,21 +133,21 @@ Class="main-grid enable-row-click" DisplayMode="DataGridDisplayMode.Table"> - + @GetResourceName(context.ApplicationView) - + - + @FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, context.TimeStamp, MillisecondsDisplay.Truncated) - + - + @if (!string.IsNullOrEmpty(context.TraceId)) { @@ -159,7 +159,7 @@ } - + @{ var id = $"details-button-{context.InternalId}"; } diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs index 7e70d7cf0cc..10b3c96c306 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs @@ -44,7 +44,7 @@ public partial class StructuredLogs : IComponentWithTelemetry, IPageWithSessionA private string _filter = string.Empty; private FluentDataGrid _dataGrid = null!; private GridColumnManager _manager = null!; - private IList _gridColumns = null!; + private List _gridColumns = null!; private ColumnResizeLabels _resizeLabels = ColumnResizeLabels.Default; private ColumnSortLabels _sortLabels = ColumnSortLabels.Default; @@ -152,12 +152,12 @@ protected override void OnInitialized() (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ - new GridColumn(Name: ResourceColumn, DesktopWidth: "20%", MobileWidth: "1fr"), - new GridColumn(Name: LogLevelColumn, DesktopWidth: "10%"), - new GridColumn(Name: TimestampColumn, DesktopWidth: "15%"), - new GridColumn(Name: MessageColumn, DesktopWidth: "50%", MobileWidth: "2.5fr"), - new GridColumn(Name: TraceColumn, DesktopWidth: "10%"), - new GridColumn(Name: ActionsColumn, DesktopWidth: "10%", MobileWidth: "0.8fr") + new GridColumn(Name: ResourceColumn, DesktopWidth: Width.Fraction(2), MobileWidth: Width.Fraction(1)), + new GridColumn(Name: LogLevelColumn, DesktopWidth: Width.Fraction(1)), + new GridColumn(Name: TimestampColumn, DesktopWidth: Width.Fraction(1.5m)), + new GridColumn(Name: MessageColumn, DesktopWidth: Width.Fraction(5), MobileWidth: Width.Fraction(2.5m)), + new GridColumn(Name: TraceColumn, DesktopWidth: Width.Fraction(1)), + new GridColumn(Name: ActionsColumn, DesktopWidth: Width.Fraction(1), MobileWidth: Width.Fraction(0.8m)) ]; if (!string.IsNullOrEmpty(TraceId)) diff --git a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor index 524bbe2ca13..34527a899d0 100644 --- a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor +++ b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor @@ -107,12 +107,12 @@ ItemsProvider="@GetData" TGridItem="SpanWaterfallViewModel" RowClass="@GetRowClass" - GridTemplateColumns="@_manager.GetGridTemplateColumns()" RowSize="DataGridRowSize.Small" OverscanCount="100" ShowHover="true" ItemKey="@(r => r.Span.SpanId)" - OnRowClick="@(r => r.ExecuteOnDefault(d => OnShowPropertiesAsync(d, buttonId: null)))"> + OnRowClick="@(r => r.ExecuteOnDefault(d => OnShowPropertiesAsync(d, buttonId: null)))" + DisplayMode="DataGridDisplayMode.Table"> @{ var isServerOrConsumer = context.Span.Kind == OtlpSpanKind.Server || context.Span.Kind == OtlpSpanKind.Consumer; diff --git a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs index f78674e2e9f..524c801e513 100644 --- a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs @@ -35,7 +35,7 @@ public partial class TraceDetail : ComponentBase, IComponentWithTelemetry, IDisp private string? _elementIdBeforeDetailsViewOpened; private FluentDataGrid _dataGrid = null!; private GridColumnManager _manager = null!; - private IList _gridColumns = null!; + private List _gridColumns = null!; private string _filter = string.Empty; [Parameter] @@ -83,9 +83,9 @@ protected override void OnInitialized() TelemetryContextProvider.Initialize(TelemetryContext); _gridColumns = [ - new GridColumn(Name: NameColumn, DesktopWidth: "6fr", MobileWidth: "6fr"), - new GridColumn(Name: TicksColumn, DesktopWidth: "12fr", MobileWidth: "12fr"), - new GridColumn(Name: ActionsColumn, DesktopWidth: "100px", MobileWidth: null) + new GridColumn(Name: NameColumn, DesktopWidth: Width.Fraction(6), MobileWidth: Width.Fraction(6)), + new GridColumn(Name: TicksColumn, DesktopWidth: Width.Fraction(12), MobileWidth: Width.Fraction(12)), + new GridColumn(Name: ActionsColumn, DesktopWidth: Width.Pixels(120), MobileWidth: null) ]; foreach (var resolver in OutgoingPeerResolvers) diff --git a/src/Aspire.Dashboard/Components/Pages/Traces.razor b/src/Aspire.Dashboard/Components/Pages/Traces.razor index f86ae4a4da3..250839fb522 100644 --- a/src/Aspire.Dashboard/Components/Pages/Traces.razor +++ b/src/Aspire.Dashboard/Components/Pages/Traces.razor @@ -97,11 +97,11 @@ ResizeColumnOnAllRows="false" ItemsProvider="@GetData" TGridItem="OtlpTrace" - GridTemplateColumns="@_manager.GetGridTemplateColumns()" ShowHover="true" ItemKey="@(r => r.TraceId)" OnRowClick="@(r => r.ExecuteOnDefault(d => NavigationManager.NavigateTo(DashboardUrls.TraceDetailUrl(d.TraceId))))" - Class="main-grid enable-row-click"> + Class="main-grid enable-row-click" + DisplayMode="DataGridDisplayMode.Table"> @FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, context.FirstSpan.StartTime, MillisecondsDisplay.Truncated) diff --git a/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs b/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs index 9e3d1936d91..40406deb1b1 100644 --- a/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Traces.razor.cs @@ -27,7 +27,7 @@ public partial class Traces : IComponentWithTelemetry, IPageWithSessionAndUrlSta private const string SpansColumn = nameof(SpansColumn); private const string DurationColumn = nameof(DurationColumn); private const string ActionsColumn = nameof(ActionsColumn); - private IList _gridColumns = null!; + private List _gridColumns = null!; private SelectViewModel _allApplication = null!; private TotalItemsFooter _totalItemsFooter = default!; @@ -154,11 +154,11 @@ protected override void OnInitialized() (_resizeLabels, _sortLabels) = DashboardUIHelpers.CreateGridLabels(ControlsStringsLoc); _gridColumns = [ - new GridColumn(Name: TimestampColumn, DesktopWidth: "0.8fr", MobileWidth: "0.8fr"), - new GridColumn(Name: NameColumn, DesktopWidth: "2fr", MobileWidth: "2fr"), - new GridColumn(Name: SpansColumn, DesktopWidth: "3fr"), - new GridColumn(Name: DurationColumn, DesktopWidth: "0.8fr"), - new GridColumn(Name: ActionsColumn, DesktopWidth: "0.5fr", MobileWidth: "1fr") + new GridColumn(Name: TimestampColumn, DesktopWidth: Width.Fraction(0.8m), MobileWidth: Width.Fraction(0.8m)), + new GridColumn(Name: NameColumn, DesktopWidth: Width.Fraction(2), MobileWidth: Width.Fraction(2)), + new GridColumn(Name: SpansColumn, DesktopWidth: Width.Fraction(3)), + new GridColumn(Name: DurationColumn, DesktopWidth: Width.Fraction(0.8m)), + new GridColumn(Name: ActionsColumn, DesktopWidth: Width.Fraction(0.5m), MobileWidth: Width.Fraction(1)) ]; _allApplication = new SelectViewModel { Id = null, Name = ControlsStringsLoc[name: nameof(ControlsStrings.LabelAll)] }; diff --git a/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs b/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs index 6a5a1247432..88180c18664 100644 --- a/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs +++ b/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Text; using Aspire.Dashboard.Model; using Microsoft.AspNetCore.Components; @@ -17,7 +16,7 @@ public partial class GridColumnManager : ComponentBase, IDisposable public required DimensionManager DimensionManager { get; init; } [Parameter] - public required IList Columns { get; set; } + public required List Columns { get; set; } [Parameter] public RenderFragment? ChildContent { get; set; } @@ -27,7 +26,34 @@ public partial class GridColumnManager : ComponentBase, IDisposable protected override void OnInitialized() { DimensionManager.OnViewportSizeChanged += OnViewportSizeChanged; + } + + protected override void OnParametersSet() + { + var desktopTotal = Columns.Where(c => c.DesktopWidth is { Unit: WidthUnit.Fraction }).Sum(c => c.DesktopWidth!.Value.Value); + var mobileTotal = Columns.Where(c => c.MobileWidth is { Unit: WidthUnit.Fraction }).Sum(c => c.MobileWidth!.Value.Value); + foreach (var item in Columns) + { + item.ResolvedDesktopWidth = ResolveWidth(item.DesktopWidth, desktopTotal); + item.ResolvedMobileWidth = ResolveWidth(item.MobileWidth, mobileTotal); + } + _columnById = Columns.ToDictionary(c => c.Name, StringComparers.GridColumn); + + static string? ResolveWidth(Width? width, decimal fractionTotal) + { + if (width is not { } w) + { + return null; + } + + return w.Unit switch + { + WidthUnit.Fraction => $"{Math.Round(w.Value / fractionTotal * 100, 1)}%", + WidthUnit.Pixels => $"{w.Value}px", + _ => throw new NotSupportedException($"Unsupported width unit: {w.Unit}") + }; + } } private void OnViewportSizeChanged(object sender, ViewportSizeChangedEventArgs e) @@ -72,33 +98,21 @@ public bool IsColumnVisible(string columnName) && column.IsVisible?.Invoke() is null or true; // Is visible. } - /// - /// Gets a string that can be used as the value for the grid-template-columns CSS property. - /// For example, 1fr 2fr 1fr. - /// - /// - public string GetGridTemplateColumns() + public string? GetColumnWidth(string columnName) { - var sb = new StringBuilder(); - - foreach (var (_, column) in _columnById) + if (!_columnById.TryGetValue(columnName, out var column)) // Is a known column. { - if (column.IsVisible?.Invoke() is null or true && - GetColumnWidth(column) is string width) - { - if (sb.Length > 0) - { - sb.Append(' '); - } - - sb.Append(width); - } + return null; } - return sb.ToString(); + var viewportInformation = _gridViewportInformation ?? DimensionManager.ViewportInformation; + + return viewportInformation.IsDesktop + ? column.ResolvedDesktopWidth + : column.ResolvedMobileWidth; } - private string? GetColumnWidth(GridColumn column) + private Width? GetColumnWidth(GridColumn column) { var viewportInformation = _gridViewportInformation ?? DimensionManager.ViewportInformation; diff --git a/src/Aspire.Dashboard/Model/GridColumn.cs b/src/Aspire.Dashboard/Model/GridColumn.cs index 76126685fc4..b962dc2210a 100644 --- a/src/Aspire.Dashboard/Model/GridColumn.cs +++ b/src/Aspire.Dashboard/Model/GridColumn.cs @@ -3,4 +3,20 @@ namespace Aspire.Dashboard.Model; -public record GridColumn(string Name, string? DesktopWidth, string? MobileWidth = null, Func? IsVisible = null); +public record GridColumn(string Name, Width? DesktopWidth, Width? MobileWidth = null, Func? IsVisible = null) +{ + public string? ResolvedDesktopWidth { get; set; } + public string? ResolvedMobileWidth { get; set; } +} + +public enum WidthUnit +{ + Pixels, + Fraction +} + +public record struct Width(decimal Value, WidthUnit Unit) +{ + public static Width Pixels(decimal value) => new(value, WidthUnit.Pixels); + public static Width Fraction(decimal value) => new(value, WidthUnit.Fraction); +} From 941c84f1f08dbf59c1d978416758b875efe928b0 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 8 Aug 2025 13:51:32 +0800 Subject: [PATCH 3/6] More --- .../Components/Pages/Resources.razor | 2 +- .../Components/Pages/StructuredLogs.razor | 2 +- .../Components/Pages/StructuredLogs.razor.cs | 14 +++++++------- .../Components/Pages/TraceDetail.razor | 2 +- .../Components/Pages/TraceDetail.razor.cs | 2 +- src/Aspire.Dashboard/Components/Pages/Traces.razor | 2 +- src/Aspire.Dashboard/Utils/DashboardUIHelpers.cs | 2 ++ 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor b/src/Aspire.Dashboard/Components/Pages/Resources.razor index 758405398e7..4875352de47 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor @@ -127,7 +127,7 @@ Virtualize="true" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" - OverscanCount="100" + OverscanCount="@DashboardUIHelpers.DefaultDataGridOverscanCount" ItemsProvider="@GetData" ResizableColumns="true" ResizeColumnOnAllRows="false" diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor index 5e93668935e..c1303f4f56f 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor @@ -122,7 +122,7 @@ RowClass="@GetRowClass" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" - OverscanCount="20" + OverscanCount="@DashboardUIHelpers.DefaultDataGridOverscanCount" ResizableColumns="true" ResizeColumnOnAllRows="false" ItemsProvider="@GetData" diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs index 10b3c96c306..8d6d551a2ec 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs @@ -382,23 +382,23 @@ protected override async Task OnAfterRenderAsync(bool firstRender) await Task.Yield(); if (_applicationChanged) { - //await JS.InvokeVoidAsync("resetContinuousScrollPosition"); + await JS.InvokeVoidAsync("resetContinuousScrollPosition"); _applicationChanged = false; } if (firstRender) { - //await JS.InvokeVoidAsync("initializeContinuousScroll"); + await JS.InvokeVoidAsync("initializeContinuousScroll"); DimensionManager.OnViewportInformationChanged += OnBrowserResize; } } private void OnBrowserResize(object? o, EventArgs args) { - //InvokeAsync(async () => - //{ - // await JS.InvokeVoidAsync("resetContinuousScrollPosition"); - // await JS.InvokeVoidAsync("initializeContinuousScroll"); - //}); + InvokeAsync(async () => + { + await JS.InvokeVoidAsync("resetContinuousScrollPosition"); + await JS.InvokeVoidAsync("initializeContinuousScroll"); + }); } private string? PauseText => PauseManager.AreStructuredLogsPaused(out var startTime) diff --git a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor index 34527a899d0..37e34567abd 100644 --- a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor +++ b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor @@ -108,7 +108,7 @@ TGridItem="SpanWaterfallViewModel" RowClass="@GetRowClass" RowSize="DataGridRowSize.Small" - OverscanCount="100" + OverscanCount="@DashboardUIHelpers.DefaultDataGridOverscanCount" ShowHover="true" ItemKey="@(r => r.Span.SpanId)" OnRowClick="@(r => r.ExecuteOnDefault(d => OnShowPropertiesAsync(d, buttonId: null)))" diff --git a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs index 524c801e513..eeca09c4c0f 100644 --- a/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs @@ -85,7 +85,7 @@ protected override void OnInitialized() _gridColumns = [ new GridColumn(Name: NameColumn, DesktopWidth: Width.Fraction(6), MobileWidth: Width.Fraction(6)), new GridColumn(Name: TicksColumn, DesktopWidth: Width.Fraction(12), MobileWidth: Width.Fraction(12)), - new GridColumn(Name: ActionsColumn, DesktopWidth: Width.Pixels(120), MobileWidth: null) + new GridColumn(Name: ActionsColumn, DesktopWidth: Width.Pixels(100), MobileWidth: null) ]; foreach (var resolver in OutgoingPeerResolvers) diff --git a/src/Aspire.Dashboard/Components/Pages/Traces.razor b/src/Aspire.Dashboard/Components/Pages/Traces.razor index 250839fb522..dee92d63375 100644 --- a/src/Aspire.Dashboard/Components/Pages/Traces.razor +++ b/src/Aspire.Dashboard/Components/Pages/Traces.razor @@ -92,7 +92,7 @@ RowClass="@GetRowClass" GenerateHeader="GenerateHeaderOption.Sticky" ItemSize="46" - OverscanCount="100" + OverscanCount="@DashboardUIHelpers.DefaultDataGridOverscanCount" ResizableColumns="true" ResizeColumnOnAllRows="false" ItemsProvider="@GetData" diff --git a/src/Aspire.Dashboard/Utils/DashboardUIHelpers.cs b/src/Aspire.Dashboard/Utils/DashboardUIHelpers.cs index bcd533ce063..b6b862077ab 100644 --- a/src/Aspire.Dashboard/Utils/DashboardUIHelpers.cs +++ b/src/Aspire.Dashboard/Utils/DashboardUIHelpers.cs @@ -14,6 +14,8 @@ internal static class DashboardUIHelpers { public const string MessageBarSection = "MessagesTop"; + public const int DefaultDataGridOverscanCount = 20; + // The initial data fetch for a FluentDataGrid doesn't include a count of items to return. // The data grid doesn't specify a count because it doesn't know how many items fit in the UI. // Once it knows the height of items and the height of the grid then it specifies the desired item count From dd61e2d34cd13b484e9fefee8bc111c5ebc7e5df Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 8 Aug 2025 14:13:00 +0800 Subject: [PATCH 4/6] Update --- src/Aspire.Dashboard/Components/Controls/GridValue.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Aspire.Dashboard/Components/Controls/GridValue.razor b/src/Aspire.Dashboard/Components/Controls/GridValue.razor index 0663ed94b71..fda87aa4714 100644 --- a/src/Aspire.Dashboard/Components/Controls/GridValue.razor +++ b/src/Aspire.Dashboard/Components/Controls/GridValue.razor @@ -3,7 +3,7 @@ @inject IStringLocalizer Loc @inject IStringLocalizer DialogsLoc -
+
@* Value area *@ From 01364db13193f49665a58c9fd3924d5cbf165c96 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 8 Aug 2025 14:13:52 +0800 Subject: [PATCH 5/6] Update --- src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs index 8d6d551a2ec..7f4c0258607 100644 --- a/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor.cs @@ -379,7 +379,6 @@ private List GetFilterMenuItems() protected override async Task OnAfterRenderAsync(bool firstRender) { - await Task.Yield(); if (_applicationChanged) { await JS.InvokeVoidAsync("resetContinuousScrollPosition"); From fd5834c466f819ccef508e5d7f1bf9107c4df987 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 8 Aug 2025 16:18:23 +0800 Subject: [PATCH 6/6] More updates --- .../Resize/GridColumnManager.razor.cs | 93 ++++++++++++------- src/Aspire.Dashboard/Model/GridColumn.cs | 7 +- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs b/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs index 88180c18664..fc1a375f025 100644 --- a/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs +++ b/src/Aspire.Dashboard/Components/Resize/GridColumnManager.razor.cs @@ -8,7 +8,8 @@ namespace Aspire.Dashboard.Components.Resize; public partial class GridColumnManager : ComponentBase, IDisposable { - private Dictionary _columnById = null!; + private Dictionary _columnDesktopById = null!; + private Dictionary _columnMobileById = null!; private float _availableFraction = 1; private ViewportInformation? _gridViewportInformation; @@ -30,29 +31,55 @@ protected override void OnInitialized() protected override void OnParametersSet() { - var desktopTotal = Columns.Where(c => c.DesktopWidth is { Unit: WidthUnit.Fraction }).Sum(c => c.DesktopWidth!.Value.Value); - var mobileTotal = Columns.Where(c => c.MobileWidth is { Unit: WidthUnit.Fraction }).Sum(c => c.MobileWidth!.Value.Value); - foreach (var item in Columns) + _columnDesktopById = Columns.Where(c => c.DesktopWidth is not null) + .Select(c => new GridColumnView(c.Name, c.DesktopWidth!.Value, c.IsVisible)) + .ToDictionary(c => c.Name, StringComparers.GridColumn); + _columnMobileById = Columns.Where(c => c.MobileWidth is not null) + .Select(c => new GridColumnView(c.Name, c.MobileWidth!.Value, c.IsVisible)) + .ToDictionary(c => c.Name, StringComparers.GridColumn); + + if (ViewportInformation.IsDesktop) { - item.ResolvedDesktopWidth = ResolveWidth(item.DesktopWidth, desktopTotal); - item.ResolvedMobileWidth = ResolveWidth(item.MobileWidth, mobileTotal); + SetWidths(_columnDesktopById); } + else + { + SetWidths(_columnMobileById); + } + } - _columnById = Columns.ToDictionary(c => c.Name, StringComparers.GridColumn); - - static string? ResolveWidth(Width? width, decimal fractionTotal) + private static void SetWidths(Dictionary columnById) + { + var visibleColumns = columnById.Values.Where(c => c.IsVisible?.Invoke() is null or true).ToList(); + var lastPercentageColumn = columnById.Values.Where(c => c.IsVisible?.Invoke() is null or true).LastOrDefault(); + var fractionTotal = visibleColumns.Where(c => c.Width is { Unit: WidthUnit.Fraction }).Sum(c => c.Width.Value); + + // We want percentages to add up to exactly 100% on the browser. This can be a problem with rounding. + // The fix is to use the remaining percentage value for the value percentage column. + var remainingPercentage = 100m; + for (var i = 0; i < visibleColumns.Count; i++) { - if (width is not { } w) + var column = visibleColumns[i]; + + if (column.Width.Unit == WidthUnit.Pixels) { - return null; + column.ResolvedBrowserWidth = $"{column.Width.Value}px"; } - - return w.Unit switch + else { - WidthUnit.Fraction => $"{Math.Round(w.Value / fractionTotal * 100, 1)}%", - WidthUnit.Pixels => $"{w.Value}px", - _ => throw new NotSupportedException($"Unsupported width unit: {w.Unit}") - }; + var isLast = column == lastPercentageColumn; + if (isLast) + { + column.ResolvedBrowserWidth = $"{remainingPercentage}%"; + } + else + { + var percentage = Math.Round(column.Width.Value / fractionTotal * 100, 1); + column.ResolvedBrowserWidth = $"{percentage}%"; + + remainingPercentage -= percentage; + } + } } } @@ -93,32 +120,36 @@ public void SetWidthFraction(float fraction) /// public bool IsColumnVisible(string columnName) { - return _columnById.TryGetValue(columnName, out var column) // Is a known column. - && GetColumnWidth(column) is not null // Has width for current viewport. - && column.IsVisible?.Invoke() is null or true; // Is visible. + if (GetColumnView(columnName) is not { } column) + { + return false; + } + + return column.IsVisible?.Invoke() is null or true; } public string? GetColumnWidth(string columnName) { - if (!_columnById.TryGetValue(columnName, out var column)) // Is a known column. + if (GetColumnView(columnName) is not { } column) { return null; } - var viewportInformation = _gridViewportInformation ?? DimensionManager.ViewportInformation; - - return viewportInformation.IsDesktop - ? column.ResolvedDesktopWidth - : column.ResolvedMobileWidth; + return column.ResolvedBrowserWidth; } - private Width? GetColumnWidth(GridColumn column) + private GridColumnView? GetColumnView(string columnName) { - var viewportInformation = _gridViewportInformation ?? DimensionManager.ViewportInformation; + var columnsById = ViewportInformation.IsDesktop + ? _columnDesktopById + : _columnMobileById; + + if (!columnsById.TryGetValue(columnName, out var column)) // Is a known column. + { + return null; + } - return viewportInformation.IsDesktop - ? column.DesktopWidth - : column.MobileWidth; + return column; } public void Dispose() diff --git a/src/Aspire.Dashboard/Model/GridColumn.cs b/src/Aspire.Dashboard/Model/GridColumn.cs index b962dc2210a..c7d95e28398 100644 --- a/src/Aspire.Dashboard/Model/GridColumn.cs +++ b/src/Aspire.Dashboard/Model/GridColumn.cs @@ -3,10 +3,11 @@ namespace Aspire.Dashboard.Model; -public record GridColumn(string Name, Width? DesktopWidth, Width? MobileWidth = null, Func? IsVisible = null) +public record GridColumn(string Name, Width? DesktopWidth, Width? MobileWidth = null, Func? IsVisible = null); + +public record GridColumnView(string Name, Width Width, Func? IsVisible = null) { - public string? ResolvedDesktopWidth { get; set; } - public string? ResolvedMobileWidth { get; set; } + public string? ResolvedBrowserWidth { get; set; } } public enum WidthUnit