Skip to content
Open
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
11 changes: 10 additions & 1 deletion src/Web/AdminPanel/Components/App.razor
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
@using MUnique.OpenMU.Web.AdminPanel.Components.Layout
@using MUnique.OpenMU.Web.Shared.Services

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

private string CurrentTheme =>
ThemeController.NormalizeTheme(this.HttpContext?.Request.Cookies[ThemeController.CookieName]);
}

<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="@this.CurrentTheme">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

.configuration-search__result-path {
font-size: 0.78rem;
color: #5f6368;
color: var(--omu-text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Expand All @@ -45,7 +45,7 @@

.configuration-search__result:hover .configuration-search__result-path,
.configuration-search__result:focus .configuration-search__result-path {
color: rgba(0, 0, 0, 0.7);
color: var(--omu-text);
}

@media (max-width: 640.98px) {
Expand Down
13 changes: 7 additions & 6 deletions src/Web/AdminPanel/Components/Layout/CreationPanel.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
flex-shrink: 0;
display: flex;
flex-direction: row;
background-color: #fff;
border-left: 1px solid #d6d5d5;
background-color: var(--omu-surface-2);
color: var(--omu-text);
border-left: 1px solid var(--omu-border);
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.08);
overflow: hidden;
z-index: 2;
Expand All @@ -22,14 +23,14 @@
width: 1.75rem;
padding: 0;
border: none;
border-right: 1px solid #d6d5d5;
background-color: #f7f7f7;
border-right: 1px solid var(--omu-border);
background-color: var(--omu-surface-muted);
cursor: pointer;
color: #333;
color: var(--omu-text);
}

.collapse-toggle:hover {
background-color: #e9e9e9;
background-color: var(--omu-surface-hover);
}

.creation-panel-body {
Expand Down
18 changes: 15 additions & 3 deletions src/Web/AdminPanel/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
@using MUnique.OpenMU.Web.AdminPanel.Properties
@using MUnique.OpenMU.Web.Shared.Services

@inherits LayoutComponentBase

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

private bool IsDarkTheme => string.Equals(
this.HttpContext?.Request.Cookies[ThemeController.CookieName],
"dark",
StringComparison.OrdinalIgnoreCase);
}

<div class="page">
<div class="sidebar">
<NavMenu />
Expand All @@ -14,14 +25,15 @@
<div class="header-search flex-grow-1">
<ConfigurationSearch />
</div>
<div class="header-actions ml-3">
<a href="https://munique.net" target="_blank" class="text-secondary small">@Resources.About</a>
<div class="header-actions ml-3 d-flex align-items-center">
<ThemeSelector IsDark="@this.IsDarkTheme" />
<a href="https://munique.net" target="_blank" class="text-secondary small ml-3">@Resources.About</a>
</div>
</div>
</div>
</header>

<div class="breadcrumb-bar bg-white border-bottom px-4 py-3">
<div class="breadcrumb-bar border-bottom px-4 py-3">
<div class="d-flex align-items-center">
<BreadcrumbNavigation />
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/Web/AdminPanel/Components/Layout/MainLayout.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ main {
}

.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
background-image: var(--omu-sidebar-gradient);
}

