mirror of
				https://github.com/lantean-code/qbtmud.git
				synced 2025-11-04 05:53:22 +00:00 
			
		
		
		
	Fix filter crash on files and add local storage for column sorts and filter nav
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using Microsoft.AspNetCore.Components.Rendering;
 | 
					using Microsoft.AspNetCore.Components.Rendering;
 | 
				
			||||||
using System.Collections.ObjectModel;
 | 
					using System.Collections.ObjectModel;
 | 
				
			||||||
 | 
					using System.Runtime.ExceptionServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -13,10 +14,18 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
        public EventCallback OnClear { get; set; }
 | 
					        public EventCallback OnClear { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        public bool Disabled { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override Task OnErrorAsync(Exception exception)
 | 
					        protected override Task OnErrorAsync(Exception exception)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _exceptions.Add(exception);
 | 
					            _exceptions.Add(exception);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (Disabled)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ExceptionDispatchInfo.Capture(exception).Throw();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Task.CompletedTask;
 | 
					            return Task.CompletedTask;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,8 @@
 | 
				
			|||||||
    OnRowClick="RowClick"
 | 
					    OnRowClick="RowClick"
 | 
				
			||||||
    RowStyleFunc="RowStyle"
 | 
					    RowStyleFunc="RowStyle"
 | 
				
			||||||
    RowClassFunc="RowClass"
 | 
					    RowClassFunc="RowClass"
 | 
				
			||||||
    AllowUnsorted="false">
 | 
					    AllowUnsorted="false"
 | 
				
			||||||
 | 
					    Virtualize="true">
 | 
				
			||||||
    <ColGroup>
 | 
					    <ColGroup>
 | 
				
			||||||
        <col style="width: 30px" />
 | 
					        <col style="width: 30px" />
 | 
				
			||||||
        @foreach (var column in GetColumns())
 | 
					        @foreach (var column in GetColumns())
 | 
				
			||||||
@@ -41,7 +42,7 @@
 | 
				
			|||||||
            <MudTh>
 | 
					            <MudTh>
 | 
				
			||||||
                @if (column.SortSelector is not null)
 | 
					                @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
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,14 +16,15 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly bool _refreshEnabled = true;
 | 
					        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 readonly CancellationTokenSource _timerCancellationToken = new();
 | 
				
			||||||
        private bool _disposedValue;
 | 
					        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]
 | 
					        [Parameter]
 | 
				
			||||||
        public bool Active { get; set; }
 | 
					        public bool Active { get; set; }
 | 
				
			||||||
@@ -55,6 +56,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        protected HashSet<ContentItem> SelectedItems { get; set; } = [];
 | 
					        protected HashSet<ContentItem> SelectedItems { get; set; } = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected List<ColumnDefinition<ContentItem>> _columns = [];
 | 
					        protected List<ColumnDefinition<ContentItem>> _columns = [];
 | 
				
			||||||
 | 
					        private List<PropertyFilterDefinition<ContentItem>>? _filterDefinitions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected ContentItem? SelectedItem { get; set; }
 | 
					        protected ContentItem? SelectedItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,20 +82,20 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        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;
 | 
					                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()
 | 
					        protected IEnumerable<ColumnDefinition<ContentItem>> GetColumns()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return _columns.Where(c => SelectedColumns.Contains(c.Id));
 | 
					            return _columns.Where(c => SelectedColumns.Contains(c.Id));
 | 
				
			||||||
@@ -116,14 +118,14 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            SelectedColumns = (HashSet<string>)result.Data;
 | 
					            SelectedColumns = (HashSet<string>)result.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await LocalStorage.SetItemAsync(_columnStorageKey, SelectedColumns);
 | 
					            await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task ShowFilterDialog()
 | 
					        protected async Task ShowFilterDialog()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var parameters = new DialogParameters
 | 
					            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);
 | 
					            var result = await DialogService.ShowAsync<FilterOptionsDialog<ContentItem>>("Filters", parameters, DialogHelper.FormDialogOptions);
 | 
				
			||||||
