feat: add MergeModal shared component for merge destination selection
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<ModalFrame Title="@GetTitle()" Show="Show" OnClose="OnClose" Size="normal">
|
||||
<Body>
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="alert alert-warning py-2 mb-0">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
This action is irreversible. Merged @LabelLower data will be reassigned to the destination.
|
||||
</div>
|
||||
<p class="text-muted mb-0">Select the destination @LabelLower to keep:</p>
|
||||
@foreach (var item in Items) {
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio"
|
||||
name="merge-destination"
|
||||
id="merge-@item.Id"
|
||||
value="@item.Id"
|
||||
checked="@(DestinationId == item.Id)"
|
||||
@onchange="() => SelectDestination(item.Id)" />
|
||||
<label class="form-check-label" for="merge-@item.Id">
|
||||
<strong>@item.Name</strong>
|
||||
<span class="text-muted">(@item.Subtitle)</span>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Body>
|
||||
<Footer>
|
||||
<button class="btn btn-secondary" @onclick="OnClose">Cancel</button>
|
||||
<button class="btn btn-warning" @onclick="OnMerge" disabled="@(!DestinationId.HasValue)">
|
||||
<i class="bi bi-arrow-right-circle"></i> Merge
|
||||
</button>
|
||||
</Footer>
|
||||
</ModalFrame>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Represents an item in the merge list.
|
||||
/// </summary>
|
||||
public sealed class MergeItem {
|
||||
/// <summary>
|
||||
/// Gets or sets the item ID.
|
||||
/// </summary>
|
||||
public required Guid Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the item display name.
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the item subtitle (e.g. asset count).
|
||||
/// </summary>
|
||||
public string Subtitle { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the modal is visible.
|
||||
/// </summary>
|
||||
[Parameter] public bool Show { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label for the item type (e.g. "Album", "Cosplayer").
|
||||
/// </summary>
|
||||
[Parameter] public string ItemLabel { get; set; } = "Item";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of items available for merge.
|
||||
/// </summary>
|
||||
[Parameter] public required List<MergeItem> Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback invoked when the user confirms the merge.
|
||||
/// Passes the selected destination ID and the full list of source IDs.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<(Guid DestinationId, List<Guid> SourceIds)> OnMergeConfirmed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback invoked when the modal is closed.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback OnClose { get; set; }
|
||||
|
||||
Guid? DestinationId;
|
||||
|
||||
string GetTitle() => $"Merge {ItemLabel}(s)";
|
||||
|
||||
string LabelLower => ItemLabel.ToLowerInvariant();
|
||||
|
||||
protected override void OnParametersSet() {
|
||||
if (Show && Items.Count > 0 && DestinationId is null) {
|
||||
DestinationId = Items[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
void SelectDestination(Guid id) {
|
||||
DestinationId = id;
|
||||
}
|
||||
|
||||
async Task OnMerge() {
|
||||
if (!DestinationId.HasValue) return;
|
||||
var sourceIds = Items.Where(i => i.Id != DestinationId.Value).Select(i => i.Id).ToList();
|
||||
await OnMergeConfirmed.InvokeAsync((DestinationId.Value, sourceIds));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user