.header-search {
Expand All @@ -24,6 +24,7 @@ main {

.breadcrumb-bar {
min-height: 2.5rem;
background-color: var(--omu-surface-2);
}

.breadcrumb-bar ::deep .breadcrumb-nav {
Expand Down Expand Up @@ -58,8 +59,8 @@ main {
}

#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
background: var(--omu-error-bg);
color: var(--omu-error-text);
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
Expand Down
1 change: 1 addition & 0 deletions src/Web/AdminPanel/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Http
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
Expand Down
11 changes: 6 additions & 5 deletions src/Web/Shared/Components/Form/AutoForm.razor.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.auto-form {
background: #fff;
border: 1px solid #dee2e6;
background: var(--omu-surface-2);
color: var(--omu-text);
border: 1px solid var(--omu-border);
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1rem;
Expand All @@ -9,9 +10,9 @@
.form-actions {
position: sticky;
bottom: 0;
background-color: #fff;
background-color: var(--omu-surface-2);
margin: 1rem 0 0 0;
border-top: 1px solid #dee2e6;
border-top: 1px solid var(--omu-border);
display: flex;
gap: 0.5rem;
justify-content: flex-start;
Expand All @@ -38,6 +39,6 @@
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
color: var(--omu-text-muted);
pointer-events: none;
}
20 changes: 11 additions & 9 deletions src/Web/Shared/Components/Form/Typeahead.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
padding: 0.375rem 2.2rem;
min-height: calc(1.5em + 0.75rem + 2px);
cursor: text;
border: 1px solid #ced4da;
border: 1px solid var(--omu-border);
border-radius: 0.25rem;
background-color: #fff;
background-color: var(--omu-surface-2);
color: var(--omu-text);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.typeahead-controls:focus-within {
color: #495057;
background-color: #fff;
border-color: #80bdff;
color: var(--omu-text);
background-color: var(--omu-surface-2);
border-color: var(--omu-link);
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
Expand All @@ -39,8 +40,9 @@
.typeahead-multi-value {
display: inline-flex;
align-items: center;
background-color: #e9ecef;
border: 1px solid #ced4da;
background-color: var(--omu-surface-hover);
color: var(--omu-text);
border: 1px solid var(--omu-border);
border-radius: 0.2rem;
padding: 0.1rem 0.4rem;
font-size: 0.875rem;
Expand All @@ -51,7 +53,7 @@
.typeahead-multi-value-remove {
background: none;
border: none;
color: #6c757d;
color: var(--omu-text-muted);
cursor: pointer;
font-size: 1rem;
font-weight: bold;
Expand Down Expand Up @@ -93,7 +95,7 @@
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
color: var(--omu-text-muted);
pointer-events: none;
font-size: 0.9rem;
}
Expand Down
20 changes: 20 additions & 0 deletions src/Web/Shared/Components/ThemeSelector.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@*
A simple toggle that switches the UI between the "light" and "dark" themes.
Persists the selection by calling the ThemeController (cookie-based, same pattern as CultureSelector).
*@
@using MUnique.OpenMU.Web.Shared.Properties

<button type="button"
class="theme-selector btn btn-link p-0"
title="@(this.EffectiveIsDark ? Resources.SwitchToLightMode : Resources.SwitchToDarkMode)"
aria-label="@Resources.ToggleTheme"
@onclick="this.Toggle">
@if (this.EffectiveIsDark)
{
<span class="oi oi-sun theme-selector__icon" aria-hidden="true"></span>
}
else
{
<span class="oi oi-moon theme-selector__icon" aria-hidden="true"></span>
}
</button>
105 changes: 105 additions & 0 deletions src/Web/Shared/Components/ThemeSelector.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// <copyright file="ThemeSelector.razor.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Web.Shared.Components;

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

/// <summary>
/// A toggle component which switches between the light and dark UI themes.
/// Persists the selection by calling the <see cref="MUnique.OpenMU.Web.Shared.Services.ThemeController"/>
/// (cookie-based, mirrors the <see cref="CultureSelector"/> approach).
/// </summary>
public partial class ThemeSelector : IAsyncDisposable
{
private static readonly string JsModulePath =
$"./_content/{typeof(ThemeSelector).Assembly.GetName().Name}/themeSelector.js";

private IJSObjectReference? _jsModule;

private bool _isDarkInternal;

private bool _hydrated;

/// <summary>
/// Gets or sets the current dark-mode state as seen by the server-side renderer.
/// </summary>
/// <remarks>
/// Only used until the first interactive render hydrates the value from the live DOM
/// (the cascading HttpContext is null during interactive updates so this parameter
/// would otherwise flip back to false even when the cookie is "dark").
/// </remarks>
[Parameter]
public bool IsDark { get; set; }

/// <summary>
/// Gets or sets the navigation manager used for the post-toggle redirect.
/// </summary>
[Inject]
private NavigationManager NavigationManager { get; set; } = null!;

/// <summary>
/// Gets or sets the JS runtime used to load the theme reader module.
/// </summary>
[Inject]
private IJSRuntime JS { get; set; } = null!;

private bool EffectiveIsDark => this._hydrated ? this._isDarkInternal : this.IsDark;

/// <inheritdoc />
public async ValueTask DisposeAsync()
{
if (this._jsModule is not null)
{
try
{
await this._jsModule.DisposeAsync().ConfigureAwait(false);
}
catch (JSDisconnectedException)
{
// Ignore: circuit already gone.
}
}
}

/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender).ConfigureAwait(true);
if (!firstRender)
{
return;
}

try
{
this._jsModule = await this.JS
.InvokeAsync<IJSObjectReference>("import", JsModulePath)
.ConfigureAwait(true);
var theme = await this._jsModule
.InvokeAsync<string?>("current")
.ConfigureAwait(true);
this._isDarkInternal = string.Equals(theme, "dark", StringComparison.OrdinalIgnoreCase);
this._hydrated = true;
this.StateHasChanged();
}
catch
{
// Fall back to the SSR parameter if JS is unavailable.
}
}

private void Toggle()
{
var next = this.EffectiveIsDark ? "light" : "dark";
var uri = new Uri(this.NavigationManager.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var themeEscaped = Uri.EscapeDataString(next);
var uriEscaped = Uri.EscapeDataString(uri);

var fullUri = $"/Theme/Set?theme={themeEscaped}&redirectUri={uriEscaped}";
this.NavigationManager.NavigateTo(fullUri, forceLoad: true);
}
}
22 changes: 22 additions & 0 deletions src/Web/Shared/Components/ThemeSelector.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.theme-selector {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
line-height: 1;
color: inherit;
text-decoration: none;
border-radius: 999px;
}

.theme-selector:hover,
.theme-selector:focus {
background-color: rgba(0, 0, 0, 0.06);
text-decoration: none;
color: inherit;
}

.theme-selector__icon {
font-size: 1.1rem;
}
1 change: 1 addition & 0 deletions src/Web/Shared/Exports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private static IEnumerable<string> SharedStylesheets
get
{
yield return $"{Prefix}/css/shared.css";
yield return $"{Prefix}/css/theme.css";
}
}
}
Loading