mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
EffectiveConfig: fix pending null display, OptionStateBadge, OptionHelpLabel
- Use pending != null ? pending.NewValue : Descriptor.GetValue() so clearing a value (port, log-queries, local-service) shows '(not set)' instead of reverting to the old value; fix effective value and commit logic in EffectiveConfigFieldDisplay. - Rename ReadonlyBadge to OptionStateBadge; add editable/pending state and use it from EffectiveConfigOptionItem, EffectiveConfigFieldDisplay, EffectiveConfigMultiValueItem. - OptionHelpLabel: default CssClass from HelpKey (EffectiveCssClass).
This commit is contained in:
+6
-10
@@ -14,9 +14,9 @@
|
||||
var showEditableBadge = !isReadonly;
|
||||
var isActiveEditor = IsEditMode && IsActive;
|
||||
var fragmentWithCallback = isActiveEditor ? _fragmentWithCallback : null;
|
||||
var effectiveValue = _effectiveValue ?? Descriptor.GetValue();
|
||||
var effectiveValue = _effectiveValue;
|
||||
<li class="ec-field @(isActiveEditor ? "ec-field-editing" : "")" title="@(Descriptor.GetSource()?.GetReadOnlyTooltip())">
|
||||
<OptionHelpLabel AnchorId="@_anchorId" HelpKey="@_optionHelpKey" OptionLabel="@Descriptor.OptionName" CssClass="@(_optionHelpKey != null ? "ec-field-label ec-field-label-help" : "ec-field-label")" Title="@(labelTooltip ?? Descriptor.GetSource()?.GetReadOnlyTooltip())"><strong>@Descriptor.OptionName:</strong></OptionHelpLabel>
|
||||
<OptionHelpLabel AnchorId="@_anchorId" HelpKey="@_optionHelpKey" OptionLabel="@Descriptor.OptionName" Title="@(labelTooltip ?? Descriptor.GetSource()?.GetReadOnlyTooltip())"><strong>@Descriptor.OptionName:</strong></OptionHelpLabel>
|
||||
<span class="ec-field-value">
|
||||
@if (isActiveEditor)
|
||||
{
|
||||
@@ -34,11 +34,7 @@
|
||||
@fragment(Descriptor)
|
||||
</CascadingValue>
|
||||
}
|
||||
<ReadonlyBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" />
|
||||
@if (showEditableBadge)
|
||||
{
|
||||
<button type="button" class="badge ec-badge-inline border-0 ms-1 @(_hasPendingChange ? "bg-warning text-dark" : "bg-primary")" @onclick="OnActivateEdit" @onclick:stopPropagation="true" title="@(_hasPendingChange ? "Pending change" : "Edit this field")" aria-label="Edit">@(_hasPendingChange ? "pending" : "editable")</button>
|
||||
}
|
||||
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" OnActivateEdit="@OnActivateEdit" />
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
@@ -49,7 +45,7 @@ else if (Descriptor.IsMultiValue)
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = _effectiveValue ?? Descriptor.GetValue();
|
||||
var value = _effectiveValue;
|
||||
var displayValue = value == null ? "(not set)" : (value is IReadOnlyList<string> list ? (list.Count == 0 ? "(none)" : string.Join(", ", list)) : value.ToString());
|
||||
var isReadonly = Descriptor.GetSource()?.IsReadOnly == true;
|
||||
var showReadonlyHint = IsEditMode && isReadonly;
|
||||
@@ -103,7 +99,7 @@ else
|
||||
string.Equals(c.SectionId, Descriptor.SectionId, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(c.OptionName, Descriptor.OptionName, StringComparison.OrdinalIgnoreCase));
|
||||
_hasPendingChange = pending != null;
|
||||
_effectiveValue = pending?.NewValue ?? Descriptor.GetValue();
|
||||
_effectiveValue = pending != null ? pending.NewValue : Descriptor.GetValue();
|
||||
if (!_wasActive && IsActive)
|
||||
_valueAtActivation = _effectiveValue;
|
||||
if (_wasActive && !IsActive)
|
||||
@@ -124,7 +120,7 @@ else
|
||||
|
||||
private async Task CommitIfChangedAsync()
|
||||
{
|
||||
var newValue = _draftValue ?? _effectiveValue ?? Descriptor.GetValue();
|
||||
var newValue = _draftValue ?? _effectiveValue;
|
||||
if (!Equals(_valueAtActivation, newValue))
|
||||
{
|
||||
var source = Descriptor.GetSource();
|
||||
|
||||
+1
-5
@@ -4,11 +4,7 @@
|
||||
|
||||
<span class="ec-field-value-item" title="@(Source?.GetReadOnlyTooltip())">
|
||||
@Value
|
||||
<ReadonlyBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@OptionName" Value="@Value" IsInteractive="@(IsEditMode && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" />
|
||||
@if (Source?.IsReadOnly != true && IsEditMode)
|
||||
{
|
||||
<span class="badge bg-primary ec-badge-inline ms-1">editable</span>
|
||||
}
|
||||
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@OptionName" Value="@Value" IsInteractive="@(IsEditMode && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@(Source?.IsReadOnly != true && IsEditMode)" EditableAsButton="false" />
|
||||
</span>
|
||||
|
||||
@code {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||
|
||||
<li class="ec-field">
|
||||
<OptionHelpLabel AnchorId="@(OptionHelpAnchorId ?? "")" HelpKey="@OptionHelpKey" OptionLabel="@Label" CssClass="@(OptionHelpKey != null ? "ec-field-label ec-field-label-help" : "ec-field-label")" Title="@LabelTooltip"><strong>@Label:</strong></OptionHelpLabel>
|
||||
<OptionHelpLabel AnchorId="@(OptionHelpAnchorId ?? "")" HelpKey="@OptionHelpKey" OptionLabel="@Label" Title="@LabelTooltip"><strong>@Label:</strong></OptionHelpLabel>
|
||||
@if (IsActiveEditor)
|
||||
{
|
||||
<span class="ec-field-value ec-field-value-multi" @onclick:stopPropagation="true">
|
||||
|
||||
+3
-10
@@ -2,26 +2,19 @@
|
||||
@using DnsmasqWebUI.Models.Dnsmasq
|
||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||
|
||||
<OptionHelpLabel AnchorId="@(OptionHelpAnchorId ?? "")" HelpKey="@OptionHelpKey" OptionLabel="@Label" CssClass="@(OptionHelpKey != null ? "ec-field-label ec-field-label-help" : "ec-field-label")" Title="@(LabelTooltip ?? Source?.GetReadOnlyTooltip())"><strong>@Label:</strong></OptionHelpLabel>
|
||||
<OptionHelpLabel AnchorId="@(OptionHelpAnchorId ?? "")" HelpKey="@OptionHelpKey" OptionLabel="@Label" Title="@(LabelTooltip ?? Source?.GetReadOnlyTooltip())"><strong>@Label:</strong></OptionHelpLabel>
|
||||
@if (DisplayMode == EffectiveConfigDisplayMode.Edit && IsActive && Source?.IsReadOnly != true)
|
||||
{
|
||||
<span class="ec-field-value" @onclick:stopPropagation="true">
|
||||
<input type="text" class="form-control form-control-sm" value="@(_editValue ?? DisplayValue)" @oninput="OnValueInput" @onfocusout="OnInputBlur" />
|
||||
@if (ShowEditableBadge)
|
||||
{
|
||||
<button type="button" class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")" @onclick="OnActivateEdit" @onclick:stopPropagation="true" title="@(HasPendingChange ? "Pending change" : "Edit this field")" aria-label="Edit">@(HasPendingChange ? "pending" : "editable")</button>
|
||||
}
|
||||
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" OnActivateEdit="@OnActivateEdit" />
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="ec-field-value">
|
||||
@DisplayValue
|
||||
<ReadonlyBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" />
|
||||
@if (ShowEditableBadge && Source?.IsReadOnly != true)
|
||||
{
|
||||
<button type="button" class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")" @onclick="OnActivateEdit" @onclick:stopPropagation="true" title="@(HasPendingChange ? "Pending change" : "Edit this field")" aria-label="Edit">@(HasPendingChange ? "pending" : "editable")</button>
|
||||
}
|
||||
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" OnActivateEdit="@OnActivateEdit" />
|
||||
</span>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig
|
||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||
|
||||
<span id="@AnchorId" class="@CssClass" title="@Title" @onclick="OnClick" @onmouseleave="OnMouseLeave">
|
||||
<span id="@AnchorId" class="@EffectiveCssClass" title="@Title" @onclick="OnClick" @onmouseleave="OnMouseLeave">
|
||||
@ChildContent
|
||||
</span>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
[Parameter] public string? HelpKey { get; set; }
|
||||
[Parameter] public string OptionLabel { get; set; } = "";
|
||||
[Parameter] public string? CssClass { get; set; }
|
||||
|
||||
private string EffectiveCssClass =>
|
||||
CssClass ?? (!string.IsNullOrEmpty(HelpKey) ? "ec-field-label ec-field-label-help" : "ec-field-label");
|
||||
[Parameter] public string? Title { get; set; }
|
||||
[CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; }
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig
|
||||
@using DnsmasqWebUI.Models.Dnsmasq
|
||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||
|
||||
@if (Source?.IsReadOnly == true)
|
||||
{
|
||||
@if (IsInteractive && Ui != null)
|
||||
{
|
||||
<button type="button" id="@AnchorId" class="badge bg-secondary ec-badge-inline border-0 ms-1" title="@(Source?.GetReadOnlyTooltip())"
|
||||
@onclick="OnReadonlyClick" @onmouseleave="OnReadonlyMouseLeave">
|
||||
readonly
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary ec-badge-inline ms-1" title="@(Source?.GetReadOnlyTooltip())">readonly</span>
|
||||
}
|
||||
}
|
||||
else if (ShowEditableBadge)
|
||||
{
|
||||
@if (EditableAsButton)
|
||||
{
|
||||
<button type="button"
|
||||
class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")"
|
||||
@onclick="OnActivateEdit"
|
||||
@onclick:stopPropagation="true"
|
||||
title="@(HasPendingChange ? "Pending change" : "Edit this field")"
|
||||
aria-label="Edit">
|
||||
@(HasPendingChange ? "pending" : "editable")
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-primary ec-badge-inline ms-1">editable</span>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string AnchorId { get; set; } = "";
|
||||
[Parameter] public ConfigValueSource? Source { get; set; }
|
||||
[Parameter] public string OptionName { get; set; } = "";
|
||||
[Parameter] public object? Value { get; set; }
|
||||
/// <summary>When true, render readonly as button and raise click/mouseleave via context.</summary>
|
||||
[Parameter] public bool IsInteractive { get; set; }
|
||||
/// <summary>When true, show editable/pending badge for non-readonly fields.</summary>
|
||||
[Parameter] public bool ShowEditableBadge { get; set; }
|
||||
/// <summary>When true, badge shows "pending" and warning style.</summary>
|
||||
[Parameter] public bool HasPendingChange { get; set; }
|
||||
/// <summary>When true, editable badge is a button; otherwise a static span.</summary>
|
||||
[Parameter] public bool EditableAsButton { get; set; } = true;
|
||||
[Parameter] public EventCallback OnActivateEdit { get; set; }
|
||||
[CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; }
|
||||
|
||||
private Task OnReadonlyClick()
|
||||
{
|
||||
if (Ui == null || Source == null) return Task.CompletedTask;
|
||||
return Ui.RequestReadonlyPopoverAsync(new ReadonlyBadgeClickedEventArgs(AnchorId, Source, OptionName, Value));
|
||||
}
|
||||
|
||||
private Task OnReadonlyMouseLeave()
|
||||
{
|
||||
if (Ui == null) return Task.CompletedTask;
|
||||
return Ui.ScheduleReadonlyPopoverCloseAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig
|
||||
@using DnsmasqWebUI.Models.Dnsmasq
|
||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||
|
||||
@if (Source?.IsReadOnly == true)
|
||||
{
|
||||
@if (IsInteractive && Ui != null)
|
||||
{
|
||||
<button type="button" id="@AnchorId" class="badge bg-secondary ec-badge-inline border-0 ms-1" title="@(Source?.GetReadOnlyTooltip())"
|
||||
@onclick="OnClick" @onmouseleave="OnMouseLeave">
|
||||
readonly
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary ec-badge-inline ms-1" title="@(Source?.GetReadOnlyTooltip())">readonly</span>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string AnchorId { get; set; } = "";
|
||||
[Parameter] public ConfigValueSource? Source { get; set; }
|
||||
[Parameter] public string OptionName { get; set; } = "";
|
||||
[Parameter] public object? Value { get; set; }
|
||||
/// <summary>When true, render as button and raise click/mouseleave via context.</summary>
|
||||
[Parameter] public bool IsInteractive { get; set; }
|
||||
[CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; }
|
||||
|
||||
private Task OnClick()
|
||||
{
|
||||
if (Ui == null || Source == null) return Task.CompletedTask;
|
||||
return Ui.RequestReadonlyPopoverAsync(new ReadonlyBadgeClickedEventArgs(AnchorId, Source, OptionName, Value));
|
||||
}
|
||||
|
||||
private Task OnMouseLeave()
|
||||
{
|
||||
if (Ui == null) return Task.CompletedTask;
|
||||
return Ui.ScheduleReadonlyPopoverCloseAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user