Fix filter crash on files and add local storage for column sorts and filter nav

This commit is contained in:
ahjephson
2024-05-13 12:01:07 +01:00
parent 10eeeb9060
commit baa80220e4
10 changed files with 161 additions and 76 deletions

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System.Collections.ObjectModel;
using System.Runtime.ExceptionServices;
namespace Lantean.QBTMudBlade.Components
{
@@ -13,10 +14,18 @@ namespace Lantean.QBTMudBlade.Components
[Parameter]
public EventCallback OnClear { get; set; }
[Parameter]
public bool Disabled { get; set; }
protected override Task OnErrorAsync(Exception exception)
{
_exceptions.Add(exception);
if (Disabled)
{
ExceptionDispatchInfo.Capture(exception).Throw();
}
return Task.CompletedTask;
}

View File

@@ -26,7 +26,8 @@
OnRowClick="RowClick"
RowStyleFunc="RowStyle"
RowClassFunc="RowClass"
AllowUnsorted="false">
AllowUnsorted="false"
Virtualize="true">
<ColGroup>
<col style="width: 30px" />
@foreach (var column in GetColumns())
@@ -41,7 +42,7 @@
<MudTh>
@if (column.SortSelector is not null)
{
<MudTableSortLabel T="Torrent" SortDirectionChanged="@(c => SetSort(column.SortSelector, c))" InitialDirection="column.InitialDirection">@column.Header</MudTableSortLabel>
<MudTableSortLabel T="ContentItem" SortDirectionChanged="@(c => SetSort(column.Id, c))" InitialDirection="column.InitialDirection">@column.Header</MudTableSortLabel>
}
else
{

View File

@@ -16,14 +16,15 @@ namespace Lantean.QBTMudBlade.Components
{
private readonly bool _refreshEnabled = true;
private const string _columnStorageKey = "FilesTab.Columns";
private const string _columnSelectionStorageKey = "FilesTab.ColumnSelection";
private const string _columnSortStorageKey = "FilesTab.ColumnSort";
private readonly CancellationTokenSource _timerCancellationToken = new();
private bool _disposedValue;
private Func<ContentItem, object?> SortSelector { get; set; } = c => c.Name;
private string? _sortColumn;
private SortDirection SortDirection { get; set; } = SortDirection.Ascending;
private SortDirection _sortDirection = SortDirection.Ascending;
[Parameter]
public bool Active { get; set; }
@@ -55,6 +56,7 @@ namespace Lantean.QBTMudBlade.Components
protected HashSet<ContentItem> SelectedItems { get; set; } = [];
protected List<ColumnDefinition<ContentItem>> _columns = [];
private List<PropertyFilterDefinition<ContentItem>>? _filterDefinitions;
protected ContentItem? SelectedItem { get; set; }
@@ -80,20 +82,20 @@ namespace Lantean.QBTMudBlade.Components
protected override async Task OnInitializedAsync()
{
if (!await LocalStorage.ContainKeyAsync(_columnStorageKey))
var selectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnSelectionStorageKey);
if (selectedColumns is not null)
{
return;
}
var selectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnStorageKey);
if (selectedColumns is null)
{
return;
}
SelectedColumns = selectedColumns;
}
var columnSort = await LocalStorage.GetItemAsync<Tuple<string, SortDirection>>(_columnSortStorageKey);
if (columnSort is not null)
{
_sortColumn = columnSort.Item1;
_sortDirection = columnSort.Item2;
}
}
protected IEnumerable<ColumnDefinition<ContentItem>> GetColumns()
{
return _columns.Where(c => SelectedColumns.Contains(c.Id));
@@ -116,14 +118,14 @@ namespace Lantean.QBTMudBlade.Components
SelectedColumns = (HashSet<string>)result.Data;
await LocalStorage.SetItemAsync(_columnStorageKey, SelectedColumns);
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
}
protected async Task ShowFilterDialog()
{
var parameters = new DialogParameters
{
{ nameof(FilterOptionsDialog<ContentItem>.FilterDefinitions), Filters },
{ nameof(FilterOptionsDialog<ContentItem>.FilterDefinitions), _filterDefinitions },
};
var result = await DialogService.ShowAsync<FilterOptionsDialog<ContentItem>>("Filters", parameters, DialogHelper.FormDialogOptions);
@@ -134,14 +136,15 @@ namespace Lantean.QBTMudBlade.Components
return;
}
var filterDefinitions = (List<PropertyFilterDefinition<ContentItem>>?)dialogResult.Data;
if (filterDefinitions is null)
_filterDefinitions = (List<PropertyFilterDefinition<ContentItem>>?)dialogResult.Data;
if (_filterDefinitions is null)
{
Filters = null;
return;
}
var filters = new List<Func<ContentItem, bool>>();
foreach (var filterDefinition in filterDefinitions)
foreach (var filterDefinition in _filterDefinitions)
{
var expression = Filter.FilterExpressionGenerator.GenerateExpression(filterDefinition, false);
filters.Add(expression.Compile());
@@ -154,6 +157,11 @@ namespace Lantean.QBTMudBlade.Components
{
Filters = null;
await InvokeAsync(StateHasChanged);
if (FileList is null)
{
return;
}
SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
}
public async ValueTask DisposeAsync()
@@ -378,10 +386,12 @@ namespace Lantean.QBTMudBlade.Components
}
}
private void SetSort(Func<ContentItem, object?> sortSelector, SortDirection sortDirection)
private async Task SetSort(string columnId, SortDirection sortDirection)
{
SortSelector = sortSelector;
SortDirection = sortDirection;
_sortColumn = columnId;
_sortDirection = sortDirection;
await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
}
protected void ToggleNode(ContentItem contentItem, MouseEventArgs args)
@@ -411,11 +421,19 @@ namespace Lantean.QBTMudBlade.Components
return Files.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
}
private Func<ContentItem, object?> GetSortSelector()
{
var sortSelector = _columns.Find(c => c.Id == _sortColumn)?.SortSelector;
return sortSelector ?? (i => i.Name);
}
private IEnumerable<ContentItem> GetDescendants(ContentItem folder, int level)
{
level++;
var descendantsKey = folder.GetDescendantsKey(level);
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey)).OrderByDirection(SortDirection, SortSelector))
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey)).OrderByDirection(_sortDirection, GetSortSelector()))
{
if (item.IsFolder)
{
@@ -474,12 +492,12 @@ namespace Lantean.QBTMudBlade.Components
// this is a flat file structure
if (maxLevel == 0)
{
return FileList.Values.Where(FilterContentItem).OrderByDirection(SortDirection, SortSelector).ToList().AsReadOnly();
return FileList.Values.Where(FilterContentItem).OrderByDirection(_sortDirection, GetSortSelector()).ToList().AsReadOnly();
}
var list = new List<ContentItem>();
var folders = FileList.Values.Where(c => c.IsFolder && c.Level == 0).OrderByDirection(SortDirection, SortSelector).ToList();
var folders = FileList.Values.Where(c => c.IsFolder && c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
foreach (var folder in folders)
{
list.Add(folder);

View File

@@ -1,4 +1,5 @@
using Lantean.QBTMudBlade.Models;
using Blazored.LocalStorage;
using Lantean.QBTMudBlade.Models;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -6,6 +7,11 @@ namespace Lantean.QBTMudBlade.Components
{
public partial class FiltersNav
{
private const string _statusSelectionStorageKey = "FiltersNav.Selection.Status";
private const string _categorySelectionStorageKey = "FiltersNav.Selection.Category";
private const string _tagSelectionStorageKey = "FiltersNav.Selection.Tag";
private const string _trackerSelectionStorageKey = "FiltersNav.Selection.Tracker";
private bool _statusExpanded = true;
private bool _categoriesExpanded = true;
private bool _tagsExpanded = true;
@@ -19,6 +25,9 @@ namespace Lantean.QBTMudBlade.Components
protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
[Inject]
public ILocalStorageService LocalStorage { get; set; } = default!;
[CascadingParameter]
public MainData? MainData { get; set; }
@@ -42,28 +51,55 @@ namespace Lantean.QBTMudBlade.Components
public Dictionary<string, int> Statuses => MainData?.StatusState.ToDictionary(d => d.Key, d => d.Value.Count) ?? [];
protected override async Task OnInitializedAsync()
{
if (await LocalStorage.ContainKeyAsync(_statusSelectionStorageKey))
{
Status = await LocalStorage.GetItemAsStringAsync(_statusSelectionStorageKey) ?? Models.Status.All.ToString();
}
if (await LocalStorage.ContainKeyAsync(_categorySelectionStorageKey))
{
Category = await LocalStorage.GetItemAsStringAsync(_categorySelectionStorageKey) ?? FilterHelper.CATEGORY_ALL;
}
if (await LocalStorage.ContainKeyAsync(_tagSelectionStorageKey))
{
Tag = await LocalStorage.GetItemAsStringAsync(_tagSelectionStorageKey) ?? FilterHelper.TAG_ALL;
}
if (await LocalStorage.ContainKeyAsync(_trackerSelectionStorageKey))
{
Tracker = await LocalStorage.GetItemAsStringAsync(_trackerSelectionStorageKey) ?? FilterHelper.TRACKER_ALL;
}
}
protected async Task StatusValueChanged(string value)
{
Status = value;
await StatusChanged.InvokeAsync(Enum.Parse<Status>(value));
await LocalStorage.SetItemAsStringAsync(_statusSelectionStorageKey, value);
}
protected async Task CategoryValueChanged(string value)
{
Category = value;
await CategoryChanged.InvokeAsync(value);
await LocalStorage.SetItemAsStringAsync(_categorySelectionStorageKey, value);
}
protected async Task TagValueChanged(string value)
{
Tag = value;
await TagChanged.InvokeAsync(value);
await LocalStorage.SetItemAsStringAsync(_tagSelectionStorageKey, value);
}
protected async Task TrackerValueChanged(string value)
{
Tracker = value;
await TrackerChanged.InvokeAsync(value);
await LocalStorage.SetItemAsStringAsync(_trackerSelectionStorageKey, value);
}
protected static string GetHostName(string tracker)
@@ -78,7 +114,5 @@ namespace Lantean.QBTMudBlade.Components
return tracker;
}
}
}
}

View File

@@ -28,6 +28,11 @@ namespace Lantean.QBTMudBlade
return "∞";
}
if (seconds < 60)
{
return "< 1m";
}
var time = TimeSpan.FromSeconds(seconds.Value);
var sb = new StringBuilder();
if (prefix is not null)
@@ -85,6 +90,11 @@ namespace Lantean.QBTMudBlade
return "";
}
if (size < 0)
{
size = 0;
}
var stringBuilder = new StringBuilder();
if (prefix is not null)
{
@@ -275,6 +285,11 @@ namespace Lantean.QBTMudBlade
return "";
}
if (value < 0)
{
value = 0;
}
if (value == 0)
{
return "0%";

View File

@@ -30,17 +30,17 @@ namespace Lantean.QBTMudBlade.Filter
return filter.Operator switch
{
FilterOperator.String.Contains =>
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && x.Contains(value, stringComparer))),
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && value != null && ((string)x).Contains(value, stringComparer))),
FilterOperator.String.NotContains =>
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && !x.Contains(value, stringComparer))),
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && value != null && !((string)x).Contains(value, stringComparer))),
FilterOperator.String.Equal =>
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && x.Equals(value, stringComparer))),
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && ((string)x).Equals(value, stringComparer))),
FilterOperator.String.NotEqual =>
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && !x.Equals(value, stringComparer))),
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && !((string)x).Equals(value, stringComparer))),
FilterOperator.String.StartsWith =>
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && x.StartsWith(value, stringComparer))),
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && value != null && ((string)x).StartsWith(value, stringComparer))),
FilterOperator.String.EndsWith =>
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && x.EndsWith(value, stringComparer))),
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && value != null && ((string)x).EndsWith(value, stringComparer))),
FilterOperator.String.Empty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => string.IsNullOrWhiteSpace(x))),
FilterOperator.String.NotEmpty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => !string.IsNullOrWhiteSpace(x))),
_ => x => true

