Skip to content

Commit

Permalink
Add viewer for long strings in GridValue (#5018)
Browse files Browse the repository at this point in the history
* Move GridValue copy button inside a menu

* add empty dialog

* test

* Style dialog, work around fluentui onclick issue for FluentMenuItem

* Remove hardcoded string

* Update unformatted name, use JsonSerializer.Serialize, use key comparer for dictionaries, switch button appearance and icon

* Display dialog like in console logs

* Format based on language

* stream data

* Add TextVisualizerDialog component tests

* fix logical error

* fix console log css

* Don't loose comments when formatting JSON

* run custom tool after merge

* Update ThemeManager to obtain effective theme, conditionally highlight in light or dark mode based on effective theme

* remove unnecessary top margin, re-highlight if container content has changed

* Add more tests

* start at line 1, move copy button to top of dialog

* change width/height display of dialog

* Update src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs

* Style fixes

* Change text visualizer theming from server to client side

* Move parameter to codebehind, properly disconnect observer

* Allow users to click the menu item icons to prompt copy/open text visualizer action

* set width to 0 on cell menubutton to show as much column content as possible

---------

Co-authored-by: Adam Ratzman <[email protected]>
Co-authored-by: James Newton-King <[email protected]>
  • Loading branch information
3 people authored Aug 12, 2024
1 parent 7263e53 commit 55eb46f
Show file tree
Hide file tree
Showing 44 changed files with 2,722 additions and 363 deletions.
2 changes: 2 additions & 0 deletions src/Aspire.Dashboard/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
<link rel="stylesheet" href="_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css" />
<link rel="stylesheet" href="_content/Microsoft.FluentUI.AspNetCore.Components/Microsoft.FluentUI.AspNetCore.Components.bundle.scp.css" />
<link rel="stylesheet" href="css/app.css" />
<link rel="stylesheet" href="css/highlight.css" />
<link rel="stylesheet" href="Aspire.Dashboard.styles.css" />

<HeadOutlet @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />
<!-- Make the body background dark if dark mode will be enabled to prevent a flash of white
before fully rendered. This value is from --neutral-layer-2, but that custom
Expand Down
38 changes: 29 additions & 9 deletions src/Aspire.Dashboard/Components/Controls/GridValue.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@using Aspire.Dashboard.Extensions
@using Aspire.Dashboard.Resources
@using Aspire.Dashboard.Resources
@inject IStringLocalizer<ControlsStrings> Loc
@inject IStringLocalizer<Dialogs> DialogsLoc

<div class="@GetContainerClass()" style="width: inherit;">
@if (EnableMasking && IsMasked)
Expand Down Expand Up @@ -36,23 +36,43 @@
}

