diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor index 76ac00d0..6897e66e 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor @@ -4,6 +4,7 @@ @using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates @using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites @using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories +@using ZB.MOM.WW.ScadaBridge.Commons.Types @using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums @using ZB.MOM.WW.ScadaBridge.TemplateEngine @using ZB.MOM.WW.ScadaBridge.TemplateEngine.Services @@ -80,6 +81,10 @@ private string _attrName = string.Empty; private string? _attrValue; private DataType _attrDataType; + // List-attribute authoring state (DataType.List only): the element scalar + // type + the per-element string rows. Encoded to canonical JSON on submit. + private DataType _attrElementDataType = DataType.String; + private List _attrListRows = new(); private bool _attrIsLocked; private string? _attrDataSourceRef; private string? _attrFormError; @@ -553,17 +558,32 @@
- @foreach (var dt in Enum.GetValues()) { } + @if (editing) + { +
Data type is fixed once the attribute is created.
+ }
-
- - -
+ @if (_attrDataType == DataType.List) + { +
+ +
+ } + else + { +
+ + +
+ }
@@ -1535,6 +1555,8 @@ _attrName = string.Empty; _attrValue = null; _attrDataType = default; + _attrElementDataType = DataType.String; + _attrListRows = new(); _attrIsLocked = false; _attrDataSourceRef = null; } @@ -1547,6 +1569,8 @@ _attrName = attr.Name; _attrValue = attr.Value; _attrDataType = attr.DataType; + _attrElementDataType = attr.ElementDataType ?? DataType.String; + _attrListRows = DecodeListRows(attr.Value, attr.ElementDataType); _attrIsLocked = attr.IsLocked; _attrDataSourceRef = attr.DataSourceReference; } @@ -1558,12 +1582,72 @@ _attrFormError = null; } + // Switching the data type clears stale list state so a List ⇄ scalar + // toggle never carries the other mode's value into the submit. + private void OnAttrDataTypeChanged(ChangeEventArgs e) + { + if (!Enum.TryParse((string?)e.Value, out var dt) || dt == _attrDataType) return; + _attrDataType = dt; + if (dt == DataType.List) + { + _attrValue = null; + if (_attrListRows.Count == 0) _attrListRows = new(); + if (!AttributeValueCodec.IsValidElementType(_attrElementDataType)) + _attrElementDataType = DataType.String; + } + else + { + _attrListRows = new(); + } + } + + // Decodes a stored List JSON value into editable string rows. A malformed + // stored value (e.g. hand-edited / element-type mismatch) is shown as empty + // rather than crashing the editor — the user can rebuild it. + private List DecodeListRows(string? value, DataType? elementType) + { + if (string.IsNullOrEmpty(value)) return new(); + try + { + var decoded = AttributeValueCodec.Decode(value, DataType.List, elementType ?? DataType.String); + if (decoded is System.Collections.IEnumerable items) + return items.Cast() + .Select(x => AttributeValueCodec.Encode(x) ?? string.Empty) + .ToList(); + } + catch (FormatException) + { + // Malformed stored value — start from empty so the editor still opens. + } + return new(); + } + private async Task SaveAttribute() { if (_selectedTemplate == null) return; _attrFormError = null; if (string.IsNullOrWhiteSpace(_attrName)) { _attrFormError = "Name is required."; return; } + // Resolve the value + element type per data type. List attributes encode + // their rows to canonical JSON and validate them locally before submit + // (TemplateService persists directly and does not list-validate). + string? attrValue; + DataType? elementType; + if (_attrDataType == DataType.List) + { + elementType = _attrElementDataType; + attrValue = AttributeValueCodec.Encode(_attrListRows); + // Round-trip through Decode to surface any un-parseable element + // (e.g. non-numeric in an Int32 list) before hitting the server. + try { AttributeValueCodec.Decode(attrValue, DataType.List, elementType); } + catch (FormatException ex) { _attrFormError = ex.Message; return; } + } + else + { + elementType = null; + attrValue = _attrValue?.Trim(); + } + var user = await GetCurrentUserAsync(); if (_editAttrId is int id) @@ -1573,7 +1657,8 @@ var proposed = new TemplateAttribute(existing.Name) { DataType = _attrDataType, - Value = _attrValue?.Trim(), + ElementDataType = elementType, + Value = attrValue, IsLocked = _attrIsLocked, DataSourceReference = _attrDataSourceRef?.Trim(), Description = existing.Description, @@ -1598,7 +1683,8 @@ var attr = new TemplateAttribute(_attrName.Trim()) { DataType = _attrDataType, - Value = _attrValue?.Trim(), + ElementDataType = elementType, + Value = attrValue, IsLocked = _attrIsLocked, DataSourceReference = _attrDataSourceRef?.Trim() }; diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AttributeListEditor.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AttributeListEditor.razor new file mode 100644 index 00000000..34e0dd26 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AttributeListEditor.razor @@ -0,0 +1,130 @@ +@using ZB.MOM.WW.ScadaBridge.Commons.Types +@using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums + +@* + Repeatable list-value editor for a structured multi-value (List) attribute. + Reveals an element-type when the + element type is fixed (e.g. an + instance override inherits it). + - Disabled (default false) : render read-only. + Both ElementDataTypeChanged and RowsChanged fire on every edit. +*@ + +
+ @if (ShowElementType) + { +
+ + +
+ } + + + @if (Rows.Count == 0) + { +

No elements. Use “Add element” to add one.

+ } + else + { +
+ @for (var i = 0; i < Rows.Count; i++) + { + var index = i; +
+ @index + + @if (!Disabled) + { + + } +
+ } +
+ } + + @if (!Disabled) + { + + } +
+ +@code { + /// The chosen element scalar type. Two-way bound. + [Parameter] public DataType ElementDataType { get; set; } = DataType.String; + [Parameter] public EventCallback ElementDataTypeChanged { get; set; } + + /// The per-element string values. Two-way bound. + [Parameter] public List Rows { get; set; } = new(); + [Parameter] public EventCallback> RowsChanged { get; set; } + + /// + /// When false, the element-type