new movie layout, new dark ui (#55)
* include cache header on artwork responses * rework movie page to include fan art * full width app bar * dark mode * cleanup * fix placeholder color
This commit is contained in:
@@ -42,6 +42,7 @@ namespace ErsatzTV.Application.Images.Queries
|
||||
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
|
||||
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
|
||||
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder),
|
||||
ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder),
|
||||
_ => FileSystemLayout.LegacyImageCacheFolder
|
||||
};
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ namespace ErsatzTV.Application.Movies
|
||||
metadata.Title,
|
||||
metadata.Year?.ToString(),
|
||||
metadata.Plot,
|
||||
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Poster))
|
||||
.Match(a => a.Path, string.Empty));
|
||||
Artwork(metadata, ArtworkKind.Poster),
|
||||
Artwork(metadata, ArtworkKind.FanArt));
|
||||
}
|
||||
|
||||
private static string Artwork(Metadata metadata, ArtworkKind artworkKind) =>
|
||||
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind))
|
||||
.Match(a => a.Path, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ErsatzTV.Application.Movies
|
||||
{
|
||||
public record MovieViewModel(string Title, string Year, string Plot, string Poster);
|
||||
public record MovieViewModel(string Title, string Year, string Plot, string Poster, string FanArt);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
Poster = 0,
|
||||
Thumbnail = 1,
|
||||
Logo = 2
|
||||
Logo = 2,
|
||||
FanArt = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,6 @@ namespace ErsatzTV.Core
|
||||
public static readonly string PosterCacheFolder = Path.Combine(ArtworkCacheFolder, "posters");
|
||||
public static readonly string ThumbnailCacheFolder = Path.Combine(ArtworkCacheFolder, "thumbnails");
|
||||
public static readonly string LogoCacheFolder = Path.Combine(ArtworkCacheFolder, "logos");
|
||||
public static readonly string FanArtCacheFolder = Path.Combine(ArtworkCacheFolder, "fanart");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +97,10 @@ namespace ErsatzTV.Core.Metadata
|
||||
|
||||
metadata.Artwork ??= new List<Artwork>();
|
||||
|
||||
Option<Artwork> maybePoster =
|
||||
Option<Artwork> maybeArtwork =
|
||||
Optional(metadata.Artwork).Flatten().FirstOrDefault(a => a.ArtworkKind == artworkKind);
|
||||
|
||||
bool shouldRefresh = maybePoster.Match(
|
||||
bool shouldRefresh = maybeArtwork.Match(
|
||||
artwork => artwork.DateUpdated < lastWriteTime,
|
||||
true);
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
_logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile);
|
||||
string cacheName = _imageCache.CopyArtworkToCache(artworkFile, artworkKind);
|
||||
|
||||
maybePoster.Match(
|
||||
maybeArtwork.Match(
|
||||
artwork =>
|
||||
{
|
||||
artwork.Path = cacheName;
|
||||
|
||||
@@ -80,7 +80,8 @@ namespace ErsatzTV.Core.Metadata
|
||||
.GetOrAdd(libraryPath, file)
|
||||
.BindT(movie => UpdateStatistics(movie, ffprobePath).MapT(_ => movie))
|
||||
.BindT(UpdateMetadata)
|
||||
.BindT(UpdatePoster);
|
||||
.BindT(UpdatePoster)
|
||||
.BindT(UpdateFanArt);
|
||||
|
||||
maybeMovie.IfLeft(
|
||||
error => _logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value));
|
||||
@@ -139,7 +140,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
{
|
||||
try
|
||||
{
|
||||
await LocatePoster(movie).IfSomeAsync(
|
||||
await LocateArtwork(movie, ArtworkKind.Poster).IfSomeAsync(
|
||||
async posterFile =>
|
||||
{
|
||||
MovieMetadata metadata = movie.MovieMetadata.Head();
|
||||
@@ -157,6 +158,28 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Movie>> UpdateFanArt(Movie movie)
|
||||
{
|
||||
try
|
||||
{
|
||||
await LocateArtwork(movie, ArtworkKind.FanArt).IfSomeAsync(
|
||||
async posterFile =>
|
||||
{
|
||||
MovieMetadata metadata = movie.MovieMetadata.Head();
|
||||
if (RefreshArtwork(posterFile, metadata, ArtworkKind.FanArt))
|
||||
{
|
||||
await _movieRepository.Update(movie);
|
||||
}
|
||||
});
|
||||
|
||||
return movie;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BaseError.New(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private Option<string> LocateNfoFile(Movie movie)
|
||||
{
|
||||
string path = movie.MediaVersions.Head().MediaFiles.Head().Path;
|
||||
@@ -167,12 +190,19 @@ namespace ErsatzTV.Core.Metadata
|
||||
.HeadOrNone();
|
||||
}
|
||||
|
||||
private Option<string> LocatePoster(Movie movie)
|
||||
private Option<string> LocateArtwork(Movie movie, ArtworkKind artworkKind)
|
||||
{
|
||||
string segment = artworkKind switch
|
||||
{
|
||||
ArtworkKind.Poster => "poster",
|
||||
ArtworkKind.FanArt => "fanart",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(artworkKind))
|
||||
};
|
||||
|
||||
string path = movie.MediaVersions.Head().MediaFiles.Head().Path;
|
||||
string folder = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
IEnumerable<string> possibleMoviePosters = ImageFileExtensions.Collect(
|
||||
ext => new[] { $"poster.{ext}", Path.GetFileNameWithoutExtension(path) + $"-poster.{ext}" })
|
||||
ext => new[] { $"{segment}.{ext}", Path.GetFileNameWithoutExtension(path) + $"-{segment}.{ext}" })
|
||||
.Map(f => Path.Combine(folder, f));
|
||||
Option<string> result = possibleMoviePosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone();
|
||||
return result;
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace ErsatzTV.Infrastructure.Images
|
||||
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
|
||||
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
|
||||
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder),
|
||||
ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder),
|
||||
_ => FileSystemLayout.LegacyImageCacheFolder
|
||||
};
|
||||
string target = Path.Combine(baseFolder, hex);
|
||||
@@ -85,6 +86,7 @@ namespace ErsatzTV.Infrastructure.Images
|
||||
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
|
||||
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
|
||||
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder),
|
||||
ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder),
|
||||
_ => FileSystemLayout.LegacyImageCacheFolder
|
||||
};
|
||||
string target = Path.Combine(baseFolder, hex);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=drawtext/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ersatztv/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=etvignore/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=fanart/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=faststart/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=featurette/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=featurettes/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ErsatzTV.Controllers
|
||||
{
|
||||
[ResponseCache(Duration = 3600)]
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class PostersController : ControllerBase
|
||||
@@ -37,6 +38,16 @@ namespace ErsatzTV.Controllers
|
||||
Right: r => new FileContentResult(r.Contents, r.MimeType));
|
||||
}
|
||||
|
||||
[HttpGet("/artwork/fanart/{fileName}")]
|
||||
public async Task<IActionResult> GetFanArt(string fileName)
|
||||
{
|
||||
Either<BaseError, ImageViewModel> imageContents =
|
||||
await _mediator.Send(new GetImageContents(fileName, ArtworkKind.FanArt));
|
||||
return imageContents.Match<IActionResult>(
|
||||
Left: _ => new NotFoundResult(),
|
||||
Right: r => new FileContentResult(r.Contents, r.MimeType));
|
||||
}
|
||||
|
||||
[HttpGet("/artwork/posters/plex/{plexMediaSourceId}/{*path}")]
|
||||
public async Task<IActionResult> GetPlexPoster(int plexMediaSourceId, string path)
|
||||
{
|
||||
|
||||
@@ -10,55 +10,57 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Channel" : "Add Channel")</MudText>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Channel" : "Add Channel")</MudText>
|
||||
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Label="Number" @bind-Value="_model.Number" For="@(() => _model.Number)" Immediate="true"/>
|
||||
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
|
||||
@foreach (StreamingMode streamingMode in Enum.GetValues<StreamingMode>())
|
||||
{
|
||||
<MudSelectItem Value="@streamingMode">@streamingMode</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect Class="mt-3" Label="FFmpeg Profile" @bind-Value="_model.FFmpegProfileId" For="@(() => _model.FFmpegProfileId)"
|
||||
Disabled="@(_model.StreamingMode != StreamingMode.TransportStream)">
|
||||
@foreach (FFmpegProfileViewModel profile in _ffmpegProfiles)
|
||||
{
|
||||
<MudSelectItem Value="@profile.Id">@profile.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudGrid Class="mt-3" Style="align-items: center" Justify="Justify.Center">
|
||||
<MudItem xs="6">
|
||||
<InputFile id="fileInput" OnChange="UploadLogo" hidden/>
|
||||
@if (!string.IsNullOrWhiteSpace(_model.Logo))
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Label="Number" @bind-Value="_model.Number" For="@(() => _model.Number)" Immediate="true"/>
|
||||
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
|
||||
@foreach (StreamingMode streamingMode in Enum.GetValues<StreamingMode>())
|
||||
{
|
||||
<MudElement HtmlTag="img" src="@($"iptv/logos/{_model.Logo}")" Style="max-height: 50px"/>
|
||||
<MudSelectItem Value="@streamingMode">@streamingMode</MudSelectItem>
|
||||
}
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudButton Class="ml-auto" HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.CloudUpload"
|
||||
for="fileInput">
|
||||
Upload Logo
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@(IsEdit ? "Save Changes" : "Add Channel")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
</MudSelect>
|
||||
<MudSelect Class="mt-3" Label="FFmpeg Profile" @bind-Value="_model.FFmpegProfileId" For="@(() => _model.FFmpegProfileId)"
|
||||
Disabled="@(_model.StreamingMode != StreamingMode.TransportStream)">
|
||||
@foreach (FFmpegProfileViewModel profile in _ffmpegProfiles)
|
||||
{
|
||||
<MudSelectItem Value="@profile.Id">@profile.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudGrid Class="mt-3" Style="align-items: center" Justify="Justify.Center">
|
||||
<MudItem xs="6">
|
||||
<InputFile id="fileInput" OnChange="UploadLogo" hidden/>
|
||||
@if (!string.IsNullOrWhiteSpace(_model.Logo))
|
||||
{
|
||||
<MudElement HtmlTag="img" src="@($"iptv/logos/{_model.Logo}")" Style="max-height: 50px"/>
|
||||
}
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudButton Class="ml-auto" HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.CloudUpload"
|
||||
for="fileInput">
|
||||
Upload Logo
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@(IsEdit ? "Save Changes" : "Add Channel")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Items="_channels">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Channels</MudText>
|
||||
@@ -18,7 +18,7 @@
|
||||
<col style="width: 20%"/>
|
||||
<col style="width: 20%"/>
|
||||
<col style="width: 20%"/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 120px;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
@@ -49,14 +49,18 @@
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Direction="Direction.Left" OffsetX="true">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Edit" Link="@($"/channels/{context.Id}")">
|
||||
Edit
|
||||
</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" OnClick="@(_ => DeleteChannelAsync(context))">
|
||||
Delete
|
||||
</MudMenuItem>
|
||||
</MudMenu>
|
||||
<div style="align-items: center; display: flex;">
|
||||
<MudTooltip Text="Edit Channel">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Link="@($"/channels/{context.Id}")">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Delete Channel">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => DeleteChannelAsync(context))">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@@ -8,23 +8,25 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Collection" : "Add Collection")</MudText>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Collection" : "Add Collection")</MudText>
|
||||
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">
|
||||
@(IsEdit ? "Save Changes" : "Add Collection")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">
|
||||
@(IsEdit ? "Save Changes" : "Add Collection")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@@ -9,75 +9,77 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<div class="mb-6" style="display: flex; flex-direction: row;">
|
||||
<MudText GutterBottom="true" Typo="Typo.h2">@_data.Name</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Link="@($"/media/collections/{Id}/edit")"
|
||||
Style="margin-bottom: auto; margin-top: auto;"/>
|
||||
</div>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div class="mb-6" style="display: flex; flex-direction: row;">
|
||||
<MudText GutterBottom="true" Typo="Typo.h2">@_data.Name</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Link="@($"/media/collections/{Id}/edit")"
|
||||
Style="margin-bottom: auto; margin-top: auto;"/>
|
||||
</div>
|
||||
|
||||
@if (_data.MovieCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Movies</MudText>
|
||||
@if (_data.MovieCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Movies</MudText>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MovieCardViewModel card in _data.MovieCards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/movies/{card.MovieId}")"
|
||||
DeleteClicked="@RemoveMovieFromCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MovieCardViewModel card in _data.MovieCards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/movies/{card.MovieId}")"
|
||||
DeleteClicked="@RemoveMovieFromCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
@if (_data.ShowCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Shows</MudText>
|
||||
@if (_data.ShowCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Shows</MudText>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
||||
DeleteClicked="@RemoveShowFromCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
||||
DeleteClicked="@RemoveShowFromCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
@if (_data.SeasonCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Seasons</MudText>
|
||||
@if (_data.SeasonCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Seasons</MudText>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
|
||||
Title="@card.ShowTitle"
|
||||
Subtitle="@card.Title"
|
||||
DeleteClicked="@RemoveSeasonFromCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards.OrderBy(m => m.SortTitle))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
|
||||
Title="@card.ShowTitle"
|
||||
Subtitle="@card.Title"
|
||||
DeleteClicked="@RemoveSeasonFromCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
@if (_data.EpisodeCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Episodes</MudText>
|
||||
@if (_data.EpisodeCards.Any())
|
||||
{
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Episodes</MudText>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionEpisodeCardViewModel card in _data.EpisodeCards.OrderBy(e => e.Aired))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/episodes/{card.EpisodeId}")"
|
||||
Title="@card.ShowTitle"
|
||||
Subtitle="@card.Title"
|
||||
ContainerClass="media-card-episode-container mx-2"
|
||||
CardClass="media-card-episode"
|
||||
DeleteClicked="@RemoveEpisodeFromCollection"
|
||||
ArtworkKind="@ArtworkKind.Thumbnail"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionEpisodeCardViewModel card in _data.EpisodeCards.OrderBy(e => e.Aired))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/episodes/{card.EpisodeId}")"
|
||||
Title="@card.ShowTitle"
|
||||
Subtitle="@card.Title"
|
||||
ContainerClass="media-card-episode-container mx-2"
|
||||
CardClass="media-card-episode"
|
||||
DeleteClicked="@RemoveEpisodeFromCollection"
|
||||
ArtworkKind="@ArtworkKind.Thumbnail"/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@@ -6,21 +6,23 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MediaCollectionViewModel card in _data)
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/collections/{card.Id}")"
|
||||
ContainerClass="media-card-episode-container mr-4"
|
||||
CardClass="media-card-episode"
|
||||
DeleteClicked="@DeleteMediaCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MediaCollectionViewModel card in _data)
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/collections/{card.Id}")"
|
||||
ContainerClass="media-card-episode-container mr-4"
|
||||
CardClass="media-card-episode"
|
||||
DeleteClicked="@DeleteMediaCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="/media/collections/add" Class="mt-4">
|
||||
Add Collection
|
||||
</MudButton>
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="/media/collections/add" Class="mt-4">
|
||||
Add Collection
|
||||
</MudButton>
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
+18
-14
@@ -5,7 +5,7 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
@@ -37,7 +37,7 @@
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">FFmpeg Profiles</MudText>
|
||||
<MudToolBarSpacer></MudToolBarSpacer>
|
||||
<MudText Color="Color.Primary">Colored settings will be normalized</MudText>
|
||||
<MudText Color="Color.Tertiary">Colored settings will be normalized</MudText>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col/>
|
||||
@@ -45,7 +45,7 @@
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 120px;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
@@ -61,29 +61,33 @@
|
||||
@(context.Transcode ? "Yes" : "No")
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Resolution">
|
||||
<MudText Color="@(context.Transcode && context.NormalizeResolution ? Color.Primary : Color.Inherit)">
|
||||
<MudText Color="@(context.Transcode && context.NormalizeResolution ? Color.Tertiary : Color.Inherit)">
|
||||
@context.Resolution.Name
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Video Codec">
|
||||
<MudText Color="@(context.Transcode && context.NormalizeVideoCodec ? Color.Primary : Color.Inherit)">
|
||||
<MudText Color="@(context.Transcode && context.NormalizeVideoCodec ? Color.Tertiary : Color.Inherit)">
|
||||
@context.VideoCodec
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Audio Codec">
|
||||
<MudText Color="@(context.Transcode && context.NormalizeAudioCodec ? Color.Primary : Color.Inherit)">
|
||||
<MudText Color="@(context.Transcode && context.NormalizeAudioCodec ? Color.Tertiary : Color.Inherit)">
|
||||
@context.AudioCodec
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Direction="Direction.Left" OffsetX="true">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Edit" Link="@($"/ffmpeg/{context.Id}")">
|
||||
Edit
|
||||
</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" OnClick="@(_ => DeleteProfileAsync(context))">
|
||||
Delete
|
||||
</MudMenuItem>
|
||||
</MudMenu>
|
||||
<div style="align-items: center; display: flex;">
|
||||
<MudTooltip Text="Edit Channel">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Link="@($"/ffmpeg/{context.Id}")">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Delete Channel">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => DeleteProfileAsync(context))">
|
||||
</MudIconButton>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@@ -10,98 +10,99 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">@(IsEdit ? "Edit FFmpeg Profile" : "Add FFmpeg Profile")</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">General</MudText>
|
||||
<MudTextField Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Label="Thread Count" @bind-Value="@_model.ThreadCount" For="@(() => _model.ThreadCount)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudSelect Disabled="@(!_model.Transcode)" Label="Preferred Resolution" @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
|
||||
@foreach (ResolutionViewModel resolution in _resolutions)
|
||||
{
|
||||
<MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Label="Transcode" @bind-Checked="@_model.Transcode" For="@(() => _model.Transcode)"/>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">Video</MudText>
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Codec" @bind-Value="_model.VideoCodec" For="@(() => _model.VideoCodec)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Bitrate" @bind-Value="_model.VideoBitrate" For="@(() => _model.VideoBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudSelect Disabled="@(!_model.Transcode)" Label="Hardware Acceleration" @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)">
|
||||
@foreach (HardwareAccelerationKind hwAccel in Enum.GetValues<HardwareAccelerationKind>())
|
||||
{
|
||||
<MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">Audio</MudText>
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Codec" @bind-Value="_model.AudioCodec" For="@(() => _model.AudioCodec)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Bitrate" @bind-Value="_model.AudioBitrate" For="@(() => _model.AudioBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.AudioBufferSize" For="@(() => _model.AudioBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Volume" @bind-Value="_model.AudioVolume" For="@(() => _model.AudioVolume)" Adornment="Adornment.End" AdornmentText="%"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Channels" @bind-Value="_model.AudioChannels" For="@(() => _model.AudioChannels)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Sample Rate" @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">Normalization</MudText>
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Resolution" @bind-Checked="@_model.NormalizeResolution" For="@(() => _model.NormalizeResolution)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Video Codec" @bind-Checked="@_model.NormalizeVideoCodec" For="@(() => _model.NormalizeVideoCodec)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio Codec" @bind-Checked="@_model.NormalizeAudioCodec" For="@(() => _model.NormalizeAudioCodec)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio" @bind-Checked="@_model.NormalizeAudio" For="@(() => _model.NormalizeAudio)"/>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@(IsEdit ? "Save Changes" : "Add Profile")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
|
||||
</EditForm>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h5">@(IsEdit ? "Edit FFmpeg Profile" : "Add FFmpeg Profile")</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">General</MudText>
|
||||
<MudTextField Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Label="Thread Count" @bind-Value="@_model.ThreadCount" For="@(() => _model.ThreadCount)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudSelect Disabled="@(!_model.Transcode)" Label="Preferred Resolution" @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
|
||||
@foreach (ResolutionViewModel resolution in _resolutions)
|
||||
{
|
||||
<MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Label="Transcode" @bind-Checked="@_model.Transcode" For="@(() => _model.Transcode)"/>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">Video</MudText>
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Codec" @bind-Value="_model.VideoCodec" For="@(() => _model.VideoCodec)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Bitrate" @bind-Value="_model.VideoBitrate" For="@(() => _model.VideoBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudSelect Disabled="@(!_model.Transcode)" Label="Hardware Acceleration" @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)">
|
||||
@foreach (HardwareAccelerationKind hwAccel in Enum.GetValues<HardwareAccelerationKind>())
|
||||
{
|
||||
<MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">Audio</MudText>
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Codec" @bind-Value="_model.AudioCodec" For="@(() => _model.AudioCodec)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Bitrate" @bind-Value="_model.AudioBitrate" For="@(() => _model.AudioBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.AudioBufferSize" For="@(() => _model.AudioBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Volume" @bind-Value="_model.AudioVolume" For="@(() => _model.AudioVolume)" Adornment="Adornment.End" AdornmentText="%"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Channels" @bind-Value="_model.AudioChannels" For="@(() => _model.AudioChannels)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudTextField Disabled="@(!_model.Transcode)" Label="Sample Rate" @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.h6">Normalization</MudText>
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Resolution" @bind-Checked="@_model.NormalizeResolution" For="@(() => _model.NormalizeResolution)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Video Codec" @bind-Checked="@_model.NormalizeVideoCodec" For="@(() => _model.NormalizeVideoCodec)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio Codec" @bind-Checked="@_model.NormalizeAudioCodec" For="@(() => _model.NormalizeAudioCodec)"/>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio" @bind-Checked="@_model.NormalizeAudio" For="@(() => _model.NormalizeAudio)"/>
|
||||
</MudElement>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@(IsEdit ? "Save Changes" : "Add Profile")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
+70
-68
@@ -1,70 +1,72 @@
|
||||
@page "/"
|
||||
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h3">Welcome to ErsatzTV!</MudText>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Channels</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/channels">Channels</MudLink> are not directly associated with any media. Channels have a <b>number</b>, a <b>name</b>, and a <b>streaming mode</b> that indicates how the channel will play media.
|
||||
</MudText>
|
||||
<MudText Class="mt-3">
|
||||
In <b>TransportStream</b> mode, the channel will also require an <b>FFmpeg profile</b> to configure transcoding and normalization.
|
||||
In <b>HttpLiveStreaming</b> mode, the channel will attempt to serve the channel's media without transcoding or normalization beyond the container format.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">FFmpeg Profiles</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/ffmpeg">FFmpeg Profiles</MudLink> are collections of FFmpeg settings that are applied at the channel level.
|
||||
All content on a given channel will use the same FFmpeg settings. This also means the same content on different channels can use different settings.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Libraries</MudText>
|
||||
<MudText>
|
||||
Two local <MudLink Href="/media/libraries">libraries</MudLink> are available, one for each <b>media kind</b>: Shows and Movies. Libraries contain <b>paths</b> (folders) to regularly scan for media items.
|
||||
Support for Plex libraries is under active development; Jellyfin and Emby library support is planned.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Collections</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/media/collections">Collections</MudLink> have a <b>name</b> and contain a logical grouping of media items.
|
||||
Collections may contain shows, seasons, episodes or movies.
|
||||
Collections containing shows and seasons are automatically updated as media is added or removed from the linked shows and seasons.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Schedules</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/schedules">Schedules</MudLink> have a <b>name</b>, a <b>collection playback order</b> and <b>items</b> to continually loop through.
|
||||
</MudText>
|
||||
<MudText Class="mt-3 mb-2">Three <b>collection playback orders</b> are supported:</MudText>
|
||||
<ul class="mud-typography-body1">
|
||||
<li><b>Random</b> - to randomly play collection items; repeating is allowed before all collection items have been played.</li>
|
||||
<li><b>Shuffle</b> - to randomly play collection items; repeating is <i>not</i> allowed until all collection items have been played.</li>
|
||||
<li><b>Chronological</b> - to play collection items sorted by air date and then by season and episode number (for when multiple episodes aired on a single day).</li>
|
||||
</ul>
|
||||
<MudText Class="mt-3">
|
||||
Schedule items have a <b>start type</b>, a <b>start time</b>, a <b>collection</b> and a <b>playout mode</b>.
|
||||
</MudText>
|
||||
<MudText Class="mt-3">
|
||||
A <b>fixed</b> start type requires a <b>start time</b>, while a <b>dynamic</b> start type means the schedule item will start immediately after the preceding schedule item.
|
||||
</MudText>
|
||||
<MudText Class="mt-3 mb-2">Four <b>playout modes</b> are supported:</MudText>
|
||||
<ul class="mud-typography-body1">
|
||||
<li><b>One</b> - to play one media item from the collection before advancing to the next schedule item.</li>
|
||||
<li><b>Multiple</b> - to play a specified <b>count</b> of media items from the collection before advancing to the next schedule item.</li>
|
||||
<li><b>Duration</b> - to play the maximum number of complete media items that will fit in the specified <b>playout duration</b>, before either going offline for the remainder of the <b>playout duration</b> (an <b>offline tail</b>), or immediately advancing to the next schedule item.</li>
|
||||
<li><b>Flood</b> - to play media items from the collection forever, or until the next schedule item's <b>start time</b> if one exists.</li>
|
||||
</ul>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Playouts</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/playouts">Playouts</MudLink> assign a <b>schedule</b> to a <b>channel</b> and individually track the ordered playback of collection items.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h3">Welcome to ErsatzTV!</MudText>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Channels</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/channels">Channels</MudLink> are not directly associated with any media. Channels have a <b>number</b>, a <b>name</b>, and a <b>streaming mode</b> that indicates how the channel will play media.
|
||||
</MudText>
|
||||
<MudText Class="mt-3">
|
||||
In <b>TransportStream</b> mode, the channel will also require an <b>FFmpeg profile</b> to configure transcoding and normalization.
|
||||
In <b>HttpLiveStreaming</b> mode, the channel will attempt to serve the channel's media without transcoding or normalization beyond the container format.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">FFmpeg Profiles</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/ffmpeg">FFmpeg Profiles</MudLink> are collections of FFmpeg settings that are applied at the channel level.
|
||||
All content on a given channel will use the same FFmpeg settings. This also means the same content on different channels can use different settings.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Libraries</MudText>
|
||||
<MudText>
|
||||
Two local <MudLink Href="/media/libraries">libraries</MudLink> are available, one for each <b>media kind</b>: Shows and Movies. Libraries contain <b>paths</b> (folders) to regularly scan for media items.
|
||||
Support for Plex libraries is under active development; Jellyfin and Emby library support is planned.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Collections</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/media/collections">Collections</MudLink> have a <b>name</b> and contain a logical grouping of media items.
|
||||
Collections may contain shows, seasons, episodes or movies.
|
||||
Collections containing shows and seasons are automatically updated as media is added or removed from the linked shows and seasons.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Schedules</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/schedules">Schedules</MudLink> have a <b>name</b>, a <b>collection playback order</b> and <b>items</b> to continually loop through.
|
||||
</MudText>
|
||||
<MudText Class="mt-3 mb-2">Three <b>collection playback orders</b> are supported:</MudText>
|
||||
<ul class="mud-typography-body1">
|
||||
<li><b>Random</b> - to randomly play collection items; repeating is allowed before all collection items have been played.</li>
|
||||
<li><b>Shuffle</b> - to randomly play collection items; repeating is <i>not</i> allowed until all collection items have been played.</li>
|
||||
<li><b>Chronological</b> - to play collection items sorted by air date and then by season and episode number (for when multiple episodes aired on a single day).</li>
|
||||
</ul>
|
||||
<MudText Class="mt-3">
|
||||
Schedule items have a <b>start type</b>, a <b>start time</b>, a <b>collection</b> and a <b>playout mode</b>.
|
||||
</MudText>
|
||||
<MudText Class="mt-3">
|
||||
A <b>fixed</b> start type requires a <b>start time</b>, while a <b>dynamic</b> start type means the schedule item will start immediately after the preceding schedule item.
|
||||
</MudText>
|
||||
<MudText Class="mt-3 mb-2">Four <b>playout modes</b> are supported:</MudText>
|
||||
<ul class="mud-typography-body1">
|
||||
<li><b>One</b> - to play one media item from the collection before advancing to the next schedule item.</li>
|
||||
<li><b>Multiple</b> - to play a specified <b>count</b> of media items from the collection before advancing to the next schedule item.</li>
|
||||
<li><b>Duration</b> - to play the maximum number of complete media items that will fit in the specified <b>playout duration</b>, before either going offline for the remainder of the <b>playout duration</b> (an <b>offline tail</b>), or immediately advancing to the next schedule item.</li>
|
||||
<li><b>Flood</b> - to play media items from the collection forever, or until the next schedule item's <b>start time</b> if one exists.</li>
|
||||
</ul>
|
||||
</MudElement>
|
||||
<MudElement HtmlTag="div" Class="mt-6">
|
||||
<MudText Typo="Typo.h4" GutterBottom="true">Playouts</MudText>
|
||||
<MudText>
|
||||
<MudLink Href="/playouts">Playouts</MudLink> assign a <b>schedule</b> to a <b>channel</b> and individually track the ordered playback of collection items.
|
||||
</MudText>
|
||||
</MudElement>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
@@ -8,7 +8,7 @@
|
||||
@inject IEntityLocker Locker
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Items="_libraries" Dense="true">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Libraries</MudText>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Items="_libraryPaths" Dense="true">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Library Paths</MudText>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@inject IEntityLocker Locker
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@_library.Name - Add Local Library Path</MudText>
|
||||
<div style="max-width: 400px;">
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@using ErsatzTV.Application.Logs.Queries
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable FixedHeader="true" Dense="true" Items="_logEntries">
|
||||
<HeaderContent>
|
||||
<MudTh>Timestamp</MudTh>
|
||||
|
||||
+33
-24
@@ -7,33 +7,42 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_movie.Poster))
|
||||
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container">
|
||||
<div class="fanart-tint"></div>
|
||||
@if (!string.IsNullOrWhiteSpace(_movie.FanArt))
|
||||
{
|
||||
<img src="@($"/artwork/fanart/{_movie.FanArt}")" alt="fan art"/>
|
||||
}
|
||||
</MudContainer>
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_movie.Poster))
|
||||
{
|
||||
<img class="mud-elevation-2 mr-6"
|
||||
style="border-radius: 4px; max-height: 440px"
|
||||
src="@($"/artwork/posters/{_movie.Poster}")" alt="movie poster"/>
|
||||
}
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h2" Class="media-item-title">@_movie.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_movie.Year</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(_movie.Plot))
|
||||
{
|
||||
<MudPaper Style="flex-shrink: 0;">
|
||||
<MudCardMedia Image="@($"/artwork/posters/{_movie.Poster}")" Style="height: 440px; width: 304px;"/>
|
||||
</MudPaper>
|
||||
<MudCard Elevation="2" Class="mb-6">
|
||||
<MudCardContent Class="mx-3 my-3" Style="height: 100%">
|
||||
<MudText Style="flex-grow: 1">@_movie.Plot</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
<MudCardContent Class="mx-3 my-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h3">@_movie.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_movie.Year</MudText>
|
||||
<MudText Style="flex-grow: 1">@_movie.Plot</MudText>
|
||||
<div>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
<div>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
</MudCard>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -12,29 +12,31 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="mb-6" Style="max-width: 300px">
|
||||
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
|
||||
OnClick="@PrevPage"
|
||||
Disabled="@(PageNumber <= 1)">
|
||||
</MudIconButton>
|
||||
<MudText Style="flex-grow: 1"
|
||||
Align="Align.Center">
|
||||
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
|
||||
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
|
||||
</MudIconButton>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="mb-6" Style="max-width: 300px">
|
||||
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
|
||||
OnClick="@PrevPage"
|
||||
Disabled="@(PageNumber <= 1)">
|
||||
</MudIconButton>
|
||||
<MudText Style="flex-grow: 1"
|
||||
Align="Align.Center">
|
||||
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
|
||||
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)">
|
||||
</MudIconButton>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MovieCardViewModel card in _data.Cards.Where(d => !string.IsNullOrWhiteSpace(d.Title)))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/movies/{card.MovieId}")"
|
||||
AddToCollectionClicked="@AddToCollection"/>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (MovieCardViewModel card in _data.Cards.Where(d => !string.IsNullOrWhiteSpace(d.Title)))
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/movies/{card.MovieId}")"
|
||||
AddToCollectionClicked="@AddToCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -8,24 +8,26 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Add Playout</MudText>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Add Playout</MudText>
|
||||
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudAutocomplete T="ChannelViewModel" Label="Channel" @bind-value="_model.Channel" SearchFunc="@SearchChannels" ToStringFunc="@(c => c is null ? null : $"{c.Number} - {c.Name}")"/>
|
||||
<MudAutocomplete Class="mt-3" T="ProgramScheduleViewModel" Label="Schedule" @bind-value="_model.ProgramSchedule" SearchFunc="@SearchProgramSchedules" ToStringFunc="@(s => s?.Name)"/>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
Add Playout
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudAutocomplete T="ChannelViewModel" Label="Channel" @bind-value="_model.Channel" SearchFunc="@SearchChannels" ToStringFunc="@(c => c is null ? null : $"{c.Number} - {c.Name}")"/>
|
||||
<MudAutocomplete Class="mt-3" T="ProgramScheduleViewModel" Label="Schedule" @bind-value="_model.ProgramSchedule" SearchFunc="@SearchProgramSchedules" ToStringFunc="@(s => s?.Name)"/>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
Add Playout
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Dense="true" Items="_playouts" SelectedItemChanged="@(async (PlayoutViewModel x) => await PlayoutSelected(x))">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Playouts</MudText>
|
||||
|
||||
@@ -7,29 +7,31 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Schedule" : "Add Schedule")</MudText>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div style="max-width: 400px;">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Schedule" : "Add Schedule")</MudText>
|
||||
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudSelect Class="mt-3" Label="Collection Playback Order" @bind-Value="_model.MediaCollectionPlaybackOrder" For="@(() => _model.MediaCollectionPlaybackOrder)">
|
||||
@foreach (PlaybackOrder playbackOrder in Enum.GetValues<PlaybackOrder>())
|
||||
{
|
||||
<MudSelectItem Value="@playbackOrder">@playbackOrder</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@(IsEdit ? "Save Changes" : "Add Schedule")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync">
|
||||
<FluentValidator/>
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTextField Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||
<MudSelect Class="mt-3" Label="Collection Playback Order" @bind-Value="_model.MediaCollectionPlaybackOrder" For="@(() => _model.MediaCollectionPlaybackOrder)">
|
||||
@foreach (PlaybackOrder playbackOrder in Enum.GetValues<PlaybackOrder>())
|
||||
{
|
||||
<MudSelectItem Value="@playbackOrder">@playbackOrder</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@(IsEdit ? "Save Changes" : "Add Schedule")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@@ -11,138 +11,140 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudTable Hover="true" Items="_schedule.Items.OrderBy(i => i.Index)" Dense="true" Class="mt-8" @bind-SelectedItem="_selectedItem">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">@_schedule.Name Items</MudText>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 60px;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Start Time</MudTh>
|
||||
<MudTh>Collection</MudTh>
|
||||
<MudTh>Playout Mode</MudTh>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start Time">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime?.ToString(@"hh\:mm") ?? string.Empty : "Dynamic")
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Collection">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@context.CollectionName
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Playout Mode">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@context.PlayoutMode
|
||||
@if (context.PlayoutMode == PlayoutMode.Multiple && context.MultipleCount.HasValue)
|
||||
{
|
||||
@($" ({context.MultipleCount})")
|
||||
}
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
|
||||
OnClick="@(_ => MoveItemUp(context))"
|
||||
Disabled="@(_schedule.Items.All(x => x.Index >= context.Index))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
|
||||
OnClick="@(_ => MoveItemDown(context))"
|
||||
Disabled="@(_schedule.Items.All(x => x.Index <= context.Index))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => RemoveScheduleItem(context))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager/>
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddScheduleItem())" Class="mt-4">
|
||||
Add Schedule Item
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4">
|
||||
Save Changes
|
||||
</MudButton>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Items="_schedule.Items.OrderBy(i => i.Index)" Dense="true" @bind-SelectedItem="_selectedItem">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">@_schedule.Name Items</MudText>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col/>
|
||||
<col/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 60px;"/>
|
||||
<col style="width: 60px;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Start Time</MudTh>
|
||||
<MudTh>Collection</MudTh>
|
||||
<MudTh>Playout Mode</MudTh>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
<MudTh/>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start Time">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime?.ToString(@"hh\:mm") ?? string.Empty : "Dynamic")
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Collection">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@context.CollectionName
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Playout Mode">
|
||||
<MudText Typo="@(context == _selectedItem ? Typo.subtitle2 : Typo.body2)">
|
||||
@context.PlayoutMode
|
||||
@if (context.PlayoutMode == PlayoutMode.Multiple && context.MultipleCount.HasValue)
|
||||
{
|
||||
@($" ({context.MultipleCount})")
|
||||
}
|
||||
</MudText>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowUpward"
|
||||
OnClick="@(_ => MoveItemUp(context))"
|
||||
Disabled="@(_schedule.Items.All(x => x.Index >= context.Index))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowDownward"
|
||||
OnClick="@(_ => MoveItemDown(context))"
|
||||
Disabled="@(_schedule.Items.All(x => x.Index <= context.Index))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => RemoveScheduleItem(context))">
|
||||
</MudIconButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager/>
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Default" OnClick="@(_ => AddScheduleItem())" Class="mt-4">
|
||||
Add Schedule Item
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveChanges())" Class="mt-4 ml-4">
|
||||
Save Changes
|
||||
</MudButton>
|
||||
|
||||
@if (_selectedItem is not null)
|
||||
{
|
||||
<div style="max-width: 400px;">
|
||||
<EditForm Model="_selectedItem">
|
||||
<FluentValidator/>
|
||||
<MudCard Class="mt-6">
|
||||
<MudCardContent>
|
||||
<MudSelect Label="Start Type" @bind-Value="_selectedItem.StartType" For="@(() => _selectedItem.StartType)">
|
||||
@foreach (StartType startType in Enum.GetValues<StartType>())
|
||||
@if (_selectedItem is not null)
|
||||
{
|
||||
<div style="max-width: 400px;">
|
||||
<EditForm Model="_selectedItem">
|
||||
<FluentValidator/>
|
||||
<MudCard Class="mt-6">
|
||||
<MudCardContent>
|
||||
<MudSelect Label="Start Type" @bind-Value="_selectedItem.StartType" For="@(() => _selectedItem.StartType)">
|
||||
@foreach (StartType startType in Enum.GetValues<StartType>())
|
||||
{
|
||||
<MudSelectItem Value="@startType">@startType</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTimePicker Class="mt-3" Label="Start Time" @bind-Time="@_selectedItem.StartTime" For="@(() => _selectedItem.StartTime)" Disabled="@(_selectedItem.StartType == StartType.Dynamic)"/>
|
||||
<MudSelect Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)">
|
||||
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
|
||||
{
|
||||
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
|
||||
{
|
||||
<MudSelectItem Value="@startType">@startType</MudSelectItem>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="MediaCollectionViewModel"
|
||||
Label="Collection"
|
||||
@bind-value="_selectedItem.Collection"
|
||||
SearchFunc="@SearchMediaCollections"
|
||||
ToStringFunc="@(c => c?.Name)"/>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTimePicker Class="mt-3" Label="Start Time" @bind-Time="@_selectedItem.StartTime" For="@(() => _selectedItem.StartTime)" Disabled="@(_selectedItem.StartType == StartType.Dynamic)"/>
|
||||
<MudSelect Label="Collection Type" @bind-Value="_selectedItem.CollectionType" For="@(() => _selectedItem.CollectionType)">
|
||||
@foreach (ProgramScheduleItemCollectionType collectionType in Enum.GetValues<ProgramScheduleItemCollectionType>())
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
|
||||
{
|
||||
<MudSelectItem Value="@collectionType">@collectionType</MudSelectItem>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Show"
|
||||
@bind-value="_selectedItem.MediaItem"
|
||||
SearchFunc="@SearchTelevisionShows"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.Collection)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="MediaCollectionViewModel"
|
||||
Label="Collection"
|
||||
@bind-value="_selectedItem.Collection"
|
||||
SearchFunc="@SearchMediaCollections"
|
||||
ToStringFunc="@(c => c?.Name)"/>
|
||||
}
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Show"
|
||||
@bind-value="_selectedItem.MediaItem"
|
||||
SearchFunc="@SearchTelevisionShows"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
|
||||
{
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Season"
|
||||
@bind-value="_selectedItem.MediaItem"
|
||||
SearchFunc="@SearchTelevisionSeasons"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
<MudSelect Class="mt-3" Label="Playout Mode" @bind-Value="@_selectedItem.PlayoutMode" For="@(() => _selectedItem.PlayoutMode)">
|
||||
@foreach (PlayoutMode playoutMode in Enum.GetValues<PlayoutMode>())
|
||||
@if (_selectedItem.CollectionType == ProgramScheduleItemCollectionType.TelevisionSeason)
|
||||
{
|
||||
<MudSelectItem Value="@playoutMode">@playoutMode</MudSelectItem>
|
||||
<MudAutocomplete Class="mt-3"
|
||||
T="NamedMediaItemViewModel"
|
||||
Label="Television Season"
|
||||
@bind-value="_selectedItem.MediaItem"
|
||||
SearchFunc="@SearchTelevisionSeasons"
|
||||
ToStringFunc="@(s => s?.Name)"/>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField Class="mt-3" Label="Multiple Count" @bind-Value="@_selectedItem.MultipleCount" For="@(() => _selectedItem.MultipleCount)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Multiple)"/>
|
||||
<MudTimePicker Class="mt-3" Label="Playout Duration" @bind-Time="@_selectedItem.PlayoutDuration" For="@(() => _selectedItem.PlayoutDuration)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudSwitch Label="Offline Tail" @bind-Checked="@_selectedItem.OfflineTail" For="@(() => _selectedItem.OfflineTail)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
|
||||
</MudElement>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
}
|
||||
<MudSelect Class="mt-3" Label="Playout Mode" @bind-Value="@_selectedItem.PlayoutMode" For="@(() => _selectedItem.PlayoutMode)">
|
||||
@foreach (PlayoutMode playoutMode in Enum.GetValues<PlayoutMode>())
|
||||
{
|
||||
<MudSelectItem Value="@playoutMode">@playoutMode</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField Class="mt-3" Label="Multiple Count" @bind-Value="@_selectedItem.MultipleCount" For="@(() => _selectedItem.MultipleCount)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Multiple)"/>
|
||||
<MudTimePicker Class="mt-3" Label="Playout Duration" @bind-Time="@_selectedItem.PlayoutDuration" For="@(() => _selectedItem.PlayoutDuration)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
|
||||
<MudElement HtmlTag="div" Class="mt-3">
|
||||
<MudSwitch Label="Offline Tail" @bind-Checked="@_selectedItem.OfflineTail" For="@(() => _selectedItem.OfflineTail)" Disabled="@(_selectedItem.PlayoutMode != PlayoutMode.Duration)"/>
|
||||
</MudElement>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</EditForm>
|
||||
</div>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject IMediator Mediator
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudTable Hover="true" Items="_schedules" Dense="true" SelectedItemChanged="@(async (ProgramScheduleViewModel x) => await ScheduleSelected(x))">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">Schedules</MudText>
|
||||
@@ -50,31 +50,32 @@
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="/schedules/add" Class="mt-4">
|
||||
Add Schedule
|
||||
</MudButton>
|
||||
|
||||
@if (_selectedScheduleItems != null)
|
||||
{
|
||||
<MudTable Hover="true" Items="_selectedScheduleItems.OrderBy(i => i.Index)" Class="mt-8">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">@_selectedSchedule.Name Items</MudText>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Start Time</MudTh>
|
||||
<MudTh>Collection</MudTh>
|
||||
<MudTh>Playout Mode</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start Time">
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime?.ToString(@"hh\:mm") ?? string.Empty : "Dynamic")
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Collection">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Playout Mode">@context.PlayoutMode</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager/>
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@if (_selectedScheduleItems != null)
|
||||
{
|
||||
<MudTable Hover="true" Items="_selectedScheduleItems.OrderBy(i => i.Index)" Class="mt-8">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">@_selectedSchedule.Name Items</MudText>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Start Time</MudTh>
|
||||
<MudTh>Collection</MudTh>
|
||||
<MudTh>Playout Mode</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Start Time">
|
||||
@(context.StartType == StartType.Fixed ? context.StartTime?.ToString(@"hh\:mm") ?? string.Empty : "Dynamic")
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Collection">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Playout Mode">@context.PlayoutMode</MudTd>
|
||||
</RowTemplate>
|
||||
<PagerContent>
|
||||
<MudTablePager/>
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<ProgramScheduleViewModel> _schedules;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<div class="mb-6" style="display: flex; flex-direction: row;">
|
||||
<MudText GutterBottom="true" Typo="Typo.h4">Search Results: "@_query"</MudText>
|
||||
</div>
|
||||
|
||||
@@ -7,33 +7,35 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_episode.Poster))
|
||||
{
|
||||
<MudPaper style="display: flex; flex-direction: column">
|
||||
<MudCardMedia Image="@($"/artwork/thumbnails/{_episode.Poster}")" Style="flex-grow: 1; height: 220px; width: 392px;"/>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudCardContent Class="ml-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h4">@_episode.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_season.Plot</MudText>
|
||||
<MudText Style="flex-grow: 1">@_episode.Plot</MudText>
|
||||
<div class="mt-6">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_episode.Poster))
|
||||
{
|
||||
<MudPaper style="display: flex; flex-direction: column">
|
||||
<MudCardMedia Image="@($"/artwork/thumbnails/{_episode.Poster}")" Style="flex-grow: 1; height: 220px; width: 392px;"/>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudCardContent Class="ml-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h4">@_episode.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_season.Plot</MudText>
|
||||
<MudText Style="flex-grow: 1">@_episode.Plot</MudText>
|
||||
<div class="mt-6">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</div>
|
||||
</MudCard>
|
||||
</MudCardContent>
|
||||
</div>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -15,53 +15,55 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_season.Poster))
|
||||
{
|
||||
<MudPaper Style="flex-shrink: 0;">
|
||||
<MudCardMedia Image="@($"/artwork/posters/{_season.Poster}")" Style="height: 440px; width: 304px;"/>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudCardContent Class="mx-3 my-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h3">@_season.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_season.Year</MudText>
|
||||
<MudText Style="flex-grow: 1">@_season.Plot</MudText>
|
||||
<div class="mt-6">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
<MudButton Class="ml-3"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Schedule"
|
||||
OnClick="@AddToSchedule">
|
||||
Add To Schedule
|
||||
</MudButton>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_season.Poster))
|
||||
{
|
||||
<MudPaper Style="flex-shrink: 0;">
|
||||
<MudCardMedia Image="@($"/artwork/posters/{_season.Poster}")" Style="height: 440px; width: 304px;"/>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudCardContent Class="mx-3 my-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h3">@_season.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_season.Year</MudText>
|
||||
<MudText Style="flex-grow: 1">@_season.Plot</MudText>
|
||||
<div class="mt-6">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
<MudButton Class="ml-3"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Schedule"
|
||||
OnClick="@AddToSchedule">
|
||||
Add To Schedule
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</div>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
</MudCardContent>
|
||||
</div>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
|
||||
@foreach (TelevisionEpisodeCardViewModel card in _data.Cards)
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Placeholder="@card.Placeholder"
|
||||
Link="@($"/media/tv/episodes/{card.EpisodeId}")"
|
||||
AddToCollectionClicked="@AddEpisodeToCollection"
|
||||
ContainerClass="media-card-episode-container mx-2"
|
||||
CardClass="media-card-episode"
|
||||
ArtworkKind="@ArtworkKind.Thumbnail"/>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
|
||||
@foreach (TelevisionEpisodeCardViewModel card in _data.Cards)
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Placeholder="@card.Placeholder"
|
||||
Link="@($"/media/tv/episodes/{card.EpisodeId}")"
|
||||
AddToCollectionClicked="@AddEpisodeToCollection"
|
||||
ContainerClass="media-card-episode-container mx-2"
|
||||
CardClass="media-card-episode"
|
||||
ArtworkKind="@ArtworkKind.Thumbnail"/>
|
||||
}
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -15,49 +15,51 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_show.Poster))
|
||||
{
|
||||
<MudPaper Style="flex-shrink: 0;">
|
||||
<MudCardMedia Image="@($"/artwork/posters/{_show.Poster}")" Style="height: 440px; width: 304px;"/>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudCardContent Class="mx-3 my-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h3">@_show.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_show.Year</MudText>
|
||||
<MudText Style="flex-grow: 1">@_show.Plot</MudText>
|
||||
<div>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
<MudButton Class="ml-3"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Schedule"
|
||||
OnClick="@AddToSchedule">
|
||||
Add To Schedule
|
||||
</MudButton>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs>
|
||||
<MudCard Class="mb-6">
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
@if (!string.IsNullOrWhiteSpace(_show.Poster))
|
||||
{
|
||||
<MudPaper Style="flex-shrink: 0;">
|
||||
<MudCardMedia Image="@($"/artwork/posters/{_show.Poster}")" Style="height: 440px; width: 304px;"/>
|
||||
</MudPaper>
|
||||
}
|
||||
<MudCardContent Class="mx-3 my-3">
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<MudText Typo="Typo.h3">@_show.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_show.Year</MudText>
|
||||
<MudText Style="flex-grow: 1">@_show.Plot</MudText>
|
||||
<div>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@AddToCollection">
|
||||
Add To Collection
|
||||
</MudButton>
|
||||
<MudButton Class="ml-3"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Schedule"
|
||||
OnClick="@AddToSchedule">
|
||||
Add To Schedule
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MudCardContent>
|
||||
</div>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
</MudCardContent>
|
||||
</div>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
|
||||
@foreach (TelevisionSeasonCardViewModel card in _data.Cards)
|
||||
{
|
||||
<MediaCard Data="@card" Placeholder="@card.Placeholder"
|
||||
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
|
||||
AddToCollectionClicked="@AddSeasonToCollection"/>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
|
||||
@foreach (TelevisionSeasonCardViewModel card in _data.Cards)
|
||||
{
|
||||
<MediaCard Data="@card" Placeholder="@card.Placeholder"
|
||||
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
|
||||
AddToCollectionClicked="@AddSeasonToCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -10,29 +10,31 @@
|
||||
@inject IDialogService Dialog
|
||||
@inject ChannelWriter<IBackgroundServiceRequest> Channel
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="mb-6" Style="max-width: 300px">
|
||||
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
|
||||
OnClick="@(() => PrevPage())"
|
||||
Disabled="@(_pageNumber <= 1)">
|
||||
</MudIconButton>
|
||||
<MudText Style="flex-grow: 1"
|
||||
Align="Align.Center">
|
||||
@Math.Min((_pageNumber - 1) * _pageSize + 1, _data.Count)-@Math.Min(_data.Count, _pageNumber * _pageSize) of @_data.Count
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
|
||||
OnClick="@(() => NextPage())" Disabled="@(_pageNumber * _pageSize >= _data.Count)">
|
||||
</MudIconButton>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="mb-6" Style="max-width: 300px">
|
||||
<MudPaper Style="align-items: center; display: flex; justify-content: center;">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft"
|
||||
OnClick="@(() => PrevPage())"
|
||||
Disabled="@(_pageNumber <= 1)">
|
||||
</MudIconButton>
|
||||
<MudText Style="flex-grow: 1"
|
||||
Align="Align.Center">
|
||||
@Math.Min((_pageNumber - 1) * _pageSize + 1, _data.Count)-@Math.Min(_data.Count, _pageNumber * _pageSize) of @_data.Count
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight"
|
||||
OnClick="@(() => NextPage())" Disabled="@(_pageNumber * _pageSize >= _data.Count)">
|
||||
</MudIconButton>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionShowCardViewModel card in _data.Cards)
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
||||
AddToCollectionClicked="@AddToCollection"/>
|
||||
}
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||
@foreach (TelevisionShowCardViewModel card in _data.Cards)
|
||||
{
|
||||
<MediaCard Data="@card"
|
||||
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
||||
AddToCollectionClicked="@AddToCollection"/>
|
||||
}
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -9,30 +9,30 @@
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1">
|
||||
<div style="min-width: 240px">
|
||||
<img src="/images/ersatztv.png" alt="ErsatzTV"/>
|
||||
</div>
|
||||
<MudTextField T="string"
|
||||
@ref="_textField"
|
||||
Placeholder="Search"
|
||||
@ref=" _textField"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Adornment="Adornment.Start"
|
||||
Variant="Variant.Outlined"
|
||||
Class="search-bar"
|
||||
OnKeyDown="@OnSearchKeyDown"
|
||||
Immediate="true">
|
||||
</MudTextField>
|
||||
<MudAppBarSpacer/>
|
||||
<MudLink Style="@($"color:{Colors.Shades.White}")" Color="Color.Inherit" Href="/iptv/channels.m3u" Target="_blank" Underline="Underline.None">M3U</MudLink>
|
||||
<MudLink Style="@($"color:{Colors.Shades.White}")" Color="Color.Inherit" Href="/iptv/xmltv.xml" Target="_blank" Class="mx-4" Underline="Underline.None">XMLTV</MudLink>
|
||||
<MudLink Style="@($"color:{Colors.Shades.White}")" Color="Color.Inherit" Href="/swagger" Target="_blank" Class="mr-4" Underline="Underline.None">API</MudLink>
|
||||
<MudDivider Vertical="true" FlexItem="true" DividerType="DividerType.Middle" Class="mx-4 my-5"/>
|
||||
<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="Discord">
|
||||
<MudIconButton Icon="fab fa-discord" Color="Color.Inherit" Link="https://discord.gg/hHaJm3yGy6" Target="_blank"/>
|
||||
<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.Inherit" Link="https://github.com/jasongdove/ErsatzTV" Target="_blank"/>
|
||||
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Primary" Link="https://github.com/jasongdove/ErsatzTV" Target="_blank"/>
|
||||
</MudTooltip>
|
||||
</MudAppBar>
|
||||
<MudDrawer Open="true" Elevation="2">
|
||||
<MudDrawerHeader>
|
||||
<MudText Typo="Typo.h6">ErsatzTV</MudText>
|
||||
</MudDrawerHeader>
|
||||
<MudDrawer Open="true" Elevation="2" ClipMode="DrawerClipMode.Always">
|
||||
<MudNavMenu>
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All">Home</MudNavLink>
|
||||
<MudNavLink Href="/channels">Channels</MudNavLink>
|
||||
@@ -57,9 +57,7 @@
|
||||
</MudNavMenu>
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
||||
@Body
|
||||
</MudContainer>
|
||||
@Body
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
||||
@@ -78,9 +76,18 @@
|
||||
{
|
||||
Palette = new Palette
|
||||
{
|
||||
DrawerBackground = current.Palette.Background,
|
||||
Background = current.Palette.BackgroundGrey,
|
||||
Tertiary = Colors.Shades.White
|
||||
ActionDefault = "rgba(255,255,255, 0.80)",
|
||||
Primary = "#009000",
|
||||
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)",
|
||||
Info = "#00c0c0",
|
||||
Tertiary = "#00c000",
|
||||
White = Colors.Shades.White
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<MudPaper Class="@($"media-card {CardClass}")" Style="@ArtworkForItem()">
|
||||
@if (string.IsNullOrWhiteSpace(Data.Poster))
|
||||
{
|
||||
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-disabled">
|
||||
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-primary">
|
||||
@GetPlaceholder(Data.SortTitle)
|
||||
</MudText>
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
<MudPaper Class="@($"media-card {CardClass}")" Style="@ArtworkForItem()">
|
||||
@if (string.IsNullOrWhiteSpace(Data.Poster))
|
||||
{
|
||||
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-disabled">
|
||||
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-primary">
|
||||
@GetPlaceholder(Data.SortTitle)
|
||||
</MudText>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
.media-card-grid {
|
||||
.mud-breadcrumb-separator > span { color: inherit !important; }
|
||||
|
||||
.media-card-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
@@ -68,4 +70,38 @@
|
||||
.search-bar .mud-input.mud-input-outlined .mud-input-outlined-border {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fanart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.fanart-container img {
|
||||
-o-object-fit: cover;
|
||||
height: 400px;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
transition: opacity 5s ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fanart-container > .fanart-tint {
|
||||
background: linear-gradient(360deg, black, transparent);
|
||||
height: 400px;
|
||||
opacity: 0.85;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.media-item-title {
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 5px #000;
|
||||
}
|
||||
|
||||
.media-item-subtitle {
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 5px #000;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Reference in New Issue
Block a user