View File

@@ -1,6 +1,10 @@
@inherits LayoutComponentBase
<EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
</EnhancedErrorBoundary>
<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />
@@ -30,4 +34,3 @@
@Body
</CascadingValue>
</MudLayout>
</EnhancedErrorBoundary>

View File

@@ -49,7 +49,7 @@ namespace Lantean.QBTMudBlade.Pages
#if DEBUG
protected override async Task OnInitializedAsync()
{
await DoLogin("admin", "a8hbfvNP2");
await DoLogin("admin", "pdqYws6EQ");
}
#endif
}

View File

@@ -48,7 +48,7 @@
<MudTh>
@if (column.SortSelector is not null)
{
<MudTableSortLabel T="Torrent" SortDirectionChanged="@(c => SetSort(column.SortSelector, c))">@column.Header</MudTableSortLabel>
<MudTableSortLabel T="Torrent" SortDirectionChanged="@(c => SetSort(column.Id, c))">@column.Header</MudTableSortLabel>
}
else
{

View File

@@ -10,7 +10,8 @@ namespace Lantean.QBTMudBlade.Pages
{
public partial class TorrentList
{
private const string _columnStorageKey = "TorrentList.Columns";
private const string _columnSelectionStorageKey = "TorrentList.ColumnSelection";
private const string _columnSortStorageKey = "TorrentList.ColumnSort";
[Inject]
protected IApiClient ApiClient { get; set; } = default!;
@@ -45,20 +46,20 @@ namespace Lantean.QBTMudBlade.Pages
protected override async Task OnInitializedAsync()
{
if (!await LocalStorage.ContainKeyAsync(_columnStorageKey))
var selectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnSelectionStorageKey);
if (selectedColumns is not null)
{
return;
}
var selectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnStorageKey);
if (selectedColumns is null)
{
return;
}
SelectedColumns = selectedColumns;
}
var columnSort = await LocalStorage.GetItemAsync<Tuple<string, SortDirection>>(_columnSortStorageKey);
if (columnSort is not null)
{
_sortColumn = columnSort.Item1;
_sortDirection = columnSort.Item2;
}
}
protected override void OnParametersSet()
{
if (SelectedColumns.Count == 0)
@@ -69,7 +70,7 @@ namespace Lantean.QBTMudBlade.Pages
SelectedColumns.Remove("#");
}
}
_sortSelector ??= _columns.First(c => c.Enabled).SortSelector;
_sortColumn ??= _columns.First(c => c.Enabled).Id;
if (SelectedTorrent is not null && Torrents is not null && !Torrents.Contains(SelectedTorrent))
{
@@ -84,7 +85,9 @@ namespace Lantean.QBTMudBlade.Pages
return null;
}
return Torrents.OrderByDirection(_sortDirection, _sortSelector ?? (t => t.Priority));
var sortSelector = _columns.Find(c => c.Id == _sortColumn)?.SortSelector;
return Torrents.OrderByDirection(_sortDirection, sortSelector ?? (t => t.Priority));
}
protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
@@ -214,7 +217,7 @@ namespace Lantean.QBTMudBlade.Pages
SelectedColumns = (HashSet<string>)result.Data;
await LocalStorage.SetItemAsync(_columnStorageKey, SelectedColumns);
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
}
protected void ShowTorrent()
@@ -243,10 +246,12 @@ namespace Lantean.QBTMudBlade.Pages
return _columns.Where(c => SelectedColumns.Contains(c.Id));
}
private void SetSort(Func<Torrent, object?> sortSelector, SortDirection sortDirection)
private async Task SetSort(string columnId, SortDirection sortDirection)
{
_sortSelector = sortSelector;
_sortColumn = columnId;
_sortDirection = sortDirection;
await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
}
protected List<ColumnDefinition<Torrent>> _columns =
@@ -286,7 +291,7 @@ namespace Lantean.QBTMudBlade.Pages
//CreateColumnDefinition("Reannounce In", t => t.Reannounce, enabled: false),
];
private Func<Torrent, object?>? _sortSelector;
private string? _sortColumn;
private SortDirection _sortDirection;
private static ColumnDefinition<Torrent> CreateColumnDefinition(string name, Func<Torrent, object?> selector, RenderFragment<RowContext<Torrent>> rowTemplate, int? width = null, string? tdClass = null, bool enabled = true)