Improved PWA options. Fully implemented auth
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -14,7 +14,7 @@ public class AuthenticationUiHandler : IAppLoader, IAppScreen
|
||||
{
|
||||
var identityService = serviceProvider.GetRequiredService<IdentityService>();
|
||||
|
||||
return Task.FromResult(identityService.IsLoggedIn);
|
||||
return Task.FromResult(!identityService.IsLoggedIn); // Only show the screen when we are not logged in
|
||||
}
|
||||
|
||||
public RenderFragment Render() => ComponentHelper.FromType<AuthenticationScreen>();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Moonlight.Client.UI.Layouts;
|
||||
|
||||
namespace Moonlight.Client.Interfaces;
|
||||
|
||||
public interface IAppLoader
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all"/>
|
||||
<PackageReference Include="MoonCore" Version="1.5.8" />
|
||||
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.0.1" />
|
||||
<PackageReference Include="MoonCore.Blazor" Version="1.2.1" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -29,4 +29,10 @@
|
||||
<Folder Include="wwwroot\css\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="MoonCore.Blazor.Tailwind">
|
||||
<HintPath>..\..\..\..\GitHub\Marcel-Baumgartner\MoonCore\MoonCore\MoonCore.Blazor.Tailwind\bin\Debug\net8.0\MoonCore.Blazor.Tailwind.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using MoonCore.Blazor.Tailwind.Extensions;
|
||||
using MoonCore.Blazor.Tailwind.Forms;
|
||||
using MoonCore.Blazor.Tailwind.Forms.Components;
|
||||
using MoonCore.Extensions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.PluginFramework.Services;
|
||||
@@ -48,8 +50,13 @@ builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
builder.Services.AddScoped(sp => new HttpApiClient(sp.GetRequiredService<HttpClient>()));
|
||||
|
||||
builder.Services.AutoAddServices<Program>();
|
||||
|
||||
builder.Services.AddMoonCoreBlazorTailwind();
|
||||
|
||||
FormComponentRepository.Set<string, StringComponent>();
|
||||
FormComponentRepository.Set<int, IntComponent>();
|
||||
|
||||
// Implementation service
|
||||
var implementationService = new ImplementationService();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using MoonCore.Attributes;
|
||||
using MoonCore.Blazor.Tailwind.Services;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Helpers;
|
||||
@@ -5,6 +6,7 @@ using Moonlight.Shared.Http.Responses.Auth;
|
||||
|
||||
namespace Moonlight.Client.Services;
|
||||
|
||||
[Scoped]
|
||||
public class IdentityService
|
||||
{
|
||||
public string Username { get; private set; }
|
||||
@@ -40,7 +42,7 @@ public class IdentityService
|
||||
IsLoggedIn = false;
|
||||
}
|
||||
|
||||
await OnStateChanged();
|
||||
//await OnStateChanged?.Invoke();
|
||||
}
|
||||
|
||||
public async Task Login(string token)
|
||||
|
||||
@@ -13,3 +13,65 @@
|
||||
#blazor-error-ui {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: block;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #9370DB;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
@apply border-t-primary-500
|
||||
}
|
||||
#loader:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
-webkit-animation: spin 3s linear infinite;
|
||||
animation: spin 3s linear infinite;
|
||||
@apply border-t-tertiary-500
|
||||
}
|
||||
#loader:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
-webkit-animation: spin 1.5s linear infinite;
|
||||
animation: spin 1.5s linear infinite;
|
||||
@apply border-t-info-500
|
||||
}
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,15 @@
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<LazyLoader Load="Load">
|
||||
|
||||
</LazyLoader>
|
||||
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center">
|
||||
<div id="loader"></div>
|
||||
</div>
|
||||
}
|
||||
else if (CurrentScreen != null)
|
||||
{
|
||||
<CascadingValue Value="this" IsFixed="true">
|
||||
@CurrentScreen
|
||||
</CascadingValue>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -60,21 +62,28 @@ else
|
||||
private bool IsLoading = true;
|
||||
private RenderFragment? CurrentScreen;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await lazyLoader.UpdateText("Retrieving data");
|
||||
await RunLoaders();
|
||||
if(!firstRender)
|
||||
return;
|
||||
|
||||
await RenderScreens();
|
||||
|
||||
IsLoading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Load();
|
||||
}
|
||||
|
||||
public async Task Reload()
|
||||
public async Task Load()
|
||||
{
|
||||
IsLoading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
//
|
||||
await RunLoaders();
|
||||
|
||||
// Screens
|
||||
await RenderScreens();
|
||||
|
||||
//
|
||||
IsLoading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task RunLoaders()
|
||||
@@ -87,6 +96,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Running application loader '{name}'", loader.GetType().Name);
|
||||
await loader.Load(ServiceProvider);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -110,9 +120,11 @@ else
|
||||
continue;
|
||||
|
||||
CurrentScreen = screen.Render();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
@using Moonlight.Client.UI.Layouts
|
||||
@using Moonlight.Client.Services
|
||||
@using Moonlight.Client.UI.Layouts
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
@inject ToastService ToastService
|
||||
|
||||
<div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 bg-gray-800/60 backdrop-blur px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
|
||||
@if (Layout.ShowMobileNavigation)
|
||||
@@ -35,7 +39,7 @@
|
||||
|
||||
<!-- Profile dropdown -->
|
||||
<div class="relative">
|
||||
<button type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
|
||||
<button @onclick="ToggleProfileNav" @onfocusout="ProfileNav_OnFocusOut" type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<img class="h-8 w-8 rounded-full" src="https://masuowo.xyz/assets/images/avatar.png" alt="">
|
||||
<span class="hidden lg:flex lg:items-center">
|
||||
@@ -51,17 +55,17 @@
|
||||
<!--
|
||||
Dropdown menu, show/hide based on menu state.
|
||||
|
||||
Entering: "transition ease-out duration-100"
|
||||
From: "transform opacity-0 scale-95"
|
||||
Entering: ""
|
||||
From: "transform scale-95"
|
||||
To: "transform opacity-100 scale-100"
|
||||
Leaving: "transition ease-in duration-75"
|
||||
From: "transform opacity-100 scale-100"
|
||||
To: "transform opacity-0 scale-95"
|
||||
-->
|
||||
<div class="hidden absolute right-0 z-10 mt-2.5 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
||||
<div class="@(ShowProfileNav ? "opacity-100" : "opacity-0") transition ease-out duration-100 absolute right-0 z-10 mt-2.5 w-44 origin-top-right rounded-md bg-gray-750 py-2 shadow-lg ring-1 ring-gray-100/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
||||
<!-- Active: "bg-gray-50", Not Active: "" -->
|
||||
<a href="#" class="block px-3 py-1 text-sm leading-6 text-gray-900" role="menuitem" tabindex="-1" id="user-menu-item-0">Your profile</a>
|
||||
<a href="#" class="block px-3 py-1 text-sm leading-6 text-gray-900" role="menuitem" tabindex="-1" id="user-menu-item-1">Sign out</a>
|
||||
<a href="/admin" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-0">Your profile</a>
|
||||
<a @onclick="Logout" @onclick:preventDefault href="#" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-1">Sign out</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,11 +76,42 @@
|
||||
{
|
||||
[Parameter] public MainLayout Layout { get; set; }
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
private bool ShowProfileNav = false;
|
||||
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if(!firstRender)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
Layout.OnStateChanged += () => InvokeAsync(StateHasChanged);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ToggleProfileNav()
|
||||
{
|
||||
ShowProfileNav = !ShowProfileNav;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private Task ProfileNav_OnFocusOut()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(200);
|
||||
|
||||
ShowProfileNav = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Logout()
|
||||
{
|
||||
await IdentityService.Logout();
|
||||
await ToastService.Info("Successfully logged out");
|
||||
|
||||
await Layout.Load();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
|
||||
<!-- Sidebar component, swap this element with another sidebar if you like -->
|
||||
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-gray-900 px-6 pb-4">
|
||||
<div class="flex h-16 shrink-0 items-center">
|
||||
<img class="h-8 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
|
||||
<div class="flex h-16 shrink-0 items-center">a
|
||||
<img class="h-8 w-auto" src="/logo.svg" alt="Moonlight">
|
||||
</div>
|
||||
<nav class="flex flex-1 flex-col">
|
||||
<ul role="list" class="flex flex-1 flex-col gap-y-7">
|
||||
|
||||
@@ -1,18 +1,141 @@
|
||||
@page "/auth/register"
|
||||
@page "/auth/login"
|
||||
|
||||
@using MoonCore.Blazor.Tailwind.Forms.Components
|
||||
@using MoonCore.Helpers
|
||||
@using Moonlight.Client.Services
|
||||
@using Moonlight.Client.UI.Layouts
|
||||
@using Moonlight.Shared.Http.Requests.Auth
|
||||
@using Moonlight.Shared.Http.Responses.Auth
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@inject NavigationManager Navigation
|
||||
@inject HttpApiClient ApiClient
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
@{
|
||||
var url = new Uri(Navigation.Uri);
|
||||
var isRegister = url.LocalPath.StartsWith("/auth/register");
|
||||
}
|
||||
|
||||
@if (isRegister)
|
||||
{
|
||||
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center px-5 md:px-0">
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (isRegister)
|
||||
{
|
||||
<div class="card card-body w-full max-w-lg">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
|
||||
<img class="mx-auto h-16 w-auto" src="/logolong.webp" alt="Moonlight">
|
||||
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-100">Register your account</h2>
|
||||
</div>
|
||||
|
||||
<HandleForm @ref="RegisterForm" Model="RegisterRequest" OnValidSubmit="OnSubmitRegister">
|
||||
<GeneratedForm Model="RegisterRequest" OnConfigure="OnConfigureRegister" Gap="gap-x-6 gap-y-3"/>
|
||||
</HandleForm>
|
||||
|
||||
<div class="mt-5 flex flex-col justify-center">
|
||||
<WButton OnClick="_ => RegisterForm.Submit()" CssClasses="btn btn-primary">Register</WButton>
|
||||
<p class="mt-3 text-center text-sm text-gray-500">
|
||||
Already registered?
|
||||
<a href="/auth/login" class="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card card-body w-full max-w-lg">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-sm mb-5">
|
||||
<img class="mx-auto h-16 w-auto" src="/logolong.webp" alt="Moonlight">
|
||||
<h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-100">Sign in to your account</h2>
|
||||
</div>
|
||||
|
||||
<HandleForm @ref="LoginForm" Model="LoginRequest" OnValidSubmit="OnSubmitLogin">
|
||||
<GeneratedForm Model="LoginRequest" OnConfigure="OnConfigureLogin" Gap="gap-x-6 gap-y-3"/>
|
||||
</HandleForm>
|
||||
|
||||
<div class="mt-5 flex flex-col justify-center">
|
||||
<WButton OnClick="_ => LoginForm.Submit()" CssClasses="btn btn-primary">Login</WButton>
|
||||
<p class="mt-3 text-center text-sm text-gray-500">
|
||||
Need an account registered?
|
||||
<a href="/auth/register" class="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">Register</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||
|
||||
// Page change handling
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Navigation.LocationChanged += NavigationOnLocationChanged;
|
||||
}
|
||||
|
||||
private async void NavigationOnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
=> await InvokeAsync(StateHasChanged);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Navigation.LocationChanged -= NavigationOnLocationChanged;
|
||||
}
|
||||
|
||||
// Register
|
||||
private RegisterRequest RegisterRequest = new();
|
||||
private HandleForm RegisterForm;
|
||||
|
||||
private async Task OnSubmitRegister()
|
||||
{
|
||||
var response = await ApiClient.PostJson<RegisterResponse>("api/auth/register", RegisterRequest);
|
||||
await IdentityService.Login(response.Token);
|
||||
|
||||
await HandleAfterAuthPage();
|
||||
}
|
||||
|
||||
private void OnConfigureRegister(FormConfiguration<RegisterRequest> configuration)
|
||||
{
|
||||
configuration.WithField(x => x.Username, fieldConfiguration => { fieldConfiguration.Columns = 6; });
|
||||
|
||||
configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; });
|
||||
|
||||
configuration
|
||||
.WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; })
|
||||
.WithComponent<StringComponent>(component => { component.Type = "password"; });
|
||||
}
|
||||
|
||||
// Login
|
||||
private LoginRequest LoginRequest = new();
|
||||
private HandleForm LoginForm;
|
||||
|
||||
private async Task OnSubmitLogin()
|
||||
{
|
||||
var response = await ApiClient.PostJson<LoginResponse>("api/auth/login", LoginRequest);
|
||||
await IdentityService.Login(response.Token);
|
||||
|
||||
await HandleAfterAuthPage();
|
||||
}
|
||||
|
||||
private void OnConfigureLogin(FormConfiguration<LoginRequest> configuration)
|
||||
{
|
||||
configuration.WithField(x => x.Email, fieldConfiguration => { fieldConfiguration.Columns = 6; });
|
||||
|
||||
configuration
|
||||
.WithField(x => x.Password, fieldConfiguration => { fieldConfiguration.Columns = 6; })
|
||||
.WithComponent<StringComponent>(component => { component.Type = "password"; });
|
||||
}
|
||||
|
||||
// Navigation handling
|
||||
private async Task HandleAfterAuthPage()
|
||||
{
|
||||
var url = new Uri(Navigation.Uri);
|
||||
|
||||
if (url.LocalPath.StartsWith("/auth/login") || url.LocalPath.StartsWith("/auth/register"))
|
||||
Navigation.NavigateTo("/");
|
||||
|
||||
await Layout.Load();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 41 KiB |
@@ -12,10 +12,12 @@
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-950 text-white font-inter">
|
||||
<body class="bg-gray-950 text-white font-inter h-full">
|
||||
<div id="app">
|
||||
|
||||
|
||||
<div class="h-full w-full min-h-[100dvh] flex items-center justify-center">
|
||||
<div id="loader"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
14
Moonlight.Client/wwwroot/logo.svg
Normal file
14
Moonlight.Client/wwwroot/logo.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="301px" viewBox="0 0 256 301" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<defs>
|
||||
<linearGradient x1="2.17771739%" y1="34.7938955%" x2="92.7221942%" y2="91.3419405%" id="linearGradient-1">
|
||||
<stop stop-color="#41A7EF" offset="0%"></stop>
|
||||
<stop stop-color="#813DDE" offset="54.2186236%"></stop>
|
||||
<stop stop-color="#8F2EE2" offset="74.4988788%"></stop>
|
||||
<stop stop-color="#A11CE6" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M124.183681,101.699 C124.183681,66.515 136.256681,34.152 156.486681,8.525 C159.197681,5.092 156.787681,0.069 152.412681,0.012 C151.775681,0.004 151.136681,0 150.497681,0 C67.6206813,0 0.390681343,66.99 0.00168134279,149.775 C-0.386318657,232.369 66.4286813,300.195 149.019681,300.988 C189.884681,301.381 227.036681,285.484 254.376681,259.395 C257.519681,256.396 255.841681,251.082 251.548681,250.42 C179.413681,239.291 124.183681,176.949 124.183681,101.699" fill="url(#linearGradient-1)"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Moonlight.Client/wwwroot/logolong.webp
Normal file
BIN
Moonlight.Client/wwwroot/logolong.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "Moonlight.Client",
|
||||
"name": "Moonlight Client",
|
||||
"short_name": "Moonlight.Client",
|
||||
"id": "./",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#03173d",
|
||||
"theme_color": "#030b1f",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user