LANCommander/LANCommander.Server/UI/Pages/Games/Edit/General.razor
2026-07-06 04:24:42 -05:00

321 lines
12 KiB
Text

@page "/Games/{id:guid}/General"
@page "/Games/{id:guid}"
@page "/Games/Add"
@attribute [Authorize(Roles = RoleService.AdministratorRoleName)]
@using PSC.Blazor.Components.MarkdownEditor
@using PSC.Blazor.Components.MarkdownEditor.Models
@inject CollectionService CollectionService
@inject CompanyService CompanyService
@inject EngineService EngineService
@inject GameService GameService
@inject GenreService GenreService
@inject PlatformService PlatformService
@inject TagService TagService
@inject IMessageService MessageService
@inject ModalService ModalService
@inject NavigationManager NavigationManager
@inject ILogger<General> Logger
<GameEditView Id="Id">
<TitleTemplate>
@if (context.Id == Guid.Empty)
{
<Text>Add New Game</Text>
}
else
{
<Text>General</Text>
}
</TitleTemplate>
<TitleExtraTemplate>
<Flex Gap="FlexGap.Small" Justify="FlexJustify.End">
@if (context != null && context.Id != Guid.Empty)
{
<Button OnClick="OpenExportDialog" Loading="_openingExportDialog">Export</Button>
}
<Button Type="ButtonType.Primary" OnClick="() => Save(context)">Save</Button>
</Flex>
</TitleExtraTemplate>
<ChildContent>
<Form
@ref="_form"
Model="@context"
Layout="@FormLayout.Vertical"
Context="formContext">
<FormItem Label="Title">
<GameMetadataLookup
ButtonText="Lookup"
AutoFocus="context.Id == Guid.Empty"
@bind-Value="context.Title"
GameId="@context.Id"
OnImported="OnGameImported" />
</FormItem>
<FormItem Label="Sort Title">
<Input @bind-Value="@context.SortTitle" />
</FormItem>
<FormItem Label="Notes">
<TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount />
</FormItem>
<FormItem Label="Description">
<MarkdownEditor
@bind-Value="context.Description"
SpellChecker="false"
CSSClass="ant-typography"
MinHeight="500px" />
</FormItem>
<FormItem Label="Engine">
<Select TItem="Engine"
TItemValue="Guid?"
DataSource="@Engines.OrderBy(e => e.Name)"
@bind-Value="@context.EngineId"
LabelName="Name"
ValueName="Id"
Placeholder="Select an Engine"
DefaultActiveFirstOption="false"
EnableSearch />
</FormItem>
<FormItem Label="Type">
<Select @bind-Value="@context.Type" TItem="GameType" TItemValue="GameType" DataSource="Enum.GetValues<GameType>()">
<LabelTemplate Context="Value">@Value.GetDisplayName()</LabelTemplate>
<ItemTemplate Context="Value">@Value.GetDisplayName()</ItemTemplate>
</Select>
</FormItem>
@if (context.Type != GameType.MainGame)
{
<FormItem Label="Base Game" Rules="@(new[] { new FormValidationRule { Required = true } })">
<Flex Gap="FlexGap.Small" Wrap="FlexWrap.NoWrap">
<Select TItem="Game"
TItemValue="Guid?"
DataSource="@Games.Where(g => g.Id != context.Id).OrderBy(g => String.IsNullOrWhiteSpace(g.SortTitle) ? g.Title : g.SortTitle)"
@bind-Value="@context.BaseGameId"
LabelName="Title"
ValueName="Id"
Placeholder="Select a Game"
DefaultActiveFirstOption="false"
EnableSearch>
<ItemTemplate Context="baseGame">
<Image Src="@GetIcon(baseGame)" Height="32" Width="32" Preview="false"></Image>
@baseGame.Title
</ItemTemplate>
</Select>
@if (context.BaseGameId == null || context.BaseGameId == Guid.Empty)
{
<Button Type="ButtonType.Primary" Disabled>Edit</Button>
}
else
{
<a href="@($"/Games/{context.BaseGameId}")" class="ant-btn ant-btn-primary" target="_blank">Edit</a>
}
</Flex>
</FormItem>
}
<FormItem Label="Key Allocation Method">
<Select @bind-Value="@context.KeyAllocationMethod" TItem="KeyAllocationMethod" TItemValue="KeyAllocationMethod" DataSource="Enum.GetValues<KeyAllocationMethod>()">
<LabelTemplate Context="Value">@Value.GetDisplayName()</LabelTemplate>
<ItemTemplate Context="Value">@Value.GetDisplayName()</ItemTemplate>
</Select>
</FormItem>
<FormItem Label="Released On">
<DatePicker TValue="DateTime?" @bind-Value="@context.ReleasedOn" Picker="@DatePickerType.Date" />
</FormItem>
<FormItem Label="Singleplayer">
<Checkbox @bind-Checked="@context.Singleplayer" />
</FormItem>
<FormItem Label="External IDs">
<Space Direction="SpaceDirection.Vertical" Style="width: 100%">
@if (context.ExternalIds != null)
{
@foreach (var externalId in context.ExternalIds.ToList())
{
<SpaceItem>
<Flex Gap="FlexGap.Small">
<Input @bind-Value="@externalId.Provider" Placeholder="Provider (e.g. Steam, GOG, IGDB)" Style="width: 200px" />
<Input @bind-Value="@externalId.ExternalId" Placeholder="ID" Style="flex: 1" />
<Button Type="ButtonType.Text" Danger OnClick="() => RemoveExternalId(context, externalId)" Icon="@IconType.Outline.Close" />
</Flex>
</SpaceItem>
}
}
<SpaceItem>
<Flex Justify="FlexJustify.End">
<Button Type="ButtonType.Primary" OnClick="() => AddExternalId(context)">Add</Button>
</Flex>
</SpaceItem>
</Space>
</FormItem>
<FormItem Label="Developers">
<TaxonomyInput DataService="CompanyService" @bind-Values="context.Developers" />
</FormItem>
<FormItem Label="Publishers">
<TaxonomyInput DataService="CompanyService" @bind-Values="context.Publishers" />
</FormItem>
<FormItem Label="Platforms">
<TaxonomyInput DataService="PlatformService" @bind-Values="context.Platforms" />
</FormItem>
<FormItem Label="Genres">
<TaxonomyInput DataService="GenreService" @bind-Values="context.Genres" />
</FormItem>
<FormItem Label="Tags">
<TaxonomyInput DataService="TagService" @bind-Values="context.Tags" />
</FormItem>
<FormItem Label="Collections">
<TaxonomyInput DataService="CollectionService" @bind-Values="context.Collections" />
</FormItem>
</Form>
</ChildContent>
</GameEditView>
@code {
[Parameter] public Guid Id { get; set; }
bool _loaded;
bool _success;
bool _openingExportDialog;
IEnumerable<Engine> Engines = new List<Engine>();
IEnumerable<Game> Games = new List<Game>();
private GameMetadataLookup? GameMetadataLookup;
Form<Game> _form;
protected override async Task OnParametersSetAsync()
{
if (!_loaded)
await LoadData();
}
private async Task LoadData()
{
var allowedTypes = new List<GameType> { GameType.MainGame, GameType.StandaloneExpansion, GameType.StandaloneMod };
Engines = await EngineService.SortBy(g => g.Name).GetAsync();
Games = (await GameService
.Include(g => g.Media.Where(m => m.Type == MediaType.Icon))
.GetAsync(g => allowedTypes.Contains(g.Type)))
.OrderByTitle(g => String.IsNullOrWhiteSpace(g.SortTitle) ? g.Title : g.SortTitle);
_loaded = true;
}
private async Task Save(Game game)
{
try
{
if (_form.Validate())
{
if (game.Id != Guid.Empty)
{
game.BaseGame = null; // clearing base game, model setup will write the BaseGameId value, referenced model will be loaded on update/get
game = await GameService.UpdateAsync(game);
await MessageService.SuccessAsync("Game updated!");
}
else
{
game = await GameService.AddAsync(game);
NavigationManager.LocationChanged += NotifyGameAdded;
NavigationManager.NavigateTo($"/Games/{game.Id}");
}
}
else
{
MessageService.Error("Could not save! Check fields and try again");
}
}
catch (Exception ex)
{
MessageService.Error("Could not save game!");
Logger.LogError(ex, "Could not save game!");
}
}
private void NotifyGameAdded(object? sender, LocationChangedEventArgs e)
{
NavigationManager.LocationChanged -= NotifyGameAdded;
MessageService.Success("Game added!");
}
private void OnGameImported(Guid gameId)
{
NavigationManager.NavigateTo($"/Games/{gameId}");
}
private string GetIcon(Game game)
{
var media = game?.Media?.FirstOrDefault(m => m.Type == SDK.Enums.MediaType.Icon);
if (media != null)
return $"/api/Media/{media.Id}/Download?fileId={media.FileId}";
else
return "/favicon.ico";
}
private void AddExternalId(Game game)
{
game.ExternalIds ??= new List<GameExternalId>();
game.ExternalIds.Add(new GameExternalId());
}
private void RemoveExternalId(Game game, GameExternalId externalId)
{
game.ExternalIds?.Remove(externalId);
}
private async Task Delete(Game game)
{
Games = new List<Game>();
try
{
await GameService.DeleteAsync(game);
}
catch (Exception ex)
{
MessageService.Error($"Could not delete the {game.Type.GetDisplayName().ToLower()}!");
Logger.LogError(ex, $"Could not delete the {game.Type.GetDisplayName().ToLower()}!");
}
}
private async Task OpenExportDialog()
{
_openingExportDialog = true;
await InvokeAsync(StateHasChanged);
await Task.Yield();
var game = await GameService.GetAsync(Id);
var modalOptions = new ModalOptions()
{
Title = $"Export {game.Title}",
Maximizable = false,
DefaultMaximized = false,
Closable = true,
OkText = "Add",
Footer = null,
};
var options = new ExportDialogOptions()
{
RecordId = Id,
RecordType = ImportExportRecordType.Game
};
var modalRef = await ModalService.CreateModalAsync<ExportDialog, ExportDialogOptions>(modalOptions, options);
_openingExportDialog = false;
await InvokeAsync(StateHasChanged);
await Task.Yield();
}
}