@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 @using LANCommander.UI.Providers @inject MediaService MediaService @inject GameService GameService @inject IMediaGrabberService MediaGrabberService @inject ModalService ModalService @inject IMessageService MessageService @inject IOptions Settings @inject ILogger Logger @inject ScriptProvider ScriptProvider
@{ var bgMedia = GetMedia(MediaType.Background); var bgUrl = GetMediaUrl(bgMedia); } @if (bgUrl != null) { }
@{ var bgInputId = "bg-upload-" + Guid.NewGuid(); }
Background
@{ var logoMedia = GetMedia(MediaType.Logo); var logoUrl = GetMediaUrl(logoMedia); } @if (logoUrl != null) { } else { @_game?.Title }
@if (!string.IsNullOrWhiteSpace(_game?.Description)) {

@_game.Description

}
Media
@{ var coverMedia = GetMedia(MediaType.Cover); var coverUrl = GetMediaUrl(coverMedia); } @if (coverUrl != null && coverMedia?.MimeType?.StartsWith("video/") == true) {
@if (_game?.Singleplayer == true || (_game?.MultiplayerModes?.Any() ?? false)) {
Players
@if (_game.Singleplayer) { Singleplayer } @if (_game.MultiplayerModes != null) { @foreach (var mode in _game.MultiplayerModes) { @mode.Type.GetDisplayName() @if (mode.MinPlayers > 0 && mode.MaxPlayers > 0) { · @mode.MinPlayers–@mode.MaxPlayers players } } }
} @if (_game?.Developers?.Any() == true) {
Developers
@foreach (var dev in _game.Developers) { @dev.Name }
} @if (_game?.Publishers?.Any() == true) {
Publishers
@foreach (var pub in _game.Publishers) { @pub.Name }
} @if (_game?.Genres?.Any() == true) {
Genres
@foreach (var genre in _game.Genres) { @genre.Name }
} @if (_game?.Tags?.Any() == true) {
Tags
@foreach (var tag in _game.Tags) { @tag.Name }
}
@code { [CascadingParameter(Name = "GameId")] public Guid GameId { get; set; } string _ssInputId = "ss-upload-" + Guid.NewGuid(); string _vidInputId = "vid-upload-" + Guid.NewGuid(); string[] ValidImageMimeTypes = [ MediaTypeNames.Image.Png, MediaTypeNames.Image.Jpeg, MediaTypeNames.Image.Gif, MediaTypeNames.Image.Webp, "image/apng", ]; string[] ValidVideoMimeTypes = [ "video/mp4", "video/webm", "video/x-matroska" ]; string[] ValidCoverMimeTypes = [ MediaTypeNames.Image.Png, MediaTypeNames.Image.Jpeg, MediaTypeNames.Image.Gif, MediaTypeNames.Image.Webp, "image/apng", "video/mp4", "video/webm", "video/x-matroska" ]; List _media = new(); Game _game; IJSObjectReference _domHelper; protected override async Task OnInitializedAsync() { _game = await GameService.Query(q => { return q .Include(g => g.Media) .ThenInclude(m => m.StorageLocation) .Include(g => g.Developers) .Include(g => g.Publishers) .Include(g => g.Genres) .Include(g => g.Tags) .Include(g => g.MultiplayerModes); }) .GetAsync(GameId); _media = _game.Media?.ToList() ?? new(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) _domHelper = await ScriptProvider.ImportModuleAsync("DomHelper"); } async Task ClickFileInput(string inputId) => await _domHelper.InvokeVoidAsync("ClickElement", inputId); Media? GetMedia(MediaType type) => _media.FirstOrDefault(m => m.Type == type); string? GetMediaUrl(Media? media) => media != null && MediaService.FileExists(media) ? $"/api/Media/{media.Id}/Download?fileId={media.FileId}&v={media.UpdatedOn.Ticks}" : null; string GetMediaDownloadUrl(Media media) => $"/api/Media/{media.Id}/Download?fileId={media.FileId}&v={media.UpdatedOn.Ticks}"; List GetMediaList(params MediaType[] types) => _media.Where(m => types.Contains(m.Type) && MediaService.FileExists(m)) .OrderBy(m => m.SortOrder) .ToList(); async Task UploadMediaByType(InputFileChangeEventArgs e, MediaType type) { var validTypes = type switch { MediaType.Video => ValidVideoMimeTypes, MediaType.Cover => ValidCoverMimeTypes, _ => ValidImageMimeTypes }; var config = Settings.Value.Server.Media.GetMediaTypeConfig(type); var isSingular = type != MediaType.Screenshot && type != MediaType.Video; foreach (var file in e.GetMultipleFiles()) { if (!validTypes.Contains(file.ContentType)) { MessageService.Error($"Unsupported file type: {file.Name}"); continue; } if (file.Size > config.MaxFileSize) { MessageService.Error($"File size must be smaller than {ByteSizeLib.ByteSize.FromBytes(config.MaxFileSize)}: {file.Name}"); continue; } // For singular types (Background, Logo, Cover), replace existing var existing = isSingular ? GetMedia(type) : null; Media media; if (existing != null) { media = existing; media.MimeType = file.ContentType; media.SourceUrl = ""; MediaService.DeleteLocalMediaFile(media); media = await MediaService.WriteToFileAsync(media, file.OpenReadStream(maxAllowedSize: config.MaxFileSize)); await MediaService.UpdateAsync(media); } else { media = new Media { GameId = GameId, Type = type, MimeType = file.ContentType, SourceUrl = "", StorageLocation = await MediaService.GetDefaultStorageLocationAsync(), Crc32 = string.Empty, SortOrder = GetNextSortOrder(type) }; media = await MediaService.WriteToFileAsync(media, file.OpenReadStream(maxAllowedSize: config.MaxFileSize)); } } await RefreshMedia(); MessageService.Success($"{type} uploaded!"); } async Task SearchMediaByType(MediaType type) { var multiSelect = type == MediaType.Screenshot || type == MediaType.Video; var modalOptions = new ModalOptions { Title = $"Download {type}", Maximizable = false, DefaultMaximized = true, Closable = true, OkText = "Select", }; var grabberOptions = new MediaGrabberOptions { Type = type, Search = _game.Title, MultiSelect = multiSelect }; var modalRef = await ModalService.CreateModalAsync(modalOptions, grabberOptions); modalRef.OnOk = async (downloadResult) => { if (multiSelect && downloadResult.Results.Any()) { foreach (var entry in downloadResult.Results) { using var download = entry.Download; var media = new Media { GameId = GameId, Type = type, SourceUrl = entry.Result.SourceUrl, MimeType = download.MimeType, StorageLocation = await MediaService.GetDefaultStorageLocationAsync(), Crc32 = string.Empty, SortOrder = GetNextSortOrder(type) }; await MediaService.WriteToFileAsync(media, download.Stream); } await RefreshMedia(); return; } using var singleDownload = downloadResult.Download; var isSingular = type != MediaType.Screenshot && type != MediaType.Video; var existing = isSingular ? GetMedia(type) : null; Media singleMedia; if (existing != null) { singleMedia = existing; singleMedia.SourceUrl = downloadResult.Result.SourceUrl; singleMedia.MimeType = singleDownload.MimeType; MediaService.DeleteLocalMediaFile(singleMedia); singleMedia = await MediaService.WriteToFileAsync(singleMedia, singleDownload.Stream); } else { singleMedia = new Media { GameId = GameId, Type = type, SourceUrl = downloadResult.Result.SourceUrl, MimeType = singleDownload.MimeType, StorageLocation = await MediaService.GetDefaultStorageLocationAsync(), Crc32 = string.Empty, SortOrder = GetNextSortOrder(type) }; singleMedia = await MediaService.WriteToFileAsync(singleMedia, singleDownload.Stream); } await RefreshMedia(); }; } async Task RemoveMediaByType(MediaType type) { var media = GetMedia(type); if (media != null) await RemoveMedia(media); } async Task RemoveMedia(Media media) { _media.Remove(media); if (media.Id != Guid.Empty) await MediaService.DeleteAsync(media); await InvokeAsync(StateHasChanged); } int GetNextSortOrder(MediaType type) { var existing = _media.Where(m => m.Type == type).ToList(); return existing.Any() ? existing.Max(m => m.SortOrder) + 1 : 0; } async Task NormalizeSortOrders() { var list = _media .Where(m => m.Type == MediaType.Screenshot || m.Type == MediaType.Video) .OrderBy(m => m.SortOrder) .ThenBy(m => m.CreatedOn) .ToList(); for (int i = 0; i < list.Count; i++) { if (list[i].SortOrder != i) { list[i].SortOrder = i; await MediaService.UpdateAsync(list[i]); } } } async Task MoveMediaLeft(Media media) { await NormalizeSortOrders(); var list = GetMediaList(MediaType.Screenshot, MediaType.Video); var index = list.IndexOf(media); if (index <= 0) return; // Swap sort orders (list[index].SortOrder, list[index - 1].SortOrder) = (list[index - 1].SortOrder, list[index].SortOrder); await MediaService.UpdateAsync(list[index]); await MediaService.UpdateAsync(list[index - 1]); await RefreshMedia(); } async Task MoveMediaRight(Media media) { await NormalizeSortOrders(); var list = GetMediaList(MediaType.Screenshot, MediaType.Video); var index = list.IndexOf(media); if (index < 0 || index >= list.Count - 1) return; // Swap sort orders (list[index].SortOrder, list[index + 1].SortOrder) = (list[index + 1].SortOrder, list[index].SortOrder); await MediaService.UpdateAsync(list[index]); await MediaService.UpdateAsync(list[index + 1]); await RefreshMedia(); } async Task RefreshMedia() { _media = (await MediaService.Include(m => m.StorageLocation).GetAsync(m => m.GameId == GameId)).ToList(); await InvokeAsync(StateHasChanged); } }