mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-11-02 04:53:19 +00:00
Code cleanup, bugfixes and rework of torrent actions submenus for touch devices.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lantean.QBitTorrentClient\Lantean.QBitTorrentClient.csproj" />
|
||||
<ProjectReference Include="..\Lantean.QBTMudBlade\Lantean.QBTMudBlade.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudFileUpload T="IReadOnlyList<IBrowserFile>" FilesChanged="UploadFiles" Accept=".torrent">
|
||||
<MudFileUpload T="IReadOnlyList<IBrowserFile>" FilesChanged="UploadFiles" Accept=".torrent" MaximumFileCount="50" >
|
||||
<ButtonTemplate>
|
||||
<MudButton HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="@Label" Value="@Value" />
|
||||
<MudTextField T="T" Label="@Label" Value="@Value" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField Label="@Label" Value="@Value" Min="Min" Max="Max" />
|
||||
<MudNumericField T="T" Label="@Label" Value="@Value" Min="@Min" Max="@Max" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSlider ValueLabel="true" Value="@Value" Min="Min" Max="Max" />
|
||||
<MudSlider T="T" ValueLabel="true" Value="@Value" Min="@Min" Max="@Max" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<MudDialog ContentStyle="mix-width: 400px">
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xl="12">
|
||||
<TorrentActions Hashes="Hashes" ParentAction="ParentAction" RenderType="RenderType.Children" AfterAction="CloseDialog" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class SubMenuDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public TorrentAction? ParentAction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<string> Hashes { get; set; } = [];
|
||||
|
||||
protected Task CloseDialog()
|
||||
{
|
||||
MudDialog.Close();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,19 +11,20 @@
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Loading="@(Items is null)"
|
||||
SelectOnRowClick="false"
|
||||
Striped="Striped"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
HorizontalScrollbar="true"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
SelectOnRowClick="false"
|
||||
Loading="@(Items is null)"
|
||||
MultiSelection="MultiSelection"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChangedInternal"
|
||||
HorizontalScrollbar="true"
|
||||
|
||||
OnRowClick="OnRowClickInternal"
|
||||
RowStyleFunc="RowStyleFuncInternal"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
Class="@Class">
|
||||
<ColGroup>
|
||||
<col style="width: 30px" />
|
||||
@@ -35,10 +36,11 @@
|
||||
<HeaderContent>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTh Class="overflow-cell" Style="@(GetColumnStyle(column))">
|
||||
var className = column.IconOnly ? null : "overflow-cell";
|
||||
<MudTh Class="@className" 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>
|
||||
<SortLabel SortDirectionChanged="@(c => SetSort(column.Id, c))" SortDirection="@(column.Id == _sortColumn ? _sortDirection : SortDirection.None)">@column.Header</SortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
public T? SelectedItem { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<ColumnDefinition<T>, bool> ColumnFilter { get; set; } = (t => true);
|
||||
public Func<ColumnDefinition<T>, bool> ColumnFilter { get; set; } = t => true;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> SortColumnChanged { get; set; }
|
||||
@@ -104,11 +104,11 @@ namespace Lantean.QBTMudBlade.Components
|
||||
string? sortColumn;
|
||||
SortDirection sortDirection;
|
||||
|
||||
var storedColumnSort = await LocalStorage.GetItemAsync<Tuple<string, SortDirection>>(_columnSortStorageKey);
|
||||
if (storedColumnSort is not null)
|
||||
var sortData = await LocalStorage.GetItemAsync<SortData>(_columnSortStorageKey);
|
||||
if (sortData is not null)
|
||||
{
|
||||
sortColumn = storedColumnSort.Item1;
|
||||
sortDirection = storedColumnSort.Item2;
|
||||
sortColumn = sortData.SortColumn;
|
||||
sortDirection = sortData.SortDirection;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -172,6 +172,12 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
{
|
||||
if (sortDirection == SortDirection.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await LocalStorage.SetItemAsync(_columnSortStorageKey, new SortData(columnId, sortDirection));
|
||||
|
||||
if (_sortColumn != columnId)
|
||||
{
|
||||
_sortColumn = columnId;
|
||||
@@ -183,8 +189,6 @@ namespace Lantean.QBTMudBlade.Components
|
||||
_sortDirection = sortDirection;
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
|
||||
await LocalStorage.SetItemAsync(_columnSortStorageKey, new Tuple<string, SortDirection>(columnId, sortDirection));
|
||||
}
|
||||
|
||||
protected async Task OnRowClickInternal(TableRowClickEventArgs<T> eventArgs)
|
||||
@@ -200,7 +204,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
var style = "user-select: none; cursor: pointer;";
|
||||
if (EqualityComparer<T>.Default.Equals(item, SelectedItem))
|
||||
{
|
||||
style += " background-color: var(--mud-palette-dark-darken)";
|
||||
style += " background-color: var(--mud-palette-grey-dark); color: var(--mud-palette-grey-light) !important;";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
@@ -208,6 +212,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
protected async Task SelectedItemsChangedInternal(HashSet<T> selectedItems)
|
||||
{
|
||||
await SelectedItemsChanged.InvokeAsync(selectedItems);
|
||||
SelectedItems = selectedItems;
|
||||
}
|
||||
|
||||
public async Task ShowColumnOptionsDialog()
|
||||
@@ -222,8 +227,8 @@ namespace Lantean.QBTMudBlade.Components
|
||||
if (!SelectedColumns.SetEquals(result.SelectedColumns))
|
||||
{
|
||||
SelectedColumns = result.SelectedColumns;
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnWidths, result.ColumnWidths))
|
||||
@@ -275,5 +280,18 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
private sealed record SortData
|
||||
{
|
||||
public SortData(string sortColumn, SortDirection sortDirection)
|
||||
{
|
||||
SortColumn = sortColumn;
|
||||
SortDirection = sortDirection;
|
||||
}
|
||||
|
||||
public string SortColumn { get; init; }
|
||||
|
||||
public SortDirection SortDirection { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
<MudListItem OnClick="ClearErrors">Clear Errors</MudListItem>
|
||||
<MudListItem OnClick="ClearErrorsAndResumeAsync">Clear Errors and Resume</MudListItem>
|
||||
<MudDivider />
|
||||
@foreach (var error in Errors)
|
||||
{
|
||||
<MudListItem OnClick="@(e => ShowException(error))">@error.Message</MudListItem>
|
||||
}
|
||||
@foreach (var error in Errors)
|
||||
{
|
||||
<MudListItem OnClick="@(e => ShowException(error))">@error.Message</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
@@ -1,63 +0,0 @@
|
||||
@inherits MudTable<T>
|
||||
@typeparam T
|
||||
|
||||
@{
|
||||
base.BuildRenderTree(__builder);
|
||||
}
|
||||
|
||||
@code {
|
||||
private RenderFragment ColGroupFragment(IEnumerable<ColumnDefinition<T>> columns) =>
|
||||
@<NonRendering>
|
||||
@if (MultiSelection)
|
||||
{
|
||||
<col />
|
||||
}
|
||||
|
||||
@foreach (var column in columns)
|
||||
{
|
||||
var style = column.Width.HasValue ? $"width: {column.Width.Value}px" : null;
|
||||
<col style="@style" />
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
private RenderFragment HeaderContentFragment(IEnumerable<ColumnDefinition<T>> columns) =>
|
||||
@<NonRendering>
|
||||
@foreach (var column in columns)
|
||||
{
|
||||
<MudTh>
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<MudTableSortLabel T="T" SortDirectionChanged="@(c => SetSort(column.SortSelector, c))">@column.Header</MudTableSortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@column.Header
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
private RenderFragment<T> RowTemplateFragment(IEnumerable<ColumnDefinition<T>> columns) => data =>
|
||||
@<NonRendering>
|
||||
@foreach (var column in columns)
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(data))
|
||||
</MudTd>
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
|
||||
private RenderFragment<T> RowTemplateFragment2(IEnumerable<ColumnDefinition<T>> columns)
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
foreach (var column in columns)
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</MudTd>
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class ExtendedTable<T> : MudTable<T>
|
||||
{
|
||||
[Parameter]
|
||||
public IEnumerable<ColumnDefinition<T>>? ColumnDefinitions { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public HashSet<ColumnDefinition<T>> SelectedColumns { get; set; } = [];
|
||||
|
||||
private Func<T, object?>? _sortSelector;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
private IEnumerable<string>? _selectedColumns;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (ColumnDefinitions is not null)
|
||||
{
|
||||
var activeColumns = GetActiveColummns(ColumnDefinitions);
|
||||
ColGroup ??= ColGroupFragment(activeColumns);
|
||||
HeaderContent ??= HeaderContentFragment(activeColumns);
|
||||
RowTemplate ??= RowTemplateFragment(activeColumns);
|
||||
_selectedColumns ??= ColumnDefinitions.Where(c => c.Enabled).Select(c => c.Id).ToList();
|
||||
_sortSelector ??= ColumnDefinitions.First(c => c.Enabled).SortSelector;
|
||||
Items = GetOrderedItems(Items, _sortSelector);
|
||||
}
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
private IEnumerable<T>? GetOrderedItems(IEnumerable<T>? items, Func<T, object?> sortSelector)
|
||||
{
|
||||
if (items is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return items.OrderByDirection(_sortDirection, sortSelector);
|
||||
}
|
||||
|
||||
private void SetSort(Func<T, object?> sortSelector, SortDirection sortDirection)
|
||||
{
|
||||
_sortSelector = sortSelector;
|
||||
_sortDirection = sortDirection;
|
||||
}
|
||||
|
||||
private IEnumerable<ColumnDefinition<T>>? GetColumns()
|
||||
{
|
||||
if (ColumnDefinitions is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetActiveColummns(ColumnDefinitions);
|
||||
}
|
||||
|
||||
private IEnumerable<ColumnDefinition<T>> GetActiveColummns(IEnumerable<ColumnDefinition<T>> columns)
|
||||
{
|
||||
if (_selectedColumns is null)
|
||||
{
|
||||
return columns;
|
||||
}
|
||||
return columns.Where(c => _selectedColumns.Contains(c.Id));
|
||||
}
|
||||
|
||||
//private RenderFragment CreateColGroup()
|
||||
//{
|
||||
// return builder =>
|
||||
// {
|
||||
// var selectedColumns = GetColumns();
|
||||
// if (selectedColumns is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (MultiSelection)
|
||||
// {
|
||||
// builder.OpenElement(0, "col");
|
||||
// builder.CloseElement();
|
||||
// }
|
||||
|
||||
// int sequence = 1;
|
||||
// foreach (var width in selectedColumns.Select(c => c.Width))
|
||||
// {
|
||||
// builder.OpenElement(sequence++, "col");
|
||||
// if (width.HasValue)
|
||||
// {
|
||||
// builder.AddAttribute(sequence++, "style", $"width: {width.Value}px");
|
||||
// }
|
||||
// builder.CloseElement();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
|
||||
//private RenderFragment CreateHeaderContent()
|
||||
//{
|
||||
// return builder =>
|
||||
// {
|
||||
// var selectedColumns = GetColumns();
|
||||
// if (selectedColumns is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// int sequence = 0;
|
||||
// foreach (var columnDefinition in selectedColumns)
|
||||
// {
|
||||
// builder.OpenComponent<MudTh>(sequence);
|
||||
// if (columnDefinition.SortSelector is not null)
|
||||
// {
|
||||
// builder.OpenComponent<MudTableSortLabel<T>>(sequence++);
|
||||
// builder.AddAttribute(sequence++, "SortDirectionChanged", EventCallback.Factory.Create<SortDirection>(this, c => SetSort(columnDefinition.SortSelector, c)));
|
||||
// RenderFragment childContent = b => b.AddContent(0, columnDefinition.Header);
|
||||
// builder.AddAttribute(sequence++, "ChildContent", childContent);
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// RenderFragment childContent = b => b.AddContent(0, columnDefinition.Header);
|
||||
// builder.AddAttribute(sequence++, "ChildContent", childContent);
|
||||
// }
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
|
||||
//private RenderFragment<T> CreateRowTemplate()
|
||||
//{
|
||||
// return context => builder =>
|
||||
// {
|
||||
// var selectedColumns = GetColumns();
|
||||
// if (selectedColumns is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// int sequence = 0;
|
||||
// foreach (var columnDefinition in selectedColumns)
|
||||
// {
|
||||
// builder.OpenComponent<MudTd>(sequence++);
|
||||
// builder.AddAttribute(sequence++, "DataLabel", columnDefinition.Header);
|
||||
// builder.AddAttribute(sequence++, "Class", columnDefinition.Class);
|
||||
// RenderFragment childContent = b => b.AddContent(0, columnDefinition.RowTemplate(columnDefinition.GetRowContext(context)));
|
||||
// builder.AddAttribute(sequence++, "ChildContent", childContent);
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,8 @@
|
||||
T="ContentItem"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Files"
|
||||
MultiSelection="true"
|
||||
MultiSelection="false"
|
||||
PreSorted="true"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
@@ -43,7 +41,7 @@
|
||||
<div style="@($"margin-left: {context.Data.Level * 14}px")">
|
||||
@if (context.Data.IsFolder)
|
||||
{
|
||||
<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>
|
||||
<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))"></MudIconButton>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-0" Style="margin-right: 4px; position: relative; top: 7px; margin-left: -15px" />
|
||||
}
|
||||
@context.Data.DisplayName
|
||||
|
||||
@@ -5,18 +5,16 @@ using Lantean.QBTMudBlade.Filter;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class FilesTab : IAsyncDisposable
|
||||
{
|
||||
private readonly bool _refreshEnabled = true;
|
||||
|
||||
private const string _expandedNodesStorageKey = "FilesTab.ExpandedNodes";
|
||||
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
@@ -36,6 +34,9 @@ namespace Lantean.QBTMudBlade.Components
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
@@ -45,8 +46,6 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
protected IEnumerable<ContentItem> Files => GetFiles();
|
||||
|
||||
protected HashSet<ContentItem> SelectedItems { get; set; } = [];
|
||||
|
||||
private List<PropertyFilterDefinition<ContentItem>>? _filterDefinitions;
|
||||
|
||||
protected ContentItem? SelectedItem { get; set; }
|
||||
@@ -59,6 +58,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
private DynamicTable<ContentItem>? Table { get; set; }
|
||||
|
||||
private string? _previousHash;
|
||||
private string? _sortColumn;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
@@ -110,15 +110,9 @@ namespace Lantean.QBTMudBlade.Components
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
protected async Task RemoveFilter()
|
||||
protected void RemoveFilter()
|
||||
{
|
||||
Filters = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -160,15 +154,9 @@ namespace Lantean.QBTMudBlade.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SearchTextChanged(string value)
|
||||
protected void SearchTextChanged(string value)
|
||||
{
|
||||
SearchText = value;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
}
|
||||
|
||||
protected async Task EnabledValueChanged(ContentItem contentItem, bool value)
|
||||
@@ -237,10 +225,25 @@ namespace Lantean.QBTMudBlade.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (Hash == _previousHash)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_previousHash = Hash;
|
||||
|
||||
var contents = await ApiClient.GetTorrentContents(Hash);
|
||||
FileList = DataManager.CreateContentsList(contents);
|
||||
|
||||
SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
var expandedNodes = await LocalStorage.GetItemAsync<HashSet<string>>($"{_expandedNodesStorageKey}.{Hash}");
|
||||
if (expandedNodes is not null)
|
||||
{
|
||||
ExpandedNodes = expandedNodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpandedNodes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task PriorityValueChanged(ContentItem contentItem, Priority priority)
|
||||
@@ -253,7 +256,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
IEnumerable<int> fileIndexes;
|
||||
if (contentItem.IsFolder)
|
||||
{
|
||||
fileIndexes = GetChildren(contentItem).Where(c => !c.IsFolder).Select(c => c.Index);
|
||||
fileIndexes = GetDescendants(contentItem).Where(c => !c.IsFolder).Select(c => c.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -269,11 +272,13 @@ namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var contentItem = FileList.Values.FirstOrDefault(c => c.Index == SelectedItem.Index);
|
||||
if (contentItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var name = contentItem.GetFileName();
|
||||
await DialogService.ShowSingleFieldDialog("Rename", "New name", name, async v => await ApiClient.RenameFile(Hash, contentItem.Name, contentItem.Path + v));
|
||||
}
|
||||
@@ -293,44 +298,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
SelectedItem = item;
|
||||
}
|
||||
|
||||
protected async Task SelectedItemsChanged(HashSet<ContentItem> selectedItems)
|
||||
{
|
||||
if (Hash is null || Files is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var unselectedItems = Files.Except(SelectedItems);
|
||||
|
||||
if (unselectedItems.Any())
|
||||
{
|
||||
await ApiClient.SetFilePriority(Hash, unselectedItems.Select(c => c.Index), QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
|
||||
foreach (var item in unselectedItems)
|
||||
{
|
||||
Files.First(f => f == item).Priority = Priority.DoNotDownload;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
var existingDoNotDownloads = Files.Where(f => f.Priority == Priority.DoNotDownload);
|
||||
var newlySelectedFiles = selectedItems.Where(f => existingDoNotDownloads.Contains(f));
|
||||
|
||||
if (newlySelectedFiles.Any())
|
||||
{
|
||||
await ApiClient.SetFilePriority(Hash, newlySelectedFiles.Select(c => c.Index), QBitTorrentClient.Models.Priority.Normal);
|
||||
|
||||
foreach (var item in newlySelectedFiles)
|
||||
{
|
||||
Files.First(f => f == item).Priority = Priority.Normal;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ToggleNode(ContentItem contentItem, MouseEventArgs args)
|
||||
protected async Task ToggleNode(ContentItem contentItem)
|
||||
{
|
||||
if (ExpandedNodes.Contains(contentItem.Name))
|
||||
{
|
||||
@@ -341,10 +309,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
ExpandedNodes.Add(contentItem.Name);
|
||||
}
|
||||
|
||||
if (FileList is not null)
|
||||
{
|
||||
SelectedItems = GetFiles().Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
}
|
||||
await LocalStorage.SetItemAsync($"{_expandedNodesStorageKey}.{Hash}", ExpandedNodes);
|
||||
}
|
||||
|
||||
private static QBitTorrentClient.Models.Priority MapPriority(Priority priority)
|
||||
@@ -359,15 +324,15 @@ namespace Lantean.QBTMudBlade.Components
|
||||
return sortSelector ?? (i => i.Name);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem contentItem)
|
||||
private IEnumerable<ContentItem> GetDescendants(ContentItem contentItem)
|
||||
{
|
||||
if (!contentItem.IsFolder || Files is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return Files.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
|
||||
}
|
||||
return FileList!.Values.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem folder, int level)
|
||||
{
|
||||
@@ -545,7 +510,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
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("Name", c => c.Name, width: 400, 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"),
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Lantean.QBTMudBlade.Components.Options
|
||||
CurrentNetworkInterface = Preferences.CurrentNetworkInterface;
|
||||
CurrentInterfaceAddress = Preferences.CurrentInterfaceAddress;
|
||||
SaveResumeDataInterval = Preferences.SaveResumeDataInterval;
|
||||
TorrentFileSizeLimit = Preferences.TorrentFileSizeLimit;
|
||||
TorrentFileSizeLimit = Preferences.TorrentFileSizeLimit / 1024 / 1024;
|
||||
RecheckCompletedTorrents = Preferences.RecheckCompletedTorrents;
|
||||
AppInstanceName = Preferences.AppInstanceName;
|
||||
RefreshInterval = Preferences.RefreshInterval;
|
||||
@@ -109,7 +109,7 @@ namespace Lantean.QBTMudBlade.Components.Options
|
||||
CheckingMemoryUse = Preferences.CheckingMemoryUse;
|
||||
DiskCache = Preferences.DiskCache;
|
||||
DiskCacheTtl = Preferences.DiskCacheTtl;
|
||||
DiskQueueSize = Preferences.DiskQueueSize;
|
||||
DiskQueueSize = Preferences.DiskQueueSize / 1024;
|
||||
DiskIoType = Preferences.DiskIoType;
|
||||
DiskIoReadMode = Preferences.DiskIoReadMode;
|
||||
DiskIoWriteMode = Preferences.DiskIoWriteMode;
|
||||
@@ -120,8 +120,8 @@ namespace Lantean.QBTMudBlade.Components.Options
|
||||
SendBufferLowWatermark = Preferences.SendBufferLowWatermark;
|
||||
SendBufferWatermarkFactor = Preferences.SendBufferWatermarkFactor;
|
||||
ConnectionSpeed = Preferences.ConnectionSpeed;
|
||||
SocketSendBufferSize = Preferences.SocketSendBufferSize;
|
||||
SocketReceiveBufferSize = Preferences.SocketReceiveBufferSize;
|
||||
SocketSendBufferSize = Preferences.SocketSendBufferSize / 1024;
|
||||
SocketReceiveBufferSize = Preferences.SocketReceiveBufferSize / 1024;
|
||||
SocketBacklogSize = Preferences.SocketBacklogSize;
|
||||
OutgoingPortsMin = Preferences.OutgoingPortsMin;
|
||||
OutgoingPortsMax = Preferences.OutgoingPortsMax;
|
||||
@@ -198,7 +198,7 @@ namespace Lantean.QBTMudBlade.Components.Options
|
||||
protected async Task TorrentFileSizeLimitChanged(int value)
|
||||
{
|
||||
TorrentFileSizeLimit = value;
|
||||
UpdatePreferences.TorrentFileSizeLimit = value;
|
||||
UpdatePreferences.TorrentFileSizeLimit = value * 1024 * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ namespace Lantean.QBTMudBlade.Components.Options
|
||||
protected async Task DiskQueueSizeChanged(int value)
|
||||
{
|
||||
DiskQueueSize = value;
|
||||
UpdatePreferences.DiskQueueSize = value;
|
||||
UpdatePreferences.DiskQueueSize = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
@@ -373,14 +373,14 @@ namespace Lantean.QBTMudBlade.Components.Options
|
||||
protected async Task SocketSendBufferSizeChanged(int value)
|
||||
{
|
||||
SocketSendBufferSize = value;
|
||||
UpdatePreferences.SocketSendBufferSize = value;
|
||||
UpdatePreferences.SocketSendBufferSize = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SocketReceiveBufferSizeChanged(int value)
|
||||
{
|
||||
SocketReceiveBufferSize = value;
|
||||
UpdatePreferences.SocketReceiveBufferSize = value;
|
||||
UpdatePreferences.SocketReceiveBufferSize = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,11 +139,31 @@
|
||||
SlowTorrentInactiveTimer = Preferences.SlowTorrentInactiveTimer;
|
||||
MaxRatioEnabled = Preferences.MaxRatioEnabled;
|
||||
MaxRatio = Preferences.MaxRatio;
|
||||
MaxSeedingTimeEnabled = Preferences.MaxSeedingTimeEnabled;
|
||||
MaxSeedingTime = Preferences.MaxSeedingTime;
|
||||
|
||||
if (Preferences.MaxSeedingTimeEnabled)
|
||||
{
|
||||
MaxSeedingTimeEnabled = true;
|
||||
MaxSeedingTime = Preferences.MaxSeedingTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSeedingTimeEnabled = false;
|
||||
MaxSeedingTime = 1440;
|
||||
}
|
||||
|
||||
MaxRatioAct = Preferences.MaxRatioAct;
|
||||
MaxInactiveSeedingTimeEnabled = Preferences.MaxInactiveSeedingTimeEnabled;
|
||||
MaxInactiveSeedingTime = Preferences.MaxInactiveSeedingTime;
|
||||
|
||||
if (Preferences.MaxInactiveSeedingTimeEnabled)
|
||||
{
|
||||
MaxInactiveSeedingTimeEnabled = true;
|
||||
MaxSeedingTime = Preferences.MaxInactiveSeedingTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxInactiveSeedingTimeEnabled = false;
|
||||
MaxInactiveSeedingTime = 1440;
|
||||
}
|
||||
|
||||
AddTrackersEnabled = Preferences.AddTrackersEnabled;
|
||||
AddTrackers = Preferences.AddTrackers;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
using ByteSizeLib;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class SpeedOptions : Options
|
||||
{
|
||||
@@ -62,10 +64,10 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
UpLimit = Preferences.UpLimit;
|
||||
DlLimit = Preferences.DlLimit;
|
||||
AltUpLimit = Preferences.AltUpLimit;
|
||||
AltDlLimit = Preferences.AltDlLimit;
|
||||
UpLimit = Preferences.UpLimit / 1024;
|
||||
DlLimit = Preferences.DlLimit / 1024;
|
||||
AltUpLimit = Preferences.AltUpLimit / 1024;
|
||||
AltDlLimit = Preferences.AltDlLimit / 1024;
|
||||
BittorrentProtocol = Preferences.BittorrentProtocol;
|
||||
LimitUtpRate = Preferences.LimitUtpRate;
|
||||
LimitTcpOverhead = Preferences.LimitTcpOverhead;
|
||||
@@ -81,28 +83,28 @@
|
||||
protected async Task UpLimitChanged(int value)
|
||||
{
|
||||
UpLimit = value;
|
||||
UpdatePreferences.UpLimit = value;
|
||||
UpdatePreferences.UpLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task DlLimitChanged(int value)
|
||||
{
|
||||
DlLimit = value;
|
||||
UpdatePreferences.DlLimit = value;
|
||||
UpdatePreferences.DlLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AltUpLimitChanged(int value)
|
||||
{
|
||||
AltUpLimit = value;
|
||||
UpdatePreferences.AltUpLimit = value;
|
||||
UpdatePreferences.AltUpLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AltDlLimitChanged(int value)
|
||||
{
|
||||
AltDlLimit = value;
|
||||
UpdatePreferences.AltDlLimit = value;
|
||||
UpdatePreferences.AltDlLimit = value * 1024;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<MudTable T="Peer" Items="Peers" >
|
||||
<MudTable
|
||||
Items="Peers"
|
||||
T="Peer"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
HeaderClass="table-head-bordered"
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Striped="true"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
HorizontalScrollbar="true"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
SelectOnRowClick="false">
|
||||
<HeaderContent>
|
||||
<MudTh>Country/Region</MudTh>
|
||||
<MudTh>IP</MudTh>
|
||||
|
||||
@@ -25,11 +25,37 @@ namespace Lantean.QBTMudBlade.Components
|
||||
[EditorRequired]
|
||||
public IReadOnlyList<PieceState> Pieces { get; set; } = [];
|
||||
|
||||
[CascadingParameter(Name = "IsDarkMode")]
|
||||
public bool IsDarkMode { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public MudTheme Theme { get; set; } = default!;
|
||||
|
||||
public Guid Id => Guid.NewGuid();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await JSRuntime.RenderPiecesBar("progress", Hash, Pieces.Select(s => (int)s).ToArray());
|
||||
await RenderPiecesBar();
|
||||
}
|
||||
|
||||
private async Task RenderPiecesBar()
|
||||
{
|
||||
string downloadingColor;
|
||||
string haveColor;
|
||||
string borderColor;
|
||||
if (IsDarkMode)
|
||||
{
|
||||
downloadingColor = Theme.PaletteDark.Success.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
haveColor = Theme.PaletteDark.Info.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
borderColor = Theme.PaletteDark.White.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadingColor = Theme.Palette.Success.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
haveColor = Theme.Palette.Info.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
borderColor = Theme.Palette.Black.ToString(MudBlazor.Utilities.MudColorOutputFormats.RGBA);
|
||||
}
|
||||
await JSRuntime.RenderPiecesBar("progress", Hash, Pieces.Select(s => (int)s).ToArray(), downloadingColor, haveColor, borderColor);
|
||||
}
|
||||
|
||||
ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new()
|
||||
@@ -50,7 +76,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
{
|
||||
await JSRuntime.RenderPiecesBar("progress", Hash, Pieces.Select(s => (int)s).ToArray());
|
||||
await RenderPiecesBar();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
|
||||
23
Lantean.QBTMudBlade/Components/SortLabel.razor
Normal file
23
Lantean.QBTMudBlade/Components/SortLabel.razor
Normal file
@@ -0,0 +1,23 @@
|
||||
@inherits MudComponentBase
|
||||
|
||||
<span @onclick="ToggleSortDirection" class="@Classname" style="@Style" @attributes="@UserAttributes">
|
||||
@if (!AppendIcon)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
@if (Enabled)
|
||||
{
|
||||
@if (SortDirection != SortDirection.None)
|
||||
{
|
||||
<MudIcon Icon="@SortIcon" Class="@GetSortIconClass()" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@SortIcon" Class="mud-table-sort-label-icon" />
|
||||
}
|
||||
}
|
||||
@if (AppendIcon)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</span>
|
||||
96
Lantean.QBTMudBlade/Components/SortLabel.razor.cs
Normal file
96
Lantean.QBTMudBlade/Components/SortLabel.razor.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Utilities;
|
||||
using static MudBlazor.CategoryTypes;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class SortLabel : MudComponentBase
|
||||
{
|
||||
protected string Classname => new CssBuilder("mud-button-root mud-table-sort-label")
|
||||
.AddClass(Class)
|
||||
.Build();
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public SortDirection InitialDirection { get; set; } = SortDirection.None;
|
||||
|
||||
/// <summary>
|
||||
/// Enable the sorting. Set to true by default.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable the sorting. Set to true by default.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool AllowUnsorted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Icon used to display SortDirection.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string SortIcon { get; set; } = Icons.Material.Filled.ArrowUpward;
|
||||
|
||||
/// <summary>
|
||||
/// If true the icon will be placed before the label text.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool AppendIcon { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public SortDirection SortDirection { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<SortDirection> SortDirectionChanged { get; set; }
|
||||
|
||||
public async Task ToggleSortDirection()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
SortDirection sortDirection;
|
||||
switch (SortDirection)
|
||||
{
|
||||
case SortDirection.None:
|
||||
sortDirection = SortDirection.Ascending;
|
||||
break;
|
||||
|
||||
case SortDirection.Ascending:
|
||||
sortDirection = SortDirection.Descending;
|
||||
break;
|
||||
|
||||
case SortDirection.Descending:
|
||||
sortDirection = AllowUnsorted ? SortDirection.None : SortDirection.Ascending;
|
||||
break;
|
||||
|
||||
default:
|
||||
sortDirection = SortDirection.None;
|
||||
break;
|
||||
}
|
||||
|
||||
await SortDirectionChanged.InvokeAsync(sortDirection);
|
||||
}
|
||||
|
||||
private string GetSortIconClass()
|
||||
{
|
||||
if (SortDirection == SortDirection.Descending)
|
||||
{
|
||||
return "mud-table-sort-label-icon mud-direction-desc";
|
||||
}
|
||||
|
||||
if (SortDirection == SortDirection.Ascending)
|
||||
{
|
||||
return "mud-table-sort-label-icon mud-direction-asc";
|
||||
}
|
||||
|
||||
return "mud-table-sort-label-icon";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,55 @@
|
||||
@if (Type == RenderType.Toolbar)
|
||||
@if (RenderType == RenderType.Toolbar)
|
||||
{
|
||||
<MudToolBar Dense="true" DisableGutters="true" WrapContent="true">
|
||||
@ToolbarContent
|
||||
</MudToolBar>
|
||||
}
|
||||
else if (Type == RenderType.ToolbarContents)
|
||||
else if (RenderType == RenderType.ToolbarContents)
|
||||
{
|
||||
@ToolbarContent
|
||||
}
|
||||
else if (Type == RenderType.MixedToolbar)
|
||||
else if (RenderType == RenderType.MixedToolbar)
|
||||
{
|
||||
<MudToolBar Dense="true" DisableGutters="true" WrapContent="true">
|
||||
@MixedToolbarContent
|
||||
</MudToolBar>
|
||||
}
|
||||
else if (Type == RenderType.MixedToolbarContents)
|
||||
else if (RenderType == RenderType.MixedToolbarContents)
|
||||
{
|
||||
@MixedToolbarContent
|
||||
}
|
||||
else if (Type == RenderType.InitialIconsOnly)
|
||||
else if (RenderType == RenderType.InitialIconsOnly)
|
||||
{
|
||||
@foreach (var option in GetOptions().Take(5))
|
||||
@foreach (var action in Actions.Take(5))
|
||||
{
|
||||
@if (option is Divider)
|
||||
@if (action is Divider)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton Title="@option.Name" Icon="@option.Icon" Color="option.Color" OnClick="option.Callback" Disabled="Disabled" />
|
||||
<MudIconButton Title="@action.Name" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
}
|
||||
|
||||
@Menu(GetOptions().Skip(5));
|
||||
@Menu(Actions.Skip(5))
|
||||
}
|
||||
else if (RenderType == RenderType.Children)
|
||||
{
|
||||
var parent = Actions.FirstOrDefault(a => a.Name == ParentAction?.Name);
|
||||
if (parent is not null)
|
||||
{
|
||||
<MudList Clickable="true">
|
||||
@foreach (var action in parent.Children)
|
||||
{
|
||||
<MudListItem Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@Menu(GetOptions());
|
||||
@Menu(Actions)
|
||||
}
|
||||
|
||||
@code {
|
||||
@@ -46,27 +59,27 @@ else
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
foreach (var option in GetOptions())
|
||||
foreach (var action in Actions)
|
||||
{
|
||||
if (option is Divider)
|
||||
if (action is Divider)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
else if (!option.Children.Any())
|
||||
else if (!action.Children.Any())
|
||||
{
|
||||
if (option.Icon is null)
|
||||
if (action.Icon is null)
|
||||
{
|
||||
<MudButton Color="option.Color" OnClick="option.Callback">@option.Name</MudButton>
|
||||
<MudButton Color="action.Color" OnClick="action.Callback">@action.Name</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton Title="@option.Name" Icon="@option.Icon" Color="option.Color" OnClick="option.Callback" Disabled="Disabled" />
|
||||
<MudIconButton Title="@action.Name" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu Icon="@option.Icon" IconColor="@option.Color" Label="@option.Name" title="@option.Name" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
@foreach (var childItem in option.Children)
|
||||
<MudMenu Icon="@action.Icon" IconColor="@action.Color" Label="@action.Name" title="@action.Name" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
@foreach (var childItem in action.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
@@ -83,27 +96,27 @@ else
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
foreach (var option in GetOptions())
|
||||
foreach (var action in Actions)
|
||||
{
|
||||
if (option is Divider)
|
||||
if (action is Divider)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
else if (!option.Children.Any())
|
||||
else if (!action.Children.Any())
|
||||
{
|
||||
if (option.Icon is null)
|
||||
if (action.Icon is null)
|
||||
{
|
||||
<MudButton Color="option.Color" OnClick="option.Callback" Disabled="Disabled">@option.Name</MudButton>
|
||||
<MudButton Color="action.Color" OnClick="action.Callback" Disabled="Disabled">@action.Name</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton Title="@option.Name" Icon="@option.Icon" Color="option.Color" OnClick="option.Callback" Disabled="Disabled" />
|
||||
<MudIconButton Title="@action.Name" Icon="@action.Icon" Color="action.Color" OnClick="action.Callback" Disabled="Disabled" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu Label="@option.Name" title="@option.Name" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" EndIcon="@Icons.Material.Filled.ArrowDropDown">
|
||||
@foreach (var childItem in option.Children)
|
||||
<MudMenu Label="@action.Name" title="@action.Name" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" EndIcon="@Icons.Material.Filled.ArrowDropDown">
|
||||
@foreach (var childItem in action.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
@@ -114,48 +127,48 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment ChildItem(Action option)
|
||||
private RenderFragment ChildItem(TorrentAction action)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
if (option is Divider)
|
||||
if (action is Divider)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenuItem Icon="@option.Icon" IconColor="option.Color" OnClick="option.Callback" OnTouch="option.Callback" Disabled="Disabled">@option.Name</MudMenuItem>
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" OnTouch="action.Callback" Disabled="Disabled">@action.Name</MudMenuItem>
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private RenderFragment Menu(IEnumerable<Action> actions)
|
||||
private RenderFragment Menu(IEnumerable<TorrentAction> actions)
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
<MudMenu Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" Label="Actions" EndIcon="@Icons.Material.Filled.ArrowDropDown" @ref="ActionsMenu" Disabled="@(!Hashes.Any())">
|
||||
@foreach (var option in actions)
|
||||
@foreach (var action in actions)
|
||||
{
|
||||
@if (option is Divider)
|
||||
@if (action is Divider)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
else if (!option.Children.Any())
|
||||
else if (!action.Children.Any())
|
||||
{
|
||||
<MudMenuItem Icon="@option.Icon" IconColor="option.Color" OnClick="option.Callback" OnTouch="option.Callback" Disabled="Disabled">
|
||||
@option.Name
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" OnTouch="action.Callback" Disabled="Disabled">
|
||||
@action.Name
|
||||
</MudMenuItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenuItem Icon="@option.Icon" IconColor="option.Color">
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnTouch="@(t => SubMenuTouch(action))" OnClick="@(t => SubMenuTouch(action))">
|
||||
<MudMenu Dense="true" AnchorOrigin="Origin.TopRight" TransformOrigin="Origin.TopLeft" ActivationEvent="MouseEvent.MouseOver" Icon="@Icons.Material.Filled.ArrowDropDown" DisableElevation="true" DisableRipple="true" Class="sub-menu">
|
||||
<ActivatorContent>
|
||||
@option.Name
|
||||
@action.Name
|
||||
</ActivatorContent>
|
||||
|
||||
<ChildContent>
|
||||
@foreach (var childItem in option.Children)
|
||||
@foreach (var childItem in action.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Lantean.QBTMudBlade.Interop;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
|
||||
@@ -39,7 +40,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
/// If true this component will render as a <see cref="MudToolBar"/> otherwise will render as a <see cref="MudMenu"/>.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderType Type { get; set; }
|
||||
public RenderType RenderType { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData MainData { get; set; } = default!;
|
||||
@@ -47,6 +48,12 @@ namespace Lantean.QBTMudBlade.Components
|
||||
[CascadingParameter]
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public TorrentAction? ParentAction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? AfterAction { get; set; }
|
||||
|
||||
protected MudMenu? ActionsMenu { get; set; }
|
||||
|
||||
protected bool Disabled => !Hashes.Any();
|
||||
@@ -224,6 +231,11 @@ namespace Lantean.QBTMudBlade.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SubMenuTouch(TorrentAction action)
|
||||
{
|
||||
await DialogService.ShowSubMenu(Hashes, action);
|
||||
}
|
||||
|
||||
private IEnumerable<Torrent> GetTorrents()
|
||||
{
|
||||
foreach (var hash in Hashes)
|
||||
@@ -235,78 +247,104 @@ namespace Lantean.QBTMudBlade.Components
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Action> GetOptions()
|
||||
private List<TorrentAction>? _actions;
|
||||
|
||||
private IReadOnlyList<TorrentAction> Actions
|
||||
{
|
||||
Torrent? torrent = null;
|
||||
if (Hashes.Any())
|
||||
get
|
||||
{
|
||||
string key = Hashes.First();
|
||||
if (!MainData.Torrents.TryGetValue(key, out torrent))
|
||||
if (_actions is not null)
|
||||
{
|
||||
Hashes = Hashes.Except([key]);
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
|
||||
var categories = new List<Action>
|
||||
{
|
||||
new Action("New", Icons.Material.Filled.Add, Color.Info, EventCallback.Factory.Create(this, AddCategory)),
|
||||
new Action("Reset", Icons.Material.Filled.Remove, Color.Error, EventCallback.Factory.Create(this, ResetCategory)),
|
||||
new Divider()
|
||||
};
|
||||
categories.AddRange(MainData.Categories.Select(c => new Action(c.Value.Name, Icons.Material.Filled.List, Color.Info, EventCallback.Factory.Create(this, () => SetCategory(c.Key)))));
|
||||
|
||||
var tags = new List<Action>
|
||||
{
|
||||
new Action("Add", Icons.Material.Filled.Add, Color.Info, EventCallback.Factory.Create(this, AddTag)),
|
||||
new Action("Remove All", Icons.Material.Filled.Remove, Color.Error, EventCallback.Factory.Create(this, RemoveTags)),
|
||||
new Divider()
|
||||
};
|
||||
tags.AddRange(MainData.Tags.Select(t => new Action(t, (torrent?.Tags.Contains(t) == true) ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank, Color.Default, EventCallback.Factory.Create(this, () => ToggleTag(t)))));
|
||||
|
||||
var options = new List<Action>
|
||||
{
|
||||
new Action("Pause", Icons.Material.Filled.Pause, Color.Warning, EventCallback.Factory.Create(this, Pause)),
|
||||
new Action("Resume", Icons.Material.Filled.PlayArrow, Color.Success, EventCallback.Factory.Create(this, Resume)),
|
||||
new Divider(),
|
||||
new Action("Remove", Icons.Material.Filled.Delete, Color.Error, EventCallback.Factory.Create(this, Remove)),
|
||||
new Divider(),
|
||||
new Action("Set location", Icons.Material.Filled.MyLocation, Color.Info, EventCallback.Factory.Create(this, SetLocation)),
|
||||
new Action("Rename", Icons.Material.Filled.DriveFileRenameOutline, Color.Info, EventCallback.Factory.Create(this, Rename)),
|
||||
new Action("Category", Icons.Material.Filled.List, Color.Info, categories, true),
|
||||
new Action("Tags", Icons.Material.Filled.Label, Color.Info, tags, true),
|
||||
new Action("Automatic Torrent Management", Icons.Material.Filled.Check, (torrent?.AutomaticTorrentManagement == true) ? Color.Info : Color.Transparent, EventCallback.Factory.Create(this, ToggleAutoTMM)),
|
||||
new Divider(),
|
||||
new Action("Limit upload rate", Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info, EventCallback.Factory.Create(this, LimitUploadRate)),
|
||||
new Action("Limit share ratio", Icons.Material.Filled.Percent, Color.Warning, EventCallback.Factory.Create(this, LimitShareRatio)),
|
||||
new Action("Super seeding mode", Icons.Material.Filled.Check, (torrent?.SuperSeeding == true) ? Color.Info : Color.Transparent, EventCallback.Factory.Create(this, ToggleSuperSeeding)),
|
||||
new Divider(),
|
||||
new Action("Force recheck", Icons.Material.Filled.Loop, Color.Info, EventCallback.Factory.Create(this, ForceRecheck)),
|
||||
new Action("Force reannounce", Icons.Material.Filled.BroadcastOnHome, Color.Info, EventCallback.Factory.Create(this, ForceReannounce)),
|
||||
new Divider(),
|
||||
new Action("Queue", Icons.Material.Filled.Queue, Color.Transparent, new List<Action>
|
||||
Torrent? torrent = null;
|
||||
if (Hashes.Any())
|
||||
{
|
||||
new Action("Move to top", Icons.Material.Filled.VerticalAlignTop, Color.Inherit, EventCallback.Factory.Create(this, MoveToTop)),
|
||||
new Action("Move up", Icons.Material.Filled.ArrowUpward, Color.Inherit, EventCallback.Factory.Create(this, MoveUp)),
|
||||
new Action("Move down", Icons.Material.Filled.ArrowDownward, Color.Inherit, EventCallback.Factory.Create(this, MoveDown)),
|
||||
new Action("Move to bottom", Icons.Material.Filled.VerticalAlignBottom, Color.Inherit, EventCallback.Factory.Create(this, MoveToBottom)),
|
||||
}),
|
||||
new Action("Copy", Icons.Material.Filled.FolderCopy, Color.Info, new List<Action>
|
||||
string key = Hashes.First();
|
||||
if (!MainData.Torrents.TryGetValue(key, out torrent))
|
||||
{
|
||||
Hashes = Hashes.Except([key]);
|
||||
}
|
||||
}
|
||||
|
||||
var categories = new List<TorrentAction>
|
||||
{
|
||||
new Action("Name", Icons.Material.Filled.TextFields, Color.Info, EventCallback.Factory.Create(this, () => Copy(t => t.Name))),
|
||||
new Action("Info hash v1", Icons.Material.Filled.Tag, Color.Info, EventCallback.Factory.Create(this, () => Copy(t => t.InfoHashV1))),
|
||||
new Action("Info hash v2", Icons.Material.Filled.Tag, Color.Info, EventCallback.Factory.Create(this, () => Copy(t => t.InfoHashV2))),
|
||||
new Action("Magnet link", Icons.Material.Filled.TextFields, Color.Info, EventCallback.Factory.Create(this, () => Copy(t => t.MagnetUri))),
|
||||
new Action("Torrent ID", Icons.Material.Filled.TextFields, Color.Info, EventCallback.Factory.Create(this, () => Copy(t => t.Hash))),
|
||||
}),
|
||||
new Action("Export", Icons.Material.Filled.SaveAlt, Color.Info, EventCallback.Factory.Create(this, Export)),
|
||||
};
|
||||
new TorrentAction("New", Icons.Material.Filled.Add, Color.Info, CreateCallback(AddCategory)),
|
||||
new TorrentAction("Reset", Icons.Material.Filled.Remove, Color.Error, CreateCallback(ResetCategory)),
|
||||
new Divider()
|
||||
};
|
||||
categories.AddRange(MainData.Categories.Select(c => new TorrentAction(c.Value.Name, Icons.Material.Filled.List, Color.Info, CreateCallback(() => SetCategory(c.Key)))));
|
||||
|
||||
if (Preferences?.QueueingEnabled == false)
|
||||
{
|
||||
options.RemoveAt(18);
|
||||
var tags = new List<TorrentAction>
|
||||
{
|
||||
new TorrentAction("Add", Icons.Material.Filled.Add, Color.Info, CreateCallback(AddTag)),
|
||||
new TorrentAction("Remove All", Icons.Material.Filled.Remove, Color.Error, CreateCallback(RemoveTags)),
|
||||
new Divider()
|
||||
};
|
||||
tags.AddRange(MainData.Tags.Select(t => new TorrentAction(t, (torrent?.Tags.Contains(t) == true) ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank, Color.Default, CreateCallback(() => ToggleTag(t)))));
|
||||
|
||||
_actions = new List<TorrentAction>
|
||||
{
|
||||
new TorrentAction("Pause", Icons.Material.Filled.Pause, Color.Warning, CreateCallback(Pause)),
|
||||
new TorrentAction("Resume", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Resume)),
|
||||
new Divider(),
|
||||
new TorrentAction("Remove", Icons.Material.Filled.Delete, Color.Error, CreateCallback(Remove)),
|
||||
new Divider(),
|
||||
new TorrentAction("Set location", Icons.Material.Filled.MyLocation, Color.Info, CreateCallback(SetLocation)),
|
||||
new TorrentAction("Rename", Icons.Material.Filled.DriveFileRenameOutline, Color.Info, CreateCallback(Rename)),
|
||||
new TorrentAction("Category", Icons.Material.Filled.List, Color.Info, categories, true),
|
||||
new TorrentAction("Tags", Icons.Material.Filled.Label, Color.Info, tags, true),
|
||||
new TorrentAction("Automatic Torrent Management", Icons.Material.Filled.Check, (torrent?.AutomaticTorrentManagement == true) ? Color.Info : Color.Transparent, CreateCallback(ToggleAutoTMM)),
|
||||
new Divider(),
|
||||
new TorrentAction("Limit upload rate", Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info, CreateCallback(LimitUploadRate)),
|
||||
new TorrentAction("Limit share ratio", Icons.Material.Filled.Percent, Color.Warning, CreateCallback(LimitShareRatio)),
|
||||
new TorrentAction("Super seeding mode", Icons.Material.Filled.Check, (torrent?.SuperSeeding == true) ? Color.Info : Color.Transparent, CreateCallback(ToggleSuperSeeding)),
|
||||
new Divider(),
|
||||
new TorrentAction("Force recheck", Icons.Material.Filled.Loop, Color.Info, CreateCallback(ForceRecheck)),
|
||||
new TorrentAction("Force reannounce", Icons.Material.Filled.BroadcastOnHome, Color.Info, CreateCallback(ForceReannounce)),
|
||||
new Divider(),
|
||||
new TorrentAction("Queue", Icons.Material.Filled.Queue, Color.Transparent, new List<TorrentAction>
|
||||
{
|
||||
new TorrentAction("Move to top", Icons.Material.Filled.VerticalAlignTop, Color.Inherit, CreateCallback(MoveToTop)),
|
||||
new TorrentAction("Move up", Icons.Material.Filled.ArrowUpward, Color.Inherit, CreateCallback(MoveUp)),
|
||||
new TorrentAction("Move down", Icons.Material.Filled.ArrowDownward, Color.Inherit, CreateCallback(MoveDown)),
|
||||
new TorrentAction("Move to bottom", Icons.Material.Filled.VerticalAlignBottom, Color.Inherit, CreateCallback(MoveToBottom)),
|
||||
}),
|
||||
new TorrentAction("Copy", Icons.Material.Filled.FolderCopy, Color.Info, new List<TorrentAction>
|
||||
{
|
||||
new TorrentAction("Name", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Name))),
|
||||
new TorrentAction("Info hash v1", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV1))),
|
||||
new TorrentAction("Info hash v2", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV2))),
|
||||
new TorrentAction("Magnet link", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.MagnetUri))),
|
||||
new TorrentAction("Torrent ID", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Hash))),
|
||||
}),
|
||||
new TorrentAction("Export", Icons.Material.Filled.SaveAlt, Color.Info, CreateCallback(Export)),
|
||||
};
|
||||
|
||||
if (Preferences?.QueueingEnabled == false)
|
||||
{
|
||||
_actions.RemoveAt(18);
|
||||
}
|
||||
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
private EventCallback CreateCallback(Func<Task> action)
|
||||
{
|
||||
if (AfterAction is not null)
|
||||
{
|
||||
return EventCallback.Factory.Create(this, async () =>
|
||||
{
|
||||
await action();
|
||||
await AfterAction();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return EventCallback.Factory.Create(this, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,18 +371,19 @@ namespace Lantean.QBTMudBlade.Components
|
||||
/// </summary>
|
||||
MixedToolbar,
|
||||
InitialIconsOnly,
|
||||
Children,
|
||||
}
|
||||
|
||||
public class Divider : Action
|
||||
public class Divider : TorrentAction
|
||||
{
|
||||
public Divider() : base("-", default!, Color.Default, default(EventCallback))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Action
|
||||
public class TorrentAction
|
||||
{
|
||||
public Action(string name, string? icon, Color color, EventCallback callback)
|
||||
public TorrentAction(string name, string? icon, Color color, EventCallback callback)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
@@ -353,7 +392,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
Children = [];
|
||||
}
|
||||
|
||||
public Action(string name, string? icon, Color color, IEnumerable<Action> children, bool useTextButton = false)
|
||||
public TorrentAction(string name, string? icon, Color color, IEnumerable<TorrentAction> children, bool useTextButton = false)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
@@ -371,7 +410,7 @@ namespace Lantean.QBTMudBlade.Components
|
||||
|
||||
public EventCallback Callback { get; }
|
||||
|
||||
public IEnumerable<Action> Children { get; }
|
||||
public IEnumerable<TorrentAction> Children { get; }
|
||||
|
||||
public bool UseTextButton { get; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<MudTable T="Lantean.QBitTorrentClient.Models.TorrentTrackers" Items="Trackers" >
|
||||
<MudTable
|
||||
T="Lantean.QBitTorrentClient.Models.TorrentTrackers"
|
||||
Items="Trackers"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
HeaderClass="table-head-bordered"
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Striped="true"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
HorizontalScrollbar="true"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
SelectOnRowClick="false">
|
||||
<HeaderContent>
|
||||
<MudTh>Tier</MudTh>
|
||||
<MudTh>URL</MudTh>
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<MudTable T="Lantean.QBitTorrentClient.Models.WebSeed" Items="WebSeeds" >
|
||||
<MudTable
|
||||
T="Lantean.QBitTorrentClient.Models.WebSeed"
|
||||
Items="WebSeeds"
|
||||
Hover="true"
|
||||
FixedHeader="true"
|
||||
HeaderClass="table-head-bordered"
|
||||
Dense="true"
|
||||
Breakpoint="Breakpoint.None"
|
||||
Bordered="true"
|
||||
Striped="true"
|
||||
Square="true"
|
||||
LoadingProgressColor="Color.Info"
|
||||
HorizontalScrollbar="true"
|
||||
Virtualize="true"
|
||||
AllowUnsorted="false"
|
||||
SelectOnRowClick="false">
|
||||
<HeaderContent>
|
||||
<MudTh>URL</MudTh>
|
||||
</HeaderContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Components;
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Filter;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
@@ -12,6 +13,8 @@ namespace Lantean.QBTMudBlade
|
||||
|
||||
private static readonly DialogOptions _confirmDialogOptions = new() { ClassBackground = "background-blur" };
|
||||
|
||||
public const long _maxFileSize = 4194304;
|
||||
|
||||
public static async Task InvokeAddTorrentFileDialog(this IDialogService dialogService, IApiClient apiClient)
|
||||
{
|
||||
var result = await dialogService.ShowAsync<AddTorrentFileDialog>("Upload local torrent", FormDialogOptions);
|
||||
@@ -28,7 +31,7 @@ namespace Lantean.QBTMudBlade
|
||||
var files = new Dictionary<string, Stream>();
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
var stream = file.OpenReadStream();
|
||||
var stream = file.OpenReadStream(_maxFileSize);
|
||||
streams.Add(stream);
|
||||
files.Add(file.Name, stream);
|
||||
}
|
||||
@@ -142,7 +145,7 @@ namespace Lantean.QBTMudBlade
|
||||
await onSuccess();
|
||||
}
|
||||
|
||||
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, Action onSuccess)
|
||||
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, System.Action onSuccess)
|
||||
{
|
||||
await ShowConfirmDialog(dialogService, title, content, () =>
|
||||
{
|
||||
@@ -175,8 +178,8 @@ namespace Lantean.QBTMudBlade
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(SliderFieldDialog<long>.Value), rate },
|
||||
{ nameof(SliderFieldDialog<long>.Min), 0 },
|
||||
{ nameof(SliderFieldDialog<long>.Max), 100 },
|
||||
{ nameof(SliderFieldDialog<long>.Min), 0L },
|
||||
{ nameof(SliderFieldDialog<long>.Max), 100L },
|
||||
};
|
||||
var result = await dialogService.ShowAsync<SliderFieldDialog<long>>("Upload Rate", parameters, FormDialogOptions);
|
||||
|
||||
@@ -194,8 +197,8 @@ namespace Lantean.QBTMudBlade
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(SliderFieldDialog<float>.Value), ratio },
|
||||
{ nameof(SliderFieldDialog<float>.Min), 0 },
|
||||
{ nameof(SliderFieldDialog<float>.Max), 100 },
|
||||
{ nameof(SliderFieldDialog<float>.Min), 0F },
|
||||
{ nameof(SliderFieldDialog<float>.Max), 100F },
|
||||
};
|
||||
var result = await dialogService.ShowAsync<SliderFieldDialog<float>>("Upload Rate", parameters, FormDialogOptions);
|
||||
|
||||
@@ -247,5 +250,16 @@ namespace Lantean.QBTMudBlade
|
||||
{
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
public static async Task ShowSubMenu(this IDialogService dialogService, IEnumerable<string> hashes, TorrentAction parent)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(SubMenuDialog.ParentAction), parent },
|
||||
{ nameof(SubMenuDialog.Hashes), hashes }
|
||||
};
|
||||
|
||||
await dialogService.ShowAsync<SubMenuDialog>("Actions", parameters, FormDialogOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,15 @@ namespace Lantean.QBTMudBlade
|
||||
return "< 1m";
|
||||
}
|
||||
|
||||
var time = TimeSpan.FromSeconds(seconds.Value);
|
||||
TimeSpan time;
|
||||
try
|
||||
{
|
||||
time = TimeSpan.FromSeconds(seconds.Value);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
var sb = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
|
||||
@@ -41,8 +41,10 @@ namespace Lantean.QBTMudBlade.Filter
|
||||
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && value != null && ((string)x).StartsWith(value, stringComparer))),
|
||||
FilterOperator.String.EndsWith =>
|
||||
propertyExpression.Modify<T>((Expression<Func<object?, bool>>)(x => (string?)x != null && value != null && ((string)x).EndsWith(value, stringComparer))),
|
||||
FilterOperator.String.Empty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => string.IsNullOrWhiteSpace(x))),
|
||||
FilterOperator.String.NotEmpty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => !string.IsNullOrWhiteSpace(x))),
|
||||
FilterOperator.String.Empty =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => string.IsNullOrWhiteSpace(x))),
|
||||
FilterOperator.String.NotEmpty =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => !string.IsNullOrWhiteSpace(x))),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace Lantean.QBTMudBlade.Filter
|
||||
{
|
||||
if (fieldType.IsString)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
return
|
||||
[
|
||||
String.Contains,
|
||||
String.NotContains,
|
||||
String.Equal,
|
||||
@@ -77,12 +77,12 @@ namespace Lantean.QBTMudBlade.Filter
|
||||
String.EndsWith,
|
||||
String.Empty,
|
||||
String.NotEmpty,
|
||||
};
|
||||
];
|
||||
}
|
||||
if (fieldType.IsNumber)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
return
|
||||
[
|
||||
Number.Equal,
|
||||
Number.NotEqual,
|
||||
Number.GreaterThan,
|
||||
@@ -91,26 +91,27 @@ namespace Lantean.QBTMudBlade.Filter
|
||||
Number.LessThanOrEqual,
|
||||
Number.Empty,
|
||||
Number.NotEmpty,
|
||||
};
|
||||
];
|
||||
}
|
||||
if (fieldType.IsEnum)
|
||||
{
|
||||
return new[] {
|
||||
return
|
||||
[
|
||||
Enum.Is,
|
||||
Enum.IsNot,
|
||||
};
|
||||
];
|
||||
}
|
||||
if (fieldType.IsBoolean)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
return
|
||||
[
|
||||
Boolean.Is,
|
||||
};
|
||||
];
|
||||
}
|
||||
if (fieldType.IsDateTime)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
return
|
||||
[
|
||||
DateTime.Is,
|
||||
DateTime.IsNot,
|
||||
DateTime.After,
|
||||
@@ -119,19 +120,19 @@ namespace Lantean.QBTMudBlade.Filter
|
||||
DateTime.OnOrBefore,
|
||||
DateTime.Empty,
|
||||
DateTime.NotEmpty,
|
||||
};
|
||||
];
|
||||
}
|
||||
if (fieldType.IsGuid)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
return
|
||||
[
|
||||
Guid.Equal,
|
||||
Guid.NotEqual,
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
// default
|
||||
return Array.Empty<string>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using MudBlazor;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Filter
|
||||
{
|
||||
@@ -27,6 +25,4 @@ namespace Lantean.QBTMudBlade.Filter
|
||||
|
||||
public Expression<Func<T, object?>> Expression { get; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,12 @@ namespace Lantean.QBTMudBlade
|
||||
|
||||
public static bool FilterStatus(Torrent torrent, Status status)
|
||||
{
|
||||
var state = torrent.State;
|
||||
return FilterStatus(torrent.State, torrent.UploadSpeed, status);
|
||||
}
|
||||
|
||||
public static bool FilterStatus(string state, long uploadSpeed, Status status)
|
||||
{
|
||||
|
||||
bool inactive = false;
|
||||
switch (status)
|
||||
{
|
||||
@@ -188,10 +193,11 @@ namespace Lantean.QBTMudBlade
|
||||
break;
|
||||
|
||||
case Status.Completed:
|
||||
if (state != "uploading" && !state.Contains("UL"))
|
||||
if ((state != "uploading") && (!state.Contains("UP")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Status.Resumed:
|
||||
@@ -217,7 +223,7 @@ namespace Lantean.QBTMudBlade
|
||||
bool check;
|
||||
if (state == "stalledDL")
|
||||
{
|
||||
check = torrent.UploadSpeed > 0;
|
||||
check = uploadSpeed > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -24,9 +24,9 @@ namespace Lantean.QBTMudBlade.Interop
|
||||
await runtime.InvokeVoidAsync("qbt.open", url, target);
|
||||
}
|
||||
|
||||
public static async Task RenderPiecesBar(this IJSRuntime runtime, string id, string hash, int[] pieces)
|
||||
public static async Task RenderPiecesBar(this IJSRuntime runtime, string id, string hash, int[] pieces, string? downloadingColor = null, string? haveColor = null, string? borderColor = null)
|
||||
{
|
||||
await runtime.InvokeVoidAsync("qbt.renderPiecesBar", id, hash, pieces);
|
||||
await runtime.InvokeVoidAsync("qbt.renderPiecesBar", id, hash, pieces, downloadingColor, haveColor, borderColor );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -14,6 +15,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="6.19.1" />
|
||||
<PackageReference Include="MudBlazor.ThemeManager" Version="1.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
|
||||
<EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
|
||||
<MudThemeProvider @ref="MudThemeProvider" @bind-IsDarkMode="IsDarkMode" />
|
||||
<MudThemeProvider @ref="MudThemeProvider" @bind-IsDarkMode="IsDarkMode" Theme="Theme" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<PageTitle>qBittorrent Web UI</PageTitle>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1">
|
||||
<MudAppBar>
|
||||
<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 />
|
||||
@@ -19,6 +19,7 @@
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
|
||||
</MudBadge>
|
||||
}
|
||||
<MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" @bind-Value="IsDarkMode" />
|
||||
@if (ShowMenu)
|
||||
{
|
||||
<Menu />
|
||||
@@ -27,9 +28,11 @@
|
||||
<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 Value="Theme">
|
||||
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
|
||||
<CascadingValue Value="IsDarkMode" Name="IsDarkMode">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</MudLayout>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Components;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Services;
|
||||
|
||||
@@ -40,6 +39,14 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
NotifyOnBreakpointOnly = true
|
||||
};
|
||||
|
||||
protected MudTheme Theme { get; set; }
|
||||
|
||||
public MainLayout()
|
||||
{
|
||||
Theme = new MudTheme();
|
||||
Theme.Typography.Default.FontFamily = ["Nunito Sans"];
|
||||
}
|
||||
|
||||
protected void ToggleDrawer()
|
||||
{
|
||||
DrawerOpen = !DrawerOpen;
|
||||
@@ -64,13 +71,13 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task OnSystemPreferenceChanged(bool value)
|
||||
protected Task OnSystemPreferenceChanged(bool value)
|
||||
{
|
||||
IsDarkMode = value;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
public Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
{
|
||||
if (browserViewportEventArgs.Breakpoint == Breakpoint.Sm && DrawerOpen)
|
||||
{
|
||||
@@ -80,7 +87,8 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
DrawerOpen = true;
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected void ToggleErrorDrawer()
|
||||
@@ -113,4 +121,4 @@ namespace Lantean.QBTMudBlade.Layout
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace Lantean.QBTMudBlade.Models
|
||||
|
||||
public long Downloaded => (long)Math.Round(Size * Progress, 0);
|
||||
|
||||
public long Remaining => Size - Downloaded;
|
||||
public long Remaining => Progress == 1 ? 0 : Size - Downloaded;
|
||||
|
||||
public bool IsFolder { get; }
|
||||
|
||||
|
||||
@@ -9,28 +9,31 @@
|
||||
}
|
||||
@if (Hash is not null)
|
||||
{
|
||||
<TorrentActions Type="RenderType.InitialIconsOnly" Hashes="@([Hash])" />
|
||||
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="@([Hash])" />
|
||||
}
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="pl-5 no-wrap">@Name</MudText>
|
||||
</MudToolBar>
|
||||
|
||||
<CascadingValue Value="RefreshInterval">
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" KeepPanelsAlive="true" Border="true">
|
||||
<MudTabPanel Text="General">
|
||||
<GeneralTab Hash="@Hash" Active="@(ActiveTab == 0)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Trackers">
|
||||
<TrackersTab Hash="@Hash" Active="@(ActiveTab == 1)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Peers">
|
||||
<PeersTab Hash="@Hash" Active="@(ActiveTab == 2)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="HTTP Sources">
|
||||
<WebSeedsTab Hash="@Hash" Active="@(ActiveTab == 3)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Content">
|
||||
<FilesTab Hash="@Hash" Active="@(ActiveTab == 4)" />
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</CascadingValue>
|
||||
@if (ShowTabs)
|
||||
{
|
||||
<CascadingValue Value="RefreshInterval">
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" KeepPanelsAlive="true" Border="true">
|
||||
<MudTabPanel Text="General">
|
||||
<GeneralTab Hash="@Hash" Active="@(ActiveTab == 0)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Trackers">
|
||||
<TrackersTab Hash="@Hash" Active="@(ActiveTab == 1)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Peers">
|
||||
<PeersTab Hash="@Hash" Active="@(ActiveTab == 2)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="HTTP Sources">
|
||||
<WebSeedsTab Hash="@Hash" Active="@(ActiveTab == 3)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Content">
|
||||
<FilesTab Hash="@Hash" Active="@(ActiveTab == 4)" />
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</CascadingValue>
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
@@ -32,6 +31,8 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
|
||||
protected string Name => GetName();
|
||||
|
||||
protected bool ShowTabs { get; set; } = true;
|
||||
|
||||
private string GetName()
|
||||
{
|
||||
if (Hash is null || MainData is null)
|
||||
@@ -47,45 +48,6 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
return torrent.Name;
|
||||
}
|
||||
|
||||
protected async Task PauseTorrent()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.PauseTorrent(Hash);
|
||||
}
|
||||
|
||||
protected async Task ResumeTorrent()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.ResumeTorrent(Hash);
|
||||
}
|
||||
|
||||
protected async Task RemoveTorrent()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var reference = await DialogService.ShowAsync<DeleteDialog>("Remove torrent(s)?");
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.DeleteTorrent(Hash, (bool)result.Data);
|
||||
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
protected void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
#if DEBUG
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await DoLogin("admin", "rp46PuPVn");
|
||||
await DoLogin("admin", "u4FR4ZQCm");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
@page "/main"
|
||||
|
||||
<PageTitle>qBittorrent @Version Web UI</PageTitle>
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Pages
|
||||
{
|
||||
public partial class Main : IDisposable
|
||||
{
|
||||
private bool _refreshEnabled = true;
|
||||
|
||||
protected bool DrawerOpen { get; set; } = true;
|
||||
|
||||
protected int RefreshInterval { get; set; } = 1500;
|
||||
|
||||
private int _requestId = 0;
|
||||
private bool _disposedValue;
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
protected Models.MainData? TorrentList { get; set; }
|
||||
|
||||
protected string Category { get; set; } = FilterHelper.CATEGORY_ALL;
|
||||
|
||||
protected string Tag { get; set; } = FilterHelper.TAG_ALL;
|
||||
|
||||
protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
|
||||
|
||||
protected Status Status { get; set; } = Status.All;
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected FilterState FilterState => new FilterState(Category, Status, Tag, Tracker, TorrentList?.ServerState.UseSubcategories ?? false, SearchText);
|
||||
|
||||
protected string? Version { get; set; }
|
||||
|
||||
private async Task SearchTextChanged(string searchText)
|
||||
{
|
||||
SearchText = searchText == "" ? null : searchText;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task SelectedTorrentChanged(string hash)
|
||||
{
|
||||
if (TorrentList is not null)
|
||||
{
|
||||
TorrentList.SelectedTorrentHash = hash;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!await ApiClient.CheckAuthState())
|
||||
{
|
||||
NavigationManager.NavigateTo("/login");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Version = await ApiClient.GetApplicationVersion();
|
||||
var data = await ApiClient.GetMainData(_requestId);
|
||||
TorrentList = DataManager.CreateMainData(data);
|
||||
_requestId = data.ResponseId;
|
||||
|
||||
RefreshInterval = TorrentList.ServerState.RefreshInterval;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
NavigationManager.NavigateTo("/login");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_refreshEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
QBitTorrentClient.Models.MainData data;
|
||||
try
|
||||
{
|
||||
data = await ApiClient.GetMainData(_requestId);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TorrentList is null || data.FullUpdate)
|
||||
{
|
||||
TorrentList = DataManager.CreateMainData(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeMainData(data, TorrentList);
|
||||
}
|
||||
|
||||
RefreshInterval = TorrentList.ServerState.RefreshInterval;
|
||||
_requestId = data.ResponseId;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawerToggle()
|
||||
{
|
||||
DrawerOpen = !DrawerOpen;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" Title="Add torrent link" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" Title="Add torrent file" />
|
||||
<MudDivider Vertical="true" />
|
||||
<TorrentActions Type="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrents()" />
|
||||
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrents()" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Info" Color="Color.Inherit" Disabled="@(!ToolbarButtonsEnabled)" OnClick="ShowTorrent" Title="View torrent details" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" Title="Choose Columns" />
|
||||
|
||||
@@ -4,7 +4,6 @@ using Lantean.QBTMudBlade.Components;
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Pages
|
||||
@@ -171,8 +170,8 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
public static List<ColumnDefinition<Torrent>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
CreateColumnDefinition("#", t => t.Priority),
|
||||
CreateColumnDefinition("", t => t.State, IconColumn),
|
||||
CreateColumnDefinition("Name", t => t.Name, width: 200),
|
||||
CreateColumnDefinition("", t => t.State, IconColumn, iconOnly: true),
|
||||
CreateColumnDefinition("Name", t => t.Name, width: 400),
|
||||
CreateColumnDefinition("Size", t => t.Size, t => DisplayHelpers.Size(t.Size)),
|
||||
CreateColumnDefinition("Total Size", t => t.TotalSize, t => DisplayHelpers.Size(t.TotalSize), enabled: false),
|
||||
CreateColumnDefinition("Done", t => t.Progress, ProgressBarColumn, tdClass: "table-progress pl-1 pr-1"),
|
||||
@@ -205,7 +204,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
//CreateColumnDefinition("Reannounce In", t => t.Reannounce, enabled: false),
|
||||
];
|
||||
|
||||
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, bool iconOnly = false)
|
||||
{
|
||||
var cd = new ColumnDefinition<Torrent>(name, selector, rowTemplate);
|
||||
cd.Class = "no-wrap";
|
||||
@@ -215,11 +214,12 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
}
|
||||
cd.Width = width;
|
||||
cd.Enabled = enabled;
|
||||
cd.IconOnly = iconOnly;
|
||||
|
||||
return cd;
|
||||
}
|
||||
|
||||
private static ColumnDefinition<Torrent> CreateColumnDefinition(string name, Func<Torrent, object?> selector, Func<Torrent, string>? formatter = null, int? width = null, string? tdClass = null, bool enabled = true)
|
||||
private static ColumnDefinition<Torrent> CreateColumnDefinition(string name, Func<Torrent, object?> selector, Func<Torrent, string>? formatter = null, int? width = null, string? tdClass = null, bool enabled = true, bool iconOnly = false)
|
||||
{
|
||||
var cd = new ColumnDefinition<Torrent>(name, selector, formatter);
|
||||
cd.Class = "no-wrap";
|
||||
@@ -229,6 +229,7 @@ namespace Lantean.QBTMudBlade.Pages
|
||||
}
|
||||
cd.Width = width;
|
||||
cd.Enabled = enabled;
|
||||
cd.IconOnly = iconOnly;
|
||||
|
||||
return cd;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using MudBlazor;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Services
|
||||
{
|
||||
@@ -563,8 +559,12 @@ namespace Lantean.QBTMudBlade.Services
|
||||
public Dictionary<string, ContentItem> CreateContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files)
|
||||
{
|
||||
var contents = new Dictionary<string, ContentItem>();
|
||||
if (files.Count == 0)
|
||||
{
|
||||
return contents;
|
||||
}
|
||||
|
||||
var folderIndex = files.Max(f => f.Index) + 1;
|
||||
var folderIndex = files.Min(f => f.Index) - 1;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
@@ -582,7 +582,7 @@ namespace Lantean.QBTMudBlade.Services
|
||||
var directoryPath = string.Join(Extensions.DirectorySeparator, paths[0..(i + 1)]);
|
||||
if (!contents.ContainsKey(directoryPath))
|
||||
{
|
||||
contents.Add(directoryPath, new ContentItem(directoryPath, directoryName, folderIndex++, Priority.Normal, 0, 0, 0, true, i));
|
||||
contents.Add(directoryPath, new ContentItem(directoryPath, directoryName, folderIndex--, Priority.Normal, 0, 0, 0, true, i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,13 +600,22 @@ namespace Lantean.QBTMudBlade.Services
|
||||
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 allContents = filesContents.Concat(directoriesContents);
|
||||
var priorities = allContents.Select(d => d.Value.Priority).Distinct();
|
||||
var downloadingContents = allContents.Where(c => c.Value.Priority != Priority.DoNotDownload).ToList();
|
||||
|
||||
|
||||
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;
|
||||
long size = 0;
|
||||
float availability = 0;
|
||||
long downloaded = 0;
|
||||
float progress = 0;
|
||||
if (downloadingContents.Count != 0)
|
||||
{
|
||||
size = downloadingContents.Sum(c => c.Value.Size);
|
||||
availability = downloadingContents.Average(c => c.Value.Availability);
|
||||
downloaded = downloadingContents.Sum(c => c.Value.Downloaded);
|
||||
progress = (float)downloaded / size;
|
||||
}
|
||||
|
||||
|
||||
if (!contents.TryGetValue(key, out var dir))
|
||||
{
|
||||
@@ -615,7 +624,6 @@ namespace Lantean.QBTMudBlade.Services
|
||||
dir.Availability = availability;
|
||||
dir.Size = size;
|
||||
dir.Progress = progress;
|
||||
var priorities = directoryContents.Select(d => d.Value.Priority).Distinct();
|
||||
if (priorities.Count() == 1)
|
||||
{
|
||||
dir.Priority = priorities.First();
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Lantean.QBTMudBlade
|
||||
|
||||
public RenderFragment<RowContext<T>> RowTemplate { get; set; }
|
||||
|
||||
public bool IconOnly { get; set; }
|
||||
|
||||
public int? Width { get; set; }
|
||||
|
||||
public Func<T, string>? Formatter { get; set; }
|
||||
|
||||
@@ -99,20 +99,12 @@ td.no-wrap {
|
||||
text-overflow: ellipsis; /* Display an ellipsis when the text overflows */
|
||||
}
|
||||
|
||||
.rotate-180 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.background-blur {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.icon-menu::after {
|
||||
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
|
||||
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h24v24H0V0z' fill='#27272f'/%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>qBittorrent Web UI</title>
|
||||
<base href="/" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap" rel="stylesheet">
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
@@ -20,7 +20,7 @@ window.qbt.open = (url, target) => {
|
||||
window.open(url, target);
|
||||
}
|
||||
|
||||
window.qbt.renderPiecesBar = (id, hash, pieces) => {
|
||||
window.qbt.renderPiecesBar = (id, hash, pieces, downloadingColor, haveColor, borderColor) => {
|
||||
const parentElement = document.getElementById(id);
|
||||
if (window.qbt.hash !== hash) {
|
||||
if (parentElement) {
|
||||
@@ -29,9 +29,19 @@ window.qbt.renderPiecesBar = (id, hash, pieces) => {
|
||||
}
|
||||
}
|
||||
window.qbt.hash = hash;
|
||||
window.qbt.piecesBar = new window.qbt.PiecesBar([], {
|
||||
const options = {
|
||||
height: 24
|
||||
});
|
||||
};
|
||||
if (downloadingColor) {
|
||||
options.downloadingColor = downloadingColor;
|
||||
}
|
||||
if (haveColor) {
|
||||
options.haveColor = haveColor;
|
||||
}
|
||||
if (borderColor) {
|
||||
options.borderColor = borderColor;
|
||||
}
|
||||
window.qbt.piecesBar = new window.qbt.PiecesBar([], options);
|
||||
window.qbt.piecesBar.clear();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user