Files
ersatztv/ErsatzTV/Shared/MainLayout.razor
T

238 lines
10 KiB
Plaintext

@inherits LayoutComponentBase
@using System.Reflection
@using ErsatzTV.Extensions
@using ErsatzTV.Application.Search
@implements IDisposable
@inject NavigationManager NavigationManager
@inject IMediator Mediator
<MudThemeProvider Theme="_ersatzTvTheme"/>
<MudDialogProvider DisableBackdropClick="true"/>
<MudSnackbarProvider/>
<MudLayout @onclick="@(() => _isOpen = false)">
<MudAppBar Elevation="1" Class="app-bar">
<div style="min-width: 240px">
<a href="">
<img src="images/ersatztv.png" alt="ErsatzTV"/>
</a>
</div>
<div class="search-form">
<EditForm Model="@_dummyModel" OnSubmit="@(_ => PerformSearch())">
<MudTextField T="string"
@bind-Value="@Query"
AdornmentIcon="@Icons.Material.Filled.Search"
Adornment="Adornment.Start"
Variant="Variant.Outlined"
Immediate="true"
Class="search-bar"
@onclick="@(() => _isOpen = true)"
OnKeyUp="OnKeyUp">
</MudTextField>
<MudPopover Open="@_isOpen" MaxHeight="300" AnchorOrigin="Origin.BottomCenter" TransformOrigin="Origin.TopCenter" RelativeWidth="true">
@if (!string.IsNullOrWhiteSpace(_query) && _query.Length >= 3)
{
var matches = _searchTargets.Where(s => s.Name.Contains(_query, StringComparison.CurrentCultureIgnoreCase)).ToList();
if (matches.Any())
{
<MudList Clickable="true" Dense="true">
@foreach (SearchTargetViewModel searchTarget in matches)
{
<MudListItem @key="@searchTarget" OnClick="@(() => NavigateTo(searchTarget))">
<MudText Typo="Typo.body1">@searchTarget.Name</MudText>
<MudText Typo="Typo.subtitle1" Class="mud-text-disabled">
@(searchTarget.Kind switch
{
SearchTargetKind.Channel => "Channel",
SearchTargetKind.FFmpegProfile => "FFmpeg Profile",
SearchTargetKind.ChannelWatermark => "Channel Watermark",
SearchTargetKind.Collection => "Collection",
SearchTargetKind.MultiCollection => "Multi Collection",
SearchTargetKind.SmartCollection => "Smart Collection",
SearchTargetKind.Schedule => "Schedule",
SearchTargetKind.ScheduleItems => "Schedule Items",
_ => string.Empty
})
</MudText>
</MudListItem>
}
</MudList>
}
}
</MudPopover>
</EditForm>
</div>
<MudSpacer/>
<MudLink Color="Color.Info" Href="iptv/channels.m3u" Target="_blank" Underline="Underline.None">M3U</MudLink>
<MudLink Color="Color.Info" Href="iptv/xmltv.xml" Target="_blank" Class="mx-4" Underline="Underline.None">XMLTV</MudLink>
@* <MudLink Color="Color.Info" Href="/swagger" Target="_blank" Class="mr-4" Underline="Underline.None">API</MudLink> *@
<MudTooltip Text="Documentation">
<MudIconButton Icon="@Icons.Material.Filled.Help" Color="Color.Primary" Link="https://ersatztv.org" Target="_blank"/>
</MudTooltip>
<MudTooltip Text="Discord">
<MudIconButton Icon="fab fa-discord" Color="Color.Primary" Link="https://discord.gg/hHaJm3yGy6" Target="_blank"/>
</MudTooltip>
<MudTooltip Text="GitHub">
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Primary" Link="https://github.com/jasongdove/ErsatzTV" Target="_blank"/>
</MudTooltip>
<AuthorizeView>
<form action="/account/logout" method="post">
<MudTooltip Text="Logout">
<MudIconButton Icon="@Icons.Material.Filled.Logout" Color="Color.Secondary" ButtonType="ButtonType.Submit"/>
</MudTooltip>
</form>
</AuthorizeView>
</MudAppBar>
<MudDrawer Open="true" Elevation="2" ClipMode="DrawerClipMode.Always">
<MudNavMenu>
<MudNavLink Href="channels">Channels</MudNavLink>
<MudNavLink Href="ffmpeg">FFmpeg Profiles</MudNavLink>
<MudNavLink Href="watermarks">Watermarks</MudNavLink>
<MudNavGroup Title="Media Sources" Expanded="true">
<MudNavLink Href="media/sources/local">Local</MudNavLink>
<MudNavLink Href="media/sources/emby">Emby</MudNavLink>
<MudNavLink Href="media/sources/jellyfin">Jellyfin</MudNavLink>
<MudNavLink Href="media/sources/plex">Plex</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="Media" Expanded="true">
<MudNavLink Href="media/libraries">Libraries</MudNavLink>
<MudNavLink Href="media/trash">Trash</MudNavLink>
<MudNavLink Href="media/tv/shows">TV Shows</MudNavLink>
<MudNavLink Href="media/movies">Movies</MudNavLink>
<MudNavLink Href="media/music/artists">Music</MudNavLink>
<MudNavLink Href="media/other/videos">Other Videos</MudNavLink>
<MudNavLink Href="media/music/songs">Songs</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="Lists" Expanded="true">
<MudNavLink Href="media/collections">Collections</MudNavLink>
<MudNavLink Href="media/trakt/lists">Trakt Lists</MudNavLink>
<MudNavLink Href="media/filler/presets">Filler Presets</MudNavLink>
</MudNavGroup>
<MudNavLink Href="schedules">Schedules</MudNavLink>
<MudNavLink Href="playouts">Playouts</MudNavLink>
<MudNavLink Href="settings">Settings</MudNavLink>
<MudNavGroup Title="Support" Expanded="true">
<MudNavLink Href="system/logs">Logs</MudNavLink>
<MudNavLink Href="system/troubleshooting">Troubleshooting</MudNavLink>
</MudNavGroup>
<MudDivider Class="my-6" DividerType="DividerType.Middle"/>
<MudContainer Style="text-align: right" Class="mr-6">
<MudText Typo="Typo.body2">ErsatzTV Version</MudText>
<MudText Typo="Typo.body2" Color="Color.Info">@InfoVersion</MudText>
</MudContainer>
</MudNavMenu>
</MudDrawer>
<MudMainContent>
@Body
</MudMainContent>
</MudLayout>
@code {
private static readonly string InfoVersion = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "unknown";
private readonly CancellationTokenSource _cts = new();
private string _query;
private record SearchModel;
private readonly SearchModel _dummyModel = new();
private bool _isOpen;
private List<SearchTargetViewModel> _searchTargets;
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
private MudTheme _ersatzTvTheme => new()
{
Palette = new PaletteDark
{
ActionDefault = "rgba(255,255,255, 0.80)",
Primary = "#009000",
Secondary = "#009090",
AppbarBackground = "#121212",
Background = "#272727",
DrawerBackground = "#1f1f1f",
Surface = "#1f1f1f",
DrawerText = "rgba(255,255,255, 0.80)",
TextPrimary = "rgba(255,255,255, 0.80)",
TextSecondary = "rgba(255,255,255, 0.80)",
TextDisabled = "rgba(255,255,255, 0.40)",
ActionDisabled = "rgba(255,255,255, 0.40)",
TableHover = "rgba(255,255,255, 0.10)",
Info = "#00c0c0",
Tertiary = "#00c000",
White = Colors.Shades.White
}
};
private string Query
{
get => _query;
set
{
if (_query == value)
{
return;
}
_query = value;
_isOpen = true;
StateHasChanged();
}
}
protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
_query = NavigationManager.Uri.GetSearchQuery();
if (_searchTargets is null)
{
_searchTargets = await Mediator.Send(new QuerySearchTargets(), _cts.Token);
}
}
private void PerformSearch()
{
NavigationManager.NavigateTo(_query.GetRelativeSearchQuery(), true);
StateHasChanged();
}
private void OnKeyUp(KeyboardEventArgs args)
{
switch (args.Key)
{
case "Enter":
case "NumpadEnter":
_isOpen = false;
break;
case "Escape":
_isOpen = false;
break;
}
}
private void NavigateTo(SearchTargetViewModel searchTarget) =>
// need to force smart collections to navigate since the query string is all that differs
NavigationManager.NavigateTo(UrlFor(searchTarget), searchTarget.Kind is SearchTargetKind.SmartCollection);
private string UrlFor(SearchTargetViewModel searchTarget) =>
searchTarget.Kind switch
{
SearchTargetKind.Channel => $"channels/{searchTarget.Id}",
SearchTargetKind.FFmpegProfile => $"ffmpeg/{searchTarget.Id}",
SearchTargetKind.ChannelWatermark => $"watermarks/{searchTarget.Id}",
SearchTargetKind.Collection => $"media/collections/{searchTarget.Id}",
SearchTargetKind.MultiCollection => $"media/multi-collections/{searchTarget.Id}/edit",
SearchTargetKind.SmartCollection when searchTarget is SmartCollectionSearchTargetViewModel sc =>
sc.Query.GetRelativeSearchQuery(),
SearchTargetKind.Schedule => $"schedules/{searchTarget.Id}",
SearchTargetKind.ScheduleItems => $"schedules/{searchTarget.Id}/items",
_ => null
};
}