Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Name/Value Grids #406

Merged
merged 2 commits into from
Oct 20, 2023
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
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