@{
(string, object)[] uncapturedAttributes = [
(string, object)[] uncapturedCopyAttributes = [
("alt", PreCopyToolTip),
("title", string.Empty),
("aria-label", Loc[nameof(ControlsStrings.GridValueCopyToClipboard)]),
("tabindex", "0")
];
}

<div @onclick:stopPropagation="true">
<div @onclick:stopPropagation="true" class="defaultHidden">
<FluentButton Appearance="Appearance.Lightweight"
Id="@_anchorId"
Id="@_menuAnchorId"
Class="defaultHidden"
Style="float: right; flex-shrink: 0"
AdditionalAttributes="@FluentUIExtensions.GetClipboardCopyAdditionalAttributes(ValueToCopy ?? Value, PreCopyToolTip, PostCopyToolTip, uncapturedAttributes)">
<FluentIcon Class="copy-icon" Style="display:inline;" Icon="Icons.Regular.Size16.Copy" />
<FluentIcon Class="checkmark-icon" Style="display:none;" Icon="Icons.Regular.Size16.Checkmark" />
OnClick="@ToggleMenuOpen">
<FluentIcon Style="display:inline;" Icon="Icons.Regular.Size16.MoreVertical" />
</FluentButton>

<FluentMenu Anchor="@_menuAnchorId" @bind-Open="_isMenuOpen" VerticalThreshold="170" HorizontalPosition="HorizontalPosition.End">
<FluentMenuItem
Id="@_copyId"
AdditionalAttributes="@FluentUIExtensions.GetClipboardCopyAdditionalAttributes(ValueToCopy ?? Value, PreCopyToolTip, PostCopyToolTip, uncapturedCopyAttributes)">
<span slot="start">
<FluentIcon Class="copy-icon" Style="display:inline; vertical-align: text-bottom" Icon="Icons.Regular.Size16.Copy" Slot="start" />
<FluentIcon Class="checkmark-icon" Style="display:none; vertical-align: text-bottom" Icon="Icons.Regular.Size16.Checkmark" Slot="start" />
</span>
@PreCopyToolTip
</FluentMenuItem>

<FluentMenuItem
Disabled="@(Value is null)"
AdditionalAttributes="@FluentUIExtensions.GetOpenTextVisualizerAdditionalAttributes(Value!, ValueDescription)">
<span slot="start">
<FluentIcon Style="display:inline; vertical-align: text-bottom" Icon="Icons.Regular.Size16.Open" Slot="start"/>
</span>

@DialogsLoc[nameof(Dialogs.OpenInTextVisualizer)]
</FluentMenuItem>
</FluentMenu>
</div>
<FluentTooltip @ref="_tooltipComponent" Anchor="@_anchorId" Position="TooltipPosition.Top" HideTooltipOnCursorLeave="true">@PreCopyToolTip</FluentTooltip>
</div>
34 changes: 25 additions & 9 deletions src/Aspire.Dashboard/Components/Controls/GridValue.razor.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
// 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.Resize;
using Aspire.Dashboard.Resources;
using Microsoft.AspNetCore.Components;
using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Aspire.Dashboard.Components.Controls;

public partial class GridValue : IDisposable
public partial class GridValue
{
[Parameter, EditorRequired]
public string? Value { get; set; }

[Parameter, EditorRequired]
public required string ValueDescription { get; set; }

/// <summary>
/// Content to include, if any, after the Value string
/// </summary>
Expand Down Expand Up @@ -54,23 +59,34 @@ public partial class GridValue : IDisposable
[Parameter]
public string? ToolTip { get; set; }

[Parameter] public string PreCopyToolTip { get; set; } = null!;
[Parameter]
public string PreCopyToolTip { get; set; } = null!;

[Parameter]
public string PostCopyToolTip { get; set; } = null!;

[Inject]
public required IDialogService DialogService { get; init; }

[Parameter] public string PostCopyToolTip { get; set; } = null!;
[Inject]
public required IJSRuntime JS { get; init; }

[CascadingParameter]
public required ViewportInformation ViewportInformation { get; init; }

private readonly Icon _maskIcon = new Icons.Regular.Size16.EyeOff();
private readonly Icon _unmaskIcon = new Icons.Regular.Size16.Eye();
private readonly string _anchorId = $"copy-{Guid.NewGuid():N}";

private FluentTooltip? _tooltipComponent;
private readonly string _copyId = $"copy-{Guid.NewGuid():N}";
private readonly string _menuAnchorId = $"menu-{Guid.NewGuid():N}";
private bool _isMenuOpen;

protected override void OnInitialized()
{
PreCopyToolTip = Loc[nameof(ControlsStrings.GridValueCopyToClipboard)];
PostCopyToolTip = Loc[nameof(ControlsStrings.GridValueCopied)];
}

private string GetContainerClass() => EnableMasking ? "container masking-enabled wrap" : "container wrap";
private string GetContainerClass() => EnableMasking ? "container masking-enabled" : "container";

private async Task ToggleMaskStateAsync()
=> await IsMaskedChanged.InvokeAsync(!IsMasked);
Expand All @@ -85,8 +101,8 @@ private string TrimLength(string? text)
return text ?? "";
}

public void Dispose()
private void ToggleMenuOpen()
{
_tooltipComponent?.Dispose();
_isMenuOpen = !_isMenuOpen;
}
}
2 changes: 2 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/GridValue.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
::deep .defaultHidden {
opacity: 0;
cursor: pointer;
width: 0;
}

