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

Theme Detection (Dark Mode) #246

Merged
merged 6 commits into from
Oct 12, 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
3 changes: 3 additions & 0 deletions src/Aspire.Dashboard/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<link rel="stylesheet" href="_content/Aspire.Dashboard/css/app.css" />
<link rel="stylesheet" href="_content/Aspire.Dashboard/Aspire.Dashboard.bundle.scp.css" />
<HeadOutlet @rendermode="@RenderMode.InteractiveServer" />
<!-- Make the body background dark if dark mode will be enabled to prevent a flash of white
before fully rendered -->
<style>@@media (prefers-color-scheme: dark) { body { background-color: #333333; } }</style>
Copy link
Member

Choose a reason for hiding this comment

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

Is this to avoid flash of white on page load? Add a comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's right. The custom properties (e.g. --neutral-fill-rest) aren't available at this point so I just took the raw color. Makes sure the page loads dark.

</head>

<body class="before-upgrade">
Expand Down
33 changes: 17 additions & 16 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
@using Aspire.Dashboard.Model
@using Aspire.Dashboard.Model
@using Microsoft.AspNetCore.Http
@inject IDashboardViewModelService dashboardViewModelService
@inherits LayoutComponentBase

<div>
<div @ref="_container">
<FluentLayout>
<FluentHeader Style="margin-bottom:0px">@dashboardViewModelService.ApplicationName Dashboard</FluentHeader>
<FluentStack Orientation="Orientation.Horizontal" Width="100%">

<FluentNavMenu Width="250" Collapsible="true">
<FluentNavLink Icon="@(new Icons.Regular.Size24.AppGeneric())" Href="/" Match="NavLinkMatch.All">Projects</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.BinFull())" Href="/Containers">Containers</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.AppGeneric())" Href="/Executables">Executables</FluentNavLink>
<FluentNavGroup Title="Logs" Icon="@(new Icons.Regular.Size24.SlideText())" Expanded="true" Gap="10px 0;">
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideText())" Href="/ProjectLogs">Project</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideText())" Href="/ContainerLogs">Container</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideText())" Href="/ExecutableLogs">Executable</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideTextSparkle())" Href="/StructuredLogs">Structured</FluentNavLink>
</FluentNavGroup>
<FluentNavLink Icon="@(new Icons.Regular.Size24.GanttChart())" Href="/Traces">Traces</FluentNavLink>
</FluentNavMenu>

<div class="nav-menu-container">
<FluentNavMenu Width="250" Collapsible="true">
<FluentNavLink Icon="@(new Icons.Regular.Size24.AppGeneric())" Href="/" Match="NavLinkMatch.All">Projects</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.BinFull())" Href="/Containers">Containers</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.AppGeneric())" Href="/Executables">Executables</FluentNavLink>
<FluentNavGroup Title="Logs" Icon="@(new Icons.Regular.Size24.SlideText())" Expanded="true" Gap="10px 0;">
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideText())" Href="/ProjectLogs">Project</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideText())" Href="/ContainerLogs">Container</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideText())" Href="/ExecutableLogs">Executable</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.SlideTextSparkle())" Href="/StructuredLogs">Structured</FluentNavLink>
</FluentNavGroup>
<FluentNavLink Icon="@(new Icons.Regular.Size24.GanttChart())" Href="/Traces">Traces</FluentNavLink>
</FluentNavMenu>
</div>
<FluentBodyContent Class="custom-body-content">
@Body
</FluentBodyContent>
Expand Down
98 changes: 98 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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.AspNetCore.Http;
using Microsoft.Fast.Components.FluentUI;
using Microsoft.Fast.Components.FluentUI.DesignTokens;
using Microsoft.JSInterop;

namespace Aspire.Dashboard.Components.Layout;

public partial class MainLayout
{
[Inject]
private IJSRuntime JS { get; set; } = default!;

[Inject]
private BaseLayerLuminance BaseLayerLuminance { get; set; } = default!;

[Inject]
private PersistentComponentState ApplicationState { get; set; } = default!;

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

private StandardLuminance _baseLayerLuminance = StandardLuminance.LightMode;
private PersistingComponentStateSubscription _persistingSubscription;

private ElementReference _container = default!;

protected override void OnParametersSet()
{
if (HttpContext is not null)
{
_persistingSubscription = ApplicationState.RegisterOnPersisting(PersistBaseLayerLuminance);

// Look to see if we have a cookie saying what the last system theme was
// and set the base layer luminance based on that
var lastSystemTheme = HttpContext.Request.Cookies["lastSystemTheme"];
_baseLayerLuminance = lastSystemTheme switch
{
"dark" => StandardLuminance.DarkMode,
_ => StandardLuminance.LightMode
};
}
}

protected override void OnInitialized()
{
// See if we got a base layer luminance value from the cookie and set the value
// This will avoid a flash of white if the last system theme and current system theme are both dark
if (ApplicationState.TryTakeFromJson<StandardLuminance>("baseLayerLuminance", out var restoredBaseLayerLuminance))
{
_baseLayerLuminance = restoredBaseLayerLuminance;
StateHasChanged();
}
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Use javascript to determine the current system theme and set the theme cookie
// based on that value. Then use the result of that to update the base layer luminance.
// If the system theme hasn't changed from last time, this will have no effect.
// If it has, we might have a flash of (last system theme color) before the current
// system theme color takes effect.
var isSystemThemeDark = await JS.InvokeAsync<bool>("setThemeCookie");
_baseLayerLuminance = isSystemThemeDark ? StandardLuminance.DarkMode : StandardLuminance.LightMode;

await BaseLayerLuminance.SetValueFor(_container, _baseLayerLuminance.GetLuminanceValue());
StateHasChanged();
}
}

