Skip to content

Commit

Permalink
Refactor Name/Value Grids (#406)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlmii authored Oct 20, 2023
1 parent 72bf2c4 commit a420bc7
Show file tree
Hide file tree
Showing 17 changed files with 279 additions and 344 deletions.
32 changes: 32 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/GridValue.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@inject IJSRuntime JS

<div class="@GetContainerClass()">
@if (EnableMasking && IsMasked)
{
<span class="cellText">
&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;
</span>
}
else
{
<span class="cellText" title="@Value">
<FluentHighlighter HighlightedText="@HighlightText"
Text="@Value" />
</span>
}
@if (EnableMasking)
{
<FluentButton Appearance="Appearance.Lightweight"
IconEnd="@(IsMasked ? _unmaskIcon : _maskIcon)"
Title="@(IsMasked ? "Show Value" : "Hide Value")"
OnClick="ToggleMaskStateAsync"
aria-label="@(IsMasked ? "Show Value" : "Hide Value")" />
}
<FluentButton Appearance="Appearance.Lightweight"
Id="@_anchorId"
IconEnd="@(new Icons.Regular.Size16.Copy())"
Class="defaultHidden"
@onclick="@(() => CopyTextToClipboardAsync(Value, @_anchorId))"
aria-label="Copy to Clipboard" />
<FluentTooltip Anchor="@_anchorId" Position="TooltipPosition.Top">@PreCopyText</FluentTooltip>
</div>
51 changes: 51 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/GridValue.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.Fast.Components.FluentUI;
using Microsoft.JSInterop;

namespace Aspire.Dashboard.Components.Controls;

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

/// <summary>
/// Determines whether or not masking support is enabled for this value
/// </summary>
[Parameter]
public bool EnableMasking { get; set; }

/// <summary>
/// Determines whether or not the value should currently be masked
/// </summary>
[Parameter]
public bool IsMasked { get; set; }

/// <summary>
/// The text to highlight within the value when the value is displayed unmasked
/// </summary>
[Parameter]
public string? HighlightText { get; set; }

[Parameter]
public EventCallback<bool> IsMaskedChanged { get; set; }

private const string PreCopyText = "Copy to clipboard";
private const string PostCopyText = "Copied!";

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 string GetContainerClass()
=> EnableMasking ? "container masking-enabled" : "container";

private async Task ToggleMaskStateAsync()
=> await IsMaskedChanged.InvokeAsync(!IsMasked);

private async Task CopyTextToClipboardAsync(string? text, string id)
=> await JS.InvokeVoidAsync("copyTextToClipboard", id, text, PreCopyText, PostCopyText);
}
22 changes: 22 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/GridValue.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.container {
display: grid;
grid-template-columns: 1fr auto;
gap: calc((6 + (var(--design-unit) * var(--density))) * 1px);
}

.masking-enabled {
grid-template-columns: 1fr auto auto;
}

::deep .cellText {
text-overflow: ellipsis;
overflow: hidden;
}

::deep .defaultHidden {
visibility: hidden;
}

::deep:hover .defaultHidden {
visibility: visible;
}
17 changes: 17 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/PropertyGrid.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@typeparam TItem

<FluentDataGrid Items="@Items"
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="@GridTemplateColumns">
<TemplateColumn Title="@NameColumnTitle" Class="nameColumn" SortBy="@NameSort" Sortable="@IsNameSortable">
<GridValue Value="@NameColumnValue(context)" HighlightText="@HighlightText" />
</TemplateColumn>
<TemplateColumn Title="@ValueColumnTitle" Class="valueColumn" SortBy="@ValueSort" Sortable="@IsValueSortable">
<GridValue Value="@ValueColumnValue(context)" HighlightText="@HighlightText"
EnableMasking="@EnableValueMasking" IsMasked="@GetIsItemMasked(context)"
IsMaskedChanged="(newValue) => OnIsMaskedChanged(context, newValue)" />
</TemplateColumn>
</FluentDataGrid>

63 changes: 63 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/PropertyGrid.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.Fast.Components.FluentUI;

namespace Aspire.Dashboard.Components.Controls;

