LANCommander/LANCommander.Server/UI/Components/ScriptEditorDialog.razor

375 lines
15 KiB
Text
Raw Normal View History

@using LANCommander.SDK.Enums
@using OptionSchema = LANCommander.SDK.Models.OptionSchema
2026-05-17 01:21:05 -05:00
@using OptionChoiceYamlConverter = LANCommander.SDK.Models.OptionChoiceYamlConverter
@using LANCommander.Server.Extensions
@using YamlDotNet.Serialization
@using YamlDotNet.Serialization.NamingConventions
@inherits FeedbackComponent<ScriptEditorOptions, Script>
@inject ScriptService ScriptService
@inject ModuleService ModuleService
@inject ArchiveService ArchiveService
@inject ServerService ServerService
@inject RedistributableService RedistributableService
@inject ModalService ModalService
@inject IMessageService MessageService
2024-01-04 18:23:54 -06:00
@inject ILogger<ScriptEditorDialog> Logger
<Flex Justify="FlexJustify.SpaceBetween" Align="FlexAlign.Center" Style="margin-bottom: 16px;">
<Flex Gap="FlexGap.Small" Wrap="FlexWrap.Wrap" Align="FlexAlign.Center">
@foreach (var group in Snippets.Select(s => s.Group).Distinct())
{
<Dropdown>
<Overlay>
<Menu>
@foreach (var snippet in Snippets.Where(s => s.Group == group))
{
<MenuItem OnClick="() => InsertSnippet(snippet)">
@snippet.Name
</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Type="@ButtonType.Primary">@group</Button>
</ChildContent>
</Dropdown>
}
@if (_variables.Any())
{
<Dropdown>
<Overlay>
<Menu>
@foreach (var variable in _variables)
{
<MenuItem OnClick="() => InsertText(variable)">
@variable
</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Type="@ButtonType.Primary">Variables</Button>
</ChildContent>
</Dropdown>
}
@if (_optionKeys.Any())
{
<Dropdown>
<Overlay>
<Menu>
@foreach (var optionKey in _optionKeys)
{
<MenuItem OnClick="() => InsertOptionValue(optionKey)">
$Options.@optionKey
</MenuItem>
}
</Menu>
</Overlay>
<ChildContent>
<Button Type="@ButtonType.Primary">Options</Button>
</ChildContent>
</Dropdown>
}
@if (_archive != null)
{
<Button Icon="@IconType.Outline.FolderOpen" OnClick="BrowseForPath" Type="@ButtonType.Text">Browse</Button>
}
@if (IsDebuggable && HasDebugTarget)
{
<Tooltip Title="Debug">
<Button Icon="@IconType.Outline.CaretRight" Type="@ButtonType.Text" OnClick="Debug" Disabled="@_debugging" />
</Tooltip>
}
</Flex>
<Checkbox @bind-Checked="_script.RequiresAdmin">Requires Admin</Checkbox>
</Flex>
<CodeInput @ref="_codeInput" @bind-Value="_script.Contents" Language="Languages.PowerShell" OnSave="Save" ScriptType="@_script.Type.ToString()" ModuleFunctions="@_moduleFunctions" OnReady="OnEditorReady" />
<PowerShellConsole @ref="_console" Id="_id" @bind-Active="_debugging"/>
<Form @ref="@_form" Model="@_script" Layout="@FormLayout.Vertical">
<FormItem Label="Name" Required Rules=@(new[] { new FormValidationRule { Required = true } })>
<Input @bind-Value="@context.Name" />
</FormItem>
<FormItem Label="Description">
<TextArea @bind-Value="context.Description" MaxLength=500 ShowCount />
</FormItem>
</Form>
@code {
Guid _id = Guid.NewGuid();
Form<Script>? _form;
CodeInput? _codeInput;
PowerShellConsole? _console;
IEnumerable<Snippet> Snippets { get; set; } = [];
List<ModuleFunctionCompletion> _moduleFunctions = new();
List<string> _variables = new();
List<string> _optionKeys = new();
string _redistributableName = string.Empty;
bool _debugging;
bool _isNewScript;
Archive? _archive;
Script _script = new();
bool IsDebuggable =>
_script.Type == ScriptType.Package;
bool HasDebugTarget =>
(Options.GameId.HasValue && Options.GameId != Guid.Empty) ||
(Options.ToolId.HasValue && Options.ToolId != Guid.Empty) ||
(Options.RedistributableId.HasValue && Options.RedistributableId != Guid.Empty);
static readonly string[] GameClientScriptTypes = ["Install", "Uninstall", "BeforeStart", "AfterStop", "NameChange", "KeyChange", "SaveUpload", "SaveDownload", "DetectInstall", "RunWrapper"];
static readonly Dictionary<string, string[]> VariablesByScriptType = new()
{
["Install"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$ToolManifest", "$RedistributableManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["Uninstall"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["BeforeStart"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$PlayerAlias", "$Server", "$ServerId", "$ServerName", "$ServerHost", "$ServerPort", "$GameTitle", "$GameId", "$ToolManifest", "$RedistributableManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["AfterStop"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$PlayerAlias", "$Server", "$ServerId", "$ServerName", "$ServerHost", "$ServerPort", "$GameTitle", "$GameId", "$ToolManifest", "$RedistributableManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["NameChange"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$OldPlayerAlias", "$NewPlayerAlias", "$RedistributableManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["KeyChange"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$AllocatedKey", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["DetectInstall"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$ToolManifest", "$RedistributableManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["SaveUpload"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["SaveDownload"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$DisplayWidth", "$DisplayHeight", "$DisplayRefreshRate", "$DisplayBitDepth", "$IPXRelayHost", "$IPXRelayPort"],
["Package"] = ["$WorkingDirectory", "$Game", "$Tool", "$Redistributable"],
["GameStarted"] = ["$WorkingDirectory", "$Server", "$Game", "$User", "$ServerId", "$ServerName", "$ServerHost", "$ServerPort", "$GameTitle", "$GameId"],
["GameStopped"] = ["$WorkingDirectory", "$Server", "$Game", "$User", "$ServerId", "$ServerName", "$ServerHost", "$ServerPort", "$GameTitle", "$GameId"],
["UserRegistration"] = ["$WorkingDirectory", "$User"],
["UserLogin"] = ["$WorkingDirectory", "$User"],
["RunWrapper"] = ["$InstallDirectory", "$WorkingDirectory", "$ServerAddress", "$DefaultInstallDirectory", "$GameManifest", "$RedistributableManifest", "$ExecutablePath", "$Arguments"],
};
void UpdateVariables()
{
var key = _script.Type.ToString();
if (VariablesByScriptType.TryGetValue(key, out var vars))
_variables = vars.ToList();
else
_variables = ["$WorkingDirectory"];
}
protected override async Task OnInitializedAsync()
{
if (Options.ScriptId != Guid.Empty)
{
_script = await ScriptService.GetAsync(Options.ScriptId);
if (_script.RedistributableId.HasValue && _script.RedistributableId != Guid.Empty)
await LoadRedistributableOptions(_script.RedistributableId.Value);
}
else
{
_isNewScript = true;
var scriptType = Options.ScriptType ?? ScriptType.Install;
_script = new Script
{
Type = scriptType,
Name = scriptType.GetDisplayName(),
};
if (Options.GameId.HasValue && Options.GameId != Guid.Empty)
{
_script.GameId = Options.GameId;
_archive = await ArchiveService.GetLatestArchiveAsync(a => a.GameId == Options.GameId);
}
else if (Options.RedistributableId.HasValue && Options.RedistributableId != Guid.Empty)
{
_script.RedistributableId = Options.RedistributableId;
_archive = await ArchiveService.GetLatestArchiveAsync(a => a.RedistributableId == Options.RedistributableId);
await LoadRedistributableOptions(Options.RedistributableId.Value);
}
else if (Options.ServerId.HasValue && Options.ServerId != Guid.Empty)
{
_script.ServerId = Options.ServerId;
var server = await ServerService.GetAsync(Options.ServerId.Value);
_archive = await ArchiveService.GetLatestArchiveAsync(a => a.GameId == server.GameId);
}
else if (Options.ToolId.HasValue && Options.ToolId != Guid.Empty)
{
_script.ToolId = Options.ToolId;
_archive = await ArchiveService.GetLatestArchiveAsync(a => a.ToolId == Options.ToolId);
}
}
UpdateVariables();
Snippets = ScriptService.GetSnippets();
_moduleFunctions = ModuleService.GetPublicFunctionCompletions()
.Select(f => new ModuleFunctionCompletion(f.Name, string.IsNullOrWhiteSpace(f.Synopsis) ? null : f.Synopsis, f.Module))
.ToList();
}
async Task OnEditorReady()
{
if (_isNewScript && _codeInput != null && Options.ScriptType.HasValue)
{
await _codeInput.InsertTemplateAsync(Options.ScriptType.Value.ToString());
}
}
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
{
var success = await Save();
if (success)
await base.OkCancelRefWithResult!.OnOk(_script);
else
args.Reject();
}
async Task<bool> Save()
{
try
{
if (_form.Validate())
{
if (_script.Id == Guid.Empty)
_script = await ScriptService.AddAsync(_script);
else
_script = await ScriptService.UpdateAsync(_script);
MessageService.Success("Script saved!");
return true;
}
return false;
}
catch (Exception ex)
{
MessageService.Error("Script could not be saved!");
Logger.LogError(ex, "Script could not be saved!");
return false;
}
}
async Task Debug()
{
if (!IsDebuggable)
return;
var saved = await Save();
if (!saved)
return;
if (Options.GameId.HasValue && Options.GameId != Guid.Empty)
await _console.DebugPackagingScript(Options.GameId.Value);
else if (Options.ToolId.HasValue && Options.ToolId != Guid.Empty)
await _console.DebugToolPackagingScript(Options.ToolId.Value);
else if (Options.RedistributableId.HasValue && Options.RedistributableId != Guid.Empty)
await _console.DebugRedistributablePackagingScript(Options.RedistributableId.Value);
}
async Task BrowseForPath()
{
var modalOptions = new ModalOptions()
{
Title = "Choose Reference",
Maximizable = false,
DefaultMaximized = true,
Closable = true,
OkText = "Insert File Path"
};
var browserOptions = new FilePickerOptions()
{
ArchiveId = _archive?.Id ?? Guid.Empty,
Select = true,
Multiple = false
};
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
modalRef.OnOk = (results) =>
{
var path = results.FirstOrDefault().Path;
InsertText($"$InstallDirectory\\{path.Replace('/', '\\')}");
StateHasChanged();
return Task.CompletedTask;
};
}
async Task InsertText(string text)
{
if (_codeInput != null)
await _codeInput.InsertText(text);
}
async Task InsertSnippet(Snippet snippet)
{
if (_codeInput != null)
await _codeInput.InsertSnippet(snippet.Content);
}
async Task InsertOptionValue(string optionKey)
{
if (_codeInput == null)
return;
var text = $"$Options.{optionKey}";
if (string.IsNullOrEmpty(_script.Contents) || !_script.Contents.Contains("Get-RedistributableOptions"))
{
var boilerplate = $"$Options = Get-RedistributableOptions -Path $InstallDirectory -Id $GameManifest.Id -Name \"{_redistributableName}\"\n";
text = boilerplate + text;
}
await _codeInput.InsertText(text);
}
async Task LoadRedistributableOptions(Guid redistributableId)
{
try
{
var redistributable = await RedistributableService.GetAsync(redistributableId);
if (redistributable == null)
return;
_redistributableName = redistributable.Name ?? string.Empty;
if (!string.IsNullOrWhiteSpace(redistributable.OptionSchema))
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
2026-05-17 01:21:05 -05:00
.WithTypeConverter(new OptionChoiceYamlConverter())
.IgnoreUnmatchedProperties()
.Build();
var schema = deserializer.Deserialize<OptionSchema>(redistributable.OptionSchema);
if (schema != null)
{
_optionKeys = schema.GetFlattenedOptions().Keys.ToList();
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to load redistributable option schema");
}
}
}