private Task PersistBaseLayerLuminance()
{
// Persist the base layer luminance value from pre-rendering (when we pull it out of the
// cookie) to rendering (when setting it is important.
ApplicationState.PersistAsJson("baseLayerLuminance", _baseLayerLuminance);
return Task.CompletedTask;
}
}

// Uncomment the code below to overload the GetLuminanceValue extension method and use custom luminosity values.
// Probably needs to be move to it's own file too.
/*
public static class StandardLuminanceExtensions
{
private const float LightMode = 1.0f;
private const float DarkMode = 0.15f;

public static float GetLuminanceValue(this StandardLuminance value)
{
return value == StandardLuminance.LightMode ? LightMode : DarkMode;
}
}
*/
11 changes: 11 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,14 @@ main {
right: 0.75rem;
top: 0.5rem;
}

::deep .layout {
min-height: 100vh;
background-color: var(--neutral-fill-layer-rest);
color: var(--neutral-foreground-rest);
}

::deep .nav-menu-container {
min-height: calc(100vh - 50px);
background-color: var(--neutral-fill-stealth-rest);
}
123 changes: 122 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,125 @@
.trace-id {
::deep .trace-id {
color: rgb(136, 136, 136);
padding-left: 0.5rem;
font-size: 12px;
}

::deep .trace-view-grid {
width: 100%;
}

::deep .trace-view-grid .selected-span {
background-color: var(--neutral-fill-active);
}

::deep .trace-view-grid .selected-span:hover {
background-color: var(--neutral-fill-hover) !important;
}

::deep .trace-view-grid fluent-data-grid-row[row-type="default"]:hover .span-bar-label-detail {
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="header"] {
background: var(--fill-color);
border-bottom: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-divider-rest);
}

::deep .trace-view-grid fluent-data-grid-row {
padding: 0;
border: 0;
align-items: center;
}

::deep .trace-view-grid fluent-data-grid-cell {
padding: 0;
border: 0;
border-radius: 0;
vertical-align: middle;
}

::deep .trace-view-grid fluent-data-grid-cell[grid-column="2"] fluent-divider {
display: none;
}

::deep .ticks {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
align-items: center;
}

::deep .ticks .tick {
width: 1px;
height: 30px;
background: #d8d8d8;
grid-row: 1;
}

::deep .ticks .end-tick {
justify-self: end;
grid-row: 1;
}

::deep .tick-label {
margin-left: 0.25rem;
margin-right: 0.25rem;
white-space: nowrap;
grid-row: 1;
}

::deep .span-container {
grid-column: 1 / span 4;
grid-row: 1;
height: 100%;
display: grid;
z-index: 1;
align-items: center;
}

::deep .span-container .span-bar {
height: 15px;
border-radius: 5px;
cursor: pointer;
grid-row: 1;
}

::deep .span-container .span-bar-label {
font-size: 12px;
color: #aaa;
padding: 0 0.5em;
cursor: pointer;
height: min-content;
}

::deep .span-container .span-bar-label-detail {
display: none;
}

::deep .span-container .span-bar-label-right {
grid-row: 1;
grid-column: 3;
}

::deep .span-container .span-bar-label-left {
grid-row: 1;
grid-column: 1;
justify-self: end;
}

::deep .uninstrumented-peer {
padding-left: 0.5rem;
}

::deep .uninstrumented-peer-icon {
vertical-align: text-bottom;
}

::deep .span-row-name {
color: rgb(136, 136, 136);
padding-left: 0.5rem;
font-size: 12px;
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/Traces.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private string GetRowStyle(OtlpTrace trace)
percentage = trace.Duration / ViewModel.MaxDuration * 100.0;
}

return $"background: linear-gradient(to right, var(--duration-color) {percentage:0.##}%, transparent {percentage:0.##}%);";
return $"background: linear-gradient(to right, var(--neutral-fill-input-alt-active) {percentage:0.##}%, transparent {percentage:0.##}%);";
}

private ValueTask<GridItemsProviderResult<OtlpTrace>> GetData(GridItemsProviderRequest<OtlpTrace> request)
Expand Down Expand Up @@ -154,4 +154,4 @@ public void Dispose()
_applicationsSubscription?.Dispose();
_tracesSubscription?.Dispose();
}
}
}
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/Traces.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
padding: 2px 7px;
margin-right: 7px;
font-size: 12px;
background: rgb(250, 250, 250)
background-color: var(--neutral-fill-rest);
color: var(--neutral-foreground-rest);
}

.trace-service-tag {
Expand Down
Loading