public partial class PropertyGrid<TItem>
{
[Parameter, EditorRequired]
public IQueryable<TItem>? Items { get; set; }

[Parameter]
public string GridTemplateColumns { get; set; } = "1fr 1fr";

[Parameter]
public string NameColumnTitle { get; set; } = "Name";

[Parameter]
public string ValueColumnTitle { get; set; } = "Value";

[Parameter]
public GridSort<TItem>? NameSort { get; set; }

[Parameter]
public GridSort<TItem>? ValueSort { get; set; }

[Parameter]
public bool IsNameSortable { get; set; } = true;

[Parameter]
public bool IsValueSortable { get; set; } = true;

[Parameter]
public bool EnableValueMasking { get; set; }

[Parameter]
public Func<TItem, string?> NameColumnValue { get; set; } = item => item?.ToString();

[Parameter]
public Func<TItem, string?> ValueColumnValue { get; set; } = item => item?.ToString();

[Parameter]
public Func<TItem, bool> GetIsItemMasked { get; set; } = item => false;

[Parameter]
public Action<TItem, bool> SetIsItemMasked { get; set; } = (item, newValue) => { };

[Parameter]
public string? HighlightText { get; set; }

[Parameter]
public EventCallback<TItem> IsMaskedChanged { get; set; }

public readonly record struct PropertyGridIsMaskedChangedArgs(TItem Item, bool NewValue);

private async Task OnIsMaskedChanged(TItem item, bool newValue)
{
SetIsItemMasked(item, newValue);
await IsMaskedChanged.InvokeAsync(item);
}
}
Empty file.
110 changes: 33 additions & 77 deletions src/Aspire.Dashboard/Components/Dialogs/EnvironmentVariables.razor
Original file line number Diff line number Diff line change
@@ -1,90 +1,46 @@
@using Aspire.Dashboard.Model
@implements IDialogContentComponent<EnvironmentVariablesDialogViewModel>
@inject IJSRuntime JS

