Effective config: do-0x20-encode revert fix, validation UI, custom displays, option help

- Fix do-0x20-encode: clearing pending when toggling back to original (Disabled/Default)
  - Do0x20EncodeDisplay: uncheck uses pending.OldValue when present
  - EffectiveConfigFieldDisplay: use pending.OldValue as _valueAtActivation when re-entering with pending
- Effective config: validation errors/warnings in toolbar and save modal, revert from modal
- Custom displays: Do0x20EncodeDisplay (tri-state), KeyOnlyOrValueDisplay
- ExplicitToggleState, config/apply support for do-0x20-encode, option help and tests
This commit is contained in:
2026-03-08 12:52:46 +10:00
parent 6cf4c07695
commit a03e93bcd3
89 changed files with 2229 additions and 67 deletions
@@ -0,0 +1,59 @@
@namespace DnsmasqWebUI.Components.EffectiveConfig.CustomDisplays
@using DnsmasqWebUI.Models.Dnsmasq
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
@inherits EffectiveConfigValueDisplayBase
@* do-0x20-encode / no-0x20-encode: tri-state. Checkbox shows Enabled vs not-Enabled; uncheck maps back to Default when that was the value at activation, else Disabled. *@
@if (IsActiveEditor && Descriptor.GetSource()?.IsReadOnly != true)
{
var isOn = GetState() == ExplicitToggleState.Enabled;
<input type="checkbox" class="form-check-input" checked="@isOn" @onchange="OnCheckedChange" @onfocusout="OnCommitRequested" />
}
else
{
@(GetState() == ExplicitToggleState.Enabled ? "Enabled" : "Disabled")
}
@code {
private ExplicitToggleState _stateAtActivation;
private bool _wasActiveEditor;
private ExplicitToggleState GetState()
{
var v = GetEffectiveDisplayValue();
if (v is ExplicitToggleState s) return s;
return ExplicitToggleState.Default;
}
protected override void OnParametersSet()
{
if (!IsActiveEditor)
_stateAtActivation = GetState();
else if (!_wasActiveEditor)
_stateAtActivation = GetState();
_wasActiveEditor = IsActiveEditor;
}
private async Task OnCheckedChange(ChangeEventArgs e)
{
var isChecked = e.Value is true;
if (isChecked)
{
await NotifyValueChanged(ExplicitToggleState.Enabled);
return;
}
var pending = Session?.PendingChanges.FirstOrDefault(c =>
string.Equals(c.SectionId, Descriptor.SectionId, StringComparison.OrdinalIgnoreCase) &&
string.Equals(c.OptionName, Descriptor.OptionName, StringComparison.OrdinalIgnoreCase));
if (pending?.OldValue is ExplicitToggleState previousState)
{
await NotifyValueChanged(previousState);
return;
}
// No existing pending edit for this field: map unchecked based on the state at activation.
// Default and Disabled both display as "Disabled" in view mode, so we preserve true intent here.
await NotifyValueChanged(_stateAtActivation == ExplicitToggleState.Default ? ExplicitToggleState.Default : ExplicitToggleState.Disabled);
}
}