@page "/scripts" @attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)] @rendermode RenderMode.InteractiveServer @using Microsoft.EntityFrameworkCore @using ZB.MOM.WW.OtOpcUa.Configuration @using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptList @inject IDbContextFactory DbFactory

Scripts

New script
@if (_rows is null) {

Loading…

} else {
@_rows.Count script@(_rows.Count == 1 ? "" : "s")
@if (_rows.Count == 0) {
No scripts defined.
} else { @foreach (var s in _rows) { }
Script
@(string.IsNullOrWhiteSpace(s.Name) ? s.ScriptId : s.Name) @s.Language
@s.ScriptId · hash=@DisplayText.Abbreviate(s.SourceHash, 12)
Edit
}
} @* The dialog's EXISTENCE is gated on the held id (operator intent); the resolve drives only its CONTENT. Gating existence on FindSourceScript() would make the modal a function of _rows, and any render where the row is momentarily unresolvable (a future reload/poll setting _rows null or replacing the list) would unmount the subtree and dispose the Close handler's binding id — a click already in flight then throws GetRequiredEventBindingEntry. Latent today (_rows is written once, in OnInitializedAsync) but the shape bit ScadaBridge live the moment a refresh landed. *@ @if (_sourceScriptId is not null) { var src = FindSourceScript(); }
@code { // A projection without SourceCode (R04 P8); the source loads per dialog open. private List? _rows; // Only the id of the script whose source dialog is open — never the record itself, so a reload // that drops or replaces the row cannot leave a stale object on screen. private string? _sourceScriptId; // The open dialog's source, fetched on open. _sourceGeneration discards a superseded fetch (the // operator opened another row, or closed the dialog, while the first one was in flight). private string? _sourceCode; private bool _sourceLoaded; private int _sourceGeneration; protected override async Task OnInitializedAsync() { await using var db = await DbFactory.CreateDbContextAsync(); _rows = await ScriptListQueries.LoadRowsAsync(db); } /// Re-resolves the open dialog's script from the current list on every render. Drives /// the dialog's CONTENT only — never its existence (see the comment at the markup): a script /// that is no longer loaded renders a notice with the frame and Close still mounted. private ScriptListRow? FindSourceScript() => _sourceScriptId is null ? null : _rows?.FirstOrDefault(s => s.ScriptId == _sourceScriptId); // One dialog at a time: opening another row replaces the id. private async Task ShowSource(string scriptId) { var generation = ++_sourceGeneration; _sourceScriptId = scriptId; _sourceCode = null; _sourceLoaded = false; await using var db = await DbFactory.CreateDbContextAsync(); var source = await ScriptListQueries.LoadSourceAsync(db, scriptId); if (generation != _sourceGeneration) return; _sourceCode = source; _sourceLoaded = true; } private void CloseSource() { _sourceGeneration++; _sourceScriptId = null; _sourceCode = null; _sourceLoaded = false; } }