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 1 commit
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
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<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" />
<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
65 changes: 36 additions & 29 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
@using Aspire.Dashboard.Model
@using Microsoft.AspNetCore.Http
@using Microsoft.Fast.Components.FluentUI.DesignTokens
@inject IDashboardViewModelService dashboardViewModelService
@inject IJSRuntime JS
@inject BaseLayerLuminance BaseLayerLuminance
@inject PersistentComponentState ApplicationState
@inherits LayoutComponentBase

<div>
<FluentLayout>
<FluentHeader Style="margin-bottom:0px">@dashboardViewModelService.ApplicationName Dashboard</FluentHeader>
<FluentStack Orientation="Orientation.Horizontal" Width="100%">
<FluentDesignSystemProvider BaseLayerLuminance="@_baseLayerLuminance">
<FluentLayout>
<FluentHeader Style="margin-bottom:0px">@dashboardViewModelService.ApplicationName Dashboard</FluentHeader>
<FluentStack Orientation="Orientation.Horizontal" Width="100%">
<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>

<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>

<FluentBodyContent Class="custom-body-content">
@Body
</FluentBodyContent>

</FluentStack>
</FluentLayout>
<FluentDialogProvider />
<FluentTooltipProvider />
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
</FluentStack>
</FluentLayout>
<FluentDialogProvider />
<FluentTooltipProvider />
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
</FluentDesignSystemProvider>
</div>
71 changes: 71 additions & 0 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.JSInterop;

namespace Aspire.Dashboard.Components.Layout;

public partial class MainLayout
{
[CascadingParameter]
public HttpContext? HttpContext { get; set; }

private const float LightModeLuminosity = 0.99f;
private const float DarkModeLuminosity = 0.15f;

private float _baseLayerLuminance = 0.9f;
private PersistingComponentStateSubscription _persistingSubscription;

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" => DarkModeLuminosity,
_ => LightModeLuminosity
};
}
}

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<float>("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 ? DarkModeLuminosity : LightModeLuminosity;
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;
}
}
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