fix(scripts): gate the source dialog's existence on the held id, not the resolve
The modal's existence was a function of _rows (FindSourceScript() as the visibility gate), so any render where the row is momentarily unresolvable — a future reload nulling or replacing the list — unmounts the subtree and disposes the Close handler's binding id mid-click (GetRequiredEventBindingEntry). Latent here (_rows is written once, in OnInitializedAsync) but the identical shape bit ScadaBridge live the moment a page refresh landed, and it presents as an unattributable intermittent once one does. ScadaBridge's shape, ported by the family sweep: intent (_sourceScriptId) gates existence, the resolve drives only content; an unresolvable script renders a notice with the frame and Close still mounted, and closing remains the operator's action rather than a side effect of list state.
This commit is contained in:
@@ -60,21 +60,42 @@ else
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (FindSourceScript() is { } src)
|
||||
@* 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();
|
||||
<div class="modal-backdrop fade show" style="display:block"></div>
|
||||
<div class="modal fade show" tabindex="-1" role="dialog" style="display:block">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
@(string.IsNullOrWhiteSpace(src.Name) ? src.ScriptId : src.Name)
|
||||
<span class="mono small text-muted ms-2">@src.ScriptId</span>
|
||||
@if (src is not null)
|
||||
{
|
||||
@(string.IsNullOrWhiteSpace(src.Name) ? src.ScriptId : src.Name)
|
||||
<span class="mono small text-muted ms-2">@src.ScriptId</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="mono">@_sourceScriptId</span>
|
||||
}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" aria-label="Close" @onclick="CloseSource"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="mono small mb-0" style="background:var(--zebra-bg);padding:1rem;border-radius:4px;overflow:auto">@src.SourceCode</pre>
|
||||
@if (src is not null)
|
||||
{
|
||||
<pre class="mono small mb-0" style="background:var(--zebra-bg);padding:1rem;border-radius:4px;overflow:auto">@src.SourceCode</pre>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-muted mb-0">This script is no longer loaded.</p>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" @onclick="CloseSource">Close</button>
|
||||
@@ -99,8 +120,9 @@ else
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>Re-resolves the open dialog's script from the current list on every render; a script
|
||||
/// that is no longer loaded renders nothing, so the dialog self-closes.</summary>
|
||||
/// <summary>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.</summary>
|
||||
private Script? FindSourceScript()
|
||||
=> _sourceScriptId is null
|
||||
? null
|
||||
|
||||
Reference in New Issue
Block a user