@namespace ScadaLink.CentralUI.Components.Shared
@implements IAsyncDisposable
@inject IJSRuntime JS
@if (ShowToolbar)
{
}
@code {
[Parameter] public string Value { get; set; } = "";
[Parameter] public EventCallback ValueChanged { get; set; }
[Parameter] public string Language { get; set; } = "csharp";
[Parameter] public string Height { get; set; } = "320px";
[Parameter] public bool ReadOnly { get; set; } = false;
[Parameter] public bool ShowToolbar { get; set; } = true;
///
/// Runtime globals surface the script is analyzed against. Defaults to
/// template/shared-script globals; set to InboundApi on the API
/// method editor so Route and Parameters type-check.
///
[Parameter] public ScriptAnalysis.ScriptKind ScriptKind { get; set; } = ScriptAnalysis.ScriptKind.Template;
///
/// Parameter names declared on the form (derived from the SchemaBuilder's
/// JSON Schema), surfaced as completions inside Parameters["..."] literals
/// and used by the unknown-key diagnostic.
///
[Parameter] public IReadOnlyList? DeclaredParameters { get; set; }
///
/// Full shapes (name + type + required) for the declared parameters.
/// Used by Parameters["name"] hover to show the declared type. If null,
/// derived from with type "Object".
///
[Parameter] public IReadOnlyList? DeclaredParameterShapes { get; set; }
///
/// Shapes (name + parameter list + return type) of other scripts on the
/// same template. Surfaced inside CallScript("...") for completion,
/// signature help, hover, and argument-count diagnostics.
///
[Parameter] public IReadOnlyList? SiblingScripts { get; set; }
///
/// Attributes declared on the current template. Surfaced inside
/// Attributes["..."] for completion and SCADA006 diagnostics.
///
[Parameter] public IReadOnlyList? SelfAttributes { get; set; }
///
/// Child compositions on the current template, each with its template's
/// attributes and scripts. Surfaced for Children["X"].Attributes,
/// Children["X"].CallScript, and SCADA007 diagnostics.
///
[Parameter] public IReadOnlyList? Children { get; set; }
///
/// Parent template when the current template is composed inside exactly
/// one other template. null at the root or when multiple parents
/// exist. Surfaced for Parent.Attributes / Parent.CallScript.
///
[Parameter] public ScriptAnalysis.CompositionContext? Parent { get; set; }
///
/// Fires whenever Monaco's marker set updates (after the 500 ms diagnostic
/// debounce). Hosts can render a with the same
/// data.
///
[Parameter] public EventCallback> MarkersChanged { get; set; }
private ElementReference _hostRef;
private DotNetObjectReference? _dotNetRef;
private readonly string _id = Guid.NewGuid().ToString("N");
private string _lastSentValue = "";
private bool _initialized;
private bool _wrap;
private bool _minimap;
private bool _dark;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_dotNetRef = DotNetObjectReference.Create(this);
_lastSentValue = Value ?? "";
try
{
await JS.InvokeVoidAsync(
"MonacoBlazor.createEditor",
_id,
_hostRef,
new
{
value = Value ?? "",
language = Language,
readOnly = ReadOnly
},
_dotNetRef);
_initialized = true;
}
catch
{
// Prerendering or JS not ready — swallow; subsequent render will retry.
}
}
else if (_initialized && (Value ?? "") != _lastSentValue)
{
_lastSentValue = Value ?? "";
try { await JS.InvokeVoidAsync("MonacoBlazor.setValue", _id, _lastSentValue); } catch { }
}
}
[JSInvokable]
public Task OnValueChanged(string newValue)
{
_lastSentValue = newValue ?? "";
return ValueChanged.InvokeAsync(_lastSentValue);
}
[JSInvokable]
public Task OnMarkersChanged(ScriptAnalysis.DiagnosticMarker[] markers) =>
MarkersChanged.InvokeAsync(markers ?? Array.Empty());
/// Programmatic scroll-to-line (called by the problems panel).
public async Task RevealLineAsync(int line, int column = 1)
{
if (!_initialized) return;
try { await JS.InvokeVoidAsync("MonacoBlazor.revealLine", _id, line, column); } catch { }
}
///
/// Called from JS at completion-request time so the form's latest state is
/// passed through, not whatever was captured when the editor was created.
///
[JSInvokable]
public ScadaContext GetContext() => new(
DeclaredParameters?.ToArray() ?? Array.Empty(),
SiblingScripts?.ToArray() ?? Array.Empty(),
DeclaredParameterShapes?.ToArray()
?? DeclaredParameters?.Select(n => new ScriptAnalysis.ParameterShape(n, "Object", true)).ToArray()
?? Array.Empty(),
SelfAttributes?.ToArray() ?? Array.Empty(),
Children?.ToArray() ?? Array.Empty(),
Parent,
ScriptKind);
private async Task FormatAsync()
{
if (!_initialized) return;
try { await JS.InvokeVoidAsync("MonacoBlazor.format", _id); } catch { }
}
private async Task ToggleWrap()
{
_wrap = !_wrap;
try { await JS.InvokeVoidAsync("MonacoBlazor.setEditorOption", _id, "wordWrap", _wrap ? "on" : "off"); } catch { }
}
private async Task ToggleMinimap()
{
_minimap = !_minimap;
try { await JS.InvokeVoidAsync("MonacoBlazor.setEditorOption", _id, "minimap", new { enabled = _minimap }); } catch { }
}
private async Task ToggleTheme()
{
_dark = !_dark;
try { await JS.InvokeVoidAsync("MonacoBlazor.setEditorOption", _id, "theme", _dark ? "vs-dark" : "vs"); } catch { }
}
public async ValueTask DisposeAsync()
{
if (_initialized)
{
try { await JS.InvokeVoidAsync("MonacoBlazor.dispose", _id); } catch { }
}
_dotNetRef?.Dispose();
}
public record ScadaContext(
string[] DeclaredParameters,
ScriptAnalysis.ScriptShape[] SiblingScripts,
ScriptAnalysis.ParameterShape[] DeclaredParameterShapes,
ScriptAnalysis.AttributeShape[] SelfAttributes,
ScriptAnalysis.CompositionContext[] Children,
ScriptAnalysis.CompositionContext? Parent,
ScriptAnalysis.ScriptKind ScriptKind);
}