249 lines
11 KiB
Plaintext
249 lines
11 KiB
Plaintext
@using Microsoft.AspNetCore.Components.Authorization
|
|
@using MoonCore.Blazor.Tailwind.Auth
|
|
@using Moonlight.Client.Interfaces
|
|
@using Moonlight.Client.Models
|
|
@using Moonlight.Client.UI.Layouts
|
|
|
|
@inject ToastService ToastService
|
|
@inject NavigationManager Navigation
|
|
@inject AuthenticationStateManager AuthStateManager
|
|
@inject IEnumerable<ISidebarItemProvider> SidebarItemProviders
|
|
|
|
@{
|
|
var url = new Uri(Navigation.Uri);
|
|
}
|
|
|
|
<div class="fixed inset-y-0 left-0 w-64 max-lg:hidden">
|
|
<nav class="flex h-full min-h-0 flex-col">
|
|
<div class="flex flex-col border-b p-4 border-white/5">
|
|
<span class="relative">
|
|
<div type="button"
|
|
class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-lg font-medium text-gray-100">
|
|
<span
|
|
class="inline-grid shrink-0 align-middle">
|
|
<img class="h-8 rounded-full"
|
|
src="/svg/logo.svg"
|
|
alt=""/>
|
|
</span>
|
|
<span class="truncate">Moonlight</span>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
<div class="flex flex-1 flex-col overflow-y-auto p-4 mt-1">
|
|
<div class="flex flex-col gap-1.5">
|
|
@foreach (var item in Items)
|
|
{
|
|
if (!string.IsNullOrEmpty(item.Key))
|
|
{
|
|
<h3 class="mt-4 px-2 text-sm/5 font-medium text-gray-400">
|
|
@item.Key
|
|
</h3>
|
|
}
|
|
|
|
foreach (var sidebarItem in item.Value)
|
|
{
|
|
var isMatch = sidebarItem.RequiresExactMatch
|
|
? url.LocalPath == sidebarItem.Path
|
|
: url.LocalPath.StartsWith(sidebarItem.Path);
|
|
|
|
@if (isMatch)
|
|
{
|
|
<div class="relative">
|
|
<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-white"
|
|
style="opacity: 1;">
|
|
</span>
|
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal bg-white/5 sm:py-2 sm:text-sm/5 text-white"
|
|
href="@sidebarItem.Path">
|
|
<i class="@sidebarItem.Icon text-lg"></i>
|
|
<span class="truncate">
|
|
@sidebarItem.Name
|
|
</span>
|
|
</a>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="relative">
|
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal sm:py-2 sm:text-sm/5 text-white hover:bg-white/5"
|
|
href="@sidebarItem.Path">
|
|
<i class="@sidebarItem.Icon text-lg"></i>
|
|
<span class="truncate">
|
|
@sidebarItem.Name
|
|
</span>
|
|
</a>
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col border-t p-4 max-lg:hidden border-white/5 mt-2.5">
|
|
<div
|
|
class="flex w-full items-center px-2 py-2.5 gap-6 rounded-lg text-left text-base/6 font-medium sm:py-2 sm:text-sm/5 text-white">
|
|
<div class="flex min-w-0 items-center gap-3">
|
|
<span class="inline-grid shrink-0 align-middle">
|
|
<img class="h-8 rounded-full"
|
|
src="/img/pfp_placeholder.png"
|
|
alt=""/>
|
|
</span>
|
|
<div class="min-w-0">
|
|
<div class="block truncate text-sm/5 font-medium text-white">
|
|
@Username
|
|
</div>
|
|
<div class="block truncate text-xs/5 font-normal text-gray-400">
|
|
@Email
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a href="#" @onclick:preventDefault @onclick="Logout" class="flex items-center">
|
|
<i class="icon-log-out text-lg"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
|
|
<div
|
|
class="lg:hidden z-50 transition-opacity ease-linear duration-300 @(Layout.ShowMobileNavigation ? "opacity-100" : "opacity-0 pointer-events-none")"
|
|
role="dialog" tabindex="-1">
|
|
<div class="fixed inset-0 bg-black/30"></div>
|
|
<div class="fixed inset-y-0 w-full max-w-80 p-2">
|
|
<div
|
|
class="relative flex h-full flex-col rounded-lg shadow-xs ring-1 bg-gray-900 ring-white/10 transition ease-in-out duration-300 transform @(Layout.ShowMobileNavigation ? "translate-x-0" : "-translate-x-full")">
|
|
<div class="border-b p-4 border-white/5 flex justify-between px-5 pt-3">
|
|
<div class="flex items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 font-medium sm:py-2 sm:text-sm/5 text-white">
|
|
<div data-slot="avatar"
|
|
class="inline-grid shrink-0 align-middle">
|
|
<img
|
|
class="h-8 rounded-full" src="/svg/logo.svg" alt=""/>
|
|
</div>
|
|
<div class="truncate">Moonlight</div>
|
|
</div>
|
|
|
|
<button @onclick="Layout.ToggleMobileNavigation" aria-label="Close navigation" type="button"
|
|
class="relative flex min-w-0 items-center gap-3 rounded-lg p-2 text-left text-base/6 text-white">
|
|
<i class="icon-x text-lg"></i>
|
|
</button>
|
|
</div>
|
|
<nav class="flex h-full min-h-0 flex-col">
|
|
<div
|
|
class="flex flex-1 flex-col overflow-y-auto p-4">
|
|
<div data-slot="section" class="flex flex-col gap-0.5">
|
|
@foreach (var item in Items)
|
|
{
|
|
if (!string.IsNullOrEmpty(item.Key))
|
|
{
|
|
<h3 class="mt-4 px-2 text-sm/5 font-medium text-gray-400">
|
|
@item.Key
|
|
</h3>
|
|
}
|
|
|
|
foreach (var sidebarItem in item.Value)
|
|
{
|
|
var isMatch = sidebarItem.RequiresExactMatch
|
|
? url.LocalPath == sidebarItem.Path
|
|
: url.LocalPath.StartsWith(sidebarItem.Path);
|
|
|
|
@if (isMatch)
|
|
{
|
|
<div class="relative">
|
|
<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-white"
|
|
style="opacity: 1;">
|
|
</span>
|
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal bg-white/5 sm:py-2 sm:text-sm/5 text-white"
|
|
href="@sidebarItem.Path">
|
|
<i class="@sidebarItem.Icon text-lg"></i>
|
|
<span class="truncate">
|
|
@sidebarItem.Name
|
|
</span>
|
|
</a>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="relative">
|
|
<a class="flex w-full items-center gap-3 rounded-lg px-3 py-1.5 text-left text-base/6 font-normal sm:py-2 sm:text-sm/5 text-white hover:bg-white/5"
|
|
href="@sidebarItem.Path">
|
|
<i class="@sidebarItem.Icon text-lg"></i>
|
|
<span class="truncate">
|
|
@sidebarItem.Name
|
|
</span>
|
|
</a>
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="mt-8 flex-1"></div>
|
|
<div class="flex flex-col gap-0.5">
|
|
<div class="relative">
|
|
|
|
<a class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 sm:py-2 sm:text-sm/5 text-white"
|
|
href="#" @onclick:preventDefault @onclick="Logout">
|
|
<i class="icon-log-out"></i>
|
|
<span class="truncate">Logout</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@code
|
|
{
|
|
[Parameter] public MainLayout Layout { get; set; }
|
|
[CascadingParameter] public Task<AuthenticationState> AuthState { get; set; }
|
|
|
|
private Dictionary<string, SidebarItem[]> Items = new();
|
|
|
|
private string Username;
|
|
private string Email;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
var identity = await AuthState;
|
|
|
|
Username = identity.User.Claims.First(x => x.Type == "username").Value;
|
|
Email = identity.User.Claims.First(x => x.Type == "email").Value;
|
|
}
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
var sidebarItems = new List<SidebarItem>();
|
|
|
|
foreach (var provider in SidebarItemProviders)
|
|
provider.ModifySidebar(sidebarItems);
|
|
|
|
Items = sidebarItems
|
|
//.Where(x => x.Permission == null || (x.Permission != null && IdentityService.HasPermission(x.Permission)))
|
|
.GroupBy(x => x.Group ?? "")
|
|
.OrderByDescending(x => string.IsNullOrEmpty(x.Key))
|
|
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Priority).ToArray());
|
|
}
|
|
|
|
protected override Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (!firstRender)
|
|
return Task.CompletedTask;
|
|
|
|
Layout.OnStateChanged += () => InvokeAsync(StateHasChanged);
|
|
|
|
Navigation.LocationChanged += async (_, _) =>
|
|
{
|
|
if (!Layout.ShowMobileNavigation)
|
|
return;
|
|
|
|
await Layout.ToggleMobileNavigation();
|
|
};
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task Logout()
|
|
{
|
|
await AuthStateManager.Logout();
|
|
}
|
|
} |