@@ -134,14 +136,15 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filterDefinitions = (List<PropertyFilterDefinition<ContentItem>>?)dialogResult.Data;
 | 
					            _filterDefinitions = (List<PropertyFilterDefinition<ContentItem>>?)dialogResult.Data;
 | 
				
			||||||
            if (filterDefinitions is null)
 | 
					            if (_filterDefinitions is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                Filters = null;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filters = new List<Func<ContentItem, bool>>();
 | 
					            var filters = new List<Func<ContentItem, bool>>();
 | 
				
			||||||
            foreach (var filterDefinition in filterDefinitions)
 | 
					            foreach (var filterDefinition in _filterDefinitions)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var expression = Filter.FilterExpressionGenerator.GenerateExpression(filterDefinition, false);
 | 
					                var expression = Filter.FilterExpressionGenerator.GenerateExpression(filterDefinition, false);
 | 
				
			||||||
                filters.Add(expression.Compile());
 | 
					                filters.Add(expression.Compile());
 | 
				
			||||||
@@ -154,6 +157,11 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            Filters = null;
 | 
					            Filters = null;
 | 
				
			||||||
            await InvokeAsync(StateHasChanged);
 | 
					            await InvokeAsync(StateHasChanged);
 | 
				
			||||||
 | 
					            if (FileList is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async ValueTask DisposeAsync()
 | 
					        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;
 | 
					            _sortColumn = columnId;
 | 
				
			||||||
            SortDirection = sortDirection;
 | 
					            _sortDirection = sortDirection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void ToggleNode(ContentItem contentItem, MouseEventArgs args)
 | 
					        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);
 | 
					            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)
 | 
					        private IEnumerable<ContentItem> GetDescendants(ContentItem folder, int level)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            level++;
 | 
					            level++;
 | 
				
			||||||
            var descendantsKey = folder.GetDescendantsKey(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)
 | 
					                if (item.IsFolder)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@@ -474,12 +492,12 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            // this is a flat file structure
 | 
					            // this is a flat file structure
 | 
				
			||||||
            if (maxLevel == 0)
 | 
					            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 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)
 | 
					            foreach (var folder in folders)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                list.Add(folder);
 | 
					                list.Add(folder);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -6,6 +7,11 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public partial class FiltersNav
 | 
					    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 _statusExpanded = true;
 | 
				
			||||||
        private bool _categoriesExpanded = true;
 | 
					        private bool _categoriesExpanded = true;
 | 
				
			||||||
        private bool _tagsExpanded = true;
 | 
					        private bool _tagsExpanded = true;
 | 
				
			||||||