::deep:hover .defaultHidden, ::deep:focus-within .defaultHidden {
opacity: 1; /* safari has a bug where hover is not always called on an invisible element, so we use opacity instead */
width: auto;
}
14 changes: 7 additions & 7 deletions src/Aspire.Dashboard/Components/Controls/LogViewer.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
@inject IJSRuntime JS
@implements IAsyncDisposable

<div class="log-overflow continuous-scroll-overflow">
<div class="log-container" id="logContainer">
<div class="log-overflow console-overflow continuous-scroll-overflow">
<div class="log-container console-container" id="logContainer">
<Virtualize Items="@LogEntries.GetEntries()" ItemSize="20" OverscanCount="100" TItem="LogEntry">
<div class="line-row-container">
<div class="line-row">
<span class="line-area" role="log">
<span class="line-number">@context.LineNumber</span>
<span class="content">
<div class="log-line-row-container">
<div class="log-line-row console-line-row">
<span class="log-line-area" role="log">
<span class="log-line-number">@context.LineNumber</span>
<span class="log-content">
@if (context.Timestamp is { } timestamp)
{
<span class="timestamp">@GetDisplayTimestamp(timestamp)</span>
Expand Down
104 changes: 10 additions & 94 deletions src/Aspire.Dashboard/Components/Controls/LogViewer.razor.css
Original file line number Diff line number Diff line change
@@ -1,117 +1,33 @@
.log-overflow {
--console-theme-black: #0C0C0C;
--console-theme-blue: #0037DA;
--console-theme-cyan: #3A96DD;
--console-theme-green: #13A10E;
--console-theme-magenta: #CD13E8;
--console-theme-bg-red: #F80F24;
--console-theme-fg-red: #C50F1F;
--console-theme-white: #CCCCCC;
--console-theme-yellow: #C19C00;

--console-theme-bright-black: #767676;
--console-theme-bright-blue: #3B78FF;
--console-theme-bright-cyan: #61D6D6;
--console-theme-bright-green: #16C60C;
--console-theme-bright-magenta: #DA01FA;
--console-theme-bright-red: #E74856;
--console-theme-bright-white: #F2F2F2;
--console-theme-bright-yellow: #F9F1A5;

--line-number-color: #848484;
--timestamp-color: var(--line-number-color);
--console-background-color: var(--console-theme-black);
--console-background-color-hover: #1D1D1D;
--console-font-color: var(--console-theme-white);

height: 100%;
width: 100%;
overflow: auto;
background-color: var(--console-background-color);
}

::deep .log-container {
::deep .console-container {
background: var(--console-background-color);
color: var(--console-font-color);
font-family: 'Cascadia Mono', Consolas, monospace;
font-size: 13px;
margin: 16px 0 0 0;
padding-bottom: 24px;
line-height: 20px;
overflow: visible;
display: flex;
flex-direction: column;
width: 100%;
margin: 16px 0;
}

::deep .line-row-container {
width: 100%;
overflow: hidden;
}

::deep .line-row {
cursor: text;
padding: 0 12px 0 12px;
white-space: pre-wrap;

display: flex;
flex-direction: row;
flex-grow: 1;
}

::deep .line-row:hover {
::deep .console-line-row:hover {
background-color: var(--console-background-color-hover);
}

::deep .line-area {
flex-grow: 1;
justify-content: flex-start;
display: flex;
flex-direction: row;
}

::deep .line-number {
padding-left: 10px;
min-width: 43px;
text-align: right;
align-self: flex-start;
flex-shrink: 0;
color: var(--line-number-color);
user-select: none;
cursor: default;
}

::deep .content {
word-break: break-all;
margin-left: 20px;
position: relative;
margin-right: 6px;
user-select: text;
cursor: text;
white-space: pre-wrap;
overflow-wrap: anywhere;
}

::deep .content .timestamp {
::deep .log-content .timestamp {
color: var(--timestamp-color);
}

::deep .content a,
::deep .content a:active,
::deep .content a:visited {
::deep .log-content a,
::deep .log-content a:active,
::deep .log-content a:visited {
color: var(--console-font-color);
text-decoration: none;
}

::deep .content a:hover {
::deep .log-content a:hover {
text-decoration: underline;
}

::deep .content > fluent-badge {
::deep .log-content > fluent-badge {
user-select: none;
}

::deep .content > .timestamp + fluent-badge {
::deep .log-content > .timestamp + fluent-badge {
margin-left: 1ch;
}

Expand Down
13 changes: 9 additions & 4 deletions src/Aspire.Dashboard/Components/Controls/PropertyGrid.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
GridTemplateColumns="@GridTemplateColumns"
ShowHover="true">
<TemplateColumn Title="@(NameColumnTitle ?? Loc[nameof(ControlsStrings.NameColumnHeader)])" Class="nameColumn" SortBy="@NameSort" Sortable="@IsNameSortable">
<GridValue Value="@NameColumnValue(context)" HighlightText="@HighlightText" />
<GridValue
ValueDescription="@(NameColumnTitle ?? Loc[nameof(ControlsStrings.NameColumnHeader)])"
Value="@NameColumnValue(context)"
HighlightText="@HighlightText" />
</TemplateColumn>
<TemplateColumn Title="@(ValueColumnTitle ?? Loc[nameof(ControlsStrings.PropertyGridValueColumnHeader)])" Class="valueColumn" SortBy="@ValueSort" Sortable="@IsValueSortable">
<GridValue Value="@ValueColumnValue(context)" HighlightText="@HighlightText"
EnableMasking="@EnableValueMasking" IsMasked="@GetIsItemMasked(context)"
IsMaskedChanged="(newValue) => OnIsMaskedChanged(context, newValue)" />
<GridValue
ValueDescription="@(ValueColumnTitle ?? Loc[nameof(ControlsStrings.PropertyGridValueColumnHeader)])"
Value="@ValueColumnValue(context)" HighlightText="@HighlightText"
EnableMasking="@EnableValueMasking" IsMasked="@GetIsItemMasked(context)"
IsMaskedChanged="(newValue) => OnIsMaskedChanged(context, newValue)"/>
@ExtraValueContent(context)
</TemplateColumn>
</FluentDataGrid>
Expand Down
17 changes: 13 additions & 4 deletions src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,16 @@
GridTemplateColumns="1fr 1.5fr"
ShowHover="true">
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
<GridValue Value="@(context.KnownProperty?.DisplayName ?? context.Key)" ToolTip="@context.Key" />
<GridValue
Value="@(context.KnownProperty?.DisplayName ?? context.Key)"
ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
ToolTip="@context.Key" />
</TemplateColumn>
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.PropertyGridValueColumnHeader)]" Class="valueColumn">
<GridValue Value="@GetDisplayedValue(TimeProvider, context)" ToolTip="@context.Tooltip" />
<GridValue
Value="@GetDisplayedValue(TimeProvider, context)"
ValueDescription="@(context.KnownProperty?.DisplayName ?? context.Key)"
ToolTip="@context.Tooltip" />
</TemplateColumn>
</FluentDataGrid>
</FluentAccordionItem>
Expand All @@ -70,10 +76,13 @@
GridTemplateColumns="1fr 1.5fr"
ShowHover="true">
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Class="nameColumn">
<GridValue Value="@context.Name" />
<GridValue Value="@context.Name" ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" />
</TemplateColumn>
<TemplateColumn Title="@ControlStringsLoc[nameof(ControlsStrings.PropertyGridValueColumnHeader)]" Class="valueColumn">
<GridValue Value="@context.Text" MaxDisplayLength="0">
<GridValue
Value="@context.Text"
ValueDescription="@context.Name"
MaxDisplayLength="0">
<ContentAfterValue>
@if (context.Url != null)
{
Expand Down
Loading

0 comments on commit 55eb46f

Please sign in to comment.