fix(adminui): Monaco text syncs on demand, diagnostics are sequenced, Problems list (D1)
R04:P1 - the editor no longer ships its full text JS->.NET on every keystroke. It reports at blur, and ScriptEdit / the VirtualTag modal read the authoritative value with MonacoEditor.GetValueAsync at Save (validation runs after the pull). The Blazor hub's MaximumReceiveMessageSize is raised from 32 KB to 1 MB. The JS refuses a value that would not fit instead of sending an oversize frame, and Save surfaces that refusal rather than persisting stale source. R04:P9 - diagnostics carry a generation counter and an AbortController. Every edit aborts the in-flight request, and only the response for the current text is applied. R04:S2 - the always-empty inlay-hints provider, endpoint, service method and contracts are removed. R04:S3 - MarkersChanged is typed (DiagnosticMarker) and feeds a new ScriptProblemsList. Clicking a problem calls RevealLineAsync. Markers cross to .NET only when a list subscribes. Refs #745 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
@namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis
|
||||
@implements IAsyncDisposable
|
||||
@inject IJSRuntime JS
|
||||
@inject Microsoft.Extensions.Logging.ILogger<MonacoEditor> Logger
|
||||
@@ -22,7 +23,13 @@
|
||||
style="height: @Height; border: 1px solid var(--bs-border-color); border-radius: 0.25rem; overflow: hidden;"></div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The editor text. Two-way bound, but the editor reports edits back only when it loses focus — never per
|
||||
/// keystroke (each report is one circuit message carrying the whole script). A page that persists the text
|
||||
/// must read the authoritative current value with <see cref="GetValueAsync"/> at save time.
|
||||
/// </summary>
|
||||
[Parameter] public string Value { get; set; } = "";
|
||||
/// <summary>Raised with the editor text when the editor loses focus after an edit (and from <see cref="GetValueAsync"/>).</summary>
|
||||
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||
[Parameter] public string Language { get; set; } = "csharp";
|
||||
[Parameter] public string Height { get; set; } = "320px";
|
||||
@@ -37,11 +44,11 @@
|
||||
[Parameter] public string? EquipmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires whenever Monaco's marker set updates (after the 500 ms diagnostic
|
||||
/// debounce). The marker DTO is not modelled yet — typed as object[] until a
|
||||
/// later task wires the Roslyn-backed diagnostics.
|
||||
/// Fires whenever Monaco's diagnostic marker set updates (after the 500 ms debounce, and only for the response
|
||||
/// to the CURRENT text — superseded responses are dropped in the JS). Feeds <c>ScriptProblemsList</c>; the
|
||||
/// markers are sent from JS only when this callback has a subscriber.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<object[]> MarkersChanged { get; set; }
|
||||
[Parameter] public EventCallback<IReadOnlyList<DiagnosticMarker>> MarkersChanged { get; set; }
|
||||
|
||||
private ElementReference _hostRef;
|
||||
private DotNetObjectReference<MonacoEditor>? _dotNetRef;
|
||||
@@ -69,7 +76,9 @@
|
||||
value = Value ?? "",
|
||||
language = Language,
|
||||
readOnly = ReadOnly,
|
||||
equipmentId = EquipmentId
|
||||
equipmentId = EquipmentId,
|
||||
maxSyncBytes = ScriptEditorLimits.MaxEditorSyncBytes,
|
||||
reportMarkers = MarkersChanged.HasDelegate
|
||||
},
|
||||
_dotNetRef);
|
||||
_initialized = true;
|
||||
@@ -126,9 +135,47 @@
|
||||
return ValueChanged.InvokeAsync(_lastSentValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the editor's current text on demand (call it at Save), raises <see cref="ValueChanged"/> when it
|
||||
/// differs from the last value reported, and returns it. Before the editor is live (prerender, failed init)
|
||||
/// the bound <see cref="Value"/> is authoritative and is returned as-is.
|
||||
/// </summary>
|
||||
/// <returns>The current text, or an error (e.g. the script is too large to send) — never a stale value.</returns>
|
||||
public async Task<EditorReadResult> GetValueAsync()
|
||||
{
|
||||
if (!_initialized) return new EditorReadResult(Value ?? "", null);
|
||||
string? current;
|
||||
try
|
||||
{
|
||||
current = await JS.InvokeAsync<string?>("MonacoBlazor.getValue", _id);
|
||||
}
|
||||
catch (JSDisconnectedException)
|
||||
{
|
||||
return new EditorReadResult(null, "The connection to the editor was lost; reload the page and try again.");
|
||||
}
|
||||
catch (JSException ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Monaco editor {EditorId}: failed to read the editor text.", _id);
|
||||
// JSException.Message carries the JS stack after the first line; the author needs only the reason.
|
||||
return new EditorReadResult(null, ex.Message.Split('\n', 2)[0].Trim());
|
||||
}
|
||||
if (current is null) return new EditorReadResult(Value ?? "", null); // editor already disposed
|
||||
await OnValueChanged(current);
|
||||
return new EditorReadResult(current, null);
|
||||
}
|
||||
|
||||
/// <summary>The outcome of <see cref="GetValueAsync"/>: the text, or why it could not be read.</summary>
|
||||
/// <param name="Value">The editor text; null when <paramref name="Error"/> is set.</param>
|
||||
/// <param name="Error">Why the text could not be read; null on success.</param>
|
||||
public sealed record EditorReadResult(string? Value, string? Error)
|
||||
{
|
||||
/// <summary>True when <see cref="Value"/> holds the editor text.</summary>
|
||||
public bool Ok => Error is null;
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public Task OnMarkersChanged(object[] markers) =>
|
||||
MarkersChanged.InvokeAsync(markers ?? Array.Empty<object>());
|
||||
public Task OnMarkersChanged(DiagnosticMarker[]? markers) =>
|
||||
MarkersChanged.InvokeAsync(markers ?? Array.Empty<DiagnosticMarker>());
|
||||
|
||||
/// <summary>Programmatic scroll-to-line (called by the problems panel).</summary>
|
||||
public async Task RevealLineAsync(int line, int column = 1)
|
||||
|
||||
Reference in New Issue
Block a user