@page "/search" @page "/search/{SearchTerms}" @using Microsoft.AspNetCore.Components.Web @using Microsoft.EntityFrameworkCore @using Microsoft.Extensions.Logging @using MudBlazor @using NpgsqlTypes @using OpenArchival.DataAccess @using Persic @using Npgsql.EntityFrameworkCore.PostgreSQL @using System.Linq @using System.Linq.Expressions @using OpenArchival.Blazor.ArtifactGroupingDisplay @using OpenArchival.Blazor.ArchiveSearch @namespace OpenArchival.Blazor.ArchiveDisplay @if (_totalResults > 0 && !_allArtifactsMode) { @_totalResults results found Clear } @if (_artifactGroupings.Count > 0) { @foreach (var grouping in _artifactGroupings) { } } @inject IDbContextFactory ContextFactory; @inject ILogger Logger; @inject NavigationManager NavigationManager; @code { [Parameter] public string SearchTerms { get; set; } = ""; private ArchiveSearchFilterType _selectedFilter = ArchiveSearchFilterType.All; // Field to store the current filter logic private Expression> _currentFilterPredicate; private List _artifactGroupings { get; set; } = []; private int _currentPage { get; set; } = 1; private int _totalPages { get; set; } = 0; private int _totalResults { get; set; } = 0; private const int PageSize = 20; private bool _allArtifactsMode = true; private string? _selectedLetter; protected override async Task OnParametersSetAsync() { if (string.IsNullOrWhiteSpace(SearchTerms)) { _allArtifactsMode = true; await LoadPageAsync(1); } else { _allArtifactsMode = false; await PerformSearchAsync(); } } private IQueryable BuildFilterQuery(IQueryable startingQuery) { // Filter if (_currentFilterPredicate != null && !_allArtifactsMode) { startingQuery = startingQuery.Where(_currentFilterPredicate); } if (!string.IsNullOrWhiteSpace(_selectedLetter)) { string likePattern = $"{_selectedLetter}%"; startingQuery = startingQuery.Where(artifact => EF.Functions.ILike(artifact.Title, likePattern)); } return startingQuery; } /// /// Called by the ArchiveSearchBar component's 'SearchTermsChanged' event. /// private async Task OnSearchSubmittedAsync(string searchTerms) { await OnClearResults(null, false, true); if (string.IsNullOrWhiteSpace(searchTerms)) { // Tell the clear operation not to navigate back to the search page and refresh _allArtifactsMode = true; await PerformSearchAsync(); return; } SearchTerms = searchTerms; NavigationManager.NavigateTo($"/search/{Uri.EscapeDataString(SearchTerms)}", replace: true); _allArtifactsMode = false; // This eventually calls LoadPageAsync await PerformSearchAsync(); } /// /// Sets the search predicate, calculates total results, and loads the first page. /// private async Task PerformSearchAsync() { // Determine which filter expression to use switch (_selectedFilter) { case ArchiveSearchFilterType.Tags: _currentFilterPredicate = x => x.TagsSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.Title: _currentFilterPredicate = x => x.TitleSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.Description: _currentFilterPredicate = x => x.DescriptionSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.Defects: _currentFilterPredicate = x => x.DefectsSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.Filenames: _currentFilterPredicate = x => x.FilenamesSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.ArtifactTranscriptions: _currentFilterPredicate = x => x.FileContentSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.ListedNames: _currentFilterPredicate = x => x.ListedNamesSearchVector.Matches(SearchTerms); break; case ArchiveSearchFilterType.All: default: _currentFilterPredicate = x => x.AllSearchVector.Matches(SearchTerms); break; } // Load the first page with the chosen filter await LoadPageAsync(1); } private async Task OnPageChangedAsync(int page) { await LoadPageAsync(page); } /// /// Fetches a specific page of data from the database using the currently set filter. /// private async Task LoadPageAsync(int page) { await using var context = await ContextFactory.CreateDbContextAsync(); IQueryable query = context.ArtifactGroupings; query = BuildFilterQuery(query); // Select child data we want query = query .Include(x => x.ChildArtifactEntries) .ThenInclude(x => x.Files); // If we are showing all artifacts, then order results alphabetically if (_allArtifactsMode) { query = query.OrderBy(artifact => artifact.Title); } else { query = query.OrderBy(artifact => artifact.Id); } _totalResults = await query.CountAsync(); _totalPages = (int)Math.Ceiling(_totalResults / (double)PageSize); _currentPage = page; query = query .Skip((_currentPage - 1) * PageSize) .Take(PageSize); _artifactGroupings = await query.ToListAsync(); StateHasChanged(); } private async Task OnClearResults(Microsoft.AspNetCore.Components.Web.MouseEventArgs args, bool navigate = true, bool resetFirstLetterFilter = false) { _totalResults = 0; _artifactGroupings.Clear(); _currentPage = 1; _totalPages = 0; SearchTerms = ""; if (navigate) { NavigationManager.NavigateTo("/search", replace: true); } if (resetFirstLetterFilter) { _selectedLetter = ""; } StateHasChanged(); } private async Task OnApplyFilters(MouseEventArgs args) { if (string.IsNullOrWhiteSpace(SearchTerms)) { _allArtifactsMode = true; } else { _allArtifactsMode = false; } await PerformSearchAsync(); } }