Fixed bug where deletes of artifact groupings would not cascade
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
@namespace OpenArchival.Blazor.AdminPages.Shared
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using OpenArchival.DataAccess
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using OpenArchival.Blazor.CustomComponents
|
||||
@using MudBlazor
|
||||
|
||||
@inject ArtifactEntrySharedHelpers Helpers;
|
||||
@inject IArtifactDefectProvider DefectsProvider;
|
||||
@inject IArtifactStorageLocationProvider StorageLocationProvider;
|
||||
@inject IArchiveEntryTagProvider TagsProvider;
|
||||
@inject IArtifactTypeProvider TypesProvider;
|
||||
@inject IListedNameProvider ListedNameProvider;
|
||||
|
||||
<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
|
||||
<MudGrid>
|
||||
<MudItem>
|
||||
@if (Model.Files.Count > 0) {
|
||||
<MudText Typo="Typo.h6">@(Model.Files[0].OriginalName)</MudText>
|
||||
}
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudButton
|
||||
Variant="Variant.Filled"
|
||||
StartIcon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="OnDeleteEntryClicked"
|
||||
>Delete</MudButton>
|
||||
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@foreach (var error in ValidationResults)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error">
|
||||
@error.ErrorMessage
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Archive Item Title</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudTextField For="@(() => Model.Title)" Required=true Placeholder="Archive Item Title" T="string" Class="pl-4 pr-4" @bind-Value=Model.Title @bind-Value:after=OnInputsChanged></MudTextField>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Archive Item Numbering</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="pb-2">Enter a unique ID for this entry</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudTextField For="@(() => Model.ArtifactNumber)" Placeholder="Numbering" T="string" @bind-Value=Model.ArtifactNumber @bind-Value:after=OnInputsChanged/>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Item Description</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudTextField For="@(() => Model.Description)" Lines=8 Placeholder="Description" T="string" Class="pl-4 pr-4" @bind-Value=Model.Description @bind-Value:after=OnInputsChanged></MudTextField>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Item Quantity</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudNumericField Min="0" @bind-Value=Model.Quantity></MudNumericField>
|
||||
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Storage Location</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudAutocomplete For="@(() => Model.StorageLocation)" T="string" Label="Storage Location" Class="pt-0 mt-0 pl-2 pr-2" @bind-Value=Model.StorageLocation @bind-Value:after=OnInputsChanged SearchFunc="Helpers.SearchStorageLocation" CoerceValue=true></MudAutocomplete>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Artifact Type</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudAutocomplete For="@(() => Model.Type)" T="string" Label="Artifact Type" Class="pt-0 mt-0 pl-2 pr-2" @bind-Value=Model.Type @bind-Value:after=OnInputsChanged SearchFunc="Helpers.SearchItemTypes" CoerceValue=true></MudAutocomplete>
|
||||
|
||||
@* Tags entry *@
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Tags</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<ChipContainer T="string" @ref="@_tagsChipContainer" @bind-Items="Model.Tags">
|
||||
<InputContent>
|
||||
<MudAutocomplete T="string"
|
||||
OnInternalInputChanged="OnInputsChanged"
|
||||
SearchFunc="Helpers.SearchTags"
|
||||
Value="_tagsInputValue"
|
||||
ValueChanged="OnTagsInputTextChanged"
|
||||
OnKeyDown="@(ev => HandleChipContainerEnter<string>(ev, _tagsChipContainer, _tagsInputValue, () => _tagsInputValue = string.Empty))"
|
||||
CoerceValue=true
|
||||
Placeholder="Add Tags..."
|
||||
ShowProgressIndicator="true"
|
||||
>
|
||||
</MudAutocomplete>
|
||||
</InputContent>
|
||||
</ChipContainer>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Listed Names</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="pb-2">Enter any names of the people associated with this entry.</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<ChipContainer T="string" @ref=_listedNamesChipContainer @bind-Items="Model.ListedNames">
|
||||
<InputContent>
|
||||
<MudAutocomplete T="string"
|
||||
SearchFunc="Helpers.SearchListedNames"
|
||||
OnInternalInputChanged="OnInputsChanged"
|
||||
Value="_listedNamesInputValue"
|
||||
ValueChanged="OnListedNamesTextChanged"
|
||||
OnKeyDown="@(ev=>HandleChipContainerEnter<string>(ev, _listedNamesChipContainer, _listedNamesInputValue, () => _listedNamesInputValue = string.Empty))"
|
||||
CoerceValue=true
|
||||
Placeholder="Add Listed Names..."
|
||||
ShowProgressIndicator=true>
|
||||
</MudAutocomplete>
|
||||
</InputContent>
|
||||
</ChipContainer>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Associated Dates</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<ChipContainer T="DateTime" @ref="_assocaitedDatesChipContainer" @bind-Items="Model.AssociatedDates" DisplayFunc="date => date.ToShortDateString()">
|
||||
<InputContent>
|
||||
<MudDatePicker @bind-Date=_associatedDateInputValue MinDate="new DateTime(1000, 1, 1)">
|
||||
</MudDatePicker>
|
||||
</InputContent>
|
||||
<SubmitButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
OnClick="HandleAssociatedDateChipContainerAdd">+</MudButton>
|
||||
</SubmitButton>
|
||||
</ChipContainer>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Defects</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<ChipContainer T="string" @ref=_defectsChipContainer @bind-Items="Model.Defects">
|
||||
<InputContent>
|
||||
<MudAutocomplete T="string"
|
||||
SearchFunc="Helpers.SearchDefects"
|
||||
OnInternalInputChanged="OnInputsChanged"
|
||||
Value="_defectsInputValue"
|
||||
ValueChanged="OnDefectsValueChanged"
|
||||
OnKeyDown="@(ev=>HandleChipContainerEnter<string>(ev, _defectsChipContainer, _defectsInputValue, () => _defectsInputValue = string.Empty))"
|
||||
CoerceValue=true
|
||||
Placeholder="Add Defects..."
|
||||
ShowProgressIndicator=true>
|
||||
</MudAutocomplete>
|
||||
</InputContent>
|
||||
</ChipContainer>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Related Artifacts</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="pb-2">Tag this entry with the identifier of any other entry to link them.</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<ChipContainer T="ArtifactEntry" @ref="_assocaitedArtifactsChipContainer" @bind-Items="Model.RelatedArtifacts" DisplayFunc="artifact => artifact.ArtifactIdentifier">
|
||||
<InputContent>
|
||||
<MudAutocomplete T="ArtifactEntry"
|
||||
OnInternalInputChanged="OnInputsChanged"
|
||||
Value="_associatedArtifactValue"
|
||||
ValueChanged="OnAssociatedArtifactChanged"
|
||||
OnKeyDown="@(EventArgs=>HandleChipContainerEnter<ArtifactEntry>(EventArgs, _assocaitedArtifactsChipContainer, _associatedArtifactValue, () => _associatedArtifactValue = null))"
|
||||
CoerceValue="false"
|
||||
Placeholder="Link artifact groupings..."
|
||||
ShowProgressIndicator=true>
|
||||
</MudAutocomplete>
|
||||
</InputContent>
|
||||
</ChipContainer>
|
||||
|
||||
<MudText Typo="Typo.h6" Color="Color.Primary" Class="pt-4 pb-0">Artifact Text Contents</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="pb-2">Input the text transcription of the words on the artifact if applicable to aid the search engine.</MudText>
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudTextField T="string"
|
||||
Value=Model.FileTextContent
|
||||
ValueChanged="OnArtifactTextContentChanged"
|
||||
Lines="5"
|
||||
For="@(() => Model.FileTextContent)"></MudTextField>
|
||||
|
||||
<MudText Typo=Typo.h6 Color="Color.Primary">Additional files</MudText>
|
||||
<UploadDropBox @ref=_uploadDropBox FilesUploaded="OnFilesUploaded"></UploadDropBox>
|
||||
</MudPaper>
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required FilePathListing MainFilePath { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<ArtifactEntryValidationModel> ModelChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback InputsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required ArtifactEntryValidationModel Model { get; set; } = new() { StorageLocation = "hello", Title = "Hello" };
|
||||
|
||||
[Parameter]
|
||||
public required int ArtifactEntryIndex {get; set;}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<(int index, string filename)> OnEntryDeletedClicked { get; set; }
|
||||
|
||||
private ChipContainer<string> _tagsChipContainer;
|
||||
|
||||
private string _tagsInputValue { get; set; } = "";
|
||||
|
||||
private ChipContainer<DateTime> _assocaitedDatesChipContainer;
|
||||
|
||||
private DateTime? _associatedDateInputValue { get; set; } = default;
|
||||
|
||||
private ChipContainer<string> _listedNamesChipContainer;
|
||||
|
||||
private string _listedNamesInputValue { get; set; } = "";
|
||||
|
||||
private ChipContainer<string> _defectsChipContainer;
|
||||
|
||||
private string _defectsInputValue = "";
|
||||
|
||||
private ChipContainer<ArtifactEntry> _assocaitedArtifactsChipContainer;
|
||||
|
||||
private ArtifactEntry? _associatedArtifactValue = null;
|
||||
|
||||
private string _artifactTextContent = "";
|
||||
|
||||
public bool IsValid { get; set; }
|
||||
|
||||
public List<ValidationResult> ValidationResults { get; private set; } = [];
|
||||
|
||||
public UploadDropBox _uploadDropBox = default!;
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
if (_uploadDropBox is not null && Model is not null && Model.Files is not null)
|
||||
{
|
||||
_uploadDropBox.ExistingFiles = Model.Files.GetRange(1, Model.Files.Count - 1);
|
||||
}
|
||||
|
||||
if (Model.Files is not null && Model.Files.Any())
|
||||
{
|
||||
MainFilePath = Model.Files[0];
|
||||
}
|
||||
|
||||
return base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
public async Task OnInputsChanged()
|
||||
{
|
||||
// 1. Clear previous validation errors
|
||||
ValidationResults.Clear();
|
||||
|
||||
var validationContext = new ValidationContext(Model);
|
||||
|
||||
// 2. Run the validator
|
||||
IsValid = Validator.TryValidateObject(Model, validationContext, ValidationResults, validateAllProperties: true);
|
||||
// 3. REMOVE this line. Let the parent's update trigger the re-render.
|
||||
// StateHasChanged();
|
||||
|
||||
await InputsChanged.InvokeAsync();
|
||||
}
|
||||
|
||||
private async Task OnFilesUploaded(List<FilePathListing> filePathListings)
|
||||
{
|
||||
if (MainFilePath is not null)
|
||||
{
|
||||
var oldFiles = Model.Files.GetRange(1, Model.Files.Count - 1);
|
||||
Model.Files = [MainFilePath];
|
||||
Model.Files.AddRange(oldFiles);
|
||||
Model.Files.AddRange(filePathListings);
|
||||
} else
|
||||
{
|
||||
Model.Files = [];
|
||||
}
|
||||
Model.Files.AddRange(filePathListings);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task OnFilesCleared()
|
||||
{
|
||||
if (MainFilePath is not null) {
|
||||
Model.Files = [MainFilePath];
|
||||
} else
|
||||
{
|
||||
Model.Files = [];
|
||||
}
|
||||
}
|
||||
|
||||
private Task OnDefectsValueChanged(string text)
|
||||
{
|
||||
_defectsInputValue = text;
|
||||
return ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
private Task OnTagsInputTextChanged(string text)
|
||||
{
|
||||
_tagsInputValue = text;
|
||||
return ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
private Task OnListedNamesTextChanged(string text)
|
||||
{
|
||||
_listedNamesInputValue = text;
|
||||
return ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
private Task OnAssociatedArtifactChanged(ArtifactEntry grouping)
|
||||
{
|
||||
if (grouping is not null)
|
||||
{
|
||||
_associatedArtifactValue = grouping;
|
||||
return ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
return ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
private Task OnArtifactTextContentChanged(string value)
|
||||
{
|
||||
Model.FileTextContent = value;
|
||||
return ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
public async Task HandleChipContainerEnter<Type>(KeyboardEventArgs args, ChipContainer<Type> container, Type value, Action resetInputAction)
|
||||
{
|
||||
if (args.Key == "Enter")
|
||||
{
|
||||
await container.AddItem(value);
|
||||
resetInputAction?.Invoke();
|
||||
StateHasChanged();
|
||||
await ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleAssociatedDateChipContainerAdd(MouseEventArgs args)
|
||||
{
|
||||
if (_associatedDateInputValue is not null)
|
||||
{
|
||||
DateTime unspecifiedDate = (DateTime)_associatedDateInputValue;
|
||||
|
||||
DateTime utcDate = DateTime.SpecifyKind(unspecifiedDate, DateTimeKind.Utc);
|
||||
|
||||
await _assocaitedDatesChipContainer.AddItem(utcDate);
|
||||
|
||||
_associatedDateInputValue = default;
|
||||
}
|
||||
}
|
||||
private async Task OnDeleteEntryClicked(MouseEventArgs args)
|
||||
{
|
||||
await OnEntryDeletedClicked.InvokeAsync((ArtifactEntryIndex, MainFilePath.OriginalName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
@namespace OpenArchival.Blazor.AdminPages.Shared
|
||||
@using System.Text
|
||||
@using MudBlazor
|
||||
|
||||
<MudText Typo="Typo.body2" Color="Color.Primary">Item Identifier: @Value</MudText>
|
||||
@if (_identifierError)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-4">
|
||||
All identifier fields must be filled in.
|
||||
</MudAlert>
|
||||
}
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<MudGrid Class="pt-0 pl-4 pr-4" Justify="Justify.FlexStart" AlignItems="AlignItems.Center">
|
||||
|
||||
@for (int index = 0; index < IdentifierFields.Count; index++)
|
||||
{
|
||||
// You must create a local variable inside the loop for binding to work correctly.
|
||||
var field = IdentifierFields[index];
|
||||
|
||||
<MudItem Class="pt-6">
|
||||
<MudTextField Label="@field.Name"
|
||||
@bind-Value="@field.Value"
|
||||
@bind-Value:after="OnInputChanged"
|
||||
DebounceInterval="100"
|
||||
Required=true/>
|
||||
</MudItem>
|
||||
|
||||
@if (index < IdentifierFields.Count - 1)
|
||||
{
|
||||
<MudItem Class="pt-6">
|
||||
<MudText>@FieldSeparator</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
@using OpenArchival.DataAccess;
|
||||
@inject IArchiveCategoryProvider CategoryProvider;
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required string FieldSeparator { get; set; } = "-";
|
||||
|
||||
private List<IdentifierFieldValidationModel> _identifierFields = new();
|
||||
|
||||
[Parameter]
|
||||
public required List<IdentifierFieldValidationModel> IdentifierFields
|
||||
{
|
||||
get => _identifierFields;
|
||||
set => _identifierFields = value ?? new();
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> ValueChanged { get; set; }
|
||||
|
||||
private ArchiveCategory _verifyFormatCategory;
|
||||
public ArchiveCategory? VerifyFormatCategory
|
||||
{
|
||||
get
|
||||
{
|
||||
return _verifyFormatCategory;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
_identifierFields.Clear();
|
||||
_verifyFormatCategory = value;
|
||||
foreach (var field in value.FieldNames)
|
||||
{
|
||||
_identifierFields.Add(new IdentifierFieldValidationModel() {Name=field, Value=""});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid { get; set; } = false;
|
||||
|
||||
// Computed property that builds the final string
|
||||
public string Value => string.Join(FieldSeparator, IdentifierFields.Select(f => f.Value).Where(v => !string.IsNullOrEmpty(v)));
|
||||
|
||||
private bool _identifierError = false;
|
||||
|
||||
/// <summary>
|
||||
/// This runs when parameters are first set, ensuring the initial state is correct.
|
||||
/// </summary>
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
ValidateFields();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This runs after the user types into a field.
|
||||
/// </summary>
|
||||
private async Task OnInputChanged()
|
||||
{
|
||||
ValidateFields();
|
||||
await ValueChanged.InvokeAsync(this.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reusable method to check the validity of the identifier fields.
|
||||
/// </summary>
|
||||
private void ValidateFields()
|
||||
{
|
||||
// Set to true if ANY field is empty or null.
|
||||
_identifierError = IdentifierFields.Any(f => string.IsNullOrEmpty(f.Value));
|
||||
IsValid = !_identifierError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
namespace OpenArchival.Blazor.AdminPages.Shared;
|
||||
|
||||
using OpenArchival.DataAccess;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class ArtifactEntryValidationModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Used when translating between the validation model and the database model
|
||||
/// </summary>
|
||||
public int? Id { get; set; }
|
||||
|
||||
[Required(AllowEmptyStrings = false, ErrorMessage = "An artifact numbering must be supplied")]
|
||||
public string? ArtifactNumber { get; set; }
|
||||
|
||||
[Required(AllowEmptyStrings = false, ErrorMessage = "A title must be provided")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string? Type { get; set; }
|
||||
|
||||
public string? StorageLocation { get; set; }
|
||||
|
||||
public List<string>? Tags { get; set; } = [];
|
||||
|
||||
public List<string>? ListedNames { get; set; } = [];
|
||||
|
||||
public List<DateTime>? AssociatedDates { get; set; } = [];
|
||||
|
||||
public List<string>? Defects { get; set; } = [];
|
||||
|
||||
public List<string>? Links { get; set; } = [];
|
||||
|
||||
public List<FilePathListing>? Files { get; set; } = [];
|
||||
|
||||
public string? FileTextContent { get; set; }
|
||||
|
||||
public List<ArtifactEntry> RelatedArtifacts { get; set; } = [];
|
||||
|
||||
public bool IsPublicallyVisible { get; set; } = true;
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public ArtifactEntry ToArtifactEntry(ArtifactGrouping? parent = null)
|
||||
{
|
||||
List<ArtifactEntryTag> tags = new();
|
||||
if (Tags is not null)
|
||||
{
|
||||
foreach (var tag in Tags)
|
||||
{
|
||||
tags.Add(new ArtifactEntryTag() { Name = tag });
|
||||
}
|
||||
}
|
||||
|
||||
List<ArtifactDefect> defects = new();
|
||||
foreach (var defect in Defects)
|
||||
{
|
||||
defects.Add(new ArtifactDefect() { Description=defect});
|
||||
}
|
||||
|
||||
var entry = new ArtifactEntry()
|
||||
{
|
||||
Id = Id ?? 0,
|
||||
Files = Files,
|
||||
Type = new DataAccess.ArtifactType() { Name = Type },
|
||||
ArtifactNumber = ArtifactNumber,
|
||||
AssociatedDates = AssociatedDates,
|
||||
Defects = defects,
|
||||
Links = Links,
|
||||
StorageLocation = null,
|
||||
Description = Description,
|
||||
FileTextContent = FileTextContent,
|
||||
IsPubliclyVisible = IsPublicallyVisible,
|
||||
Tags = tags,
|
||||
Title = Title,
|
||||
ArtifactGrouping = parent,
|
||||
RelatedTo = RelatedArtifacts,
|
||||
Quantity = Quantity
|
||||
};
|
||||
|
||||
List<ListedName> listedNames = new();
|
||||
foreach (var name in ListedNames)
|
||||
{
|
||||
listedNames.Add(new ListedName() { Value=name });
|
||||
}
|
||||
|
||||
entry.ListedNames = listedNames;
|
||||
|
||||
if (!string.IsNullOrEmpty(StorageLocation))
|
||||
{
|
||||
entry.StorageLocation = new ArtifactStorageLocation() { Location = StorageLocation };
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using OpenArchival.DataAccess;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace OpenArchival.Blazor.AdminPages.Shared;
|
||||
|
||||
public class ArtifactGroupingValidationModel : IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by update code to track the database record that corresponds to the data within this DTO
|
||||
/// </summary>
|
||||
public int? Id { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "A grouping title is required.")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "A grouping description is required.")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "A type is required.")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
public ArchiveCategory? Category { get; set; }
|
||||
|
||||
public List<IdentifierFieldValidationModel> IdentifierFieldValues { get; set; } = new();
|
||||
|
||||
public List<ArtifactEntryValidationModel> ArtifactEntries { get; set; } = new();
|
||||
|
||||
public bool IsPublicallyVisible { get; set; } = true;
|
||||
|
||||
public ArtifactGrouping ToArtifactGrouping()
|
||||
{
|
||||
IdentifierFields identifierFields = new();
|
||||
identifierFields.Values = IdentifierFieldValues.Select(p => p.Value).ToList();
|
||||
|
||||
List<ArtifactEntry> entries = [];
|
||||
foreach (var entry in ArtifactEntries)
|
||||
{
|
||||
entries.Add(entry.ToArtifactEntry());
|
||||
}
|
||||
|
||||
var grouping = new ArtifactGrouping()
|
||||
{
|
||||
Id = Id ?? default,
|
||||
Title = Title,
|
||||
Description = Description,
|
||||
Category = Category,
|
||||
IdentifierFields = identifierFields,
|
||||
IsPublicallyVisible = true,
|
||||
ChildArtifactEntries = entries,
|
||||
Type = new ArtifactType() { Name = Type }
|
||||
};
|
||||
|
||||
// Create the parent link
|
||||
foreach (var entry in grouping.ChildArtifactEntries)
|
||||
{
|
||||
entry.ArtifactGrouping = grouping;
|
||||
}
|
||||
|
||||
return grouping;
|
||||
}
|
||||
|
||||
public static ArtifactGroupingValidationModel ToValidationModel(ArtifactGrouping grouping)
|
||||
{
|
||||
var entries = new List<ArtifactEntryValidationModel>();
|
||||
|
||||
foreach (var entry in grouping.ChildArtifactEntries)
|
||||
{
|
||||
var defects = new List<string>();
|
||||
|
||||
if (entry.Defects is not null)
|
||||
{
|
||||
defects.AddRange(entry.Defects.Select(defect => defect.Description));
|
||||
}
|
||||
|
||||
var validationModel = new ArtifactEntryValidationModel()
|
||||
{
|
||||
Id = entry.Id,
|
||||
Title = entry.Title,
|
||||
StorageLocation = entry.StorageLocation.Location,
|
||||
ArtifactNumber = entry.ArtifactNumber,
|
||||
AssociatedDates = entry.AssociatedDates,
|
||||
Defects = entry?.Defects?.Select(defect => defect.Description).ToList(),
|
||||
Description = entry?.Description,
|
||||
Files = entry?.Files,
|
||||
FileTextContent = entry?.FileTextContent,
|
||||
IsPublicallyVisible = entry.IsPubliclyVisible,
|
||||
Links = entry.Links,
|
||||
ListedNames = entry?.ListedNames?.Select(name => name.Value).ToList(),
|
||||
RelatedArtifacts = entry.RelatedTo,
|
||||
Tags = entry?.Tags?.Select(tag => tag.Name).ToList(),
|
||||
Type = entry?.Type.Name,
|
||||
Quantity = entry.Quantity
|
||||
};
|
||||
|
||||
entries.Add(validationModel);
|
||||
}
|
||||
|
||||
var identifierFieldsStrings = grouping.IdentifierFields.Values;
|
||||
List<IdentifierFieldValidationModel> identifierFields = new();
|
||||
for (int index = 0; index < identifierFieldsStrings.Count; ++index)
|
||||
{
|
||||
identifierFields.Add(new IdentifierFieldValidationModel()
|
||||
{
|
||||
Value = identifierFieldsStrings[index],
|
||||
Name = grouping.Category.FieldNames[index]
|
||||
});
|
||||
}
|
||||
return new ArtifactGroupingValidationModel()
|
||||
{
|
||||
Id = grouping.Id,
|
||||
Title = grouping.Title,
|
||||
ArtifactEntries = entries,
|
||||
Category = grouping.Category,
|
||||
Description = grouping.Description,
|
||||
IdentifierFieldValues = identifierFields,
|
||||
IsPublicallyVisible = grouping.IsPublicallyVisible,
|
||||
Type = grouping.Type.Name
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
foreach (var entry in ArtifactEntries)
|
||||
{
|
||||
var context = new ValidationContext(entry);
|
||||
var validationResult = new List<ValidationResult>();
|
||||
|
||||
bool valid = Validator.TryValidateObject(entry, context, validationResult);
|
||||
foreach (var result in validationResult)
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (ArtifactEntries.Count == 0)
|
||||
{
|
||||
yield return new ValidationResult("Must upload one or more files");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace OpenArchival.Blazor.AdminPages.Shared;
|
||||
|
||||
public class IdentifierFieldValidationModel
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
}
|
||||
Reference in New Issue
Block a user