feat(adminui): create-new-script from the inline virtual-tag panel

This commit is contained in:
Joseph Doherty
2026-06-16 16:44:14 -04:00
parent 526eebb3bb
commit c98625fd9f
4 changed files with 192 additions and 1 deletions
@@ -50,7 +50,7 @@
<InputSelect id="vtag-script" @bind-Value="_form.ScriptId"
@bind-Value:after="OnScriptChangedAsync" class="form-select form-select-sm">
<option value="">— pick script —</option>
@foreach (var (id, display) in Scripts)
@foreach (var (id, display) in _scripts)
{
<option value="@id">@display</option>
}
@@ -59,6 +59,25 @@
</div>
</div>
@* No script bound yet → offer a one-click "New script" action that creates a
fresh (blank) Script row, binds this virtual tag to it, and expands the inline
editor below so the operator can author the body without leaving the modal. *@
@if (string.IsNullOrEmpty(_form.ScriptId))
{
<div class="mb-3">
<button type="button" class="btn btn-outline-primary btn-sm"
@onclick="CreateNewScriptAsync" disabled="@_scriptCreating">
@if (_scriptCreating) { <span class="spinner-border spinner-border-sm me-1"></span> }
New script
</button>
<span class="form-text ms-2">Creates a blank script bound to this virtual tag.</span>
@if (!string.IsNullOrWhiteSpace(_scriptCreateError))
{
<div class="text-danger small mt-2">@_scriptCreateError</div>
}
</div>
}
@* Inline script-source editor. Shown only when a script is bound. This panel saves
the SHARED Script row on its OWN concurrency-guarded button — it is deliberately
separate from the virtual-tag Create/Save below and never touches _form or closes
@@ -178,6 +197,10 @@
private bool _busy;
private string? _error;
// Working copy of the selectable scripts: the host-supplied list plus any script created inline via
// "New script", so the freshly-created (and now-bound) script is a real option in the dropdown.
private List<(string Id, string Display)> _scripts = new();
// Tracks which open this modal last loaded for, so unrelated Blazor Server re-renders don't
// rebuild _form / reload the script source and clobber in-progress edits. Null while closed.
private string? _loadedKey;
@@ -193,6 +216,10 @@
private bool _scriptSaved;
private string? _scriptError;
// "New script" action state (shown only when no script is bound yet).
private bool _scriptCreating;
private string? _scriptCreateError;
protected override async Task OnParametersSetAsync()
{
if (!Visible)
@@ -209,6 +236,10 @@
if (key == _loadedKey) return; // same open, re-render → preserve in-progress form + script edits
_loadedKey = key;
// Snapshot the host-supplied script options; "New script" appends to this working copy so the
// newly-created script is selectable without mutating the parent's parameter.
_scripts = Scripts.Select(s => (s.Id, s.Display)).ToList();
// Rebuild the working form whenever the host (re)opens the modal for a fresh target.
if (IsNew)
{
@@ -276,6 +307,43 @@
/// <summary>Refreshes the inline script panel when the operator picks a different script.</summary>
private Task OnScriptChangedAsync() => LoadScriptSourceAsync();
/// <summary>
/// Creates a brand-new (blank) script, binds this virtual tag to it, and expands the inline editor
/// so the operator can author the body without leaving the modal. The new script name seeds from the
/// virtual tag's name when set. This does NOT save the virtual tag — the binding lives in _form until
/// the operator clicks Create/Save below; only the new Script row is persisted here. On failure the
/// inline error is surfaced and the binding is left untouched.
/// </summary>
private async Task CreateNewScriptAsync()
{
_scriptCreating = true;
_scriptCreateError = null;
try
{
var seedName = string.IsNullOrWhiteSpace(_form.Name) ? "New script" : $"{_form.Name} script";
var result = await Svc.CreateScriptAsync(seedName);
if (!result.Ok || string.IsNullOrEmpty(result.CreatedId))
{
_scriptCreateError = result.Error ?? "Could not create the script.";
return;
}
// Bind the new script and load its (blank) source so the inline editor renders + expands.
_form.ScriptId = result.CreatedId;
await LoadScriptSourceAsync();
// Make the freshly-created script a real option in the dropdown so the select stays coherent.
if (!_scripts.Any(s => s.Id == result.CreatedId))
{
_scripts.Add((result.CreatedId, $"{seedName} (CSharp)"));
}
_scriptExpanded = true;
}
finally
{
_scriptCreating = false;
}
}
/// <summary>
/// Toggles the inline script-source panel. Clears the stale "Script saved." banner when
/// (re)expanding so a collapse/expand cycle doesn't re-show a confirmation from an earlier save.