feat(m9/T31): Monaco JSON-schema hover/completion on value-entry surface
This commit is contained in:
@@ -28,6 +28,16 @@
|
||||
[Parameter] public bool ReadOnly { get; set; } = false;
|
||||
[Parameter] public bool ShowToolbar { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Optional JSON Schema (the resolved schema text — any
|
||||
/// <c>{"$ref":"lib:Name"}</c> already inlined) applied to this editor's
|
||||
/// model when <see cref="Language"/> is <c>json</c>. Wires Monaco's
|
||||
/// built-in JSON language so the editor offers schema-driven hover,
|
||||
/// completion, and diagnostics on the entered JSON. Ignored for non-JSON
|
||||
/// languages. Updating it re-applies the schema to the live model.
|
||||
/// </summary>
|
||||
[Parameter] public string? JsonSchema { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime globals surface the script is analyzed against. Defaults to
|
||||
/// template/shared-script globals; set to <c>InboundApi</c> on the API
|
||||
@@ -87,6 +97,7 @@
|
||||
private DotNetObjectReference<MonacoEditor>? _dotNetRef;
|
||||
private readonly string _id = Guid.NewGuid().ToString("N");
|
||||
private string _lastSentValue = "";
|
||||
private string? _lastSentSchema;
|
||||
private bool _initialized;
|
||||
private bool _wrap;
|
||||
private bool _minimap;
|
||||
@@ -108,10 +119,12 @@
|
||||
{
|
||||
value = Value ?? "",
|
||||
language = Language,
|
||||
readOnly = ReadOnly
|
||||
readOnly = ReadOnly,
|
||||
jsonSchema = JsonSchema
|
||||
},
|
||||
_dotNetRef);
|
||||
_initialized = true;
|
||||
_lastSentSchema = JsonSchema;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
@@ -128,10 +141,18 @@
|
||||
Logger.LogError(ex, "Monaco editor {EditorId} failed to initialize.", _id);
|
||||
}
|
||||
}
|
||||
else if (_initialized && (Value ?? "") != _lastSentValue)
|
||||
else if (_initialized)
|
||||
{
|
||||
_lastSentValue = Value ?? "";
|
||||
await SafeInvokeAsync("MonacoBlazor.setValue", "set editor value", _id, _lastSentValue);
|
||||
if ((Value ?? "") != _lastSentValue)
|
||||
{
|
||||
_lastSentValue = Value ?? "";
|
||||
await SafeInvokeAsync("MonacoBlazor.setValue", "set editor value", _id, _lastSentValue);
|
||||
}
|
||||
if (JsonSchema != _lastSentSchema)
|
||||
{
|
||||
_lastSentSchema = JsonSchema;
|
||||
await SafeInvokeAsync("MonacoBlazor.setJsonSchema", "set JSON schema", _id, JsonSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ else
|
||||
private InboundApiSchema? _rootSchema;
|
||||
private string? _parsedFor;
|
||||
|
||||
// The resolved root schema serialized back to a JSON Schema string (with every
|
||||
// {"$ref":"lib:Name"} inlined). Fed to the raw-JSON escape-hatch Monaco editor
|
||||
// so Monaco's built-in JSON language offers schema-driven hover + completion on
|
||||
// the entered JSON. Computed once per parse alongside the resolved schema.
|
||||
private string? _resolvedSchemaJson;
|
||||
|
||||
// Raw text and per-field parse/validation errors keyed by canonical PATH
|
||||
// (e.g. "order.id", "tags[0]") so nested inputs each carry their own state.
|
||||
private readonly Dictionary<string, string> _rawText = new();
|
||||
@@ -70,6 +76,82 @@ else
|
||||
_shapes = ScriptParameterNames.ParseShapes(ParameterDefinitions);
|
||||
_rootSchema = await ParseRootAsync();
|
||||
_topLevelFields = _rootSchema?.Type == "object" ? _rootSchema.Fields : Array.Empty<InboundApiSchemaField>();
|
||||
_resolvedSchemaJson = _rootSchema is null ? null : ToJsonSchema(_rootSchema);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a (resolved) <see cref="InboundApiSchema"/> back to a JSON Schema
|
||||
/// string for the escape-hatch Monaco editor. Any <c>{"$ref":"lib:Name"}</c> the
|
||||
/// parse already inlined is emitted as its resolved shape (no <c>$ref</c> remains),
|
||||
/// so Monaco's JSON language sees the full type. The shape-only / unresolved-ref
|
||||
/// placeholder (Type not in the JSON Schema set) collapses to a permissive
|
||||
/// <c>{}</c> so the editor still validates well-formed JSON without false errors.
|
||||
/// </summary>
|
||||
private static string ToJsonSchema(InboundApiSchema schema)
|
||||
{
|
||||
using var stream = new System.IO.MemoryStream();
|
||||
using (var writer = new System.Text.Json.Utf8JsonWriter(stream))
|
||||
{
|
||||
WriteSchema(writer, schema);
|
||||
}
|
||||
|
||||
return System.Text.Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
|
||||
private static void WriteSchema(System.Text.Json.Utf8JsonWriter writer, InboundApiSchema schema)
|
||||
{
|
||||
switch (schema.Type)
|
||||
{
|
||||
case "boolean":
|
||||
case "integer":
|
||||
case "number":
|
||||
case "string":
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", schema.Type);
|
||||
writer.WriteEndObject();
|
||||
break;
|
||||
|
||||
case "object":
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", "object");
|
||||
writer.WritePropertyName("properties");
|
||||
writer.WriteStartObject();
|
||||
foreach (var field in schema.Fields)
|
||||
{
|
||||
writer.WritePropertyName(field.Name);
|
||||
WriteSchema(writer, field.Schema);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
var required = schema.Fields.Where(f => f.Required).Select(f => f.Name).ToList();
|
||||
if (required.Count > 0)
|
||||
{
|
||||
writer.WritePropertyName("required");
|
||||
writer.WriteStartArray();
|
||||
foreach (var name in required)
|
||||
{
|
||||
writer.WriteStringValue(name);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
break;
|
||||
|
||||
case "array":
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("type", "array");
|
||||
if (schema.Items is not null)
|
||||
{
|
||||
writer.WritePropertyName("items");
|
||||
WriteSchema(writer, schema.Items);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
break;
|
||||
|
||||
default: // unresolved $ref / unknown — permissive (accept any JSON).
|
||||
writer.WriteStartObject();
|
||||
writer.WriteEndObject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -165,10 +247,13 @@ else
|
||||
@RenderArray(schema, path)
|
||||
break;
|
||||
|
||||
default: // unresolved $ref / unknown — raw-JSON escape hatch.
|
||||
<textarea class="form-control form-control-sm font-monospace" rows="3" id="@id"
|
||||
placeholder='@($"JSON {DisplayType(schema).ToLowerInvariant()}")'
|
||||
@oninput="e => SetJson(path, (string?)e.Value)">@AsRaw(path)</textarea>
|
||||
default: // unresolved $ref / unknown — raw-JSON escape hatch (Monaco).
|
||||
<div class="param-json-editor" data-path="@path">
|
||||
<MonacoEditor Language="json" Height="120px" ShowToolbar="false"
|
||||
JsonSchema="@_resolvedSchemaJson"
|
||||
Value="@AsRaw(path)"
|
||||
ValueChanged="@(v => SetJson(path, v))" />
|
||||
</div>
|
||||
@RenderError(path)
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user