304 lines
14 KiB
Plaintext
304 lines
14 KiB
Plaintext
@inherits LayoutComponentBase
|
|
@using System.Reflection
|
|
@using ErsatzTV.Application.Search
|
|
@using ErsatzTV.Core.Interfaces.Search
|
|
@using ErsatzTV.Extensions
|
|
@implements IDisposable
|
|
@inject NavigationManager NavigationManager
|
|
@inject IMediator Mediator
|
|
@inject SystemStartup SystemStartup
|
|
@inject ISearchTargets SearchTargets;
|
|
|
|
<MudThemeProvider Theme="ErsatzTvTheme"/>
|
|
<MudDialogProvider BackdropClick="false"/>
|
|
<MudSnackbarProvider/>
|
|
<MudPopoverProvider />
|
|
|
|
<MudLayout @onclick="@(() => _isOpen = false)">
|
|
<MudAppBar Elevation="1" Class="app-bar">
|
|
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
|
|
<div style="min-width: 240px" class="ml-3 d-none d-md-flex">
|
|
<a href="">
|
|
<img src="images/ersatztv.png" alt="ErsatzTV"/>
|
|
</a>
|
|
</div>
|
|
@if (SystemStartup.IsDatabaseReady && SystemStartup.IsSearchIndexReady)
|
|
{
|
|
<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="DropdownWidth.Relative">
|
|
@if (!string.IsNullOrWhiteSpace(_query) && _query.Length >= 3)
|
|
{
|
|
var matches = _searchTargets.Where(s => s.Name.Contains(_query, StringComparison.CurrentCultureIgnoreCase)).ToList();
|
|
if (matches.Any())
|
|
{
|
|
<MudList T="SearchTargetViewModel" ReadOnly="false" 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>
|
|
}
|
|
<div class="flex-grow-1 d-none d-md-flex"></div>
|
|
<div style="align-items: center; display: flex;" class="d-none d-md-flex">
|
|
@if (SystemStartup.IsDatabaseReady && SystemStartup.IsSearchIndexReady)
|
|
{
|
|
<MudLink Color="Color.Info" Href="@IptvUrl("channels.m3u")" Target="_blank" Underline="Underline.None">M3U</MudLink>
|
|
<MudLink Color="Color.Info" Href="@IptvUrl("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" Href="https://ersatztv.org" Target="_blank"/>
|
|
</MudTooltip>
|
|
<MudTooltip Text="Discord">
|
|
<MudIconButton Icon="fab fa-discord" Color="Color.Primary" Href="https://discord.gg/hHaJm3yGy6" Target="_blank"/>
|
|
</MudTooltip>
|
|
<MudTooltip Text="GitHub">
|
|
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Primary" Href="https://github.com/ErsatzTV/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>
|
|
</div>
|
|
</MudAppBar>
|
|
@if (SystemStartup.IsDatabaseReady && SystemStartup.IsSearchIndexReady)
|
|
{
|
|
<MudDrawer @bind-Open="@_drawerIsOpen" 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">
|
|
<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">
|
|
<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>
|
|
<MudNavLink Href="media/browser/images">Images</MudNavLink>
|
|
</MudNavGroup>
|
|
<MudNavGroup Title="Lists">
|
|
<MudNavLink Href="media/collections">Collections</MudNavLink>
|
|
<MudNavLink Href="media/playlists">Playlists</MudNavLink>
|
|
<MudNavLink Href="media/trakt/lists">Trakt Lists</MudNavLink>
|
|
<MudNavLink Href="media/filler/presets">Filler Presets</MudNavLink>
|
|
</MudNavGroup>
|
|
<MudNavGroup Title="Scheduling">
|
|
<MudNavLink Href="schedules">Schedules</MudNavLink>
|
|
<MudNavLink Href="blocks">Blocks</MudNavLink>
|
|
<MudNavLink Href="templates">Templates</MudNavLink>
|
|
<MudNavLink Href="decos">Decos</MudNavLink>
|
|
<MudNavLink Href="deco-templates">Deco Templates</MudNavLink>
|
|
<MudNavLink Href="playouts">Playouts</MudNavLink>
|
|
</MudNavGroup>
|
|
<MudNavLink Href="settings">Settings</MudNavLink>
|
|
<MudNavGroup Title="Support" Expanded="true">
|
|
<MudNavLink Href="system/health">Health Checks</MudNavLink>
|
|
<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 _drawerIsOpen = true;
|
|
private bool _isOpen;
|
|
private List<SearchTargetViewModel> _searchTargets;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
SystemStartup.OnDatabaseReady += OnStartupProgress;
|
|
SystemStartup.OnSearchIndexReady += OnStartupProgress;
|
|
|
|
SearchTargets.OnSearchTargetsChanged += OnSearchTargetsChanged;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
SystemStartup.OnDatabaseReady -= OnStartupProgress;
|
|
SystemStartup.OnSearchIndexReady -= OnStartupProgress;
|
|
|
|
SearchTargets.OnSearchTargetsChanged -= OnSearchTargetsChanged;
|
|
|
|
_cts.Cancel();
|
|
_cts.Dispose();
|
|
}
|
|
|
|
private static MudTheme ErsatzTvTheme => new()
|
|
{
|
|
PaletteLight = new PaletteLight
|
|
{
|
|
ActionDefault = "rgba(255,255,255, 0.80)",
|
|
Primary = "#009000",
|
|
Secondary = "#009090",
|
|
Surface = "#1f1f1f",
|
|
AppbarBackground = "#121212",
|
|
AppbarText = "rgba(255,255,255, 0.80)",
|
|
DrawerBackground = "#1f1f1f",
|
|
DrawerText = "rgba(255,255,255, 0.80)",
|
|
Background = "#272727",
|
|
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)",
|
|
TableLines = "rgba(255,255,255,0.11)",
|
|
Info = "#00c0c0",
|
|
Tertiary = "#00c000",
|
|
White = Colors.Shades.White
|
|
}
|
|
};
|
|
|
|
private string Query
|
|
{
|
|
get => _query;
|
|
set
|
|
{
|
|
if (_query == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_query = value;
|
|
_isOpen = true;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async void OnStartupProgress(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
catch
|
|
{
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
await base.OnParametersSetAsync();
|
|
_query = NavigationManager.Uri.GetSearchQuery();
|
|
|
|
if (SystemStartup.IsDatabaseReady && _searchTargets is null)
|
|
{
|
|
_searchTargets = await Mediator.Send(new QuerySearchTargets(), _cts.Token);
|
|
}
|
|
}
|
|
|
|
protected async void OnSearchTargetsChanged(object sender, EventArgs e) => _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
|
|
};
|
|
|
|
private string IptvUrl(string path)
|
|
{
|
|
var uri = new Uri(NavigationManager.Uri);
|
|
return $"{uri.Scheme}://{uri.Host}:{Settings.StreamingPort}/iptv/{path}";
|
|
}
|
|
|
|
private void ToggleDrawer()
|
|
{
|
|
_drawerIsOpen = !_drawerIsOpen;
|
|
}
|
|
} |