Extracted some pages to their own assembly and finished the artifact display page code
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src=@Assets["_content/MudBlazor/MudBlazor.min.js"]></script>
|
||||
<script src="_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="js/downloadHelper.js"></script>
|
||||
<script src="js/imageSizeGetter.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -5,9 +5,17 @@
|
||||
@* Loop through and display each item as a chip *@
|
||||
@foreach (var item in Items)
|
||||
{
|
||||
<MudChip Color="Color.Primary" OnClose="() => RemoveItem(item)" T="T">
|
||||
@DisplayFunc(item)
|
||||
</MudChip>
|
||||
@if (DeleteEnabled)
|
||||
{
|
||||
<MudChip Color="Color.Primary" OnClose="() => RemoveItem(item)" T="T">
|
||||
@DisplayFunc(item)
|
||||
</MudChip>
|
||||
} else
|
||||
{
|
||||
<MudChip Color="Color.Primary" T="T">
|
||||
@DisplayFunc(item)
|
||||
</MudChip>
|
||||
}
|
||||
}
|
||||
|
||||
@* Render the input control provided by the consumer *@
|
||||
@@ -22,6 +30,9 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public bool DeleteEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The list of items to display and manage.
|
||||
/// </summary>
|
||||
@@ -51,8 +62,6 @@
|
||||
[Parameter]
|
||||
public Func<T, string> DisplayFunc { get; set; } = item => item?.ToString() ?? string.Empty;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A public method that the consumer's input control can call to add a new item.
|
||||
/// </summary>
|
||||
|
||||
@@ -230,4 +230,19 @@
|
||||
|
||||
private void ClearDragClass()
|
||||
=> _dragClass = DefaultDragClass;
|
||||
|
||||
public void RemoveFile(string filename)
|
||||
{
|
||||
var existingFile = ExistingFiles.Where(f => f.OriginalName == filename).FirstOrDefault();
|
||||
if (existingFile is not null)
|
||||
{
|
||||
ExistingFiles.Remove(existingFile);
|
||||
}
|
||||
|
||||
var file = Files.Where(f => f.Name == filename).FirstOrDefault();
|
||||
if (file is not null)
|
||||
{
|
||||
Files.Remove(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
if (result is not null && !result.Canceled)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@using OpenArchival.Blazor.Components.Pages.Administration.Categories
|
||||
@using OpenArchival.DataAccess;
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using MudBlazor
|
||||
|
||||
@inject IDialogService DialogService
|
||||
@inject NavigationManager NavigationManager;
|
||||
@@ -193,6 +194,9 @@
|
||||
{
|
||||
// The data entry should only be shown if a category has been selected
|
||||
_isFormDivVisible = true;
|
||||
} else
|
||||
{
|
||||
_isFormDivVisible = false;
|
||||
}
|
||||
|
||||
if (Model is not null)
|
||||
@@ -223,11 +227,11 @@
|
||||
|
||||
if (IsValid && ClearOnPublish)
|
||||
{
|
||||
Model = new();
|
||||
Model = new();
|
||||
await _uploadComponent.ClearClicked.InvokeAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
await GroupingPublished.InvokeAsync(oldModel);
|
||||
}
|
||||
|
||||
@@ -343,10 +347,10 @@
|
||||
return categories;
|
||||
}
|
||||
|
||||
private async void OnDeleteEntryClicked(int index)
|
||||
private async void OnDeleteEntryClicked((int index, string filename) data)
|
||||
{
|
||||
Model.ArtifactEntries.RemoveAt(index);
|
||||
_uploadComponent.Files.RemoveAt(index);
|
||||
Model.ArtifactEntries.RemoveAt(data.index);
|
||||
_uploadComponent.RemoveFile(data.filename);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<MudDivider DividerType="DividerType.Middle"></MudDivider>
|
||||
<ChipContainer T="DateTime" @ref="_assocaitedDatesChipContainer" @bind-Items="Model.AssociatedDates" DisplayFunc="date => date.ToShortDateString()">
|
||||
<InputContent>
|
||||
<MudDatePicker @bind-Date=_associatedDateInputValue>
|
||||
<MudDatePicker @bind-Date=_associatedDateInputValue MinDate="new DateTime(1000, 1, 1)">
|
||||
</MudDatePicker>
|
||||
</InputContent>
|
||||
<SubmitButton>
|
||||
@@ -149,7 +149,7 @@
|
||||
For="@(() => Model.FileTextContent)"></MudTextField>
|
||||
|
||||
<MudText Typo=Typo.h6 Color="Color.Primary">Additional files</MudText>
|
||||
<UploadDropBox FilesUploaded="OnFilesUploaded"></UploadDropBox>
|
||||
<UploadDropBox @ref=_uploadDropBox FilesUploaded="OnFilesUploaded"></UploadDropBox>
|
||||
</MudPaper>
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
public required int ArtifactEntryIndex {get; set;}
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<int> OnEntryDeletedClicked { get; set; }
|
||||
public EventCallback<(int index, string filename)> OnEntryDeletedClicked { get; set; }
|
||||
|
||||
private ChipContainer<string> _tagsChipContainer;
|
||||
|
||||
@@ -198,6 +198,23 @@
|
||||
|
||||
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
|
||||
@@ -217,12 +234,17 @@
|
||||
{
|
||||
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()
|
||||
@@ -296,6 +318,6 @@
|
||||
}
|
||||
private async Task OnDeleteEntryClicked(MouseEventArgs args)
|
||||
{
|
||||
await OnEntryDeletedClicked.InvokeAsync(ArtifactEntryIndex);
|
||||
await OnEntryDeletedClicked.InvokeAsync((ArtifactEntryIndex, MainFilePath.OriginalName));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,11 @@ 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; }
|
||||
|
||||
@@ -33,14 +38,8 @@ public class ArtifactEntryValidationModel
|
||||
public string? FileTextContent { get; set; }
|
||||
|
||||
public List<ArtifactEntry> RelatedArtifacts { get; set; } = [];
|
||||
|
||||
/*
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext context)
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
public bool IsPublicallyVisible { get; set; }
|
||||
|
||||
public bool IsPublicallyVisible { get; set; } = true;
|
||||
|
||||
public ArtifactEntry ToArtifactEntry(ArtifactGrouping? parent = null)
|
||||
{
|
||||
@@ -59,10 +58,9 @@ public class ArtifactEntryValidationModel
|
||||
defects.Add(new ArtifactDefect() { Description=defect});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var entry = new ArtifactEntry()
|
||||
{
|
||||
Id = Id ?? 0,
|
||||
Files = Files,
|
||||
Type = new DataAccess.ArtifactType() { Name = Type },
|
||||
ArtifactNumber = ArtifactNumber,
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ArtifactGroupingValidationModel : IValidatableObject
|
||||
|
||||
public List<ArtifactEntryValidationModel> ArtifactEntries { get; set; } = new();
|
||||
|
||||
public bool IsPublicallyVisible { get; set; }
|
||||
public bool IsPublicallyVisible { get; set; } = true;
|
||||
|
||||
public ArtifactGrouping ToArtifactGrouping()
|
||||
{
|
||||
@@ -75,6 +75,7 @@ public class ArtifactGroupingValidationModel : IValidatableObject
|
||||
|
||||
var validationModel = new ArtifactEntryValidationModel()
|
||||
{
|
||||
Id = entry.Id,
|
||||
Title = entry.Title,
|
||||
StorageLocation = entry.StorageLocation.Location,
|
||||
ArtifactNumber = entry.ArtifactNumber,
|
||||
@@ -131,7 +132,7 @@ public class ArtifactGroupingValidationModel : IValidatableObject
|
||||
}
|
||||
}
|
||||
|
||||
if (ArtifactEntries.IsNullOrEmpty())
|
||||
if (ArtifactEntries is null || ArtifactEntries.Count == 0)
|
||||
{
|
||||
yield return new ValidationResult("Must upload one or more files");
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class CategoryValidationModel
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext context)
|
||||
{
|
||||
if (FieldNames.IsNullOrEmpty() || FieldDescriptions.IsNullOrEmpty())
|
||||
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",
|
||||
|
||||
@@ -1,5 +1,85 @@
|
||||
<h3>ArchiveEntryDisplay</h3>
|
||||
@using OpenArchival.Blazor.FileViewer
|
||||
|
||||
<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
|
||||
<MudGrid Spacing="2">
|
||||
<MudItem lg="8" xs="12" Class="d-flex align-center justify-center flex-column">
|
||||
<MudText Class="d-flex" Typo=Typo.h6>@ArtifactEntry.Title</MudText>
|
||||
<FileViewerCarousel FilePathListings="ArtifactEntry.Files" MaxHeight="350" ShowUnsupportedFiles=true></FileViewerCarousel>
|
||||
</MudItem>
|
||||
|
||||
<MudItem lg="4" xs="12">
|
||||
<MudText Typo="Typo.h6">Artifact Identifier</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.caption">@ArtifactEntry.ArtifactIdentifier</MudText>
|
||||
|
||||
<MudText Typo="Typo.h6">Primary Artifact Type</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.caption">@ArtifactEntry.Type.Name</MudText>
|
||||
|
||||
@if (!string.IsNullOrEmpty(ArtifactEntry.StorageLocation.Location))
|
||||
{
|
||||
<MudText Typo="Typo.h6">Storage Location</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.caption">@ArtifactEntry.StorageLocation.Location</MudText>
|
||||
}
|
||||
|
||||
@if (ArtifactEntry.Tags.Count > 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6">Tags</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<OpenArchival.Blazor.Components.CustomComponents.ChipContainer DeleteEnabled=false @bind-Items="ArtifactEntry.Tags"></OpenArchival.Blazor.Components.CustomComponents.ChipContainer>
|
||||
}
|
||||
|
||||
@if (ArtifactEntry.ListedNames.Count > 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6">Listed Names</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<OpenArchival.Blazor.Components.CustomComponents.ChipContainer DeleteEnabled=false @bind-Items="ArtifactEntry.ListedNames"></OpenArchival.Blazor.Components.CustomComponents.ChipContainer>
|
||||
}
|
||||
|
||||
@if (ArtifactEntry.AssociatedDates.Count > 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6">Associated Dates</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<OpenArchival.Blazor.Components.CustomComponents.ChipContainer DeleteEnabled=false @bind-Items="ArtifactEntry.AssociatedDates" DisplayFunc="@(date => date.ToString("d"))"></OpenArchival.Blazor.Components.CustomComponents.ChipContainer>
|
||||
}
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Class="mt-4" Typo="Typo.h6">Description</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.body1">@ArtifactEntry.Description</MudText>
|
||||
|
||||
<MudExpansionPanel Text="Downloads">
|
||||
<MudList T="string">
|
||||
@foreach (FilePathListing file in ArtifactEntry.Files)
|
||||
{
|
||||
<MudListItem Icon="@Icons.Material.Filled.Download" IconColor="Color.Primary" OnClick="() => OnFileDownloadClicked(file)">@file.OriginalName</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudExpansionPanel>
|
||||
</MudPaper>
|
||||
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject ISnackbar Snackbar
|
||||
@code {
|
||||
[Parameter]
|
||||
public required ArtifactEntry ArtifactEntry { get; set; }
|
||||
|
||||
private async Task OnFileDownloadClicked(FilePathListing file)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] fileBytes = await File.ReadAllBytesAsync(file.Path);
|
||||
|
||||
string mimeType = "";
|
||||
|
||||
await JSRuntime.InvokeVoidAsync("downloadFileFromBytes", file.OriginalName, mimeType, Convert.ToBase64String(fileBytes));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Failed to download file {file.OriginalName}", Severity.Error);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,55 @@
|
||||
@page "/archive/{GroupingIdString}"
|
||||
@using OpenArchival.DataAccess;
|
||||
@using OpenArchival.Blazor.FileViewer
|
||||
|
||||
@inject IArtifactGroupingProvider GroupingProvider;
|
||||
@inject NavigationManager NavigationManager;
|
||||
|
||||
@if (_artifactGrouping is not null)
|
||||
{
|
||||
<MudText Typo="Typo.h1">@_artifactGrouping.Title</MudText>
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudPaper Class="pa-4 ma-2 rounded d-flex justify-center" Elevation="3">
|
||||
<MudText Typo="Typo.h3">@_artifactGrouping.Title</MudText>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-4 ma-2 rounded d-flex justify-center" Elevation="3">
|
||||
@*<MudImage Class="d-flex justify-center" Src="https://dummyimage.com/600x400/000/fff"></MudImage>*@
|
||||
<FileViewerCarousel FilePathListings="_artifactGrouping.ChildFilePathListings" MaxHeight="500" ShowUnsupportedFiles=false></FileViewerCarousel>
|
||||
</MudPaper>
|
||||
|
||||
<MudGrid Spacing="2">
|
||||
<MudItem xs="8">
|
||||
<MudPaper Style="height: 100%;" Class="pa-4 ma-2 rounded" Elevation="3">
|
||||
<MudText Typo="Typo.h6">Description</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.body2">@_artifactGrouping.Description</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudPaper Style="height: 100%;" Class="pa-4 ma-2 rounded" Elevation="3">
|
||||
<MudText Typo="Typo.h6">Artifact Identifier</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.caption">@_artifactGrouping.ArtifactGroupingIdentifier</MudText>
|
||||
|
||||
<MudText Typo="Typo.h6">Primary Artifact Category</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.caption">@_artifactGrouping.Category.Name</MudText>
|
||||
|
||||
<MudText Typo="Typo.h6">Primary Artifact Type</MudText>
|
||||
<MudDivider></MudDivider>
|
||||
<MudText Typo="Typo.caption">@_artifactGrouping.Type.Name</MudText>
|
||||
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudPaper Class="pa-4 mt-8 ma-2 rounded" Elevation="3">
|
||||
@foreach (ArtifactEntry child in _artifactGrouping.ChildArtifactEntries) {
|
||||
<ArchiveEntryDisplay ArtifactEntry="child"></ArchiveEntryDisplay>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<h3>FileDisplayBase</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@if (File is not null)
|
||||
{
|
||||
<MudImage Fluid="true" Src="@File.Path" Alt="Swedish Farm House" Class="rounded-lg"/>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required FilePathListing File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the width of the image box while it loads
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Width { get; set; } = 600;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the height of the image box while it loads
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public int Height { get; set; } = 400;
|
||||
|
||||
[Parameter]
|
||||
public string AltText { get; set; } = "";
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
if (File is not null || !string.IsNullOrEmpty(AltText))
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
return base.OnParametersSetAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using OpenArchival.Blazor.Components.Pages.ArchiveDisplay;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace OpenArchival.Blazor;
|
||||
|
||||
public class FileDisplayComponentFactory
|
||||
{
|
||||
private Dictionary<string, Type> _extensionToComponents { get; set; } = [];
|
||||
|
||||
public FileDisplayComponentFactory()
|
||||
{
|
||||
// Supported image file types taken from https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types
|
||||
RegisterComponent<FileDisplayImage>(".apng");
|
||||
RegisterComponent<FileDisplayImage>(".png");
|
||||
RegisterComponent<FileDisplayImage>(".avif");
|
||||
RegisterComponent<FileDisplayImage>(".gif");
|
||||
RegisterComponent<FileDisplayImage>(".jpg");
|
||||
RegisterComponent<FileDisplayImage>(".jpeg");
|
||||
RegisterComponent<FileDisplayImage>(".jfif");
|
||||
RegisterComponent<FileDisplayImage>(".pjpeg");
|
||||
RegisterComponent<FileDisplayImage>(".pjg");
|
||||
RegisterComponent<FileDisplayImage>(".png");
|
||||
RegisterComponent<FileDisplayImage>(".svg");
|
||||
RegisterComponent<FileDisplayImage>(".webp");
|
||||
}
|
||||
|
||||
private string GetExtensionKey(string pathOrExtension)
|
||||
{
|
||||
return Path.GetExtension(pathOrExtension).ToUpperInvariant();
|
||||
}
|
||||
|
||||
public bool RegisterComponent<ComponentType>(string filenameOrExtension) where ComponentType : IFileDisplayComponent
|
||||
{
|
||||
string extensionString = GetExtensionKey(filenameOrExtension);
|
||||
|
||||
_extensionToComponents.Add(extensionString, typeof(ComponentType));
|
||||
return true;
|
||||
}
|
||||
|
||||
public Type? GetDisplayComponentType(string filenameOrExtension) {
|
||||
var result = _extensionToComponents.TryGetValue(GetExtensionKey(filenameOrExtension), out var component);
|
||||
|
||||
return (result) ? component : null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<h3>FileDisplayImage</h3>
|
||||
@implements IFileDisplayComponent
|
||||
<h3>FileDisplayImage</h3>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public required FilePathListing FilePath { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace OpenArchival.Blazor;
|
||||
using OpenArchival.DataAccess;
|
||||
|
||||
namespace OpenArchival.Blazor;
|
||||
|
||||
public interface IFileDisplayComponent
|
||||
{
|
||||
public FilePathListing FilePath { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@using OpenArchival.Blazor.Components.Account.Shared
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
|
||||
<Router AppAssembly="typeof(Program).Assembly"
|
||||
AdditionalAssemblies="new[] {typeof(OpenArchival.Blazor.AdminPages.ArchiveConfiguration).Assembly}">
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
||||
<NotAuthorized>
|
||||
|
||||
Reference in New Issue
Block a user