<FluentDialogBody>
<div class="GridContainer">
<div>
<FluentDialogBody>
<FluentStack Orientation="Orientation.Vertical">
<FluentToolbar Orientation="Orientation.Horizontal">
@if (Content?.ShowSpecOnlyToggle == true)
{
<FluentButton Appearance="Appearance.Lightweight"
IconEnd="@(_showAll ? _showSpecOnlyIcon : _showAllIcon)"
Title="@(_showAll ? "Show Spec Only" : "Show All")"
aria-label="@(_showAll ? "Show Spec Only" : "Show All")"
OnClick="() => _showAll = !_showAll"
slot="end" />
IconEnd="@(_showAll ? _showSpecOnlyIcon : _showAllIcon)"
Title="@(_showAll ? "Show Spec Only" : "Show All")"
aria-label="@(_showAll ? "Show Spec Only" : "Show All")"
OnClick="() => _showAll = !_showAll"
slot="end" />
}
<FluentButton Appearance="Appearance.Lightweight"
IconEnd="@(_defaultMasked ? _unmaskIcon : _maskIcon)"
Title="@(_defaultMasked ? "Show Values" : "Hide Values")"
aria-label="@(_defaultMasked ? "Show Values" : "Hide Values")"
OnClick="ToggleMaskState"
slot="end" />
IconEnd="@(_defaultMasked ? _unmaskIcon : _maskIcon)"
Title="@(_defaultMasked ? "Show Values" : "Hide Values")"
aria-label="@(_defaultMasked ? "Show Values" : "Hide Values")"
OnClick="ToggleMaskState"
slot="end" />
<FluentSearch Placeholder="Filter..."
Immediate="true"
Autofocus="true"
@bind-Value="_filter"
@oninput="HandleFilter"
AfterBindValue="HandleClear"
slot="end" />
Immediate="true"
Autofocus="true"
@bind-Value="_filter"
@oninput="HandleFilter"
AfterBindValue="HandleClear"
slot="end" />
</FluentToolbar>
<FluentDataGrid Items="@FilteredItems"
ResizableColumns="true"
Style="width:100%"
GenerateHeader="GenerateHeaderOption.Sticky"
GridTemplateColumns="1fr 1fr">
<TemplateColumn Title="Name" Class="nameColumn" SortBy="@_nameSort" Sortable="true" Tooltip="true" >
<FluentStack Orientation="Orientation.Horizontal">
<span class="cellText" title="@context.Name">
<FluentHighlighter HighlightedText="@_filter"
Text="@context.Name" />
</span>
@{
var anchor = "name-" + context.Name;
}
<FluentButton Appearance="Appearance.Lightweight"
Id="@anchor"
IconEnd="@(new Icons.Regular.Size16.Copy())"
Class="defaultHidden"
@onclick="@(() => CopyTextToClipboardAsync(context.Name, @anchor))" />
<FluentTooltip Anchor="@anchor" Position="TooltipPosition.Top">@PreCopyText</FluentTooltip>
</FluentStack>
</TemplateColumn>
<TemplateColumn Title="Value" Class="valueColumn" SortBy="@_valueSort" Sortable="true">
<FluentStack Orientation="Orientation.Horizontal" Width="400px;">
@if (context.IsValueMasked)
{
<span class="cellText">
&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;&#x25cf;
</span>
}
else
{
<span class="cellText" title="@context.Value">
<FluentHighlighter HighlightedText="@_filter"
Text="@context.Value" />
</span>
}
<FluentButton Appearance="Appearance.Lightweight"
IconEnd="@(context.IsValueMasked ? _unmaskIcon : _maskIcon)"
Title="@(context.IsValueMasked ? "Show Value" : "Hide Value")"
OnClick="() => ToggleMaskState(context)"
aria-label="@(context.IsValueMasked ? "Show Value" : "Hide Value")" />
@{
var anchor = "copy-" + context.Name;
}
<FluentButton Appearance="Appearance.Lightweight"
Id="@anchor"
IconEnd="@(new Icons.Regular.Size16.Copy())"
Class="defaultHidden"
@onclick="@(() => CopyTextToClipboardAsync(context.Value, @anchor))"
aria-label="Copy to Clipboard" />
<FluentTooltip Anchor="@anchor" Position="TooltipPosition.Top">@PreCopyText</FluentTooltip>
</FluentStack>
</TemplateColumn>
</FluentDataGrid>
<div class="dialog-grid-container">
<PropertyGrid TItem="EnvironmentVariableViewModel"
Items="@FilteredItems"
NameColumnValue="(vm) => vm.Name"
ValueColumnValue="(vm) => vm.Value"
NameSort="_nameSort"
ValueSort="_valueSort"
EnableValueMasking="true"
GetIsItemMasked="(vm) => vm.IsValueMasked"
SetIsItemMasked="(vm, newValue) => vm.IsValueMasked = newValue"
IsMaskedChanged="@CheckAllMaskStates"
HighlightText="@_filter" />
</div>
</FluentStack>

</div>
</FluentDialogBody>
</FluentDialogBody>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Aspire.Dashboard.Model;
using Microsoft.AspNetCore.Components;
using Microsoft.Fast.Components.FluentUI;
using Microsoft.JSInterop;

namespace Aspire.Dashboard.Components.Dialogs;
public partial class EnvironmentVariables
Expand All @@ -22,9 +21,6 @@ public partial class EnvironmentVariables
vm.Value?.Contains(_filter, StringComparison.CurrentCultureIgnoreCase) == true)
)?.AsQueryable();

private const string PreCopyText = "Copy to clipboard";
private const string PostCopyText = "Copied!";

private string _filter = "";
private bool _defaultMasked = true;

Expand All @@ -48,12 +44,6 @@ private void ToggleMaskState()
}
}

private void ToggleMaskState(EnvironmentVariableViewModel vm)
{
vm.IsValueMasked = !vm.IsValueMasked;
CheckAllMaskStates();
}

private void HandleFilter(ChangeEventArgs args)
{
if (args.Value is string newFilter)
Expand Down Expand Up @@ -89,9 +79,4 @@ private void CheckAllMaskStates()
}
}
}

private async Task CopyTextToClipboardAsync(string? text, string id)
{
await JS.InvokeVoidAsync("copyTextToClipboard", id, text, PreCopyText, PostCopyText);
}
}

This file was deleted.

Loading

0 comments on commit a420bc7

Please sign in to comment.