Extracted some pages to their own assembly and finished the artifact display page code
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
@namespace OpenArchival.Blazor.AdminPages
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using MudBlazor.Interfaces
|
||||
@using OpenArchival.DataAccess
|
||||
@using MudBlazor
|
||||
@using MudExtensions
|
||||
|
||||
@page "/categorieslist"
|
||||
|
||||
<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
|
||||
<MudText Typo="Typo.h6">Categories</MudText>
|
||||
<MudDivider Class="mb-2"></MudDivider>
|
||||
<MudList T="string" Clickable="true">
|
||||
@foreach (ArchiveCategory category in _categories)
|
||||
{
|
||||
<MudListItem OnClick="@(() => OnCategoryItemClicked(category))">
|
||||
@category.Name
|
||||
@if (ShowDeleteButton)
|
||||
{
|
||||
<MudListItemMeta ActionPosition="ActionPosition.End">
|
||||
<MudIconButton
|
||||
Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
Size="Size.Small"
|
||||
OnClick="@((e) => HandleDeleteClick(category))"
|
||||
/>
|
||||
</MudListItemMeta>
|
||||
}
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
@ChildContent
|
||||
</MudPaper>
|
||||
|
||||
@inject IArchiveCategoryProvider CategoryProvider;
|
||||
@inject ILogger<CategoriesListComponent> Logger;
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowDeleteButton { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ArchiveCategory> ListItemClickedCallback { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ArchiveCategory> OnDeleteClickedCallback { get; set; }
|
||||
|
||||
private List<ArchiveCategory> _categories = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadCategories();
|
||||
}
|
||||
|
||||
private async Task LoadCategories()
|
||||
{
|
||||
var categories = await CategoryProvider.GetAllArchiveCategories();
|
||||
if (categories is null)
|
||||
{
|
||||
Logger.LogError("There were no categories in the database when attempting to load the list of categories.");
|
||||
_categories.Clear();
|
||||
return;
|
||||
}
|
||||
_categories = categories.ToList();
|
||||
}
|
||||
|
||||
public async Task RefreshData()
|
||||
{
|
||||
await LoadCategories();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task OnCategoryItemClicked(ArchiveCategory category)
|
||||
{
|
||||
await ListItemClickedCallback.InvokeAsync(category);
|
||||
}
|
||||
|
||||
private async Task HandleDeleteClick(ArchiveCategory category)
|
||||
{
|
||||
await OnDeleteClickedCallback.InvokeAsync(category);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
@namespace OpenArchival.Blazor.AdminPages
|
||||
@using System.ComponentModel.DataAnnotations;
|
||||
@using OpenArchival.DataAccess;
|
||||
@using MudBlazor
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">Create a Category</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm @ref="_form">
|
||||
<MudTextField @bind-Value="ValidationModel.Name"
|
||||
For="@(() => ValidationModel.Name)"
|
||||
Label="Category Name"
|
||||
Variant="Variant.Filled" />
|
||||
|
||||
<MudDivider Class="pt-4" DividerType="DividerType.Middle"/>
|
||||
|
||||
<MudText Typo="Typo.h6">Item Tag Identifier</MudText>
|
||||
<MudText Typo="Typo.subtitle2">This will be the format of the identifier used for each archive entry.</MudText>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudText Typo="Typo.body2">Format Preview: </MudText>
|
||||
<MudText Type="Typo.body2" Color="Color.Primary">@FormatPreview</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudTextField @bind-Value="ValidationModel.FieldSeparator"
|
||||
@bind-Value:after="UpdateFormatPreview"
|
||||
For="@(() => ValidationModel.FieldSeparator)"
|
||||
Label="Field Separator"
|
||||
Variant="Variant.Filled"
|
||||
MaxLength="1" />
|
||||
|
||||
<MudDivider Class="pt-4" />
|
||||
|
||||
<MudNumericField
|
||||
Value="ValidationModel.NumFields"
|
||||
ValueChanged="@((int newCount) => OnNumFieldsChanged(newCount))"
|
||||
Label="Number of fields in the item identifiers"
|
||||
Variant="Variant.Filled"
|
||||
Min="1"></MudNumericField>
|
||||
|
||||
<MudDivider Class="pt-4" />
|
||||
|
||||
<MudGrid Class="pr-2 pt-2 pb-2 pl-8" Justify="Justify.FlexStart" Spacing="3">
|
||||
@for (int index = 0; index < ValidationModel.FieldNames.Count; ++index)
|
||||
{
|
||||
var localIndex = index;
|
||||
|
||||
<MudItem xs="12" sm="6" md="6">
|
||||
<CategoryFieldCardComponent Index="localIndex"
|
||||
FieldName="@ValidationModel.FieldNames[localIndex]"
|
||||
FieldDescription="@ValidationModel.FieldDescriptions[localIndex]"
|
||||
OnNameUpdate="HandleNameUpdate"
|
||||
OnDescriptionUpdate="HandleDescriptionUpdate"/>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">OK</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@inject IArchiveCategoryProvider CategoryProvider;
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public CategoryValidationModel ValidationModel { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public bool IsUpdate { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string OriginalName { get; set; } = string.Empty;
|
||||
|
||||
|
||||
private MudForm _form = default!;
|
||||
private string FormatPreview { get; set; } = string.Empty;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (ValidationModel is null)
|
||||
{
|
||||
ValidationModel = new CategoryValidationModel { NumFields = 1 };
|
||||
} else
|
||||
{
|
||||
ValidationModel.NumFields = ValidationModel.FieldNames.Count;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNumFieldsChanged(int newCount)
|
||||
{
|
||||
if (newCount < 1) return;
|
||||
ValidationModel.NumFields = newCount;
|
||||
UpdateStateFromModel();
|
||||
}
|
||||
|
||||
private void UpdateStateFromModel()
|
||||
{
|
||||
ValidationModel.FieldNames ??= new List<string>();
|
||||
ValidationModel.FieldDescriptions ??= new List<string>();
|
||||
|
||||
while (ValidationModel.FieldNames.Count < ValidationModel.NumFields)
|
||||
{
|
||||
ValidationModel.FieldNames.Add($"Field {ValidationModel.FieldNames.Count + 1}");
|
||||
}
|
||||
while (ValidationModel.FieldNames.Count > ValidationModel.NumFields)
|
||||
{
|
||||
ValidationModel.FieldNames.RemoveAt(ValidationModel.FieldNames.Count - 1);
|
||||
}
|
||||
|
||||
while (ValidationModel.FieldDescriptions.Count < ValidationModel.NumFields)
|
||||
{
|
||||
ValidationModel.FieldDescriptions.Add("");
|
||||
}
|
||||
while (ValidationModel.FieldDescriptions.Count > ValidationModel.NumFields)
|
||||
{
|
||||
ValidationModel.FieldDescriptions.RemoveAt(ValidationModel.FieldDescriptions.Count - 1);
|
||||
}
|
||||
|
||||
UpdateFormatPreview();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void UpdateFormatPreview()
|
||||
{
|
||||
var fieldNames = ValidationModel.FieldNames.Select(name => string.IsNullOrEmpty(name) ? "<...>" : $"<{name}>");
|
||||
FormatPreview = string.Join(ValidationModel.FieldSeparator, fieldNames);
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_form.IsValid) return;
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(ValidationModel));
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
// In your MudDialog component's @code block
|
||||
|
||||
private void HandleNameUpdate((int Index, string NewValue) data)
|
||||
{
|
||||
if (data.Index < ValidationModel.FieldNames.Count)
|
||||
{
|
||||
ValidationModel.FieldNames[data.Index] = data.NewValue;
|
||||
UpdateFormatPreview(); // Update the preview in real-time
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDescriptionUpdate((int Index, string NewValue) data)
|
||||
{
|
||||
if (data.Index < ValidationModel.FieldDescriptions.Count)
|
||||
{
|
||||
ValidationModel.FieldDescriptions[data.Index] = data.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
@namespace OpenArchival.Blazor.AdminPages
|
||||
@using MudBlazor
|
||||
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudTextField @bind-Value="FieldName"
|
||||
@bind-Value:after="OnNameChanged"
|
||||
Label="Field Name"
|
||||
Variant="Variant.Filled"
|
||||
Immediate="true"
|
||||
/>
|
||||
|
||||
<MudTextField @bind-Value="FieldDescription"
|
||||
@bind-Value:after="OnDescriptionChanged"
|
||||
Label="Field Description"
|
||||
Variant="Variant.Filled"
|
||||
Lines="2"
|
||||
Class="mt-3"
|
||||
Immediate="true"
|
||||
/>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
[Parameter] public int Index { get; set; }
|
||||
|
||||
[Parameter] public string FieldName { get; set; } = "";
|
||||
[Parameter] public string FieldDescription { get; set; } = "";
|
||||
|
||||
[Parameter] public EventCallback<(int Index, string NewValue)> OnNameUpdate { get; set; }
|
||||
[Parameter] public EventCallback<(int Index, string NewValue)> OnDescriptionUpdate { get; set; }
|
||||
|
||||
private async Task OnNameChanged()
|
||||
{
|
||||
await OnNameUpdate.InvokeAsync((Index, FieldName));
|
||||
}
|
||||
|
||||
private async Task OnDescriptionChanged()
|
||||
{
|
||||
await OnDescriptionUpdate.InvokeAsync((Index, FieldDescription));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace OpenArchival.Blazor;
|
||||
|
||||
public class ArtifactGroupingRowElement
|
||||
{
|
||||
public required int Id { get; set; }
|
||||
|
||||
public required string ArtifactGroupingIdentifier { get; set; }
|
||||
|
||||
public required string CategoryName { get; set; }
|
||||
|
||||
public required string Title { get; set; }
|
||||
|
||||
public bool IsPublicallyVisible { get; set; }
|
||||
|
||||
public bool Equals(ArtifactGroupingRowElement? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id == other.Id; // Compare based on the unique Id
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => Equals(obj as ArtifactGroupingRowElement);
|
||||
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
namespace OpenArchival.Blazor;
|
||||
|
||||
using Microsoft.IdentityModel.Abstractions;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using OpenArchival.DataAccess;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class CategoryValidationModel
|
||||
{
|
||||
public int? DatabaseId { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Category name is required.")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Field separator is required.")]
|
||||
[StringLength(1, ErrorMessage = "Separator must be a single character.")]
|
||||
public string FieldSeparator { get; set; } = "-";
|
||||
|
||||
[Required(ErrorMessage = "At least one field is needed")]
|
||||
[Range(1, int.MaxValue, ErrorMessage = "At least one field must be created.")]
|
||||
public int NumFields { get; set; } = 1;
|
||||
|
||||
public List<string> FieldNames { get; set; } = [""];
|
||||
|
||||
public List<string> FieldDescriptions { get; set; } = [""];
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext context)
|
||||
{
|
||||
if ((FieldNames is null || FieldNames.Count == 0) || (FieldDescriptions is null || FieldDescriptions.Count > 0))
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
"Either the FieldNames or FieldDescriptions were null or empty. At least one is required",
|
||||
new[] { nameof(FieldNames), nameof(FieldDescriptions) }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryValidationModel FromArchiveCategory(ArchiveCategory category)
|
||||
{
|
||||
return new CategoryValidationModel()
|
||||
{
|
||||
Name = category.Name,
|
||||
Description = category.Description,
|
||||
DatabaseId = category.Id,
|
||||
FieldSeparator = category.FieldSeparator,
|
||||
FieldNames = category.FieldNames,
|
||||
FieldDescriptions = category.FieldDescriptions,
|
||||
};
|
||||
}
|
||||
|
||||
public static ArchiveCategory ToArchiveCategory(CategoryValidationModel model)
|
||||
{
|
||||
return new ArchiveCategory()
|
||||
{
|
||||
Name = model.Name,
|
||||
FieldSeparator = model.FieldSeparator,
|
||||
Description = model.Description,
|
||||
FieldNames = model.FieldNames,
|
||||
FieldDescriptions = model.FieldDescriptions
|
||||
};
|
||||
}
|
||||
|
||||
public static void UpdateArchiveValidationModel(CategoryValidationModel model, ArchiveCategory category)
|
||||
{
|
||||
category.Name = model.Name ?? throw new ArgumentNullException(nameof(model.Name), "The model name was null.");
|
||||
category.Description = model.Description;
|
||||
category.FieldSeparator = model.FieldSeparator;
|
||||
category.FieldNames = model.FieldNames;
|
||||
category.FieldDescriptions = model.FieldDescriptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
@page "/categories"
|
||||
|
||||
@namespace OpenArchival.Blazor.AdminPages
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using MudBlazor
|
||||
@using OpenArchival.DataAccess;
|
||||
|
||||
@inject IDialogService DialogService
|
||||
@inject IArchiveCategoryProvider CategoryProvider;
|
||||
@inject IDbContextFactory<ApplicationDbContext> DbContextFactory;
|
||||
@inject ILogger<ViewAddCategoriesComponent> Logger;
|
||||
|
||||
<CategoriesListComponent
|
||||
@ref=_categoriesListComponent
|
||||
ListItemClickedCallback="ShowFilledDialog"
|
||||
ShowDeleteButton=true
|
||||
OnDeleteClickedCallback="DeleteCategory">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnAddClick">Add Category</MudButton>
|
||||
</CategoriesListComponent>
|
||||
|
||||
|
||||
@code {
|
||||
CategoriesListComponent _categoriesListComponent = default!;
|
||||
|
||||
private async Task DeleteCategory(ArchiveCategory category)
|
||||
{
|
||||
// 1. Show a confirmation dialog (recommended)
|
||||
var confirmed = await DialogService.ShowMessageBox("Confirm", $"Delete {category.Name}?", yesText:"Delete", cancelText:"Cancel");
|
||||
if (confirmed != true) return;
|
||||
|
||||
await CategoryProvider.DeleteCategoryAsync(category);
|
||||
await _categoriesListComponent.RefreshData();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ShowFilledDialog(ArchiveCategory category)
|
||||
{
|
||||
CategoryValidationModel validationModel = CategoryValidationModel.FromArchiveCategory(category);
|
||||
|
||||
var parameters = new DialogParameters { ["ValidationModel"] = validationModel, ["IsUpdate"] = true, ["OriginalName"] = category.Name};
|
||||
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick=false};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<CategoryCreatorDialog>("Create a Category", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result is not null && !result.Canceled && _categoriesListComponent is not null)
|
||||
{
|
||||
if (result.Data is null)
|
||||
{
|
||||
Logger.LogError($"The new category received by the result had a null data result member.");
|
||||
throw new NullReferenceException($"The new category received by the result had a null data result member.");
|
||||
}
|
||||
|
||||
CategoryValidationModel model = (CategoryValidationModel)result.Data;
|
||||
CategoryValidationModel.UpdateArchiveValidationModel(model, category);
|
||||
|
||||
await using var context = await DbContextFactory.CreateDbContextAsync();
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
StateHasChanged();
|
||||
await _categoriesListComponent.RefreshData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAddClick()
|
||||
{
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick=false };
|
||||
var dialog = await DialogService.ShowAsync<CategoryCreatorDialog>("Create a Category", options);
|
||||
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result is not null && !result.Canceled && _categoriesListComponent is not null && result.Data is not null)
|
||||
{
|
||||
await using var context = await DbContextFactory.CreateDbContextAsync();
|
||||
CategoryValidationModel model = (CategoryValidationModel)result.Data;
|
||||
context.ArchiveCategories.Add(CategoryValidationModel.ToArchiveCategory(model));
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
StateHasChanged();
|
||||
await _categoriesListComponent.RefreshData();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user