mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
EffectiveConfig: toolbar, save modal, cancel/revert UX
- Toolbar: single expand/collapse toggle; change count after Save/Exit. - Save modal: selective revert per change; modern list styling, empty state; Session + OnReverted; Save disabled when no changes. - Cancel: prevent blur-before-click (mousedown preventDefault on cancel badge); _cancelRequested so cancel never commits. - DeactivateFieldAsync in UiContext + Section; OptionStateBadge cancel clears draft and deactivates.
This commit is contained in:
+14
-3
@@ -34,7 +34,7 @@
|
|||||||
@fragment(Descriptor)
|
@fragment(Descriptor)
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
}
|
}
|
||||||
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" />
|
<OptionStateBadge AnchorId="@_readonlyBadgeAnchorId" Source="@Descriptor.GetSource()" OptionName="@Descriptor.OptionName" Value="@effectiveValue" IsInteractive="@IsEditMode" ShowEditableBadge="@showEditableBadge" HasPendingChange="@_hasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" IsActiveEditor="@isActiveEditor" OnCancelRequested="@HandleCancelEdit" />
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ else
|
|||||||
<EffectiveConfigOptionItem Label="@Descriptor.OptionName" DisplayValue="@(displayValue ?? "(not set)")" Source="@Descriptor.GetSource()" LabelTooltip="@_labelTooltip" OptionHelpKey="@_optionHelpKey" OptionHelpAnchorId="@_anchorId"
|
<EffectiveConfigOptionItem Label="@Descriptor.OptionName" DisplayValue="@(displayValue ?? "(not set)")" Source="@Descriptor.GetSource()" LabelTooltip="@_labelTooltip" OptionHelpKey="@_optionHelpKey" OptionHelpAnchorId="@_anchorId"
|
||||||
ShowEditableBadge="true" FieldKey="@FieldKey" IsActive="@IsActive"
|
ShowEditableBadge="true" FieldKey="@FieldKey" IsActive="@IsActive"
|
||||||
ReadonlyBadgeAnchorId="@_readonlyBadgeAnchorId" RawValue="@value" HasPendingChange="@_hasPendingChange"
|
ReadonlyBadgeAnchorId="@_readonlyBadgeAnchorId" RawValue="@value" HasPendingChange="@_hasPendingChange"
|
||||||
DisplayValueChanged="@HandleDefaultValueChanged" OnBlur="@HandleDefaultBlur" />
|
DisplayValueChanged="@HandleDefaultValueChanged" OnBlur="@HandleDefaultBlur" OnCancelRequested="@HandleCancelEdit" />
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,7 @@ else
|
|||||||
private object? _valueAtActivation;
|
private object? _valueAtActivation;
|
||||||
private object? _draftValue;
|
private object? _draftValue;
|
||||||
private bool _wasActive;
|
private bool _wasActive;
|
||||||
|
private bool _cancelRequested;
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
@@ -100,8 +101,10 @@ else
|
|||||||
_effectiveValue = pending != null ? pending.NewValue : Descriptor.GetValue();
|
_effectiveValue = pending != null ? pending.NewValue : Descriptor.GetValue();
|
||||||
if (!_wasActive && IsActive)
|
if (!_wasActive && IsActive)
|
||||||
_valueAtActivation = _effectiveValue;
|
_valueAtActivation = _effectiveValue;
|
||||||
if (_wasActive && !IsActive)
|
if (_wasActive && !IsActive && !_cancelRequested)
|
||||||
_ = InvokeAsync(CommitIfChangedAsync);
|
_ = InvokeAsync(CommitIfChangedAsync);
|
||||||
|
if (_wasActive && !IsActive)
|
||||||
|
_cancelRequested = false;
|
||||||
_wasActive = IsActive;
|
_wasActive = IsActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,4 +146,12 @@ else
|
|||||||
await CommitIfChangedAsync();
|
await CommitIfChangedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleCancelEdit()
|
||||||
|
{
|
||||||
|
_cancelRequested = true;
|
||||||
|
_draftValue = null;
|
||||||
|
if (Ui != null)
|
||||||
|
await Ui.DeactivateFieldAsync();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
{
|
{
|
||||||
<span class="ec-field-value" @onclick:stopPropagation="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" />
|
<input type="text" class="form-control form-control-sm" value="@(_editValue ?? DisplayValue)" @oninput="OnValueInput" @onfocusout="OnInputBlur" />
|
||||||
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" />
|
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" IsActiveEditor="true" OnCancelRequested="@OnCancelRequested" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="ec-field-value">
|
<span class="ec-field-value">
|
||||||
@DisplayValue
|
@DisplayValue
|
||||||
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" />
|
<OptionStateBadge AnchorId="@(ReadonlyBadgeAnchorId ?? "")" Source="@Source" OptionName="@Label" Value="@(RawValue ?? DisplayValue)" IsInteractive="@(DisplayMode == EffectiveConfigDisplayMode.Edit && !string.IsNullOrEmpty(ReadonlyBadgeAnchorId))" ShowEditableBadge="@ShowEditableBadge" HasPendingChange="@HasPendingChange" EditableAsButton="true" FieldKey="@FieldKey" IsActiveEditor="false" OnCancelRequested="@OnCancelRequested" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +42,8 @@ else
|
|||||||
[Parameter] public EventCallback<string> DisplayValueChanged { get; set; }
|
[Parameter] public EventCallback<string> DisplayValueChanged { get; set; }
|
||||||
/// <summary>When Edit and not readonly: raised when the input loses focus; argument is the current value.</summary>
|
/// <summary>When Edit and not readonly: raised when the input loses focus; argument is the current value.</summary>
|
||||||
[Parameter] public EventCallback<string?> OnBlur { get; set; }
|
[Parameter] public EventCallback<string?> OnBlur { get; set; }
|
||||||
|
/// <summary>When user clicks Cancel while this field is the active editor; parent should clear draft and deactivate.</summary>
|
||||||
|
[Parameter] public EventCallback OnCancelRequested { get; set; }
|
||||||
[CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View;
|
[CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View;
|
||||||
|
|
||||||
private string? _editValue;
|
private string? _editValue;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
@if (ShowModal)
|
@if (ShowModal)
|
||||||
{
|
{
|
||||||
<EffectiveConfigSaveModal PendingChanges="@PendingChanges" Status="@Status" OnClose="@OnClose" OnSave="@HandleSave" />
|
<EffectiveConfigSaveModal PendingChanges="@PendingChanges" Status="@Status" Session="@Session" OnClose="@OnClose" OnSave="@HandleSave" OnReverted="@HandleReverted" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -16,6 +16,11 @@
|
|||||||
[Parameter] public EventCallback OnSaveCompleted { get; set; }
|
[Parameter] public EventCallback OnSaveCompleted { get; set; }
|
||||||
[Parameter] public IEffectiveConfigEditSession Session { get; set; } = null!;
|
[Parameter] public IEffectiveConfigEditSession Session { get; set; } = null!;
|
||||||
|
|
||||||
|
private async Task HandleReverted()
|
||||||
|
{
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleSave()
|
private async Task HandleSave()
|
||||||
{
|
{
|
||||||
if (Session.PendingChanges.Count == 0) return;
|
if (Session.PendingChanges.Count == 0) return;
|
||||||
|
|||||||
+48
-17
@@ -1,6 +1,7 @@
|
|||||||
@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig
|
@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig
|
||||||
@using DnsmasqWebUI.Models.Dnsmasq
|
@using DnsmasqWebUI.Models.Dnsmasq
|
||||||
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig
|
||||||
|
@using DnsmasqWebUI.Infrastructure.Services.Abstractions
|
||||||
|
|
||||||
<div class="modal fade show" style="display: block;" tabindex="-1" role="dialog" aria-labelledby="ec-save-modal-title" aria-modal="true">
|
<div class="modal fade show" style="display: block;" tabindex="-1" role="dialog" aria-labelledby="ec-save-modal-title" aria-modal="true">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
@@ -9,29 +10,50 @@
|
|||||||
<h5 class="modal-title" id="ec-save-modal-title">Save effective config changes</h5>
|
<h5 class="modal-title" id="ec-save-modal-title">Save effective config changes</h5>
|
||||||
<button type="button" class="btn-close" @onclick="Close" aria-label="Close"></button>
|
<button type="button" class="btn-close" @onclick="Close" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body ec-save-modal-body">
|
||||||
<p class="small text-muted">The following changes will be written to your managed config file. Values set in other files will be overridden by these entries.</p>
|
<p class="text-muted mb-3">These changes will be written to your config. Remove any you don’t want to keep.</p>
|
||||||
@if (ManagedFilePath != null)
|
@if (ManagedFilePath != null)
|
||||||
{
|
{
|
||||||
<p class="small mb-2"><strong>Target file:</strong> <code>@ManagedFilePath</code></p>
|
<p class="ec-save-modal-target mb-3">
|
||||||
|
<span class="text-muted">Target file:</span> <code class="small">@ManagedFilePath</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
@if (PendingChanges.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="ec-save-modal-empty alert alert-light border text-center text-muted py-4 mb-0">
|
||||||
|
No changes to save. Revert all above or close to keep editing.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="ec-save-modal-changes list-group list-group-flush">
|
||||||
|
@foreach (var c in PendingChanges)
|
||||||
|
{
|
||||||
|
var change = c;
|
||||||
|
<div class="list-group-item ec-save-modal-item d-flex align-items-center gap-3 py-3 px-0 border-0 border-bottom">
|
||||||
|
<div class="ec-save-modal-item-body flex-grow-1 min-width-0">
|
||||||
|
<span class="ec-save-modal-option badge rounded-pill bg-light text-dark border me-2">@change.OptionName</span>
|
||||||
|
<span class="ec-save-modal-diff text-body">
|
||||||
|
<span class="text-muted text-decoration-line-through">@FormatValue(change.OldValue)</span>
|
||||||
|
<span class="text-muted mx-1" aria-hidden="true">→</span>
|
||||||
|
<span>@FormatValue(change.NewValue)</span>
|
||||||
|
</span>
|
||||||
|
@if (change.CurrentSourceFilePath != null)
|
||||||
|
{
|
||||||
|
<div class="ec-save-modal-override small text-muted mt-1">Overrides @System.IO.Path.GetFileName(change.CurrentSourceFilePath)</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger flex-shrink-0" @onclick="() => Revert(change)" title="Remove this change from the list" aria-label="Revert @change.OptionName">
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<ul class="list-unstyled mb-0">
|
|
||||||
@foreach (var c in PendingChanges)
|
|
||||||
{
|
|
||||||
<li class="small py-1 border-bottom border-light">
|
|
||||||
<strong>@c.OptionName:</strong>
|
|
||||||
@FormatValue(c.OldValue) → @FormatValue(c.NewValue)
|
|
||||||
@if (c.CurrentSourceFilePath != null)
|
|
||||||
{
|
|
||||||
<br /><span class="text-muted">(overrides value from @System.IO.Path.GetFileName(c.CurrentSourceFilePath))</span>
|
|
||||||
}
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Close">Cancel</button>
|
<button type="button" class="btn btn-secondary" @onclick="Close">Cancel</button>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Save">Save</button>
|
<button type="button" class="btn btn-primary" @onclick="Save" disabled="@(PendingChanges.Count == 0)">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,8 +63,11 @@
|
|||||||
@code {
|
@code {
|
||||||
[Parameter] public IReadOnlyList<PendingEffectiveConfigChange> PendingChanges { get; set; } = null!;
|
[Parameter] public IReadOnlyList<PendingEffectiveConfigChange> PendingChanges { get; set; } = null!;
|
||||||
[Parameter] public DnsmasqServiceStatus? Status { get; set; }
|
[Parameter] public DnsmasqServiceStatus? Status { get; set; }
|
||||||
|
[Parameter] public IEffectiveConfigEditSession Session { get; set; } = null!;
|
||||||
[Parameter] public EventCallback OnClose { get; set; }
|
[Parameter] public EventCallback OnClose { get; set; }
|
||||||
[Parameter] public EventCallback OnSave { get; set; }
|
[Parameter] public EventCallback OnSave { get; set; }
|
||||||
|
/// <summary>Invoked after reverting a change so the parent can re-render.</summary>
|
||||||
|
[Parameter] public EventCallback OnReverted { get; set; }
|
||||||
|
|
||||||
private string? ManagedFilePath => Status?.ManagedFilePath;
|
private string? ManagedFilePath => Status?.ManagedFilePath;
|
||||||
|
|
||||||
@@ -53,6 +78,12 @@
|
|||||||
return v.ToString() ?? "(not set)";
|
return v.ToString() ?? "(not set)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Revert(PendingEffectiveConfigChange change)
|
||||||
|
{
|
||||||
|
Session.RevertChange(change.SectionId, change.OptionName);
|
||||||
|
await OnReverted.InvokeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Close()
|
private async Task Close()
|
||||||
{
|
{
|
||||||
await OnClose.InvokeAsync();
|
await OnClose.InvokeAsync();
|
||||||
|
|||||||
+34
@@ -7,3 +7,37 @@
|
|||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-body {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-target {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-changes {
|
||||||
|
margin: 0 -1rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-item:last-child {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-option {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-diff {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-override {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ec-save-modal-empty {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
ActivateFieldAsync = fieldKey => { ActivateField(fieldKey); return Task.CompletedTask; },
|
ActivateFieldAsync = fieldKey => { ActivateField(fieldKey); return Task.CompletedTask; },
|
||||||
|
DeactivateFieldAsync = () => { Session.DeactivateField(); return InvokeAsync(StateHasChanged); },
|
||||||
CommitFieldAsync = args => { HandleFieldEditCommitted(args); return Task.CompletedTask; }
|
CommitFieldAsync = args => { HandleFieldEditCommitted(args); return Task.CompletedTask; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@
|
|||||||
<button type="button" class="btn btn-sm btn-primary" @onclick="OnOpenSaveModal" disabled="@(PendingChangesCount == 0)" title="Save changes to managed config" aria-label="Save">
|
<button type="button" class="btn btn-sm btn-primary" @onclick="OnOpenSaveModal" disabled="@(PendingChangesCount == 0)" title="Save changes to managed config" aria-label="Save">
|
||||||
<i class="bi bi-floppy-fill" aria-hidden="true"></i>
|
<i class="bi bi-floppy-fill" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="OnExitEditModeWithConfirm" title="Exit edit mode" aria-label="Exit edit mode">
|
||||||
|
<i class="bi bi-box-arrow-right" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
@if (PendingChangesCount > 0)
|
@if (PendingChangesCount > 0)
|
||||||
{
|
{
|
||||||
<span class="text-muted small">@PendingChangesCount change(s)</span>
|
<span class="text-muted small">@PendingChangesCount change(s)</span>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="OnExitEditModeWithConfirm" title="Exit edit mode" aria-label="Exit edit mode">
|
|
||||||
<i class="bi bi-box-arrow-right" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,14 +20,29 @@ else if (ShowEditableBadge)
|
|||||||
{
|
{
|
||||||
@if (EditableAsButton)
|
@if (EditableAsButton)
|
||||||
{
|
{
|
||||||
<button type="button"
|
@if (IsActiveEditor)
|
||||||
class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")"
|
{
|
||||||
@onclick="OnEditableClick"
|
<button type="button"
|
||||||
@onclick:stopPropagation="true"
|
class="badge ec-badge-inline border-0 ms-1 bg-secondary"
|
||||||
title="@(HasPendingChange ? "Pending change" : "Edit this field")"
|
@onmousedown:preventDefault
|
||||||
aria-label="Edit">
|
@onclick="OnCancelClick"
|
||||||
@(HasPendingChange ? "pending" : "editable")
|
@onclick:stopPropagation="true"
|
||||||
</button>
|
title="Cancel and clear unsaved changes for this field"
|
||||||
|
aria-label="Cancel">
|
||||||
|
cancel
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="badge ec-badge-inline border-0 ms-1 @(HasPendingChange ? "bg-warning text-dark" : "bg-primary")"
|
||||||
|
@onclick="OnEditableClick"
|
||||||
|
@onclick:stopPropagation="true"
|
||||||
|
title="@(HasPendingChange ? "Pending change" : "Edit this field")"
|
||||||
|
aria-label="Edit">
|
||||||
|
@(HasPendingChange ? "pending" : "editable")
|
||||||
|
</button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -48,10 +63,22 @@ else if (ShowEditableBadge)
|
|||||||
[Parameter] public bool HasPendingChange { get; set; }
|
[Parameter] public bool HasPendingChange { get; set; }
|
||||||
/// <summary>When true, editable badge is a button; otherwise a static span.</summary>
|
/// <summary>When true, editable badge is a button; otherwise a static span.</summary>
|
||||||
[Parameter] public bool EditableAsButton { get; set; } = true;
|
[Parameter] public bool EditableAsButton { get; set; } = true;
|
||||||
|
/// <summary>When true, this field is the active editor; show Cancel instead of Edit and invoke OnCancelRequested on click.</summary>
|
||||||
|
[Parameter] public bool IsActiveEditor { get; set; }
|
||||||
|
/// <summary>When user clicks Cancel (while IsActiveEditor). Invoke before deactivating so parent can clear draft.</summary>
|
||||||
|
[Parameter] public EventCallback OnCancelRequested { get; set; }
|
||||||
/// <summary>Field key for activation via context when editable badge is clicked.</summary>
|
/// <summary>Field key for activation via context when editable badge is clicked.</summary>
|
||||||
[Parameter] public string? FieldKey { get; set; }
|
[Parameter] public string? FieldKey { get; set; }
|
||||||
[CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; }
|
[CascadingParameter] public EffectiveConfigUiContext? Ui { get; set; }
|
||||||
|
|
||||||
|
private async Task OnCancelClick()
|
||||||
|
{
|
||||||
|
if (OnCancelRequested.HasDelegate)
|
||||||
|
await OnCancelRequested.InvokeAsync();
|
||||||
|
else if (Ui != null)
|
||||||
|
await Ui.DeactivateFieldAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private Task OnEditableClick()
|
private Task OnEditableClick()
|
||||||
{
|
{
|
||||||
if (Ui == null || string.IsNullOrEmpty(FieldKey)) return Task.CompletedTask;
|
if (Ui == null || string.IsNullOrEmpty(FieldKey)) return Task.CompletedTask;
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ public sealed class EffectiveConfigUiContext
|
|||||||
public required Func<ReadonlyBadgeClickedEventArgs, Task> RequestReadonlyPopoverAsync { get; init; }
|
public required Func<ReadonlyBadgeClickedEventArgs, Task> RequestReadonlyPopoverAsync { get; init; }
|
||||||
public required Func<Task> ScheduleReadonlyPopoverCloseAsync { get; init; }
|
public required Func<Task> ScheduleReadonlyPopoverCloseAsync { get; init; }
|
||||||
public required Func<string, Task> ActivateFieldAsync { get; init; }
|
public required Func<string, Task> ActivateFieldAsync { get; init; }
|
||||||
|
public required Func<Task> DeactivateFieldAsync { get; init; }
|
||||||
public required Func<EffectiveConfigEditCommittedArgs, Task> CommitFieldAsync { get; init; }
|
public required Func<EffectiveConfigEditCommittedArgs, Task> CommitFieldAsync { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user