Added tag filter state persistence.
All checks were successful
ci / build-test (push) Successful in 2m26s
ci / publish-image (push) Successful in 1m32s

This commit is contained in:
2026-04-26 23:39:47 -04:00
parent 2355d7fe65
commit 77a02a543d
3 changed files with 197 additions and 101 deletions

View File

@@ -13,14 +13,18 @@
@using JSMR.UI.Blazor.Filters
@using JSMR.UI.Blazor.Models
@using JSMR.UI.Blazor.Services
@using JSMR.UI.Blazor.Shared
@using Microsoft.AspNetCore.WebUtilities
@using System.Text.Json
@inherits SearchPageBase<TagFilterState, TagSearchItem>
<PageTitle>Tags</PageTitle>
<div class="fdsfds">
<AntDesign.Card Title=@("Tags") Class="ant-blurred-card">
<Extra>
<AntDesign.Input TValue="string" Value="Keywords" ValueChanged="OnKeywordsChanged" DebounceMilliseconds="500" Placeholder="Filter">
<AntDesign.Input TValue="string" Value="State.Keywords" ValueChanged="OnKeywordsChanged" DebounceMilliseconds="500" Placeholder="Filter">
<Prefix>
<AntDesign.Icon Type="@AntDesign.IconType.Outline.Search" />
</Prefix>
@@ -28,10 +32,10 @@
</Extra>
<Body>
<AntDesign.Table Responsive
DataSource="@(searchResults?.Items ?? Enumerable.Empty<TagSearchItem>())"
Total="@(searchResults?.TotalItems ?? 0)"
DataSource="@(Result?.Items ?? Enumerable.Empty<TagSearchItem>())"
Total="@(Result?.TotalItems ?? 0)"
TItem="TagSearchItem"
Loading="LoadingData"
Loading="IsLoading"
HidePagination="@true"
RemoteDataSource="@true"
RowKey="x=>x.TagId"
@@ -82,7 +86,11 @@
</AntDesign.ActionColumn>
</ColumnDefinitions>
</AntDesign.Table>
<JPagination2 PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" TotalItems="TotalItems" />
<JPagination2 PageNumber="State.PageNumber"
PageNumberChanged="@(pageNumber => UpdateAsync(State with { PageNumber = pageNumber }))"
PageSize="State.PageSize"
PageSizeChanged="@(pageSize => UpdateAsync(State with { PageSize = pageSize, PageNumber = 1 }))"
TotalItems="@(Result?.TotalItems ?? 0)" />
</Body>
</AntDesign.Card>
</div>
@@ -157,98 +165,30 @@
[Inject]
ModalService ModalService { get; set; } = default!;
public string? Keywords { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 100;
public int TotalItems => searchResults?.TotalItems ?? 0;
Func<PaginationTotalContext, string> ShowTotal = ctx => $"{ctx.Range.from} - {ctx.Range.to} of {ctx.Total} items";
public bool LoadingData { get; set; }
SearchResult<TagSearchItem>? searchResults;
protected override async Task OnInitializedAsync()
{
//await UpdateDataAsync(true);
}
public async Task OnKeywordsChanged(string? newKeywords)
{
Keywords = newKeywords;
await UpdateDataAsync(true);
await UpdateAsync(State with
{
Keywords = newKeywords,
PageNumber = 1
});
}
public async Task OnPaginationChange(PaginationEventArgs args)
{
bool resetPageNumber = PageSize != args.PageSize;
PageNumber = args.Page;
PageSize = args.PageSize;
await UpdateDataAsync(resetPageNumber);
}
public async Task OnPageNumberChanged(int newPageNumber)
{
PageNumber = newPageNumber;
await UpdateDataAsync(false);
}
public async Task OnPageSizeChanged(int newPageSize)
{
PageSize = newPageSize;
await UpdateDataAsync(true);
}
private async Task UpdateDataAsync(bool resetPageNumber)
{
if (resetPageNumber)
PageNumber = 1;
await LoadTagsAsync();
}
private async Task LoadTagsAsync()
{
LoadingData = true;
SearchTagsRequest request = new(
Options: new()
{
PageNumber = PageNumber,
PageSize = PageSize,
Criteria = new()
{
Name = Keywords
},
SortOptions = [.. _sortOptions]
}
);
await JS.InvokeVoidAsync("pageHelpers.scrollToTop");
var result = await Client.SearchAsync(request);
searchResults = result?.Results ?? new();
LoadingData = false;
//await InvokeAsync(StateHasChanged);
}
private List<SortOption<TagSortField>> _sortOptions =
[
new(TagSortField.Name, Application.Common.Search.SortDirection.Ascending)
];
private async Task HandleTableChange(AntDesign.TableModels.QueryModel<TagSearchItem> queryModel)
{
// PageNumber = queryModel.PageIndex;
// PageSize = queryModel.PageSize;
if (IsLoading)
return;
_sortOptions = MapSortOptions(queryModel);
var nextSortOptions = MapSortOptions(queryModel);
await LoadTagsAsync();
if (SortOptionsEqual(nextSortOptions, State.SortOptions))
return;
await UpdateAsync(State with
{
SortOptions = nextSortOptions,
PageNumber = 1
});
}
private List<SortOption<TagSortField>> MapSortOptions(AntDesign.TableModels.QueryModel<TagSearchItem> queryModel)
@@ -329,16 +269,15 @@
item.Blacklisted = response.TagStatus is TagStatus.Blacklisted;
}
await InvokeAsync(StateHasChanged);
//await InvokeAsync(StateHasChanged);
var config = new NotificationConfig()
MessageConfig messageConfig = new()
{
Message = $"Tag Status Update",
Description = $"Tag '{item.Name}' set to {status.ToString()}.",
Placement = NotificationPlacement.Top
Content = $"Tag '{item.Name}' set to {status.ToString()}.",
Type = MessageType.Success
};
await NotificationService.Open(config);
_ = MessageService.OpenAsync(messageConfig);
}
private async Task OpenSetEnglishNameModal(TagSearchItem item)
@@ -381,11 +320,59 @@
await InvokeAsync(StateHasChanged);
await NotificationService.Open(new NotificationConfig
// await NotificationService.Open(new NotificationConfig
// {
// Message = "Tag English Name Updated",
// Description = $"Tag '{item.Name}' English name set to '{response.EnglishName}'.",
// Placement = NotificationPlacement.Top
// });
MessageConfig messageConfig = new()
{
Message = "Tag English Name Updated",
Description = $"Tag '{item.Name}' English name set to '{response.EnglishName}'.",
Placement = NotificationPlacement.Top
});
Content = $"Tag '{item.Name}' English name set to '{response.EnglishName}'.",
Type = MessageType.Success
};
_ = MessageService.OpenAsync(messageConfig);
}
protected override TagFilterState ParseStateFromUri(string absoluteUri)
=> TagFilterState.FromQuery(new Uri(absoluteUri).Query);
protected override string BuildUri(TagFilterState state)
{
var basePath = new Uri(Nav.Uri).GetLeftPart(UriPartial.Path);
return QueryHelpers.AddQueryString(basePath, state.ToQuery());
}
protected override bool IsThisPage(string absoluteUri)
=> Nav.ToBaseRelativePath(absoluteUri).StartsWith("tags", StringComparison.OrdinalIgnoreCase);
protected override async Task<SearchResult<TagSearchItem>> ExecuteSearchAsync(TagFilterState state, CancellationToken ct)
{
var response = await Client.SearchAsync(state.ToSearchRequest(), ct);
return response?.Results ?? new SearchResult<TagSearchItem>();
}
private AntDesign.SortDirection? GetAntSortDirection(TagSortField field)
{
var sort = State.SortOptions.FirstOrDefault(x => x.Field == field);
return sort?.Direction switch
{
Application.Common.Search.SortDirection.Ascending => AntDesign.SortDirection.Ascending,
Application.Common.Search.SortDirection.Descending => AntDesign.SortDirection.Descending,
_ => null
};
}
private static bool SortOptionsEqual(
IReadOnlyList<SortOption<TagSortField>> left,
IReadOnlyList<SortOption<TagSortField>> right)
{
return left.Count == right.Count
&& left.Zip(right).All(x =>
x.First.Field == x.Second.Field &&
x.First.Direction == x.Second.Direction);
}
}