@namespace ScadaLink.CentralUI.Components.Shared @* Bootstrap-only JSON Schema editor. Two modes: - "object" parameters: edits a top-level object schema (named properties). - "value" return type: edits a single value schema; object/array fall back to the same property editor as Mode=object. Recurses through methods (not nested components) so we stay in one file. *@ @if (_root.Type == "object" && Mode == "object") { @PropertyList(_root, isRoot: true) } else { @ValueRoot(_root) } @code { /// "object" for parameters, "value" for return type. [Parameter] public string Mode { get; set; } = "object"; /// JSON Schema text. Empty/null seeds the mode's default. [Parameter] public string? Value { get; set; } [Parameter] public EventCallback ValueChanged { get; set; } private SchemaNode _root = new(); private string? _lastSeenJson; private bool _initialized; protected override void OnParametersSet() { // OnInitialized fires before this on first mount; OnParametersSet runs // on every parameter change. Guard against the initial null==null case // where the early-exit would skip applying the mode-appropriate default. if (_initialized && Value == _lastSeenJson) return; _initialized = true; _lastSeenJson = Value; _root = SchemaBuilderModel.Parse( Value, Mode == "object" ? SchemaBuilderModel.NewObject() : SchemaBuilderModel.NewValue()); } private async Task Emit() { var json = SchemaBuilderModel.Serialize(_root); _lastSeenJson = json; await ValueChanged.InvokeAsync(json); } private async Task OnTypeChange(SchemaNode node) { if (node.Type == "array" && node.Items == null) node.Items = new SchemaNode { Type = "string" }; await Emit(); } private async Task AddProperty(SchemaNode parent) { parent.Properties.Add(new SchemaProperty { Schema = new SchemaNode { Type = "string" } }); await Emit(); } private async Task RemoveProperty(SchemaNode parent, SchemaProperty prop) { parent.Properties.Remove(prop); await Emit(); } // ── Render helpers ───────────────────────────────────────────────────────── /// /// Renders the property list for an object schema node. /// just tweaks the wording on the Add button ("parameter" at root vs "field" /// inside a nested object). /// private RenderFragment PropertyList(SchemaNode node, bool isRoot = false) => __builder => {
@if (node.Properties.Count == 0) {
@(isRoot ? "No parameters defined." : "No fields defined.")
} @foreach (var prop in node.Properties) {
@PropertyRow(node, prop) @NestedEditor(prop.Schema)
}
}; /// /// One property's compact horizontal row: name, type, (items type if array), /// required toggle, remove button. Nested object / array-of-object editors /// render below the row via . /// private RenderFragment PropertyRow(SchemaNode parent, SchemaProperty prop) => __builder => {
@if (prop.Schema.Type == "array") { items: }
}; /// /// Renders the indented sub-editor for object / array-of-object properties. /// No-op for scalar properties. /// private RenderFragment NestedEditor(SchemaNode schema) => __builder => { if (schema.Type == "object") {
@PropertyList(schema)
} else if (schema.Type == "array" && schema.Items?.Type == "object") {
item properties:
@PropertyList(schema.Items)
} }; /// /// Mode=value root: a single type picker. When the user picks object /// or array we expose the same nested editors used by Mode=object. /// private RenderFragment ValueRoot(SchemaNode node) => __builder => {
@if (node.Type == "array") { }
@if (node.Type == "object") {
Properties of return value:
@PropertyList(node) } else if (node.Type == "array" && node.Items?.Type == "object") {
Item properties:
@PropertyList(node.Items) } }; }