@@ -19,6 +25,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
 | 
					        protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        public ILocalStorageService LocalStorage { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [CascadingParameter]
 | 
					        [CascadingParameter]
 | 
				
			||||||
        public MainData? MainData { get; set; }
 | 
					        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) ?? [];
 | 
					        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)
 | 
					        protected async Task StatusValueChanged(string value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Status = value;
 | 
					            Status = value;
 | 
				
			||||||
            await StatusChanged.InvokeAsync(Enum.Parse<Status>(value));
 | 
					            await StatusChanged.InvokeAsync(Enum.Parse<Status>(value));
 | 
				
			||||||
 | 
					            await LocalStorage.SetItemAsStringAsync(_statusSelectionStorageKey, value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task CategoryValueChanged(string value)
 | 
					        protected async Task CategoryValueChanged(string value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Category = value;
 | 
					            Category = value;
 | 
				
			||||||
            await CategoryChanged.InvokeAsync(value);
 | 
					            await CategoryChanged.InvokeAsync(value);
 | 
				
			||||||
 | 
					            await LocalStorage.SetItemAsStringAsync(_categorySelectionStorageKey, value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task TagValueChanged(string value)
 | 
					        protected async Task TagValueChanged(string value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Tag = value;
 | 
					            Tag = value;
 | 
				
			||||||
            await TagChanged.InvokeAsync(value);
 | 
					            await TagChanged.InvokeAsync(value);
 | 
				
			||||||
 | 
					            await LocalStorage.SetItemAsStringAsync(_tagSelectionStorageKey, value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task TrackerValueChanged(string value)
 | 
					        protected async Task TrackerValueChanged(string value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Tracker = value;
 | 
					            Tracker = value;
 | 
				
			||||||
            await TrackerChanged.InvokeAsync(value);
 | 
					            await TrackerChanged.InvokeAsync(value);
 | 
				
			||||||
 | 
					            await LocalStorage.SetItemAsStringAsync(_trackerSelectionStorageKey, value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected static string GetHostName(string tracker)
 | 
					        protected static string GetHostName(string tracker)
 | 
				
			||||||
@@ -78,7 +114,5 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
                return tracker;
 | 
					                return tracker;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -28,6 +28,11 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
                return "∞";
 | 
					                return "∞";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (seconds < 60)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return "< 1m";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var time = TimeSpan.FromSeconds(seconds.Value);
 | 
					            var time = TimeSpan.FromSeconds(seconds.Value);
 | 
				
			||||||
            var sb = new StringBuilder();
 | 
					            var sb = new StringBuilder();
 | 
				
			||||||
            if (prefix is not null)
 | 
					            if (prefix is not null)
 | 
				
			||||||
@@ -85,6 +90,11 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
                return "";
 | 
					                return "";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (size < 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                size = 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var stringBuilder = new StringBuilder();
 | 
					            var stringBuilder = new StringBuilder();
 | 
				
			||||||
            if (prefix is not null)
 | 
					            if (prefix is not null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -275,6 +285,11 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
                return "";
 | 
					                return "";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (value < 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                value = 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (value == 0)
 | 
					            if (value == 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return "0%";
 | 
					                return "0%";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,17 +30,17 @@ namespace Lantean.QBTMudBlade.Filter
 | 
				
			|||||||
                return filter.Operator switch
 | 
					                return filter.Operator switch
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    FilterOperator.String.Contains =>
 | 
					                    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 =>
 | 
					                    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 =>
 | 
					                    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 =>
 | 
					                    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 =>
 | 
					                    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 =>
 | 
					                    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.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))),
 | 
					                    FilterOperator.String.NotEmpty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => !string.IsNullOrWhiteSpace(x))),
 | 
				
			||||||
                    _ => x => true
 | 
					                    _ => x => true
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,17 @@
 | 
				
			|||||||
@inherits LayoutComponentBase
 | 
					@inherits LayoutComponentBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
 | 
					<EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
 | 
				
			||||||
    <MudThemeProvider />
 | 
					 | 
				
			||||||
    <MudDialogProvider />
 | 
					 | 
				
			||||||
    <MudSnackbarProvider />
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    <PageTitle>qBittorrent Web UI</PageTitle>
 | 
					</EnhancedErrorBoundary>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <MudLayout>
 | 
					<MudThemeProvider />
 | 
				
			||||||
 | 
					<MudDialogProvider />
 | 
				
			||||||
 | 
					<MudSnackbarProvider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<PageTitle>qBittorrent Web UI</PageTitle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<MudLayout>
 | 
				
			||||||
    <MudAppBar Elevation="1">
 | 
					    <MudAppBar Elevation="1">
 | 
				
			||||||
        <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="ToggleDrawer" />
 | 
					        <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="ToggleDrawer" />
 | 
				
			||||||
        <MudText Typo="Typo.h5" Class="ml-3">qBittorrent Web UI</MudText>
 | 
					        <MudText Typo="Typo.h5" Class="ml-3">qBittorrent Web UI</MudText>
 | 
				
			||||||
@@ -29,5 +33,4 @@
 | 
				
			|||||||
    <CascadingValue Value="DrawerOpen" Name="DrawerOpen">
 | 
					    <CascadingValue Value="DrawerOpen" Name="DrawerOpen">
 | 
				
			||||||
        @Body
 | 
					        @Body
 | 
				
			||||||
    </CascadingValue>
 | 
					    </CascadingValue>
 | 
				
			||||||
    </MudLayout>
 | 
					</MudLayout>
 | 
				
			||||||
</EnhancedErrorBoundary>
 | 
					 | 
				
			||||||
@@ -49,7 +49,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
#if DEBUG
 | 
					#if DEBUG
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        protected override async Task OnInitializedAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await DoLogin("admin", "a8hbfvNP2");
 | 
					            await DoLogin("admin", "pdqYws6EQ");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,7 +48,7 @@
 | 
				
			|||||||
            <MudTh>
 | 
					            <MudTh>
 | 
				
			||||||
                @if (column.SortSelector is not null)
 | 
					                @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
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,8 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public partial class TorrentList
 | 
					    public partial class TorrentList
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private const string _columnStorageKey = "TorrentList.Columns";
 | 
					        private const string _columnSelectionStorageKey = "TorrentList.ColumnSelection";
 | 
				
			||||||
 | 
					        private const string _columnSortStorageKey = "TorrentList.ColumnSort";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
        protected IApiClient ApiClient { get; set; } = default!;
 | 
					        protected IApiClient ApiClient { get; set; } = default!;
 | 
				
			||||||
@@ -45,20 +46,20 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        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;
 | 
					                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()
 | 
					        protected override void OnParametersSet()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (SelectedColumns.Count == 0)
 | 
					            if (SelectedColumns.Count == 0)
 | 
				
			||||||
@@ -69,7 +70,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
                    SelectedColumns.Remove("#");
 | 
					                    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))
 | 
					            if (SelectedTorrent is not null && Torrents is not null && !Torrents.Contains(SelectedTorrent))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -84,7 +85,9 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
                return null;
 | 
					                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)
 | 
					        protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
 | 
				
			||||||
@@ -214,7 +217,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            SelectedColumns = (HashSet<string>)result.Data;
 | 
					            SelectedColumns = (HashSet<string>)result.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await LocalStorage.SetItemAsync(_columnStorageKey, SelectedColumns);
 | 
					            await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void ShowTorrent()
 | 
					        protected void ShowTorrent()
 | 
				
			||||||
@@ -243,10 +246,12 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            return _columns.Where(c => SelectedColumns.Contains(c.Id));
 | 
					            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;
 | 
					            _sortDirection = sortDirection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected List<ColumnDefinition<Torrent>> _columns =
 | 
					        protected List<ColumnDefinition<Torrent>> _columns =
 | 
				
			||||||
@@ -286,7 +291,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            //CreateColumnDefinition("Reannounce In", t => t.Reannounce, enabled: false),
 | 
					            //CreateColumnDefinition("Reannounce In", t => t.Reannounce, enabled: false),
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Func<Torrent, object?>? _sortSelector;
 | 
					        private string? _sortColumn;
 | 
				
			||||||
        private SortDirection _sortDirection;
 | 
					        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)
 | 
					        private static ColumnDefinition<Torrent> CreateColumnDefinition(string name, Func<Torrent, object?> selector, RenderFragment<RowContext<Torrent>> rowTemplate, int? width = null, string? tdClass = null, bool enabled = true)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user