7b0b9c7365
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
236 lines
9.6 KiB
Plaintext
236 lines
9.6 KiB
Plaintext
@namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared
|
|
@implements IAsyncDisposable
|
|
@inject IJSRuntime JS
|
|
@inject Microsoft.Extensions.Logging.ILogger<MonacoEditor> Logger
|
|
|
|
@if (ShowToolbar)
|
|
{
|
|
<div class="d-flex justify-content-end align-items-center gap-3 mb-1 small text-muted">
|
|
<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none" @onclick="FormatAsync"
|
|
title="Format document (Ctrl/Cmd+Shift+F)">Format</button>
|
|
<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none" @onclick="ToggleWrap"
|
|
title="Word wrap">@(_wrap ? "Wrap on" : "Wrap off")</button>
|
|
<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none" @onclick="ToggleMinimap"
|
|
title="Toggle minimap">@(_minimap ? "Minimap on" : "Minimap off")</button>
|
|
<button type="button" class="btn btn-link btn-sm p-0 text-decoration-none" @onclick="ToggleTheme"
|
|
title="Toggle theme">@(_dark ? "Dark" : "Light")</button>
|
|
</div>
|
|
}
|
|
|
|
<div @ref="_hostRef" class="monaco-editor-host"
|
|
style="height: @Height; border: 1px solid var(--bs-border-color); border-radius: 0.25rem; overflow: hidden;"></div>
|
|
|
|
@code {
|
|
[Parameter] public string Value { get; set; } = "";
|
|
[Parameter] public EventCallback<string> 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;
|
|
|
|
/// <summary>
|
|
/// Runtime globals surface the script is analyzed against. Defaults to
|
|
/// template/shared-script globals; set to <c>InboundApi</c> on the API
|
|
/// method editor so <c>Route</c> and <c>Parameters</c> type-check.
|
|
/// </summary>
|
|
[Parameter] public ScriptAnalysis.ScriptKind ScriptKind { get; set; } = ScriptAnalysis.ScriptKind.Template;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[Parameter] public IReadOnlyList<string>? DeclaredParameters { get; set; }
|
|
|
|
/// <summary>
|
|
/// Full shapes (name + type + required) for the declared parameters.
|
|
/// Used by Parameters["name"] hover to show the declared type. If null,
|
|
/// derived from <see cref="DeclaredParameters"/> with type "Object".
|
|
/// </summary>
|
|
[Parameter] public IReadOnlyList<ScriptAnalysis.ParameterShape>? DeclaredParameterShapes { get; set; }
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[Parameter] public IReadOnlyList<ScriptAnalysis.ScriptShape>? SiblingScripts { get; set; }
|
|
|
|
/// <summary>
|
|
/// Attributes declared on the current template. Surfaced inside
|
|
/// <c>Attributes["..."]</c> for completion and SCADA006 diagnostics.
|
|
/// </summary>
|
|
[Parameter] public IReadOnlyList<ScriptAnalysis.AttributeShape>? SelfAttributes { get; set; }
|
|
|
|
/// <summary>
|
|
/// Child compositions on the current template, each with its template's
|
|
/// attributes and scripts. Surfaced for <c>Children["X"].Attributes</c>,
|
|
/// <c>Children["X"].CallScript</c>, and SCADA007 diagnostics.
|
|
/// </summary>
|
|
[Parameter] public IReadOnlyList<ScriptAnalysis.CompositionContext>? Children { get; set; }
|
|
|
|
/// <summary>
|
|
/// Parent template when the current template is composed inside exactly
|
|
/// one other template. <c>null</c> at the root or when multiple parents
|
|
/// exist. Surfaced for <c>Parent.Attributes</c> / <c>Parent.CallScript</c>.
|
|
/// </summary>
|
|
[Parameter] public ScriptAnalysis.CompositionContext? Parent { get; set; }
|
|
|
|
/// <summary>
|
|
/// Fires whenever Monaco's marker set updates (after the 500 ms diagnostic
|
|
/// debounce). Hosts can render a <see cref="ProblemsPanel"/> with the same
|
|
/// data.
|
|
/// </summary>
|
|
[Parameter] public EventCallback<IReadOnlyList<ScriptAnalysis.DiagnosticMarker>> MarkersChanged { get; set; }
|
|
|
|
private ElementReference _hostRef;
|
|
private DotNetObjectReference<MonacoEditor>? _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 (InvalidOperationException)
|
|
{
|
|
// Prerendering: JS interop is not available yet — the next
|
|
// (interactive) render retries. Expected, not logged.
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
// Circuit disconnected before init completed — nothing to do.
|
|
}
|
|
catch (JSException ex)
|
|
{
|
|
// A genuine Monaco init failure — surface it instead of hiding it.
|
|
Logger.LogError(ex, "Monaco editor {EditorId} failed to initialize.", _id);
|
|
}
|
|
}
|
|
else if (_initialized && (Value ?? "") != _lastSentValue)
|
|
{
|
|
_lastSentValue = Value ?? "";
|
|
await SafeInvokeAsync("MonacoBlazor.setValue", "set editor value", _id, _lastSentValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invokes a Monaco JS function, swallowing the expected disconnect case but
|
|
/// logging any genuine JS error (CentralUI-018) so failures are not silent.
|
|
/// </summary>
|
|
private async ValueTask SafeInvokeAsync(string fn, string action, params object?[] args)
|
|
{
|
|
try
|
|
{
|
|
await JS.InvokeVoidAsync(fn, args);
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
// Circuit gone — the editor no longer exists; nothing to log.
|
|
}
|
|
catch (JSException ex)
|
|
{
|
|
Logger.LogWarning(ex, "Monaco editor {EditorId}: failed to {Action}.", _id, action);
|
|
}
|
|
}
|
|
|
|
[JSInvokable]
|
|
public Task OnValueChanged(string newValue)
|
|
{
|
|
_lastSentValue = newValue ?? "";
|
|
return ValueChanged.InvokeAsync(_lastSentValue);
|
|
}
|
|
|
|
[JSInvokable]
|
|
public Task OnMarkersChanged(ScriptAnalysis.DiagnosticMarker[] markers) =>
|
|
MarkersChanged.InvokeAsync(markers ?? Array.Empty<ScriptAnalysis.DiagnosticMarker>());
|
|
|
|
/// <summary>Programmatic scroll-to-line (called by the problems panel).</summary>
|
|
public async Task RevealLineAsync(int line, int column = 1)
|
|
{
|
|
if (!_initialized) return;
|
|
await SafeInvokeAsync("MonacoBlazor.revealLine", "reveal line", _id, line, column);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[JSInvokable]
|
|
public ScadaContext GetContext() => new(
|
|
DeclaredParameters?.ToArray() ?? Array.Empty<string>(),
|
|
SiblingScripts?.ToArray() ?? Array.Empty<ScriptAnalysis.ScriptShape>(),
|
|
DeclaredParameterShapes?.ToArray()
|
|
?? DeclaredParameters?.Select(n => new ScriptAnalysis.ParameterShape(n, "Object", true)).ToArray()
|
|
?? Array.Empty<ScriptAnalysis.ParameterShape>(),
|
|
SelfAttributes?.ToArray() ?? Array.Empty<ScriptAnalysis.AttributeShape>(),
|
|
Children?.ToArray() ?? Array.Empty<ScriptAnalysis.CompositionContext>(),
|
|
Parent,
|
|
ScriptKind);
|
|
|
|
private async Task FormatAsync()
|
|
{
|
|
if (!_initialized) return;
|
|
await SafeInvokeAsync("MonacoBlazor.format", "format document", _id);
|
|
}
|
|
|
|
private async Task ToggleWrap()
|
|
{
|
|
_wrap = !_wrap;
|
|
await SafeInvokeAsync("MonacoBlazor.setEditorOption", "toggle word wrap", _id, "wordWrap", _wrap ? "on" : "off");
|
|
}
|
|
|
|
private async Task ToggleMinimap()
|
|
{
|
|
_minimap = !_minimap;
|
|
await SafeInvokeAsync("MonacoBlazor.setEditorOption", "toggle minimap", _id, "minimap", new { enabled = _minimap });
|
|
}
|
|
|
|
private async Task ToggleTheme()
|
|
{
|
|
_dark = !_dark;
|
|
await SafeInvokeAsync("MonacoBlazor.setEditorOption", "toggle theme", _id, "theme", _dark ? "vs-dark" : "vs");
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_initialized)
|
|
{
|
|
// Disposal commonly races a circuit disconnect — JSDisconnectedException
|
|
// here is expected and silent; a real JSException is still logged.
|
|
await SafeInvokeAsync("MonacoBlazor.dispose", "dispose editor", _id);
|
|
}
|
|
_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);
|
|
}
|