This commit is contained in:
2026-05-17 20:54:09 -04:00
parent 6da2183583
commit 74c21ee5cc
3000 changed files with 11794 additions and 15301 deletions

View File

@@ -1,7 +1,11 @@
@page "/admin/homepageeditor"
@page "/admin/homepageeditor"
@layout AdminControlPanelLayout
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.EntityFrameworkCore
@using Microsoft.Extensions.Caching.Memory
@using Microsoft.Extensions.Options
@using Microsoft.AspNetCore.Authorization
@@ -11,55 +15,296 @@
@using OpenArchival.Blazor.AdminPages.Shared
@using OpenArchival.Blazor.Config
@using OpenArchival.DataAccess
@using OpenArchival.DataAccess.FileAccessManager
@using OpenArchival.Blazor.CustomComponents
@attribute [Authorize(Roles = UserRoles.Admin)]
<MudText Typo="Typo.h6">Home Page Editor</MudText>
<MudDivider></MudDivider>
<MudText Typo="Typo.body2">This is the content that will be displayed on the site's home page</MudText>
<MudExRichTextEdit @ref="@_editor"
ReadOnly="false"
Height="444"
Class="m-2 mt-4"
Placeholder="Edit html"
ValueHtmlBehavior="MudExRichTextEditor.Types.GetHtmlBehavior.SemanticHtml"
@bind-Value="_editorContent"
>
@((MarkupString)_editorContent)
</MudExRichTextEdit>
@if (!_homePageConfigMissing)
{
<MudFileUpload T="IBrowserFile"
@ref="_bannerFileUpload"
AppendMultipleFiles=false
Accept=".jpg,.png,.webp"
MaximumFileCount="1"
Files=_bannerPhotoFile
OnFilesChanged="OnFilesChanged"
Class="mt-4">
<ActivatorContent>
<MudButton Variant="Variant.Filled"
Color="Color.Primary">
Upload Main Photo
</MudButton>
</ActivatorContent>
<SelectedTemplate>
@if (context != null)
{
<MudText>@context.Name</MudText>
}
else if (_homePageConfiguration.HomePageBanner != null)
{
<MudText>@_homePageConfiguration.HomePageBanner.OriginalName</MudText>
}
else
{
<MudText>No Main Photo Selected</MudText>
}
</SelectedTemplate>
</MudFileUpload>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnSaveHomePage" Class="mt-4">Save</MudButton>
<MudText Typo="Typo.body2">This is the content that will be displayed on the site's home page underneath the banner image</MudText>
<MudExRichTextEdit @ref="@_editor"
ReadOnly="false"
Height="444"
Class="m-2 mt-4"
Placeholder="Edit html"
ValueHtmlBehavior="MudExRichTextEditor.Types.GetHtmlBehavior.SemanticHtml"
Immediate="true"
@bind-Value="_homePageConfiguration.Content"
>
</MudExRichTextEdit>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnSaveHomePage" Class="mt-4">Save</MudButton>
<MudText Typo="Typo.h6" Class="mt-8">Home Page Featured Sliders</MudText>
<MudDivider></MudDivider>
<MudText Typo="Typo.subtitle1">Controls what artifacts are shown on the home page. This is controlled via selecting tags. The first artifact with the listed tags will be shown in the slider. Only tags belonging to valid archive entries can be used. Entries must have all of the listed tags to be included in the slider.</MudText>
<MudTextField T="string" Placeholder="Slider Title" @bind-Text="_titleInputValue"></MudTextField>
<MudTextField T="string" Placeholder="Slider Description" @bind-Text="_descriptionInputValue"></MudTextField>
<MudGrid Justify="Justify.Center" Class="mt-4">
<MudItem>
<ChipContainer T="string" @ref="@_tagsChipContainer" @bind-Items="_selectedTagStrings" ItemRemoved="OnTagRemoved">
<InputContent>
<MudAutocomplete T="string"
SearchFunc="Helpers.SearchTags"
OnKeyDown="@(ev => HandleChipContainerEnter<string>(ev, _tagsChipContainer, _tagsInputValue, () => _tagsInputValue = string.Empty))"
CoerceValue=true
Placeholder="Add Tags..."
ShowProgressIndicator="true"
@bind-Value=@_tagsInputValue
CoerceText=true>
</MudAutocomplete>
</InputContent>
</ChipContainer>
</MudItem>
<MudItem>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="OnAddSlider">Add Slider</MudButton>
</MudItem>
</MudGrid>
@if (_homePageConfiguration.SliderEntries != null)
{
@foreach (SearchPageSliderEntry entry in _homePageConfiguration.SliderEntries)
{
<MudPaper Class="pa-4 ma-2 rounded" Elevation="3">
<MudButton StartIcon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="() => OnDeleteSliderClicked(entry)">Delete</MudButton>
<MudText Typo="Typo.h6">Title: @entry.Title</MudText>
<MudText Typo="Typo.h6">Description: @entry.Description</MudText>
<MudText Typo="Typo.h6">Tags:</MudText>
<ChipContainer T="ArtifactEntryTag" Items="entry.FilterTags" DeleteEnabled=false></ChipContainer>
</MudPaper>
}
}
} else
{
<MudText Typo="Typo.h4" Color="Color.Error">Failed to query home page configuration from database, was your database modified outside the application?</MudText>
}
@inject ArtifactEntrySharedHelpers Helpers;
@inject ISnackbar Snackbar;
@inject IOptions<ApplicationOptions> AppOptions;
@inject IMemoryCache MemoryCache;
@inject IDbContextFactory<ApplicationDbContext> ContextFactory;
@inject IFileAccessManager FileAccessManager;
@code {
private MudFileUpload<IBrowserFile> _bannerFileUpload = default!;
private MudExRichTextEdit _editor = default!;
private bool _hasLoaded = false;
private string _editorContent = "";
protected override void OnInitialized()
private IBrowserFile? _bannerPhotoFile { get; set; }
// Flipped if the browser file has changed for the file picker, this lets us know to delete the old file before creating a new one
private bool _hasFileChanged { get; set; }
private HomePageConfiguration _homePageConfiguration { get; set; }
private bool _homePageConfigMissing { get; set; } = true;
// Slider management state
private string _tagsInputValue { get; set; } = "";
private List<string> _selectedTagStrings { get; set; } = [];
private List<ArtifactEntryTag> _selectedTags { get; set; } = [];
private string _titleInputValue { get; set; } = "";
private string _descriptionInputValue { get; set; } = "";
private ChipContainer<string> _tagsChipContainer { get; set; } = default!;
protected override async Task OnInitializedAsync()
{
string html = "";
await using var context = await ContextFactory.CreateDbContextAsync();
var tempConfig = await context.HomePageConfiguration
.Include(config => config.HomePageBanner)
.Include(config => config.SliderEntries)
.ThenInclude(se => se.FilterTags)
.FirstOrDefaultAsync();
if (File.Exists(AppOptions.Value.HomepageContentLocation)) {
using var reader = new StreamReader(AppOptions.Value.HomepageContentLocation);
html = reader.ReadToEnd();
} else
if (tempConfig == null)
{
Snackbar.Add("Database invalid, no HomePageConfiguration", Severity.Error);
_homePageConfigMissing = true;
}
else
{
_homePageConfigMissing = false;
_homePageConfiguration = tempConfig;
if (_homePageConfiguration.SliderEntries == null)
{
Snackbar.Add("Homepage file not found, will create one on save!", Severity.Warning);
_homePageConfiguration.SliderEntries = new List<SearchPageSliderEntry>();
}
_editorContent = html;
}
}
private async Task OnSaveHomePage(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
{
using var writer = new StreamWriter(AppOptions.Value.HomepageContentLocation);
await using var context = await ContextFactory.CreateDbContextAsync();
if (_bannerPhotoFile != null)
{
// In this case we know to delete the current FilePathListing and make a new one
// as the file has changed
if (_hasFileChanged)
{
FilePathListing? bannerListing = _homePageConfiguration.HomePageBanner;
if (bannerListing != null)
{
await FileAccessManager.DeleteFileAsync(bannerListing.Id);
}
await writer.WriteLineAsync(_editorContent);
FilePathListing? newBannerFileListing = null;
newBannerFileListing = await FileAccessManager.UploadFileAsync(_bannerPhotoFile);
newBannerFileListing.HomePageConfiguration = _homePageConfiguration;
_homePageConfiguration.HomePageBanner = newBannerFileListing;
}
}
Snackbar.Add($"Wrote homepage to {AppOptions.Value.HomepageContentLocation}!", Severity.Success);
// Page content is already bound by the text editor so just save
context.Update(_homePageConfiguration);
await context.SaveChangesAsync();
// Clear the memory cache of the home page values
MemoryCache.Remove(OpenArchivalConstants.HomePageConfigurationCacheKey);
Snackbar.Add("Saved changes to home page configuration!", Severity.Success);
}
private async Task OnFilesChanged(InputFileChangeEventArgs args)
{
_hasFileChanged = true;
_bannerPhotoFile = args.File;
}
public async Task HandleChipContainerEnter<Type>(KeyboardEventArgs args, ChipContainer<Type> container, Type value, Action resetInputAction)
{
if (args.Key == "Enter")
{
await using var context = await ContextFactory.CreateDbContextAsync();
ArtifactEntryTag? tagToAdd = await context.ArtifactEntryTags.Where(tag => tag.Name == _tagsInputValue).FirstOrDefaultAsync();
if (tagToAdd is null)
{
tagToAdd = new ArtifactEntryTag() { Name = _tagsInputValue };
}
_selectedTags.Add(tagToAdd);
await container.AddItem(value);
resetInputAction?.Invoke();
StateHasChanged();
}
}
private async Task OnAddSlider(MouseEventArgs args)
{
if (string.IsNullOrEmpty(_titleInputValue))
{
Snackbar.Add("A slider title is required.", Severity.Error);
return;
}
if (!_selectedTags.Any())
{
Snackbar.Add("At least one tag is required", Severity.Error);
return;
}
await using var context = await ContextFactory.CreateDbContextAsync();
// Attach all of the existing tags to the context
foreach (var tag in _selectedTags.Where(t => t.Id != 0))
{
context.ArtifactEntryTags.Attach(tag);
}
var newEntry = new SearchPageSliderEntry()
{
Title = _titleInputValue,
Description = _descriptionInputValue,
FilterTags = _selectedTags,
IsHomePageSlider = true,
HomePageConfiguration = _homePageConfiguration
};
_homePageConfiguration.SliderEntries!.Add(newEntry);
context.Update(_homePageConfiguration);
await context.SaveChangesAsync();
Snackbar.Add("Added slider", Severity.Success);
_selectedTags = new List<ArtifactEntryTag>();
_selectedTagStrings.Clear();
_titleInputValue = "";
_descriptionInputValue = "";
// Clear the memory cache of the home page values
MemoryCache.Remove(OpenArchivalConstants.HomePageConfigurationCacheKey);
StateHasChanged();
}
private void OnTagRemoved(string tagString)
{
int countRemoved = _selectedTags.RemoveAll(t => t.Name == tagString);
if (countRemoved != 1)
{
throw new ArgumentException("Did not remove 1 element from the list of tags");
}
}
private async Task OnDeleteSliderClicked(SearchPageSliderEntry entry)
{
await using var context = await ContextFactory.CreateDbContextAsync();
SearchPageSliderEntry? entryToRemove = await context.SearchPageSliderEntries.Where(en => en.Id == entry.Id).FirstOrDefaultAsync();
if (entryToRemove is null)
{
throw new ArgumentException("The entry to remove was null and not in the database");
}
_homePageConfiguration.SliderEntries!.Remove(entry);
context.SearchPageSliderEntries.Remove(entryToRemove);
await context.SaveChangesAsync();
// Clear the memory cache of the home page values
MemoryCache.Remove(OpenArchivalConstants.HomePageConfigurationCacheKey);
StateHasChanged();
}
}