mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-10-22 20:42:24 +00:00
Add dynamic table and fix several bugs
This commit is contained in:
@@ -3,14 +3,25 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudCard Class="w-100">
|
||||
<MudList>
|
||||
@foreach (var column in Columns)
|
||||
<MudGrid>
|
||||
@for (var i = 0; i < Columns.Count; i++)
|
||||
{
|
||||
<MudListItem>
|
||||
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPosition="LabelPosition.End" Value="@(SelectedColumns.Contains(column.Id))" />
|
||||
</MudListItem>
|
||||
var column = Columns[i];
|
||||
var index = i;
|
||||
<MudItem xs="7">
|
||||
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPosition="LabelPosition.End" Value="@(SelectedColumns.Contains(column.Id))" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudTextField T="string" Value="@(GetValue(column.Width, column.Id))" ValueChanged="@(c => SetWidth(c, column.Id))" Label="Width" Variant="Variant.Text" HelperText="px" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Outlined.WidthNormal" ShrinkLabel="true" OnAdornmentClick="@(c => SetWidth("auto", column.Id))" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.KeyboardArrowUp" Disabled="@(index == 0)" OnClick="@(e => MoveUp(index))" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.KeyboardArrowDown" Disabled="@(index == Columns.Count - 1)" OnClick="@(e => MoveDown(index))" />
|
||||
</MudItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudGrid>
|
||||
</MudCard>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
[EditorRequired]
|
||||
public List<ColumnDefinition<T>> Columns { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public Dictionary<string, int?> Widths { get; set; } = [];
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = [];
|
||||
|
||||
protected override void OnParametersSet()
|
||||
@@ -26,7 +29,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetSelected(bool selected, string id)
|
||||
protected void SetSelected(bool selected, string id)
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
@@ -38,6 +41,75 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetWidth(string? value, string id)
|
||||
{
|
||||
var column = Columns.Find(c => c.Id == id);
|
||||
var defaultWidth = column?.Width;
|
||||
|
||||
if (int.TryParse(value, out var width))
|
||||
{
|
||||
if (width == defaultWidth)
|
||||
{
|
||||
Widths.Remove(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Widths[id] = width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (defaultWidth is null)
|
||||
{
|
||||
Widths.Remove(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Widths[id] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void MoveUp(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(Columns[index], Columns[index - 1]) = (Columns[index - 1], Columns[index]);
|
||||
}
|
||||
|
||||
protected void MoveDown(int index)
|
||||
{
|
||||
if (index >= Columns.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(Columns[index], Columns[index + 1]) = (Columns[index + 1], Columns[index]);
|
||||
}
|
||||
|
||||
protected string GetValue(int? value, string columnId)
|
||||
{
|
||||
if (Widths.TryGetValue(columnId, out var newWidth))
|
||||
{
|
||||
value = newWidth;
|
||||
}
|
||||
|
||||
if (!value.HasValue)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value.Value <= 0)
|
||||
{
|
||||
return "auto";
|
||||
}
|
||||
|
||||
return value.Value.ToString();
|
||||
}
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
@@ -45,7 +117,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(SelectedColumns));
|
||||
MudDialog.Close(DialogResult.Ok((SelectedColumns, Widths)));
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Lantean.QBTMudBlade/Components/DynamicTable.razor
Normal file
58
Lantean.QBTMudBlade/Components/DynamicTable.razor
Normal file
@@ -0,0 +1,58 @@
|
||||
@typeparam T
|
||||
@inherits MudComponentBase
|
||||
|
||||
<MudTable
|
||||
@ref="Table"
|
||||
Items="OrderedItems"
|
||||
T="T"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
HeaderClass="table-head-bordered"
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Loading="@(Items is null)"
|
||||
SelectOnRowClick="false"
|
||||
Striped="Striped"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
MultiSelection="MultiSelection"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChangedInternal"
|
||||
HorizontalScrollbar="true"
|
||||
OnRowClick="OnRowClickInternal"
|
||||
RowStyleFunc="RowStyleFuncInternal"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
Class="@Class">
|
||||
<ColGroup>
|
||||
<col style="width: 30px" />
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<col style="@(GetColumnStyle(column))" />
|
||||
}
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTh Class="overflow-cell" Style="@(GetColumnStyle(column))">
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<MudTableSortLabel T="T" SortDirectionChanged="@(c => SetSort(column.Id, c))" SortDirection="@(column.Id == _sortColumn ? _sortDirection : SortDirection.None)">@column.Header</MudTableSortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@column.Header
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@(GetColumnClass(column, context))" Style="@(GetColumnStyle(column))">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
279
Lantean.QBTMudBlade/Components/DynamicTable.razor.cs
Normal file
279
Lantean.QBTMudBlade/Components/DynamicTable.razor.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class DynamicTable<T> : MudComponentBase
|
||||
{
|
||||
private static readonly string _typeName = typeof(T).Name;
|
||||
private readonly string _columnSelectionStorageKey = $"DynamicTable{_typeName}.ColumnSelection";
|
||||
private readonly string _columnSortStorageKey = $"DynamicTable{_typeName}.ColumnSort";
|
||||
private readonly string _columnWidthsStorageKey = $"DynamicTable{_typeName}.ColumnWidths";
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<ColumnDefinition<T>> ColumnDefinitions { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<T>? Items { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool MultiSelection { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Striped { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Hover { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool PreSorted { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool SelectOnRowClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<TableRowClickEventArgs<T>> OnRowClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public HashSet<T> SelectedItems { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<HashSet<T>> SelectedItemsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<T> SelectedItemChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T? SelectedItem { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<ColumnDefinition<T>, bool> ColumnFilter { get; set; } = (t => true);
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> SortColumnChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<SortDirection> SortDirectionChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<HashSet<string>> SelectedColumnsChanged { get; set; }
|
||||
|
||||
protected IEnumerable<T>? OrderedItems => GetOrderedItems();
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = [];
|
||||
|
||||
private Dictionary<string, int?> _columnWidths = [];
|
||||
|
||||
private MudTable<T>? Table { get; set; }
|
||||
|
||||
private string? _sortColumn;
|
||||
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
HashSet<string> selectedColumns;
|
||||
var storedSelectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnSelectionStorageKey);
|
||||
if (storedSelectedColumns is not null)
|
||||
{
|
||||
selectedColumns = storedSelectedColumns;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedColumns = ColumnDefinitions.Where(c => c.Enabled).Select(c => c.Id).ToHashSet();
|
||||
}
|
||||
|
||||
if (!SelectedColumns.SetEquals(selectedColumns))
|
||||
{
|
||||
SelectedColumns = selectedColumns;
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
}
|
||||
|
||||
string? sortColumn;
|
||||
SortDirection sortDirection;
|
||||
|
||||
var storedColumnSort = await LocalStorage.GetItemAsync<Tuple<string, SortDirection>>(_columnSortStorageKey);
|
||||
if (storedColumnSort is not null)
|
||||
{
|
||||
sortColumn = storedColumnSort.Item1;
|
||||
sortDirection = storedColumnSort.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
sortColumn = ColumnDefinitions.First(c => c.Enabled).Id;
|
||||
sortDirection = SortDirection.Ascending;
|
||||
}
|
||||
|
||||
if (_sortColumn != sortColumn)
|
||||
{
|
||||
_sortColumn = sortColumn;
|
||||
await SortColumnChanged.InvokeAsync(_sortColumn);
|
||||
}
|
||||
|
||||
if (_sortDirection != sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
|
||||
var storedColumnsWidths = await LocalStorage.GetItemAsync<Dictionary<string, int?>>(_columnWidthsStorageKey);
|
||||
if (storedColumnsWidths is not null)
|
||||
{
|
||||
_columnWidths = storedColumnsWidths;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<T>? GetOrderedItems()
|
||||
{
|
||||
if (Items is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PreSorted)
|
||||
{
|
||||
return Items;
|
||||
}
|
||||
|
||||
var sortSelector = ColumnDefinitions.FirstOrDefault(c => c.Id == _sortColumn)?.SortSelector;
|
||||
if (sortSelector is null)
|
||||
{
|
||||
return Items;
|
||||
}
|
||||
|
||||
return Items.OrderByDirection(_sortDirection, sortSelector);
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<T>> GetColumns()
|
||||
{
|
||||
var filteredColumns = ColumnDefinitions.Where(c => SelectedColumns.Contains(c.Id)).Where(ColumnFilter);
|
||||
foreach (var column in filteredColumns)
|
||||
{
|
||||
if (_columnWidths.TryGetValue(column.Id, out var value))
|
||||
{
|
||||
column.Width = value;
|
||||
}
|
||||
|
||||
yield return column;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
{
|
||||
if (_sortColumn != columnId)
|
||||
{
|
||||
_sortColumn = columnId;
|
||||
await SortColumnChanged.InvokeAsync(_sortColumn);
|
||||
}
|
||||
|
||||
if (_sortDirection != sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
|
||||
await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
|
||||
}
|
||||
|
||||
protected async Task OnRowClickInternal(TableRowClickEventArgs<T> eventArgs)
|
||||
{
|
||||
SelectedItem = eventArgs.Item;
|
||||
|
||||
await SelectedItemChanged.InvokeAsync(SelectedItem);
|
||||
await OnRowClick.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
protected string RowStyleFuncInternal(T item, int index)
|
||||
{
|
||||
var style = "user-select: none; cursor: pointer;";
|
||||
if (EqualityComparer<T>.Default.Equals(item, SelectedItem))
|
||||
{
|
||||
style += " background-color: var(--mud-palette-dark-darken)";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
protected async Task SelectedItemsChangedInternal(HashSet<T> selectedItems)
|
||||
{
|
||||
await SelectedItemsChanged.InvokeAsync(selectedItems);
|
||||
}
|
||||
|
||||
public async Task ShowColumnOptionsDialog()
|
||||
{
|
||||
var result = await DialogService.ShowColumnsOptionsDialog(ColumnDefinitions.Where(ColumnFilter).ToList(), _columnWidths);
|
||||
|
||||
if (result == default)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SelectedColumns.SetEquals(result.SelectedColumns))
|
||||
{
|
||||
SelectedColumns = result.SelectedColumns;
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnWidths, result.ColumnWidths))
|
||||
{
|
||||
_columnWidths = result.ColumnWidths;
|
||||
await LocalStorage.SetItemAsync(_columnWidthsStorageKey, _columnWidths);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DictionaryEqual(Dictionary<string, int?> left, Dictionary<string, int?> right)
|
||||
{
|
||||
return left.Keys.Count == right.Keys.Count && left.Keys.All(k => right.ContainsKey(k) && left[k] == right[k]);
|
||||
}
|
||||
|
||||
private static string? GetColumnStyle(ColumnDefinition<T> column)
|
||||
{
|
||||
string? style = null;
|
||||
if (column.Width.HasValue)
|
||||
{
|
||||
style = $"width: {column.Width.Value}px; max-width: {column.Width.Value}px;";
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
private static string? GetColumnClass(ColumnDefinition<T> column, T data)
|
||||
{
|
||||
var className = column.Class;
|
||||
if (column.ClassFunc is not null)
|
||||
{
|
||||
var funcClass = column.ClassFunc(data);
|
||||
if (funcClass is not null)
|
||||
{
|
||||
if (className is null)
|
||||
{
|
||||
className = funcClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
className = $"{className} {column.ClassFunc(data)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (column.Width.HasValue)
|
||||
{
|
||||
className = $"overflow-cell {className}";
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,48 +18,20 @@
|
||||
<MudSpacer />
|
||||
<MudTextField T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
<MudTable T="ContentItem" Hover="true" FixedHeader="true" HeaderClass="table-head-bordered" Breakpoint="Breakpoint.None" Bordered="false"
|
||||
MultiSelection="true" Dense="true" SelectOnRowClick="false"
|
||||
Items="Files"
|
||||
SelectedItems="SelectedItems"
|
||||
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="ContentItem"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Files"
|
||||
MultiSelection="true"
|
||||
PreSorted="true"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
OnRowClick="RowClick"
|
||||
RowStyleFunc="RowStyle"
|
||||
RowClassFunc="RowClass"
|
||||
AllowUnsorted="false"
|
||||
Virtualize="true">
|
||||
<ColGroup>
|
||||
<col style="width: 30px" />
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
var style = column.Width.HasValue ? $"width: {column.Width.Value}px" : null;
|
||||
<col style="@style" />
|
||||
}
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTh>
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<MudTableSortLabel T="ContentItem" SortDirectionChanged="@(c => SetSort(column.Id, c))" InitialDirection="column.InitialDirection">@column.Header</MudTableSortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@column.Header
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
/>
|
||||
|
||||
@code {
|
||||
private RenderFragment<RowContext<ContentItem>> NameColumn
|
||||
@@ -68,11 +40,11 @@
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
<div style="@($"margin-left: {context.Data.Level * 56}px")">
|
||||
<div style="@($"margin-left: {context.Data.Level * 14}px")">
|
||||
@if (context.Data.IsFolder)
|
||||
{
|
||||
<MudIconButton ButtonType="ButtonType.Button" Icon="@Icons.Material.Filled.ExpandLess" Class="@("pa-0 " + (ExpandedNodes.Contains(context.Data.Name) ? "rotate-180" : "rotate-90"))" OnClick="@(c => ToggleNode(context.Data, c))"></MudIconButton>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-2" Style="margin-right: 4px; position: relative; top: 3px" />
|
||||
<MudIconButton Edge="Edge.Start" ButtonType="ButtonType.Button" Icon="@(ExpandedNodes.Contains(context.Data.Name) ? Icons.Material.Filled.KeyboardArrowDown : Icons.Material.Filled.KeyboardArrowRight)" OnClick="@(c => ToggleNode(context.Data, c))"></MudIconButton>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-0" Style="margin-right: 4px; position: relative; top: 7px; margin-left: -15px" />
|
||||
}
|
||||
@context.Data.DisplayName
|
||||
</div>;
|
||||
@@ -95,4 +67,19 @@
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static RenderFragment<RowContext<ContentItem>> ProgressBarColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
var value = (float?)context.GetValue();
|
||||
var color = value < 1 ? Color.Success : Color.Info;
|
||||
<MudProgressLinear title="Progress" Color="@color" Value="@((value ?? 0) * 100)" Class="progress-expand" Size="Size.Large">
|
||||
@DisplayHelpers.Percentage(value)
|
||||
</MudProgressLinear>;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
@@ -16,16 +17,10 @@ namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
private readonly bool _refreshEnabled = true;
|
||||
|
||||
private const string _columnSelectionStorageKey = "FilesTab.ColumnSelection";
|
||||
private const string _columnSortStorageKey = "FilesTab.ColumnSort";
|
||||
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
private string? _sortColumn;
|
||||
|
||||
private SortDirection _sortDirection = SortDirection.Ascending;
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
@@ -44,9 +39,6 @@ namespace Lantean.QBTMudBlade.Components
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> ExpandedNodes { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, ContentItem>? FileList { get; set; }
|
||||
@@ -55,70 +47,35 @@ 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; }
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected int? _selectedIndex { get; set; }
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; }
|
||||
|
||||
public IEnumerable<Func<ContentItem, bool>>? Filters { get; set; }
|
||||
|
||||
private readonly Dictionary<string, RenderFragment<RowContext<ContentItem>>> _columnRenderFragments = [];
|
||||
|
||||
private DynamicTable<ContentItem>? Table { get; set; }
|
||||
|
||||
private string? _sortColumn;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
public FilesTab()
|
||||
{
|
||||
_columns.Add(CreateColumnDefinition("Name", c => c.Name, NameColumn, width: 200, initialDirection: SortDirection.Ascending));
|
||||
_columns.Add(CreateColumnDefinition("Total Size", c => c.Size, c => DisplayHelpers.Size(c.Size)));
|
||||
_columns.Add(CreateColumnDefinition("Progress", c => c.Progress, c => DisplayHelpers.Percentage(c.Progress)));
|
||||
_columns.Add(CreateColumnDefinition("Priority", c => c.Priority, PriorityColumn));
|
||||
_columns.Add(CreateColumnDefinition("Remaining", c => c.Remaining, c => DisplayHelpers.Size(c.Remaining)));
|
||||
_columns.Add(CreateColumnDefinition("Availability", c => c.Availability, c => c.Availability.ToString("0.00")));
|
||||
|
||||
SelectedColumns = _columns.Where(c => c.Enabled).Select(c => c.Id).ToHashSet();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var selectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnSelectionStorageKey);
|
||||
if (selectedColumns is not null)
|
||||
{
|
||||
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));
|
||||
_columnRenderFragments.Add("Name", NameColumn);
|
||||
_columnRenderFragments.Add("Priority", PriorityColumn);
|
||||
}
|
||||
|
||||
protected async Task ColumnOptions()
|
||||
{
|
||||
DialogParameters parameters = new DialogParameters
|
||||
{
|
||||
{ "Columns", _columns }
|
||||
};
|
||||
|
||||
var reference = await DialogService.ShowAsync<ColumnOptionsDialog<ContentItem>>("ColumnOptions", parameters, DialogHelper.FormDialogOptions);
|
||||
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
if (Table is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedColumns = (HashSet<string>)result.Data;
|
||||
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
await Table.ShowColumnOptionsDialog();
|
||||
}
|
||||
|
||||
protected async Task ShowFilterDialog()
|
||||
@@ -306,26 +263,13 @@ namespace Lantean.QBTMudBlade.Components
|
||||
await ApiClient.SetFilePriority(Hash, fileIndexes, MapPriority(priority));
|
||||
}
|
||||
|
||||
protected string RowClass(ContentItem contentItem, int index)
|
||||
{
|
||||
if (contentItem.Level == 0)
|
||||
{
|
||||
return "d-table-row";
|
||||
}
|
||||
if (ExpandedNodes.Contains(contentItem.Path))
|
||||
{
|
||||
return "d-table-row";
|
||||
}
|
||||
return "d-none";
|
||||
}
|
||||
|
||||
protected async Task RenameFile()
|
||||
{
|
||||
if (Hash is null || FileList is null || _selectedIndex is null)
|
||||
if (Hash is null || FileList is null || SelectedItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var contentItem = FileList.Values.FirstOrDefault(c => c.Index == _selectedIndex.Value);
|
||||
var contentItem = FileList.Values.FirstOrDefault(c => c.Index == SelectedItem.Index);
|
||||
if (contentItem is null)
|
||||
{
|
||||
return;
|
||||
@@ -334,19 +278,19 @@ namespace Lantean.QBTMudBlade.Components
|
||||
await DialogService.ShowSingleFieldDialog("Rename", "New name", name, async v => await ApiClient.RenameFile(Hash, contentItem.Name, contentItem.Path + v));
|
||||
}
|
||||
|
||||
protected void RowClick(TableRowClickEventArgs<ContentItem> eventArgs)
|
||||
protected void SortColumnChanged(string sortColumn)
|
||||
{
|
||||
_selectedIndex = eventArgs.Item.Index;
|
||||
_sortColumn = sortColumn;
|
||||
}
|
||||
|
||||
protected string RowStyle(ContentItem item, int index)
|
||||
protected void SortDirectionChanged(SortDirection sortDirection)
|
||||
{
|
||||
var style = "user-select: none; cursor: pointer;";
|
||||
if (_selectedIndex != item.Index)
|
||||
{
|
||||
return style;
|
||||
}
|
||||
return $"{style} background: #D3D3D3";
|
||||
_sortDirection = sortDirection;
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(ContentItem item)
|
||||
{
|
||||
SelectedItem = item;
|
||||
}
|
||||
|
||||
protected async Task SelectedItemsChanged(HashSet<ContentItem> selectedItems)
|
||||
@@ -386,14 +330,6 @@ namespace Lantean.QBTMudBlade.Components
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
{
|
||||
_sortColumn = columnId;
|
||||
_sortDirection = sortDirection;
|
||||
|
||||
await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
|
||||
}
|
||||
|
||||
protected void ToggleNode(ContentItem contentItem, MouseEventArgs args)
|
||||
{
|
||||
if (ExpandedNodes.Contains(contentItem.Name))
|
||||
@@ -404,6 +340,11 @@ namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
ExpandedNodes.Add(contentItem.Name);
|
||||
}
|
||||
|
||||
if (FileList is not null)
|
||||
{
|
||||
SelectedItems = GetFiles().Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
private static QBitTorrentClient.Models.Priority MapPriority(Priority priority)
|
||||
@@ -411,6 +352,13 @@ namespace Lantean.QBTMudBlade.Components
|
||||
return (QBitTorrentClient.Models.Priority)(int)priority;
|
||||
}
|
||||
|
||||
private Func<ContentItem, object?> GetSortSelector()
|
||||
{
|
||||
var sortSelector = ColumnsDefinitions.Find(c => c.Id == _sortColumn)?.SortSelector;
|
||||
|
||||
return sortSelector ?? (i => i.Name);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem contentItem)
|
||||
{
|
||||
if (!contentItem.IsFolder || Files is null)
|
||||
@@ -421,28 +369,28 @@ 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)
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem folder, int level)
|
||||
{
|
||||
level++;
|
||||
var descendantsKey = folder.GetDescendantsKey(level);
|
||||
|
||||
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey)).OrderByDirection(_sortDirection, GetSortSelector()))
|
||||
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey) && f.Level == level).OrderByDirection(_sortDirection, GetSortSelector()))
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var descendants = GetDescendants(item, level);
|
||||
// if the filter returns some resutls then show folder item
|
||||
var descendants = GetChildren(item, level);
|
||||
// if the filter returns some results then show folder item
|
||||
if (descendants.Any())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
// if the folder is not expanded - don't return children
|
||||
if (!ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// then show children
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
@@ -497,15 +445,19 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
var list = new List<ContentItem>();
|
||||
|
||||
var folders = FileList.Values.Where(c => c.IsFolder && c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
|
||||
foreach (var folder in folders)
|
||||
var rootItems = FileList.Values.Where(c => c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
|
||||
foreach (var item in rootItems)
|
||||
{
|
||||
list.Add(folder);
|
||||
var level = 0;
|
||||
var descendants = GetDescendants(folder, level);
|
||||
foreach (var descendant in descendants)
|
||||
list.Add(item);
|
||||
|
||||
if (item.IsFolder && ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
list.Add(descendant);
|
||||
var level = 0;
|
||||
var descendants = GetChildren(item, level);
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
list.Add(descendant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +528,32 @@ namespace Lantean.QBTMudBlade.Components
|
||||
await ApiClient.SetFilePriority(Hash, files, priority);
|
||||
}
|
||||
|
||||
private static ColumnDefinition<ContentItem> CreateColumnDefinition(string name, Func<ContentItem, object?> selector, RenderFragment<RowContext<ContentItem>> rowTemplate, int? width = null, string? tdClass = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
|
||||
protected IEnumerable<ColumnDefinition<ContentItem>> Columns => GetColumnDefinitions();
|
||||
|
||||
private IEnumerable<ColumnDefinition<ContentItem>> GetColumnDefinitions()
|
||||
{
|
||||
foreach (var columnDefinition in ColumnsDefinitions)
|
||||
{
|
||||
if (_columnRenderFragments.TryGetValue(columnDefinition.Header, out var fragment))
|
||||
{
|
||||
columnDefinition.RowTemplate = fragment;
|
||||
}
|
||||
|
||||
yield return columnDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ColumnDefinition<ContentItem>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
CreateColumnDefinition("Name", c => c.Name, width: 200, initialDirection: SortDirection.Ascending, classFunc: c => c.IsFolder ? "pa-0" : "pa-3"),
|
||||
CreateColumnDefinition("Total Size", c => c.Size, c => DisplayHelpers.Size(c.Size)),
|
||||
CreateColumnDefinition("Progress", c => c.Progress, ProgressBarColumn, tdClass: "table-progress pl-2 pr-2"),
|
||||
CreateColumnDefinition("Priority", c => c.Priority, tdClass: "table-select pa-0"),
|
||||
CreateColumnDefinition("Remaining", c => c.Remaining, c => DisplayHelpers.Size(c.Remaining)),
|
||||
CreateColumnDefinition("Availability", c => c.Availability, c => c.Availability.ToString("0.00")),
|
||||
];
|
||||
|
||||
private static ColumnDefinition<ContentItem> CreateColumnDefinition(string name, Func<ContentItem, object?> selector, RenderFragment<RowContext<ContentItem>> rowTemplate, int? width = null, string? tdClass = null, Func<ContentItem, string?>? classFunc = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
|
||||
{
|
||||
var cd = new ColumnDefinition<ContentItem>(name, selector, rowTemplate);
|
||||
cd.Class = "no-wrap";
|
||||
@@ -584,6 +561,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
cd.Class += " " + tdClass;
|
||||
}
|
||||
cd.ClassFunc = classFunc;
|
||||
cd.Width = width;
|
||||
cd.Enabled = enabled;
|
||||
cd.InitialDirection = initialDirection;
|
||||
@@ -591,7 +569,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
return cd;
|
||||
}
|
||||
|
||||
private static ColumnDefinition<ContentItem> CreateColumnDefinition(string name, Func<ContentItem, object?> selector, Func<ContentItem, string>? formatter = null, int? width = null, string? tdClass = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
|
||||
private static ColumnDefinition<ContentItem> CreateColumnDefinition(string name, Func<ContentItem, object?> selector, Func<ContentItem, string>? formatter = null, int? width = null, string? tdClass = null, Func<ContentItem, string?>? classFunc = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
|
||||
{
|
||||
var cd = new ColumnDefinition<ContentItem>(name, selector, formatter);
|
||||
cd.Class = "no-wrap";
|
||||
@@ -599,6 +577,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
cd.Class += " " + tdClass;
|
||||
}
|
||||
cd.ClassFunc = classFunc;
|
||||
cd.Width = width;
|
||||
cd.Enabled = enabled;
|
||||
cd.InitialDirection = initialDirection;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using static MudBlazor.Colors;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
@@ -53,24 +54,33 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (await LocalStorage.ContainKeyAsync(_statusSelectionStorageKey))
|
||||
var status = await LocalStorage.GetItemAsStringAsync(_statusSelectionStorageKey);
|
||||
if (status is not null)
|
||||
{
|
||||
Status = await LocalStorage.GetItemAsStringAsync(_statusSelectionStorageKey) ?? Models.Status.All.ToString();
|
||||
Status = status;
|
||||
await StatusChanged.InvokeAsync(Enum.Parse<Status>(status));
|
||||
}
|
||||
|
||||
if (await LocalStorage.ContainKeyAsync(_categorySelectionStorageKey))
|
||||
var category = await LocalStorage.GetItemAsStringAsync(_categorySelectionStorageKey);
|
||||
if (category is not null)
|
||||
{
|
||||
Category = await LocalStorage.GetItemAsStringAsync(_categorySelectionStorageKey) ?? FilterHelper.CATEGORY_ALL;
|
||||
Category = category;
|
||||
await CategoryChanged.InvokeAsync(category);
|
||||
}
|
||||
|
||||
if (await LocalStorage.ContainKeyAsync(_tagSelectionStorageKey))
|
||||
{
|
||||
Tag = await LocalStorage.GetItemAsStringAsync(_tagSelectionStorageKey) ?? FilterHelper.TAG_ALL;
|
||||
}
|
||||
|
||||
if (await LocalStorage.ContainKeyAsync(_trackerSelectionStorageKey))
|
||||
var tag = await LocalStorage.GetItemAsStringAsync(_tagSelectionStorageKey);
|
||||
if (tag is not null)
|
||||
{
|
||||
Tracker = await LocalStorage.GetItemAsStringAsync(_trackerSelectionStorageKey) ?? FilterHelper.TRACKER_ALL;
|
||||
Tag = tag;
|
||||
await TagChanged.InvokeAsync(tag);
|
||||
}
|
||||
|
||||
var tracker = await LocalStorage.GetItemAsStringAsync(_trackerSelectionStorageKey);
|
||||
if (tracker is not null)
|
||||
{
|
||||
Tracker = tracker;
|
||||
await TrackerChanged.InvokeAsync(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,28 +88,60 @@ namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
Status = value;
|
||||
await StatusChanged.InvokeAsync(Enum.Parse<Status>(value));
|
||||
await LocalStorage.SetItemAsStringAsync(_statusSelectionStorageKey, value);
|
||||
|
||||
if (value != Models.Status.All.ToString())
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_statusSelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_statusSelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task CategoryValueChanged(string value)
|
||||
{
|
||||
Category = value;
|
||||
await CategoryChanged.InvokeAsync(value);
|
||||
await LocalStorage.SetItemAsStringAsync(_categorySelectionStorageKey, value);
|
||||
|
||||
if (value != FilterHelper.CATEGORY_ALL)
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_categorySelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_categorySelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task TagValueChanged(string value)
|
||||
{
|
||||
Tag = value;
|
||||
await TagChanged.InvokeAsync(value);
|
||||
await LocalStorage.SetItemAsStringAsync(_tagSelectionStorageKey, value);
|
||||
|
||||
if (value != FilterHelper.TAG_ALL)
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_tagSelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_tagSelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task TrackerValueChanged(string value)
|
||||
{
|
||||
Tracker = value;
|
||||
await TrackerChanged.InvokeAsync(value);
|
||||
await LocalStorage.SetItemAsStringAsync(_trackerSelectionStorageKey, value);
|
||||
|
||||
if (value != FilterHelper.TRACKER_ALL)
|
||||
{
|
||||
await LocalStorage.SetItemAsStringAsync(_trackerSelectionStorageKey, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_trackerSelectionStorageKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected static string GetHostName(string tracker)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<MudNavMenu>
|
||||
<MudNavLink Icon="@(Icons.Material.Outlined.NavigateBefore)" OnClick="NavigateBack">Back</MudNavLink>
|
||||
<MudDivider />
|
||||
@if (Torrents is null)
|
||||
@if (OrderedTorrents is null)
|
||||
{
|
||||
@for (var i = 0; i < 10; i++)
|
||||
{
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var torrent in Torrents)
|
||||
foreach (var torrent in OrderedTorrents)
|
||||
{
|
||||
<MudNavLink Href="@("/details/" + torrent.Hash)">@torrent.Name</MudNavLink>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Pages;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
@@ -14,6 +16,26 @@ namespace Lantean.QBTMudBlade.Components
|
||||
[Parameter]
|
||||
public string? SelectedTorrent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public SortDirection SortDirection { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? SortColumn { get; set; }
|
||||
|
||||
protected IEnumerable<Torrent>? OrderedTorrents => GetOrderedTorrents();
|
||||
|
||||
private IEnumerable<Torrent>? GetOrderedTorrents()
|
||||
{
|
||||
if (Torrents is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sortSelector = TorrentList.ColumnsDefinitions.Find(t => t.Id == SortColumn)?.SortSelector ?? (t => t.Name);
|
||||
|
||||
return Torrents.OrderByDirection(SortDirection, sortSelector);
|
||||
}
|
||||
|
||||
protected void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
|
||||
@@ -226,6 +226,23 @@ namespace Lantean.QBTMudBlade
|
||||
return (List<PropertyFilterDefinition<T>>?)dialogResult.Data;
|
||||
}
|
||||
|
||||
public static async Task<(HashSet<string> SelectedColumns, Dictionary<string, int?> ColumnWidths)> ShowColumnsOptionsDialog<T>(this IDialogService dialogService, List<ColumnDefinition<T>> columnDefinitions, Dictionary<string, int?> widths)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ColumnOptionsDialog<T>.Columns), columnDefinitions },
|
||||
};
|
||||
|
||||
var reference = await dialogService.ShowAsync<ColumnOptionsDialog<T>>("Column Options", parameters, FormDialogOptions);
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return ((HashSet<string>, Dictionary<string, int?>))result.Data;
|
||||
}
|
||||
|
||||
public static async Task InvokeRssRulesDialog(this IDialogService dialogService)
|
||||
{
|
||||
await Task.Delay(0);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@layout LoggedInLayout
|
||||
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" DisableOverlay="true">
|
||||
<TorrentsListNav Torrents="Torrents" SelectedTorrent="@SelectedTorrent" />
|
||||
<TorrentsListNav Torrents="Torrents" SelectedTorrent="@SelectedTorrent" SortDirection="SortDirection" SortColumn="@SortColumn" />
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
@Body
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
@@ -11,6 +12,13 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
[CascadingParameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SortColumn")]
|
||||
public string? SortColumn { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SortDirection")]
|
||||
public SortDirection SortDirection { get; set; }
|
||||
|
||||
|
||||
protected string? SelectedTorrent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
|
||||
@@ -12,13 +12,21 @@
|
||||
<CascadingValue Value="Torrents">
|
||||
<CascadingValue Value="MainData">
|
||||
<CascadingValue Value="Preferences">
|
||||
<CascadingValue Value="CategoryChanged" Name="CategoryChanged">
|
||||
<CascadingValue Value="StatusChanged" Name="StatusChanged">
|
||||
<CascadingValue Value="TagChanged" Name="TagChanged">
|
||||
<CascadingValue Value="TrackerChanged" Name="TrackerChanged">
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
<CascadingValue Value="@(MainData?.LostConnection ?? false)" Name="LostConnection">
|
||||
@Body
|
||||
<CascadingValue Value="SortColumnChanged" Name="SortColumnChanged">
|
||||
<CascadingValue Value="SortColumn" Name="SortColumn">
|
||||
<CascadingValue Value="SortDirectionChanged" Name="SortDirectionChanged">
|
||||
<CascadingValue Value="SortDirection" Name="SortDirection">
|
||||
<CascadingValue Value="CategoryChanged" Name="CategoryChanged">
|
||||
<CascadingValue Value="StatusChanged" Name="StatusChanged">
|
||||
<CascadingValue Value="TagChanged" Name="TagChanged">
|
||||
<CascadingValue Value="TrackerChanged" Name="TrackerChanged">
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
<CascadingValue Value="@(MainData?.LostConnection ?? false)" Name="LostConnection">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
@@ -29,28 +37,28 @@
|
||||
<MudAppBar Bottom="true" Style="background-color: var(--mud-palette-dark-lighten);">
|
||||
@if (MainData?.LostConnection == true)
|
||||
{
|
||||
<MudText Color="Color.Error">qBittorrent client is not reachable</MudText>
|
||||
<MudText Class="pl-1 pr-1 pt-1" Color="Color.Error">qBittorrent client is not reachable</MudText>
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudText Class="pl-1 pr-1">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
|
||||
<MudText Class="pl-1 pr-1 pt-1">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="pl-1 pr-1">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
|
||||
<MudText Class="pl-1 pr-1 pt-1">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
@{
|
||||
var (icon, colour) = GetConnectionIcon(MainData?.ServerState.ConnectionStatus);
|
||||
}
|
||||
<MudIcon Class="pl-1 pr-1" Icon="@icon" Color="@colour" Title="MainData?.ServerState.ConnectionStatus" />
|
||||
<MudIcon Class="pl-1 pr-1 pt-1" Icon="@icon" Color="@colour" Title="MainData?.ServerState.ConnectionStatus" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="pl-1 pr-1" Icon="@Icons.Material.Outlined.Speed" Color="@((MainData?.ServerState.UseAltSpeedLimits ?? false) ? Color.Error : Color.Success)" />
|
||||
<MudIcon Class="pl-1 pr-1 pt-1" Icon="@Icons.Material.Outlined.Speed" Color="@((MainData?.ServerState.UseAltSpeedLimits ?? false) ? Color.Error : Color.Success)" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="pl-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowUp" Color="Color.Success" />
|
||||
<MudText Class="pr-1">
|
||||
<MudIcon Class="pl-1 pt-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowUp" Color="Color.Success" />
|
||||
<MudText Class="pr-1 pt-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoData, "(", ")")
|
||||
</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="pl-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowDown" Color="Color.Info" />
|
||||
<MudText Class="pr-1">
|
||||
<MudIcon Class="pl-1 pt-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowDown" Color="Color.Info" />
|
||||
<MudText Class="pr-1 pt-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoData, "(", ")")
|
||||
</MudText>
|
||||
|
||||
@@ -39,6 +39,10 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
|
||||
protected QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
protected string? SortColumn { get; set; }
|
||||
|
||||
protected SortDirection SortDirection { get; set; }
|
||||
|
||||
protected string Version { get; set; } = "";
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
@@ -142,6 +146,10 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, term => SearchText = term);
|
||||
|
||||
protected EventCallback<string> SortColumnChanged => EventCallback.Factory.Create<string>(this, columnId => SortColumn = columnId);
|
||||
|
||||
protected EventCallback<SortDirection> SortDirectionChanged => EventCallback.Factory.Create<SortDirection>(this, sortDirection => SortDirection = sortDirection);
|
||||
|
||||
protected static (string, Color) GetConnectionIcon(string? status)
|
||||
{
|
||||
if (status is null)
|
||||
|
||||
@@ -2,35 +2,36 @@
|
||||
|
||||
|
||||
<EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
|
||||
|
||||
<MudThemeProvider @ref="MudThemeProvider" @bind-IsDarkMode="IsDarkMode" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<PageTitle>qBittorrent Web UI</PageTitle>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1">
|
||||
<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>
|
||||
<MudSpacer />
|
||||
@if (ErrorBoundary?.Errors.Count > 0)
|
||||
{
|
||||
<MudBadge Content="@(ErrorBoundary?.Errors.Count ?? 0)" Color="Color.Error" Overlap="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
|
||||
</MudBadge>
|
||||
}
|
||||
@if (ShowMenu)
|
||||
{
|
||||
<Menu />
|
||||
}
|
||||
</MudAppBar>
|
||||
<MudDrawer Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
|
||||
<ErrorDisplay ErrorBoundary="ErrorBoundary" />
|
||||
</MudDrawer>
|
||||
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
|
||||
<CascadingValue Value="IsDarkMode" Name="IsDarkMode">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</MudLayout>
|
||||
</EnhancedErrorBoundary>
|
||||
|
||||
<MudThemeProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<PageTitle>qBittorrent Web UI</PageTitle>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1">
|
||||
<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>
|
||||
<MudSpacer />
|
||||
@if (ErrorBoundary?.Errors.Count > 0)
|
||||
{
|
||||
<MudBadge Content="@(ErrorBoundary?.Errors.Count ?? 0)" Color="Color.Error" Overlap="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
|
||||
</MudBadge>
|
||||
}
|
||||
@if (ShowMenu)
|
||||
{
|
||||
<Menu />
|
||||
}
|
||||
</MudAppBar>
|
||||
<MudDrawer Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
|
||||
<ErrorDisplay ErrorBoundary="ErrorBoundary" />
|
||||
</MudDrawer>
|
||||
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</MudLayout>
|
||||
@@ -30,6 +30,10 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
|
||||
protected EnhancedErrorBoundary? ErrorBoundary { get; set; }
|
||||
|
||||
protected bool IsDarkMode { get; set; }
|
||||
|
||||
protected MudThemeProvider MudThemeProvider { get; set; } = default!;
|
||||
|
||||
ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new()
|
||||
{
|
||||
ReportRate = 50,
|
||||
@@ -53,10 +57,17 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
IsDarkMode = await MudThemeProvider.GetSystemPreference();
|
||||
await MudThemeProvider.WatchSystemPreference(OnSystemPreferenceChanged);
|
||||
await BrowserViewportService.SubscribeAsync(this, fireImmediately: true);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
protected async Task OnSystemPreferenceChanged(bool value)
|
||||
{
|
||||
IsDarkMode = value;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
[DebuggerDisplay("{Name}")]
|
||||
public class ContentItem
|
||||
{
|
||||
public ContentItem(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Pages
|
||||
@@ -48,7 +47,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
return torrent.Name;
|
||||
}
|
||||
|
||||
protected async Task PauseTorrent(MouseEventArgs eventArgs)
|
||||
protected async Task PauseTorrent()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
@@ -58,7 +57,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
await ApiClient.PauseTorrent(Hash);
|
||||
}
|
||||
|
||||
protected async Task ResumeTorrent(MouseEventArgs eventArgs)
|
||||
protected async Task ResumeTorrent()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
@@ -68,7 +67,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
await ApiClient.ResumeTorrent(Hash);
|
||||
}
|
||||
|
||||
protected async Task RemoveTorrent(MouseEventArgs eventArgs)
|
||||
protected async Task RemoveTorrent()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
@@ -83,6 +82,8 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
}
|
||||
|
||||
await ApiClient.DeleteTorrent(Hash, (bool)result.Data);
|
||||
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
protected void NavigateBack()
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
#if DEBUG
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await DoLogin("admin", "pdqYws6EQ");
|
||||
await DoLogin("admin", "rp46PuPVn");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -12,60 +12,22 @@
|
||||
<MudSpacer />
|
||||
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
<MudTable
|
||||
Items="OrderedTorrents"
|
||||
T="Torrent"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
HeaderClass="table-head-bordered"
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Loading="@(Torrents is null)"
|
||||
SelectOnRowClick="false"
|
||||
Striped="true"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
MultiSelection="true"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
HorizontalScrollbar="true"
|
||||
OnRowClick="RowClick"
|
||||
RowStyleFunc="RowStyle"
|
||||
Virtualize="true"
|
||||
Class="torrent-list">
|
||||
<ColGroup>
|
||||
<col style="width: 30px" />
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
var style = column.Width.HasValue ? $"width: {column.Width.Value}px" : null;
|
||||
<col style="@style" />
|
||||
}
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTh>
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<MudTableSortLabel T="Torrent" SortDirectionChanged="@(c => SetSort(column.Id, c))">@column.Header</MudTableSortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@column.Header
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0">
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="Torrent"
|
||||
ColumnDefinitions="Columns"
|
||||
ColumnFilter="@(c => c.Id != "#" || Preferences?.QueueingEnabled == true)"
|
||||
Items="Torrents"
|
||||
OnRowClick="RowClick"
|
||||
MultiSelection="true"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
SortColumnChanged="SortColumnChangedHandler"
|
||||
SortDirectionChanged="SortDirectionChangedHandler"
|
||||
/>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private static RenderFragment<RowContext<Torrent>> ProgressBarColumn
|
||||
@@ -76,7 +38,7 @@
|
||||
{
|
||||
var value = (float?)context.GetValue();
|
||||
var color = value < 1 ? Color.Success : Color.Info;
|
||||
<MudProgressLinear title="Progress" Color="@(color)" Value="@((value ?? 0) * 100)" Class="progress-expand" Size="Size.Large">
|
||||
<MudProgressLinear title="Progress" Color="@color" Value="@((value ?? 0) * 100)" Class="progress-expand" Size="Size.Large">
|
||||
@DisplayHelpers.Percentage(value)
|
||||
</MudProgressLinear>;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Components;
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -10,9 +11,6 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
{
|
||||
public partial class TorrentList
|
||||
{
|
||||
private const string _columnSelectionStorageKey = "TorrentList.ColumnSelection";
|
||||
private const string _columnSortStorageKey = "TorrentList.ColumnSort";
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
@@ -31,11 +29,15 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
[CascadingParameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
|
||||
protected IEnumerable<Torrent>? OrderedTorrents => GetOrderedTorrents();
|
||||
|
||||
[CascadingParameter(Name = "SearchTermChanged")]
|
||||
public EventCallback<string> SearchTermChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SortColumnChanged")]
|
||||
public EventCallback<string> SortColumnChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SortDirectionChanged")]
|
||||
public EventCallback<SortDirection> SortDirectionChanged { get; set; }
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected Torrent? SelectedTorrent { get; set; }
|
||||
@@ -44,51 +46,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
|
||||
protected bool ToolbarButtonsEnabled => SelectedItems.Count > 0 || SelectedTorrent is not null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var selectedColumns = await LocalStorage.GetItemAsync<HashSet<string>>(_columnSelectionStorageKey);
|
||||
if (selectedColumns is not null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
SelectedColumns = _columns.Where(c => c.Enabled).Select(c => c.Id).ToHashSet();
|
||||
if (Preferences?.QueueingEnabled == false)
|
||||
{
|
||||
SelectedColumns.Remove("#");
|
||||
}
|
||||
}
|
||||
_sortColumn ??= _columns.First(c => c.Enabled).Id;
|
||||
|
||||
if (SelectedTorrent is not null && Torrents is not null && !Torrents.Contains(SelectedTorrent))
|
||||
{
|
||||
SelectedTorrent = null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Torrent>? GetOrderedTorrents()
|
||||
{
|
||||
if (Torrents is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sortSelector = _columns.Find(c => c.Id == _sortColumn)?.SortSelector;
|
||||
|
||||
return Torrents.OrderByDirection(_sortDirection, sortSelector ?? (t => t.Priority));
|
||||
}
|
||||
protected DynamicTable<Torrent>? Table { get; set; }
|
||||
|
||||
protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
|
||||
{
|
||||
@@ -99,13 +57,28 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SortDirectionChangedHandler(SortDirection sortDirection)
|
||||
{
|
||||
await SortDirectionChanged.InvokeAsync(sortDirection);
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(Torrent torrent)
|
||||
{
|
||||
SelectedTorrent = torrent;
|
||||
}
|
||||
|
||||
protected async Task SortColumnChangedHandler(string columnId)
|
||||
{
|
||||
await SortColumnChanged.InvokeAsync(columnId);
|
||||
}
|
||||
|
||||
protected async Task SearchTextChanged(string text)
|
||||
{
|
||||
SearchText = text;
|
||||
await SearchTermChanged.InvokeAsync(SearchText);
|
||||
}
|
||||
|
||||
protected async Task PauseTorrents(MouseEventArgs eventArgs)
|
||||
protected async Task PauseTorrents()
|
||||
{
|
||||
await ApiClient.PauseTorrents(GetSelectedTorrents());
|
||||
|
||||
@@ -113,7 +86,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task ResumeTorrents(MouseEventArgs eventArgs)
|
||||
protected async Task ResumeTorrents()
|
||||
{
|
||||
await ApiClient.ResumeTorrents(GetSelectedTorrents());
|
||||
|
||||
@@ -121,7 +94,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task RemoveTorrents(MouseEventArgs eventArgs)
|
||||
protected async Task RemoveTorrents()
|
||||
{
|
||||
var reference = await DialogService.ShowAsync<DeleteDialog>("Remove torrent(s)?");
|
||||
var result = await reference.Result;
|
||||
@@ -136,44 +109,18 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task AddTorrentFile(MouseEventArgs eventArgs)
|
||||
protected async Task AddTorrentFile()
|
||||
{
|
||||
await DialogService.InvokeAddTorrentFileDialog(ApiClient);
|
||||
}
|
||||
|
||||
protected async Task AddTorrentLink(MouseEventArgs eventArgs)
|
||||
protected async Task AddTorrentLink()
|
||||
{
|
||||
await DialogService.InvokeAddTorrentLinkDialog(ApiClient);
|
||||
}
|
||||
|
||||
protected void RowClick(TableRowClickEventArgs<Torrent> eventArgs)
|
||||
{
|
||||
if (eventArgs.MouseEventArgs.CtrlKey)
|
||||
{
|
||||
if (SelectedItems.Contains(eventArgs.Item))
|
||||
{
|
||||
SelectedItems.Remove(eventArgs.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItems.Add(eventArgs.Item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedItems.Contains(eventArgs.Item))
|
||||
{
|
||||
SelectedItems.Remove(eventArgs.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
SelectedItems.Add(eventArgs.Item);
|
||||
}
|
||||
|
||||
SelectedTorrent = eventArgs.Item;
|
||||
|
||||
if (eventArgs.MouseEventArgs.Detail > 1)
|
||||
{
|
||||
NavigationManager.NavigateTo("/details/" + SelectedTorrent);
|
||||
@@ -195,29 +142,19 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
return [];
|
||||
}
|
||||
|
||||
protected void Options(MouseEventArgs eventArgs)
|
||||
protected void Options()
|
||||
{
|
||||
NavigationManager.NavigateTo("/options");
|
||||
}
|
||||
|
||||
protected async Task ColumnOptions()
|
||||
public async Task ColumnOptions()
|
||||
{
|
||||
DialogParameters parameters = new DialogParameters
|
||||
{
|
||||
{ "Columns", _columns }
|
||||
};
|
||||
|
||||
var reference = await DialogService.ShowAsync<ColumnOptionsDialog<Torrent>>("ColumnOptions", parameters, DialogHelper.FormDialogOptions);
|
||||
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
if (Table is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedColumns = (HashSet<string>)result.Data;
|
||||
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
await Table.ShowColumnOptionsDialog();
|
||||
}
|
||||
|
||||
protected void ShowTorrent()
|
||||
@@ -229,35 +166,12 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
NavigationManager.NavigateTo("/details/" + SelectedTorrent.Hash);
|
||||
}
|
||||
|
||||
protected string RowStyle(Torrent torrent, int index)
|
||||
{
|
||||
var style = "user-select: none; cursor: pointer;";
|
||||
if (torrent == SelectedTorrent)
|
||||
{
|
||||
style += " background: #D3D3D3";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
protected IEnumerable<ColumnDefinition<Torrent>> Columns => ColumnsDefinitions;
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = new HashSet<string>();
|
||||
|
||||
protected IEnumerable<ColumnDefinition<Torrent>> GetColumns()
|
||||
{
|
||||
return _columns.Where(c => SelectedColumns.Contains(c.Id));
|
||||
}
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
{
|
||||
_sortColumn = columnId;
|
||||
_sortDirection = sortDirection;
|
||||
|
||||
await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
|
||||
}
|
||||
|
||||
protected List<ColumnDefinition<Torrent>> _columns =
|
||||
public static List<ColumnDefinition<Torrent>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
CreateColumnDefinition("#", t => t.Priority),
|
||||
CreateColumnDefinition("State Icon", t => t.State, IconColumn),
|
||||
CreateColumnDefinition("", t => t.State, IconColumn),
|
||||
CreateColumnDefinition("Name", t => t.Name, width: 200),
|
||||
CreateColumnDefinition("Size", t => t.Size, t => DisplayHelpers.Size(t.Size)),
|
||||
CreateColumnDefinition("Total Size", t => t.TotalSize, t => DisplayHelpers.Size(t.TotalSize), enabled: false),
|
||||
@@ -291,9 +205,6 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
//CreateColumnDefinition("Reannounce In", t => t.Reannounce, enabled: false),
|
||||
];
|
||||
|
||||
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)
|
||||
{
|
||||
var cd = new ColumnDefinition<Torrent>(name, selector, rowTemplate);
|
||||
|
||||
@@ -594,26 +594,35 @@ namespace Lantean.QBTMudBlade.Services
|
||||
|
||||
var directories = contents.Where(c => c.Value.IsFolder).OrderByDescending(c => c.Value.Level);
|
||||
|
||||
foreach (var key in directories.Select(d => d.Key))
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
var directoryContents = contents.Where(c => c.Value.Name.StartsWith(key + Extensions.DirectorySeparator) && !c.Value.IsFolder);
|
||||
var key = directory.Key;
|
||||
var level = directory.Value.Level;
|
||||
var filesContents = contents.Where(c => c.Value.Name.StartsWith(key + Extensions.DirectorySeparator) && !c.Value.IsFolder).ToList();
|
||||
var directoriesContents = contents.Where(c => c.Value.Name.StartsWith(key + Extensions.DirectorySeparator) && c.Value.IsFolder && c.Value.Level == level + 1).ToList();
|
||||
var directoryContents = filesContents.Concat(directoriesContents);
|
||||
|
||||
|
||||
var size = directoryContents.Sum(c => c.Value.Size);
|
||||
var availability = directoryContents.Average(c => c.Value.Availability);
|
||||
var downloaded = directoryContents.Sum(c => c.Value.Downloaded);
|
||||
var progress = (float)downloaded / size;
|
||||
|
||||
var content = contents[key];
|
||||
content.Availability = availability;
|
||||
content.Size = size;
|
||||
content.Progress = progress;
|
||||
if (!contents.TryGetValue(key, out var dir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
dir.Availability = availability;
|
||||
dir.Size = size;
|
||||
dir.Progress = progress;
|
||||
var priorities = directoryContents.Select(d => d.Value.Priority).Distinct();
|
||||
if (priorities.Count() == 1)
|
||||
{
|
||||
content.Priority = priorities.First();
|
||||
dir.Priority = priorities.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
content.Priority = Priority.Mixed;
|
||||
dir.Priority = Priority.Mixed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace Lantean.QBTMudBlade
|
||||
|
||||
public string? Class { get; set; }
|
||||
|
||||
public Func<T, string?>? ClassFunc { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public SortDirection InitialDirection { get; set; } = SortDirection.None;
|
||||
|
||||
@@ -154,4 +154,16 @@ td.no-wrap {
|
||||
overflow: auto;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.overflow-cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-select .mud-input-control > .mud-input-control-input-container > div.mud-input.mud-input-text {
|
||||
margin-top: 0;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
Reference in New Issue
Block a user