318 lines
11 KiB
Text
318 lines
11 KiB
Text
@using Microsoft.Extensions.Options
|
|
@using LANCommander.Server.Settings
|
|
@using LANCommander.Server.Services.Abstractions
|
|
@using LANCommander.Server.Services.Models
|
|
@using Microsoft.EntityFrameworkCore
|
|
@using System.Net.Mime
|
|
@inject MediaService MediaService
|
|
@inject GameService GameService
|
|
@inject IMediaGrabberService MediaGrabberService
|
|
@inject ModalService ModalService
|
|
@inject IMessageService MessageService
|
|
@inject NavigationManager NavigationManager
|
|
@inject IOptions<Settings> Settings
|
|
@inject ILogger<MediaEditor> Logger
|
|
|
|
<Flex Direction="FlexDirection.Vertical" Gap="FlexGap.Large" Style="width: 100%" Class="media-editor">
|
|
<Table @ref="ResultsTable" TItem="Media" DataSource="@_media" RowKey="c => c.Id" @bind-SelectedRows="Selected" HidePagination="true" Responsive>
|
|
<TitleTemplate>
|
|
<Flex Justify="FlexJustify.End" Align="FlexAlign.Center" Gap="@("10")">
|
|
@if (Selected.Any())
|
|
{
|
|
<span>@Selected.Count() selected</span>
|
|
}
|
|
<Popconfirm Disabled="!Selected.Any()" OnConfirm="DeleteAll" Title="Are you sure you want to delete all selected media?">
|
|
<Button Disabled="!Selected.Any()" Danger>Delete</Button>
|
|
</Popconfirm>
|
|
</Flex>
|
|
</TitleTemplate>
|
|
<ColumnDefinitions Context="row">
|
|
<Selection Type="SelectionType.Checkbox" />
|
|
<PropertyColumn Property="p => p.Id" Title="Preview" Width="100px" Align="ColumnAlign.Center">
|
|
@if (MediaService.FileExists(row))
|
|
{
|
|
string originalPath = $"/api/Media/{row.Id}/Download?fileId={row.FileId}";
|
|
string thumbnailPath = row.Thumbnail != null
|
|
? $"/api/Media/{row.Thumbnail?.Id}/Download?fileId={row.Thumbnail?.FileId}"
|
|
: $"/api/Media/{row.Id}/Thumbnail?fileId={row.FileId}";
|
|
|
|
if (row.Type == MediaType.Manual)
|
|
{
|
|
<Image Width="100px" Src="@thumbnailPath" PreviewVisible="false" OnClick="() => OpenManual(row)" />
|
|
}
|
|
else if (row.Type == MediaType.Video)
|
|
{
|
|
<video width="100" src="@originalPath" style="max-width: 100px; max-height: 60px;" controls />
|
|
}
|
|
else
|
|
{
|
|
<Image Width="100px" Src="@thumbnailPath" PreviewSrc="@originalPath" />
|
|
}
|
|
}
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="p => p.Type">
|
|
<Select @key="row.Id" @bind-Value="row.Type" TItem="MediaType" TItemValue="MediaType" DataSource="AllowedMediaTypes" />
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="p => p.Name">
|
|
@if (row.Type == MediaType.Manual)
|
|
{
|
|
<Input @bind-Value="row.Name" OnBlur="() => Update(row)" />
|
|
}
|
|
</PropertyColumn>
|
|
<ActionColumn>
|
|
<Flex Gap="FlexGap.Small" Justify="FlexJustify.End">
|
|
@{
|
|
var fileInputId = Guid.NewGuid();
|
|
}
|
|
<InputFile id="@fileInputId" OnChange="(e) => UploadMedia(e, row)" hidden accept=".jpg,.jpeg,.png,.gif,.webp,.pdf,.ico,.mp4,.webm,.mkv"/>
|
|
<label class="ant-btn ant-btn-text ant-btn-icon-only" for="@fileInputId">
|
|
<Icon Type="@IconType.Outline.Upload"/>
|
|
</label>
|
|
|
|
@if (String.IsNullOrWhiteSpace(Settings.Value.Server.Media.SteamGridDbApiKey)
|
|
&& row.Type != MediaType.Video
|
|
&& row.Type != MediaType.Screenshot)
|
|
{
|
|
<Popconfirm OnConfirm="() => NavigateToSettings()" Title="Invalid SteamGridDB credentials. Setup now?">
|
|
<Button Type="ButtonType.Text" Icon="@IconType.Outline.Search"/>
|
|
</Popconfirm>
|
|
}
|
|
else
|
|
{
|
|
<Button OnClick="() => SearchMedia(row)" Type="ButtonType.Text" Icon="@IconType.Outline.Search"/>
|
|
}
|
|
|
|
<Popconfirm OnConfirm="() => RemoveMedia(row)" Title="Are you sure you want to delete this media?">
|
|
<Button Type="ButtonType.Text" Danger Icon="@IconType.Outline.Close"/>
|
|
</Popconfirm>
|
|
</Flex>
|
|
</ActionColumn>
|
|
</ColumnDefinitions>
|
|
</Table>
|
|
|
|
<GridRow Justify="RowJustify.End">
|
|
<GridCol>
|
|
<Button OnClick="AddMedia" Type="@ButtonType.Primary">Add Media</Button>
|
|
</GridCol>
|
|
</GridRow>
|
|
</Flex>
|
|
|
|
@code {
|
|
[CascadingParameter(Name = "GameId")] public Guid GameId { get; set; }
|
|
|
|
IEnumerable<Media> Selected = new List<Media>();
|
|
ITable? ResultsTable;
|
|
|
|
MediaType[] AllowedMediaTypes =
|
|
[
|
|
MediaType.Icon,
|
|
MediaType.Cover,
|
|
MediaType.Background,
|
|
MediaType.Grid,
|
|
MediaType.Logo,
|
|
MediaType.Manual,
|
|
MediaType.Screenshot,
|
|
MediaType.Video
|
|
];
|
|
|
|
string[] ValidMimeTypes =
|
|
[
|
|
MediaTypeNames.Image.Png,
|
|
MediaTypeNames.Image.Jpeg,
|
|
MediaTypeNames.Image.Gif,
|
|
MediaTypeNames.Image.Webp,
|
|
MediaTypeNames.Application.Pdf,
|
|
MediaTypeNames.Image.Icon,
|
|
"video/mp4",
|
|
"video/webm",
|
|
"video/x-matroska"
|
|
];
|
|
|
|
List<Media> _media = new();
|
|
Game _game;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
_game = await GameService.Query(q =>
|
|
{
|
|
return q
|
|
.Include(g => g.Media)
|
|
.ThenInclude(m => m.StorageLocation);
|
|
})
|
|
.GetAsync(GameId);
|
|
|
|
_media = _game.Media.ToList();
|
|
}
|
|
|
|
async Task Update(Media media)
|
|
{
|
|
try
|
|
{
|
|
if (media.Id != Guid.Empty)
|
|
{
|
|
await MediaService.UpdateAsync(media);
|
|
|
|
MessageService.Success($"{media.Type} updated!");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageService.Error($"{media.Type} could not be updated.");
|
|
Logger.LogError(ex, $"{media.Type} could not be updated.");
|
|
}
|
|
}
|
|
|
|
async Task AddMedia()
|
|
{
|
|
try
|
|
{
|
|
var types = Enum.GetValues<MediaType>();
|
|
var nextType = types.FirstOrDefault(t => !_media.Any(v => v.Type == t) && AllowedMediaTypes.Contains(t));
|
|
|
|
var media = new Media
|
|
{
|
|
GameId = GameId,
|
|
Type = nextType == MediaType.Icon && _media.Any(v => v.Type == MediaType.Icon) ? MediaType.Manual : nextType,
|
|
StorageLocation = await MediaService.GetDefaultStorageLocationAsync(),
|
|
Crc32 = String.Empty
|
|
};
|
|
|
|
media = await MediaService.AddAsync(media);
|
|
|
|
_media.Add(media);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageService.Error("Could not add new media!");
|
|
Logger.LogError(ex, "Could not add new media!");
|
|
}
|
|
}
|
|
|
|
async Task SearchMedia(Media media)
|
|
{
|
|
var modalOptions = new ModalOptions
|
|
{
|
|
Title = $"Download {media.Type}",
|
|
Maximizable = false,
|
|
DefaultMaximized = true,
|
|
Closable = true,
|
|
OkText = "Select",
|
|
};
|
|
|
|
var grabberOptions = new MediaGrabberOptions()
|
|
{
|
|
Type = media.Type,
|
|
Search = _game.Title
|
|
};
|
|
|
|
var modalRef = await ModalService.CreateModalAsync<MediaGrabberDialog, MediaGrabberOptions, MediaGrabberDownloadResult>(modalOptions, grabberOptions);
|
|
|
|
modalRef.OnOk = async (downloadResult) =>
|
|
{
|
|
using var download = downloadResult.Download;
|
|
|
|
media.SourceUrl = downloadResult.Result.SourceUrl;
|
|
media.MimeType = download.MimeType;
|
|
|
|
if (media.Id == Guid.Empty)
|
|
{
|
|
media = await MediaService.WriteToFileAsync(media, download.Stream);
|
|
}
|
|
else
|
|
{
|
|
MediaService.DeleteLocalMediaFile(media);
|
|
media = await MediaService.WriteToFileAsync(media, download.Stream);
|
|
}
|
|
|
|
_media = (await MediaService.GetAsync(m => m.GameId == media.GameId)).ToList();
|
|
|
|
await InvokeAsync(() =>
|
|
{
|
|
ResultsTable?.ReloadData();
|
|
StateHasChanged();
|
|
});
|
|
};
|
|
}
|
|
|
|
async Task UploadMedia(InputFileChangeEventArgs e, Media media)
|
|
{
|
|
if (!ValidMimeTypes.Contains(e.File.ContentType))
|
|
{
|
|
MessageService.Error("Unsupported file type");
|
|
Logger.LogError($"Unsupported file type {e.File.ContentType}");
|
|
return;
|
|
}
|
|
|
|
var config = Settings.Value.Server.Media.GetMediaTypeConfig(media.Type);
|
|
|
|
if (e.File.Size > config.MaxFileSize)
|
|
{
|
|
MessageService.Error($"File size must be smaller than {ByteSizeLib.ByteSize.FromBytes(config.MaxFileSize)}");
|
|
return;
|
|
}
|
|
|
|
media.SourceUrl = "";
|
|
media.MimeType = e.File.ContentType;
|
|
|
|
if (media.Id == Guid.Empty)
|
|
{
|
|
media = await MediaService.WriteToFileAsync(media, e.File.OpenReadStream(maxAllowedSize: config.MaxFileSize));
|
|
|
|
await MediaService.AddAsync(media);
|
|
}
|
|
else
|
|
{
|
|
MediaService.DeleteLocalMediaFile(media);
|
|
|
|
media = await MediaService.WriteToFileAsync(media, e.File.OpenReadStream(maxAllowedSize: config.MaxFileSize));
|
|
|
|
await MediaService.UpdateAsync(media);
|
|
}
|
|
|
|
_media = (await MediaService.GetAsync(m => m.GameId == media.GameId)).ToList();
|
|
|
|
ResultsTable?.ReloadData();
|
|
}
|
|
|
|
async Task DeleteAll()
|
|
{
|
|
if (!Selected.Any())
|
|
return;
|
|
|
|
_media = _media.Except(Selected).ToList();
|
|
await MediaService.DeleteRangeAsync(Selected);
|
|
|
|
Selected = [];
|
|
ResultsTable.ReloadData();
|
|
}
|
|
|
|
async Task RemoveMedia(Media media)
|
|
{
|
|
_media.Remove(media);
|
|
|
|
if (media.Id != Guid.Empty)
|
|
await MediaService.DeleteAsync(media);
|
|
}
|
|
|
|
async Task OpenManual(Media media)
|
|
{
|
|
var modalOptions = new ModalOptions()
|
|
{
|
|
Title = media.Name,
|
|
Maximizable = true,
|
|
DefaultMaximized = true,
|
|
Closable = true,
|
|
Footer = null,
|
|
Draggable = true,
|
|
Resizable = true,
|
|
WrapClassName = "pdf-reader-dialog",
|
|
};
|
|
|
|
var modalRef = await ModalService.CreateModalAsync<PdfReaderDialog, Media>(modalOptions, media);
|
|
}
|
|
|
|
void NavigateToSettings()
|
|
{
|
|
NavigationManager.NavigateTo("/Settings/Media", true);
|
|
}
|
|
}
|