feat(adminui): DriverTagPicker modal + 9 static address builders

- DriverTagPicker shell: modal chrome + per-driver picker body
  rendered as ChildContent.
- 9 picker bodies (Modbus/AbCip/AbLegacy/S7/TwinCat/FOCAS/
  OpcUaClient/Galaxy/Historian.Wonderware). 5 have computed
  builder logic + unit tests; 4 are free-text passthroughs
  (live browse for OPC UA + Galaxy is a documented follow-up).
- Each typed driver page gets a "Pick address" button that opens
  the modal with the matching body. Picked address surfaces in
  the modal footer for manual copy — no JS interop in v1.
This commit is contained in:
Joseph Doherty
2026-05-28 11:21:33 -04:00
parent ffcc8d1065
commit 063005fefa
29 changed files with 873 additions and 0 deletions
@@ -0,0 +1,57 @@
@* Static AB CIP address builder: tag name + optional element index → tag[idx] or tag *@
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Tag name</label>
<input type="text" class="form-control form-control-sm mono" placeholder="Program:Main.MyTag"
@bind="_tagName" @bind:after="OnChangedAsync" />
<div class="form-text">Use dot notation for nested tags or UDT members.</div>
</div>
<div class="col-md-3">
<label class="form-label">Element index (optional)</label>
<input type="number" class="form-control form-control-sm" min="0"
@bind="_elementIndex" @bind:after="OnChangedAsync" />
<div class="form-text">Leave 0 for non-array tags (no index appended).</div>
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" id="abCipUseIdx"
@bind="_useIndex" @bind:after="OnChangedAsync" />
<label class="form-check-label" for="abCipUseIdx">Append index</label>
</div>
</div>
</div>
<div class="mt-3">
<span class="text-muted small">Result:</span>
<code class="mono ms-2">@_built</code>
</div>
@code {
[Parameter] public string CurrentAddress { get; set; } = "";
[Parameter] public EventCallback<string> CurrentAddressChanged { get; set; }
private string _tagName = "";
private int _elementIndex = 0;
private bool _useIndex = false;
private string _built = "";
protected override void OnInitialized()
{
_built = Build();
_ = CurrentAddressChanged.InvokeAsync(_built);
}
private async Task OnChangedAsync()
{
_built = Build();
await CurrentAddressChanged.InvokeAsync(_built);
}
private string Build()
{
if (string.IsNullOrWhiteSpace(_tagName))
return "";
return _useIndex ? $"{_tagName}[{_elementIndex}]" : _tagName;
}
}