Options are defined schema that the redistributable will allow a game to customize / override. This will be useful for install scripts on a redistributable. It opens up the opportunity to define a redistributable for something such as dgVoodoo and override options on a per-game basis without having to edit the config manually in the game's scripts.
295 lines
10 KiB
Text
295 lines
10 KiB
Text
@using System.Collections.ObjectModel
|
|
@using System.Text.Json
|
|
@using YamlDotNet.Serialization
|
|
@using YamlDotNet.Serialization.NamingConventions
|
|
@using Action = LANCommander.Server.Data.Models.Action
|
|
@inject ActionService ActionService
|
|
@inject ArchiveService ArchiveService
|
|
@inject GameService GameService
|
|
@inject MessageService MessageService
|
|
@inject ILogger<ActionEditor> Logger
|
|
|
|
<Flex Vertical Gap="FlexGap.Large">
|
|
<Table TItem="Action" DataSource="@_sortedActions.OrderBy(a => a.SortOrder)" HidePagination Style="border: 1px solid #f0f0f0" Responsive>
|
|
<PropertyColumn Property="a => a.Name">
|
|
<Input Type="InputType.Text" @bind-Value="context.Name"/>
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="a => a.Path">
|
|
<FilePicker @bind-Value="context.Path" ArchiveId="@(_archive?.Id ?? Guid.Empty)" AllowDirectories Title="Select Action Executable" OnSelected="(path) => OnPathSelected(path, context)"/>
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="a => a.Arguments">
|
|
<Input Type="InputType.Text" @bind-Value="context.Arguments"/>
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="a => a.WorkingDirectory" Title="Working Dir">
|
|
<Input Type="InputType.Text" @bind-Value="context.WorkingDirectory"/>
|
|
</PropertyColumn>
|
|
<PropertyColumn Property="a => a.PrimaryAction" Title="Primary" Style="text-align: center">
|
|
<Checkbox @bind-Checked="context.PrimaryAction"/>
|
|
</PropertyColumn>
|
|
@if (_shimRedistributables.Any())
|
|
{
|
|
<PropertyColumn Property="a => a.OptionOverrides" Title="Option Overrides">
|
|
<Button Size="ButtonSize.Small" OnClick="() => ToggleOverrides(context)" Icon="@IconType.Outline.Setting" Type="ButtonType.Text">
|
|
@(HasOverrides(context) ? "Edit" : "Add")
|
|
</Button>
|
|
</PropertyColumn>
|
|
}
|
|
<ActionColumn>
|
|
<Flex Gap="FlexGap.Small" Justify="FlexJustify.End">
|
|
<Button OnClick="() => MoveUp(context)" Icon="@IconType.Outline.Up" Type="ButtonType.Text"/>
|
|
<Button OnClick="() => MoveDown(context)" Icon="@IconType.Outline.Down" Type="ButtonType.Text"/>
|
|
|
|
<Popconfirm OnConfirm="() => RemoveAction(context)" Title="Are you sure you want to remove this action?">
|
|
<Button Icon="@IconType.Outline.Close" Type="ButtonType.Text" Danger/>
|
|
</Popconfirm>
|
|
</Flex>
|
|
</ActionColumn>
|
|
</Table>
|
|
|
|
@if (_editingOverridesFor != null && _shimRedistributables.Any())
|
|
{
|
|
<Card Title="@($"Option Overrides for \"{_editingOverridesFor.Name}\"")">
|
|
@foreach (var redist in _shimRedistributables)
|
|
{
|
|
var schema = ParseOptionSchema(redist.OptionSchema);
|
|
if (schema?.Options != null && schema.Options.Any())
|
|
{
|
|
<AntDesign.Text Strong>@redist.Name</AntDesign.Text>
|
|
var flatOptions = schema.GetFlattenedOptions();
|
|
@foreach (var kvp in flatOptions)
|
|
{
|
|
var optionName = kvp.Key;
|
|
var option = kvp.Value;
|
|
var redistId = redist.Id;
|
|
<FormItem Label="@($"{optionName}{(!string.IsNullOrWhiteSpace(option.Description) ? $" — {option.Description}" : "")}")">
|
|
<Input Value="@GetOverrideValue(_editingOverridesFor, redistId, optionName)"
|
|
ValueChanged="(string v) => SetOverrideValue(_editingOverridesFor, redistId, optionName, v)"
|
|
Placeholder="Use game default" />
|
|
</FormItem>
|
|
}
|
|
}
|
|
}
|
|
<Button OnClick="() => _editingOverridesFor = null">Done</Button>
|
|
</Card>
|
|
}
|
|
|
|
<GridRow Justify="RowJustify.End">
|
|
<GridCol>
|
|
<Button OnClick="AddAction" Type="ButtonType.Primary">Add Action</Button>
|
|
</GridCol>
|
|
</GridRow>
|
|
</Flex>
|
|
|
|
@code {
|
|
[CascadingParameter(Name = "GameId")] public Guid? GameId { get; set; }
|
|
[CascadingParameter(Name = "ServerId")] public Guid? ServerId { get; set; }
|
|
[CascadingParameter(Name = "ToolId")] public Guid? ToolId { get; set; }
|
|
|
|
Archive? _archive;
|
|
Game? _game;
|
|
Server? _server;
|
|
Tool? _tool;
|
|
|
|
ObservableCollection<Action> _sortedActions = new();
|
|
List<Data.Models.Redistributable> _shimRedistributables = new();
|
|
Action _editingOverridesFor;
|
|
|
|
protected override async Task OnInitializedAsync() => await LoadActions();
|
|
|
|
async Task LoadActions()
|
|
{
|
|
if (GameId.HasValue)
|
|
{
|
|
_sortedActions = new ObservableCollection<Action>(await ActionService.SortBy(a => a.SortOrder).GetAsync(a => a.GameId == GameId.Value));
|
|
_archive = await ArchiveService.GetLatestArchiveAsync(a => a.GameId == GameId);
|
|
|
|
var game = await GameService
|
|
.AsNoTracking()
|
|
.Include(g => g.Redistributables)
|
|
.GetAsync(GameId.Value);
|
|
|
|
_shimRedistributables = game?.Redistributables?
|
|
.Where(r => !string.IsNullOrWhiteSpace(r.OptionSchema))
|
|
.ToList() ?? new();
|
|
}
|
|
|
|
if (ServerId.HasValue)
|
|
_sortedActions = new ObservableCollection<Action>(await ActionService.SortBy(a => a.SortOrder).GetAsync(a => a.ServerId == ServerId.Value));
|
|
|
|
if (ToolId.HasValue)
|
|
{
|
|
_sortedActions = new ObservableCollection<Action>(await ActionService.SortBy(a => a.SortOrder).GetAsync(a => a.ToolId == ToolId.Value));
|
|
_archive = await ArchiveService.GetLatestArchiveAsync(a => a.ToolId == ToolId);
|
|
}
|
|
}
|
|
|
|
void AddAction()
|
|
{
|
|
var newAction = new Action
|
|
{
|
|
PrimaryAction = _sortedActions.Count == 0,
|
|
SortOrder = _sortedActions.Count
|
|
};
|
|
|
|
if (GameId.HasValue)
|
|
newAction.GameId = GameId;
|
|
|
|
if (ServerId.HasValue)
|
|
newAction.ServerId = ServerId;
|
|
|
|
if (ToolId.HasValue)
|
|
newAction.ToolId = ToolId;
|
|
|
|
_sortedActions.Add(newAction);
|
|
|
|
UpdateOrder();
|
|
}
|
|
|
|
async Task RemoveAction(Action action)
|
|
{
|
|
if (action.Id != Guid.Empty)
|
|
await ActionService.DeleteAsync(action);
|
|
|
|
_sortedActions.Remove(action);
|
|
|
|
UpdateOrder();
|
|
}
|
|
|
|
async Task MoveUp(Action action)
|
|
{
|
|
var index = _sortedActions.IndexOf(action);
|
|
|
|
if (index > 0)
|
|
_sortedActions.Move(index, index - 1);
|
|
|
|
UpdateOrder();
|
|
}
|
|
|
|
async Task MoveDown(Action action)
|
|
{
|
|
var index = _sortedActions.IndexOf(action);
|
|
|
|
if (index < _sortedActions.Count - 1)
|
|
_sortedActions.Move(index, index + 1);
|
|
|
|
UpdateOrder();
|
|
}
|
|
|
|
void UpdateOrder()
|
|
{
|
|
foreach (var action in _sortedActions)
|
|
action.SortOrder = _sortedActions.IndexOf(action);
|
|
|
|
if (GameId.HasValue && _game != null)
|
|
_game.Actions = _sortedActions;
|
|
|
|
if (ServerId.HasValue && _server != null)
|
|
_server.Actions = _sortedActions;
|
|
|
|
if (ToolId.HasValue && _tool != null)
|
|
_tool.Actions = _sortedActions;
|
|
}
|
|
|
|
public async Task Save()
|
|
{
|
|
try
|
|
{
|
|
foreach (var action in _sortedActions)
|
|
{
|
|
if (action.Id == Guid.Empty)
|
|
await ActionService.AddAsync(action);
|
|
else
|
|
await ActionService.UpdateAsync(action);
|
|
}
|
|
|
|
await LoadActions();
|
|
|
|
MessageService.Success("Actions updated!");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageService.Error("Could not update actions!");
|
|
Logger.LogError(ex, "Could not update actions!");
|
|
}
|
|
}
|
|
|
|
void OnPathSelected(string path, Action action)
|
|
{
|
|
action.Path = path;
|
|
action.WorkingDirectory = "{InstallDir}";
|
|
}
|
|
|
|
void ToggleOverrides(Action action)
|
|
{
|
|
_editingOverridesFor = _editingOverridesFor == action ? null : action;
|
|
}
|
|
|
|
bool HasOverrides(Action action)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(action.OptionOverrides);
|
|
}
|
|
|
|
SDK.Models.OptionSchema ParseOptionSchema(string yaml)
|
|
{
|
|
try
|
|
{
|
|
var deserializer = new DeserializerBuilder()
|
|
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
|
.IgnoreUnmatchedProperties()
|
|
.Build();
|
|
|
|
return deserializer.Deserialize<SDK.Models.OptionSchema>(yaml);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
string GetOverrideValue(Action action, Guid redistId, string optionName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(action.OptionOverrides))
|
|
return "";
|
|
|
|
try
|
|
{
|
|
var overrides = JsonSerializer.Deserialize<Dictionary<Guid, Dictionary<string, string>>>(action.OptionOverrides);
|
|
|
|
if (overrides != null && overrides.TryGetValue(redistId, out var redistOverrides) && redistOverrides.TryGetValue(optionName, out var value))
|
|
return value;
|
|
}
|
|
catch { }
|
|
|
|
return "";
|
|
}
|
|
|
|
void SetOverrideValue(Action action, Guid redistId, string optionName, string value)
|
|
{
|
|
Dictionary<Guid, Dictionary<string, string>> overrides;
|
|
|
|
try
|
|
{
|
|
overrides = string.IsNullOrWhiteSpace(action.OptionOverrides)
|
|
? new()
|
|
: JsonSerializer.Deserialize<Dictionary<Guid, Dictionary<string, string>>>(action.OptionOverrides) ?? new();
|
|
}
|
|
catch
|
|
{
|
|
overrides = new();
|
|
}
|
|
|
|
if (!overrides.ContainsKey(redistId))
|
|
overrides[redistId] = new Dictionary<string, string>();
|
|
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
overrides[redistId].Remove(optionName);
|
|
else
|
|
overrides[redistId][optionName] = value;
|
|
|
|
// Clean up empty entries
|
|
if (!overrides[redistId].Any())
|
|
overrides.Remove(redistId);
|
|
|
|
action.OptionOverrides = overrides.Any() ? JsonSerializer.Serialize(overrides) : null;
|
|
}
|
|
}
|