mirror of
				https://github.com/lantean-code/qbtmud.git
				synced 2025-11-03 21:43:19 +00:00 
			
		
		
		
	Reorganise folder structure, add document based keyboard events, update remaining tables to use DynamicTable, and general polish.
This commit is contained in:
		@@ -12,8 +12,8 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
 | 
					    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
 | 
				
			||||||
    <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
 | 
					    <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
 | 
				
			||||||
    <PackageReference Include="xunit" Version="2.8.1" />
 | 
					    <PackageReference Include="xunit" Version="2.9.0" />
 | 
				
			||||||
    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
 | 
					    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
 | 
				
			||||||
      <PrivateAssets>all</PrivateAssets>
 | 
					      <PrivateAssets>all</PrivateAssets>
 | 
				
			||||||
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
					      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
				
			||||||
    </PackageReference>
 | 
					    </PackageReference>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								Lantean.QBTMudBlade/Components/Dialogs/AddPeerDialog.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Lantean.QBTMudBlade/Components/Dialogs/AddPeerDialog.razor
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<MudDialog>
 | 
				
			||||||
 | 
					    <DialogContent>
 | 
				
			||||||
 | 
					        <table width="100%">
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td style="width: 70%"><MudTextField T="string" Label="IP" Value="@IP" ValueChanged="SetIP" Required Variant="Variant.Outlined" /></td>
 | 
				
			||||||
 | 
					                    <td style="width: 30%"><MudNumericField T="int?" Label="Port" Value="@Port" ValueChanged="SetPort" Required Variant="Variant.Outlined" /></td>
 | 
				
			||||||
 | 
					                    <td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddTracker" /></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                @foreach (var peer in Peers)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var peerRef = peer;
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <td>@peer</td>
 | 
				
			||||||
 | 
					                        <td><MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="@(e => DeletePeer(peerRef))" /></td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </DialogContent>
 | 
				
			||||||
 | 
					    <DialogActions>
 | 
				
			||||||
 | 
					        <MudButton OnClick="Cancel">Cancel</MudButton>
 | 
				
			||||||
 | 
					        <MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
 | 
				
			||||||
 | 
					    </DialogActions>
 | 
				
			||||||
 | 
					</MudDialog>
 | 
				
			||||||
@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					using Lantean.QBitTorrentClient.Models;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Components.Dialogs
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public partial class AddPeerDialog
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [CascadingParameter]
 | 
				
			||||||
 | 
					        public MudDialogInstance MudDialog { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected HashSet<PeerId> Peers { get; } = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected string? IP { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected int? Port { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void AddTracker()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (string.IsNullOrEmpty(IP) || !Port.HasValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Peers.Add(new PeerId(IP, Port.Value));
 | 
				
			||||||
 | 
					            IP = null;
 | 
				
			||||||
 | 
					            Port = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void SetIP(string value)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            IP = value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void SetPort(int? value)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Port = value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void DeletePeer(PeerId peer)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Peers.Remove(peer);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void Cancel()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MudDialog.Cancel();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void Submit()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MudDialog.Close(Peers);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -32,10 +32,10 @@
 | 
				
			|||||||
            </MudSelect>
 | 
					            </MudSelect>
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudFieldSwitch Label="Start torrent" @bind-Value="StartTorrent" />
 | 
					            <FieldSwitch Label="Start torrent" @bind-Value="StartTorrent" />
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudFieldSwitch Label="Add to top of queue" @bind-Value="AddToTopOfQueue" />
 | 
					            <FieldSwitch Label="Add to top of queue" @bind-Value="AddToTopOfQueue" />
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudSelect Label="Stop condition" @bind-Value="StopCondition" Variant="Variant.Outlined">
 | 
					            <MudSelect Label="Stop condition" @bind-Value="StopCondition" Variant="Variant.Outlined">
 | 
				
			||||||
@@ -45,7 +45,7 @@
 | 
				
			|||||||
            </MudSelect>
 | 
					            </MudSelect>
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudFieldSwitch Label="Skip hash check" @bind-Value="SkipHashCheck" />
 | 
					            <FieldSwitch Label="Skip hash check" @bind-Value="SkipHashCheck" />
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudSelect Label="Content layout" @bind-Value="ContentLayout" Variant="Variant.Outlined">
 | 
					        <MudSelect Label="Content layout" @bind-Value="ContentLayout" Variant="Variant.Outlined">
 | 
				
			||||||
            <MudSelectItem Value="@("Original")">Original</MudSelectItem>
 | 
					            <MudSelectItem Value="@("Original")">Original</MudSelectItem>
 | 
				
			||||||
@@ -53,10 +53,10 @@
 | 
				
			|||||||
            <MudSelectItem Value="@("NoSubfolder")">Don't create subfolder'</MudSelectItem>
 | 
					            <MudSelectItem Value="@("NoSubfolder")">Don't create subfolder'</MudSelectItem>
 | 
				
			||||||
        </MudSelect>
 | 
					        </MudSelect>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudFieldSwitch Label="Download in sequentual order" @bind-Value="DownloadInSequentialOrder" />
 | 
					            <FieldSwitch Label="Download in sequentual order" @bind-Value="DownloadInSequentialOrder" />
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudFieldSwitch Label="Download first and last pieces first" @bind-Value="DownloadFirstAndLastPiecesFirst" />
 | 
					            <FieldSwitch Label="Download first and last pieces first" @bind-Value="DownloadFirstAndLastPiecesFirst" />
 | 
				
			||||||
        </MudItem>
 | 
					        </MudItem>
 | 
				
			||||||
        <MudItem xs="12">
 | 
					        <MudItem xs="12">
 | 
				
			||||||
            <MudNumericField Label="Limit download rate" @bind-Value="DownloadLimit" Variant="Variant.Outlined" Min="0" />
 | 
					            <MudNumericField Label="Limit download rate" @bind-Value="DownloadLimit" Variant="Variant.Outlined" Min="0" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
        <table width="100%">
 | 
					        <table width="100%">
 | 
				
			||||||
            <tbody>
 | 
					            <tbody>
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td style="width: 100%"><MudTextField T="string" Label="Tag" Value="@Tracker" ValueChanged="SetTracker" Required Variant="Variant.Outlined" /></td>
 | 
					                    <td style="width: 100%"><MudTextField T="string" Label="Tracker" Value="@Tracker" ValueChanged="SetTracker" Required Variant="Variant.Outlined" /></td>
 | 
				
			||||||
                    <td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddTracker" /></td>
 | 
					                    <td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddTracker" /></td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
                @foreach (var tracker in Trackers)
 | 
					                @foreach (var tracker in Trackers)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -105,7 +105,7 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected async Task AddCategory()
 | 
					        protected async Task AddCategory()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var addedCategoy = await DialogService.ShowAddCategoryDialog(ApiClient);
 | 
					            var addedCategoy = await DialogService.InvokeAddCategoryDialog(ApiClient);
 | 
				
			||||||
            if (addedCategoy is null)
 | 
					            if (addedCategoy is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					<MudDialog>
 | 
				
			||||||
 | 
					    <DialogContent>
 | 
				
			||||||
 | 
					        <table width="100%">
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td style="width: 100%"><MudTextField T="string" Label="@(Label)" Value="@Value" ValueChanged="SetValue" Required Variant="Variant.Outlined" /></td>
 | 
				
			||||||
 | 
					                    <td><MudIconButton Icon="@Icons.Material.Filled.Add" OnClick="AddValue" /></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                @foreach (var value in NewValues)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var valueRef = value;
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <td>@value</td>
 | 
				
			||||||
 | 
					                        <td><MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="@(e => DeleteValue(valueRef))" /></td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </DialogContent>
 | 
				
			||||||
 | 
					    <DialogActions>
 | 
				
			||||||
 | 
					        <MudButton OnClick="Cancel">Cancel</MudButton>
 | 
				
			||||||
 | 
					        <MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
 | 
				
			||||||
 | 
					    </DialogActions>
 | 
				
			||||||
 | 
					</MudDialog>
 | 
				
			||||||
@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Components.Dialogs
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public partial class MultipleFieldDialog
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        [CascadingParameter]
 | 
				
			||||||
 | 
					        public MudDialogInstance MudDialog { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        public string Label { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        public HashSet<string> Values { get; set; } = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected HashSet<string> NewValues { get; } = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected string? Value { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected override void OnParametersSet()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (NewValues.Count == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var value in Values)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    NewValues.Add(value);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void AddValue()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (string.IsNullOrEmpty(Value))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            NewValues.Add(Value);
 | 
				
			||||||
 | 
					            Value = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void SetValue(string tracker)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Value = tracker;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void DeleteValue(string tracker)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            NewValues.Remove(tracker);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void Cancel()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MudDialog.Cancel();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void Submit()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MudDialog.Close(NewValues);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -33,12 +33,11 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            return Task.CompletedTask;
 | 
					            return Task.CompletedTask;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task RecoverAndClearErrors()
 | 
					        public Task RecoverAndClearErrors()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Recover();
 | 
					            Recover();
 | 
				
			||||||
            _exceptions.Clear();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await OnClear.InvokeAsync();
 | 
					            return ClearErrors();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task ClearErrors()
 | 
					        public async Task ClearErrors()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,9 @@
 | 
				
			|||||||
<MudToolBar Gutters="false" Dense="true">
 | 
					<ContextMenu @ref="ContextMenu" Dense="true">
 | 
				
			||||||
    <MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFile" title="Rename" />
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileContextMenu">Rename</MudMenuItem>
 | 
				
			||||||
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<MudToolBar Gutters="false" Dense="true">
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileToolbar" title="Rename" />
 | 
				
			||||||
    <MudDivider Vertical="true" />
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
    <MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
 | 
					    <MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
 | 
				
			||||||
    <MudDivider Vertical="true" />
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
@@ -25,10 +29,13 @@
 | 
				
			|||||||
    ColumnDefinitions="Columns" 
 | 
					    ColumnDefinitions="Columns" 
 | 
				
			||||||
    Items="Files" 
 | 
					    Items="Files" 
 | 
				
			||||||
    MultiSelection="false"
 | 
					    MultiSelection="false"
 | 
				
			||||||
 | 
					    SelectOnRowClick="true"
 | 
				
			||||||
    PreSorted="true"
 | 
					    PreSorted="true"
 | 
				
			||||||
    SelectedItemChanged="SelectedItemChanged"
 | 
					    SelectedItemChanged="SelectedItemChanged"
 | 
				
			||||||
    SortColumnChanged="SortColumnChanged"
 | 
					    SortColumnChanged="SortColumnChanged"
 | 
				
			||||||
    SortDirectionChanged="SortDirectionChanged"
 | 
					    SortDirectionChanged="SortDirectionChanged"
 | 
				
			||||||
 | 
					    OnTableDataContextMenu="TableDataContextMenu"
 | 
				
			||||||
 | 
					    OnTableDataLongPress="TableDataLongPress"
 | 
				
			||||||
    Class="file-list"
 | 
					    Class="file-list"
 | 
				
			||||||
/>
 | 
					/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,10 +46,10 @@
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            return context => __builder => 
 | 
					            return context => __builder => 
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                <div style="@($"margin-left: {context.Data.Level * 14}px")">
 | 
					                <div style="@($"margin-left: {(context.Data.Level * 14) + (context.Data.Level >= 1 ? 16 : 0)}px")">
 | 
				
			||||||
                    @if (context.Data.IsFolder)
 | 
					                    @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))"></MudIconButton>
 | 
					                        <MudIconButton Class="folder-button" 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" />
 | 
					                        <MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-0" Style="margin-right: 4px; position: relative; top: 7px; margin-left: -15px" />
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    @context.Data.DisplayName
 | 
					                    @context.Data.DisplayName
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
using Blazored.LocalStorage;
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
using Lantean.QBTMudBlade.Components.Dialogs;
 | 
					using Lantean.QBTMudBlade.Components.Dialogs;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Filter;
 | 
					using Lantean.QBTMudBlade.Filter;
 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Lantean.QBTMudBlade.Services;
 | 
					using Lantean.QBTMudBlade.Services;
 | 
				
			||||||
@@ -55,13 +56,15 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected ContentItem? SelectedItem { get; set; }
 | 
					        protected ContentItem? SelectedItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected ContentItem? ContextMenuItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected string? SearchText { get; set; }
 | 
					        protected string? SearchText { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public IEnumerable<Func<ContentItem, bool>>? Filters { get; set; }
 | 
					        public IEnumerable<Func<ContentItem, bool>>? Filters { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private DynamicTable<ContentItem>? Table { get; set; }
 | 
					        private DynamicTable<ContentItem>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private ContextMenu? ContextMenu { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public FilesTab()
 | 
					        public FilesTab()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -123,22 +126,6 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            GC.SuppressFinalize(this);
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected static float CalculateProgress(IEnumerable<ContentItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return (float)items.Sum(i => i.Downloaded) / items.Sum(i => i.Size);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected static Priority GetPriority(IEnumerable<ContentItem> items)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var distinctPriorities = items.Select(i => i.Priority).Distinct();
 | 
					 | 
				
			||||||
            if (distinctPriorities.Count() == 1)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return distinctPriorities.First();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return Priority.Mixed;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected virtual async Task DisposeAsync(bool disposing)
 | 
					        protected virtual async Task DisposeAsync(bool disposing)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!_disposedValue)
 | 
					            if (!_disposedValue)
 | 
				
			||||||
@@ -155,19 +142,42 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected static Priority GetPriority(IEnumerable<ContentItem> items)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var distinctPriorities = items.Select(i => i.Priority).Distinct();
 | 
				
			||||||
 | 
					            if (distinctPriorities.Count() == 1)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return distinctPriorities.First();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Priority.Mixed;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void SearchTextChanged(string value)
 | 
					        protected void SearchTextChanged(string value)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            SearchText = value;
 | 
					            SearchText = value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task EnabledValueChanged(ContentItem contentItem, bool value)
 | 
					        protected Task TableDataContextMenu(TableDataContextMenuEventArgs<ContentItem> eventArgs)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null)
 | 
					            return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task TableDataLongPress(TableDataLongPressEventArgs<ContentItem> eventArgs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return ShowContextMenu(eventArgs.Item, eventArgs.LongPressEventArgs);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task ShowContextMenu(ContentItem? contentItem, EventArgs eventArgs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ContextMenuItem = contentItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ContextMenu is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await ApiClient.SetFilePriority(Hash, [contentItem.Index], MapPriority(value ? Priority.Normal : Priority.DoNotDownload));
 | 
					            await ContextMenu.OpenMenuAsync(eventArgs);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
					        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
				
			||||||
@@ -267,22 +277,44 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            await ApiClient.SetFilePriority(Hash, fileIndexes, MapPriority(priority));
 | 
					            await ApiClient.SetFilePriority(Hash, fileIndexes, MapPriority(priority));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task RenameFile()
 | 
					        protected Task RenameFileToolbar()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null || FileList is null || SelectedItem is null)
 | 
					            if (SelectedItem is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Task.CompletedTask;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return RenameFiles(SelectedItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task RenameFileContextMenu()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (ContextMenuItem is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Task.CompletedTask;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return RenameFiles(ContextMenuItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task RenameFiles(params ContentItem[] contentItems)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Hash is null || contentItems.Length == 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var contentItem = FileList.Values.FirstOrDefault(c => c.Index == SelectedItem.Index);
 | 
					            if (contentItems.Length == 1)
 | 
				
			||||||
            if (contentItem is null)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                var contentItem = contentItems[0];
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var name = contentItem.GetFileName();
 | 
					                var name = contentItem.GetFileName();
 | 
				
			||||||
                await DialogService.ShowSingleFieldDialog("Rename", "New name", name, async value => await ApiClient.RenameFile(Hash, contentItem.Name, contentItem.Path + value));
 | 
					                await DialogService.ShowSingleFieldDialog("Rename", "New name", name, async value => await ApiClient.RenameFile(Hash, contentItem.Name, contentItem.Path + value));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await DialogService.InvokeRenameFilesDialog(ApiClient, Hash);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void SortColumnChanged(string sortColumn)
 | 
					        protected void SortColumnChanged(string sortColumn)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -511,7 +543,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public static List<ColumnDefinition<ContentItem>> ColumnsDefinitions { get; } =
 | 
					        public static List<ColumnDefinition<ContentItem>> ColumnsDefinitions { get; } =
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            CreateColumnDefinition("Name", c => c.Name, width: 400, 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-2"),
 | 
				
			||||||
            CreateColumnDefinition("Total Size", c => c.Size, c => DisplayHelpers.Size(c.Size)),
 | 
					            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("Progress", c => c.Progress, ProgressBarColumn, tdClass: "table-progress pl-2 pr-2"),
 | 
				
			||||||
            CreateColumnDefinition("Priority", c => c.Priority, tdClass: "table-select pa-0"),
 | 
					            CreateColumnDefinition("Priority", c => c.Priority, tdClass: "table-select pa-0"),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
<ContextMenu @ref="StatusContextMenu" Dense="true" InsideDrawer="true">
 | 
					<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
 | 
				
			||||||
    @TorrentControls(_statusType)
 | 
					    @TorrentControls(_statusType)
 | 
				
			||||||
</ContextMenu>
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ContextMenu @ref="CategoryContextMenu" Dense="true" InsideDrawer="true">
 | 
					<ContextMenu @ref="CategoryContextMenu" Dense="true" AdjustmentY="-60">
 | 
				
			||||||
    <MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddCategory">Add category</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddCategory">Add category</MudMenuItem>
 | 
				
			||||||
    @if (IsCategoryTarget)
 | 
					    @if (IsCategoryTarget)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -14,7 +14,7 @@
 | 
				
			|||||||
    @TorrentControls(_categoryType)
 | 
					    @TorrentControls(_categoryType)
 | 
				
			||||||
</ContextMenu>
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ContextMenu @ref="TagContextMenu" Dense="true" InsideDrawer="true">
 | 
					<ContextMenu @ref="TagContextMenu" Dense="true" AdjustmentY="-60">
 | 
				
			||||||
    <MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddTag">Add tag</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddTag">Add tag</MudMenuItem>
 | 
				
			||||||
    @if (IsTagTarget)
 | 
					    @if (IsTagTarget)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -25,7 +25,7 @@
 | 
				
			|||||||
    @TorrentControls(_tagType)
 | 
					    @TorrentControls(_tagType)
 | 
				
			||||||
</ContextMenu>
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ContextMenu @ref="TrackerContextMenu" Dense="true" InsideDrawer="true">
 | 
					<ContextMenu @ref="TrackerContextMenu" Dense="true" AdjustmentY="-60">
 | 
				
			||||||
    <MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove tracker</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove tracker</MudMenuItem>
 | 
				
			||||||
    <MudDivider />
 | 
					    <MudDivider />
 | 
				
			||||||
    @TorrentControls(_trackerType)
 | 
					    @TorrentControls(_trackerType)
 | 
				
			||||||
@@ -36,25 +36,25 @@
 | 
				
			|||||||
        @foreach (var (status, count) in Statuses)
 | 
					        @foreach (var (status, count) in Statuses)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var (icon, color) = DisplayHelpers.GetStatusIcon(status);
 | 
					            var (icon, color) = DisplayHelpers.GetStatusIcon(status);
 | 
				
			||||||
            <FakeNavLink Class="filter-menu-item" Active="@(Status == status)" Icon="@icon" IconColor="@color" OnClick="@(e => StatusValueChanged(status))" OnContextMenu="@(e => StatusOnContextMenu(e, status))" OnLongPress="@(e => StatusOnLongPress(e, status))">@($"{status.GetStatusName()} ({count})")</FakeNavLink>
 | 
					            <CustomNavLink Class="filter-menu-item" Active="@(Status == status)" Icon="@icon" IconColor="@color" OnClick="@(e => StatusValueChanged(status))" OnContextMenu="@(e => StatusOnContextMenu(e, status))" OnLongPress="@(e => StatusOnLongPress(e, status))">@($"{status.GetStatusName()} ({count})")</CustomNavLink>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </MudNavGroup>
 | 
					    </MudNavGroup>
 | 
				
			||||||
    <MudNavGroup Title="Categories" @bind-Expanded="_categoriesExpanded">
 | 
					    <MudNavGroup Title="Categories" @bind-Expanded="_categoriesExpanded">
 | 
				
			||||||
        @foreach (var (category, count) in Categories)
 | 
					        @foreach (var (category, count) in Categories)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            <FakeNavLink Class="filter-menu-item" Active="@(Category == category)" Icon="@Icons.Material.Filled.List" IconColor="Color.Info" OnClick="@(e => CategoryValueChanged(category))" OnContextMenu="@(e => CategoryOnContextMenu(e, category))" OnLongPress="@(e => CategoryOnLongPress(e, category))">@($"{category} ({count})")</FakeNavLink>
 | 
					            <CustomNavLink Class="filter-menu-item" Active="@(Category == category)" Icon="@Icons.Material.Filled.List" IconColor="Color.Info" OnClick="@(e => CategoryValueChanged(category))" OnContextMenu="@(e => CategoryOnContextMenu(e, category))" OnLongPress="@(e => CategoryOnLongPress(e, category))">@($"{category} ({count})")</CustomNavLink>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </MudNavGroup>
 | 
					    </MudNavGroup>
 | 
				
			||||||
    <MudNavGroup Title="Tags" @bind-Expanded="_tagsExpanded">
 | 
					    <MudNavGroup Title="Tags" @bind-Expanded="_tagsExpanded">
 | 
				
			||||||
        @foreach (var (tag, count) in Tags)
 | 
					        @foreach (var (tag, count) in Tags)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            <FakeNavLink Class="filter-menu-item" Active="@(Tag == tag)" Icon="@Icons.Material.Filled.Label" IconColor="Color.Info" OnClick="@(e => TagValueChanged(tag))" OnContextMenu="@(e => TagOnContextMenu(e, tag))" OnLongPress="@(e => TagOnLongPress(e, tag))">@($"{tag} ({count})")</FakeNavLink>
 | 
					            <CustomNavLink Class="filter-menu-item" Active="@(Tag == tag)" Icon="@Icons.Material.Filled.Label" IconColor="Color.Info" OnClick="@(e => TagValueChanged(tag))" OnContextMenu="@(e => TagOnContextMenu(e, tag))" OnLongPress="@(e => TagOnLongPress(e, tag))">@($"{tag} ({count})")</CustomNavLink>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </MudNavGroup>
 | 
					    </MudNavGroup>
 | 
				
			||||||
    <MudNavGroup Title="Trackers" @bind-Expanded="_trackersExpanded">
 | 
					    <MudNavGroup Title="Trackers" @bind-Expanded="_trackersExpanded">
 | 
				
			||||||
        @foreach (var (tracker, count) in Trackers)
 | 
					        @foreach (var (tracker, count) in Trackers)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            <FakeNavLink Class="filter-menu-item" Active="@(Tracker == tracker)" Icon="@Icons.Material.Filled.PinDrop" IconColor="Color.Info" OnClick="@(e => TrackerValueChanged(tracker))" OnContextMenu="@(e => TrackerOnContextMenu(e, tracker))" OnLongPress="@(e => TrackerOnLongPress(e, tracker))">@($"{tracker} ({count})")</FakeNavLink>
 | 
					            <CustomNavLink Class="filter-menu-item" Active="@(Tracker == tracker)" Icon="@Icons.Material.Filled.PinDrop" IconColor="Color.Info" OnClick="@(e => TrackerValueChanged(tracker))" OnContextMenu="@(e => TrackerOnContextMenu(e, tracker))" OnLongPress="@(e => TrackerOnLongPress(e, tracker))">@($"{tracker} ({count})")</CustomNavLink>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </MudNavGroup>
 | 
					    </MudNavGroup>
 | 
				
			||||||
</MudNavMenu>
 | 
					</MudNavMenu>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
using Blazored.LocalStorage;
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using Microsoft.AspNetCore.Components.Web;
 | 
					using Microsoft.AspNetCore.Components.Web;
 | 
				
			||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -271,7 +271,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected async Task AddCategory()
 | 
					        protected async Task AddCategory()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await DialogService.ShowAddCategoryDialog(ApiClient);
 | 
					            await DialogService.InvokeAddCategoryDialog(ApiClient);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task EditCategory()
 | 
					        protected async Task EditCategory()
 | 
				
			||||||
@@ -281,7 +281,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await DialogService.ShowEditCategoryDialog(ApiClient, ContextMenuCategory);
 | 
					            await DialogService.InvokeEditCategoryDialog(ApiClient, ContextMenuCategory);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task RemoveCategory()
 | 
					        protected async Task RemoveCategory()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,9 @@
 | 
				
			|||||||
    <MudMenuItem Icon="@Icons.Material.Filled.List" Href="/log">Execution Log</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.List" Href="/log">Execution Log</MudMenuItem>
 | 
				
			||||||
    <MudMenuItem Icon="@Icons.Material.Filled.DisabledByDefault" Href="/blocks">Blocked IPs</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.DisabledByDefault" Href="/blocks">Blocked IPs</MudMenuItem>
 | 
				
			||||||
    <MudDivider />
 | 
					    <MudDivider />
 | 
				
			||||||
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.Label" Href="/tags">Tag Management</MudMenuItem>
 | 
				
			||||||
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.List" Href="/categories">Category Management</MudMenuItem>
 | 
				
			||||||
 | 
					    <MudDivider />
 | 
				
			||||||
    <MudMenuItem Icon="@Icons.Material.Filled.Settings" Href="/settings">Settings</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.Settings" Href="/settings">Settings</MudMenuItem>
 | 
				
			||||||
    <MudMenuItem Icon="@Icons.Material.Filled.Undo" OnClick="ResetWebUI">Reset Web UI</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.Undo" OnClick="ResetWebUI">Reset Web UI</MudMenuItem>
 | 
				
			||||||
    <MudDivider />
 | 
					    <MudDivider />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
<MudField Variant="Variant.Outlined" InnerPadding="false" Label="@Label" HelperText="@HelperText">
 | 
					 | 
				
			||||||
    <MudTickSwitch T="bool" Value="@Value" ValueChanged="ValueChangedCallback" Class="pt-1 pb-1" Disabled="Disabled" Validation="Validation" />
 | 
					 | 
				
			||||||
</MudField>
 | 
					 | 
				
			||||||
@@ -44,25 +44,25 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label=".torrent file size limit" Value="TorrentFileSizeLimit" ValueChanged="TorrentFileSizeLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
 | 
					                <MudNumericField T="int" Label=".torrent file size limit" Value="TorrentFileSizeLimit" ValueChanged="TorrentFileSizeLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Recheck torrents on completion" Value="RecheckCompletedTorrents" ValueChanged="RecheckCompletedTorrentsChanged" />
 | 
					                <FieldSwitch Label="Recheck torrents on completion" Value="RecheckCompletedTorrents" ValueChanged="RecheckCompletedTorrentsChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudNumericField T="int" Label="Refresh interval" Value="RefreshInterval" ValueChanged="RefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="ms" />
 | 
					                <MudNumericField T="int" Label="Refresh interval" Value="RefreshInterval" ValueChanged="RefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="ms" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Resolve peer countries" Value="ResolvePeerCountries" ValueChanged="ResolvePeerCountriesChanged" />
 | 
					                <FieldSwitch Label="Resolve peer countries" Value="ResolvePeerCountries" ValueChanged="ResolvePeerCountriesChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Reannounce to all trackers when IP or port changed" Value="ReannounceWhenAddressChanged" ValueChanged="ReannounceWhenAddressChangedChanged" />
 | 
					                <FieldSwitch Label="Reannounce to all trackers when IP or port changed" Value="ReannounceWhenAddressChanged" ValueChanged="ReannounceWhenAddressChangedChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable embedded tracker" Value="EnableEmbeddedTracker" ValueChanged="EnableEmbeddedTrackerChanged" />
 | 
					                <FieldSwitch Label="Enable embedded tracker" Value="EnableEmbeddedTracker" ValueChanged="EnableEmbeddedTrackerChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudNumericField T="int" Label="Embedded tracker port" Value="EmbeddedTrackerPort" ValueChanged="EmbeddedTrackerPortChanged" Min="@Options.MinPortValue" Max="@Options.MaxPortValue" Variant="Variant.Outlined" />
 | 
					                <MudNumericField T="int" Label="Embedded tracker port" Value="EmbeddedTrackerPort" ValueChanged="EmbeddedTrackerPortChanged" Min="@Options.MinPortValue" Max="@Options.MaxPortValue" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable port forwarding for embedded tracker" Value="EmbeddedTrackerPortForwarding" ValueChanged="EmbeddedTrackerPortForwardingChanged" />
 | 
					                <FieldSwitch Label="Enable port forwarding for embedded tracker" Value="EmbeddedTrackerPortForwarding" ValueChanged="EmbeddedTrackerPortForwardingChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -124,13 +124,13 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Coalesce reads & writes (requires libtorrent < 2.0)" Value="EnableCoalesceReadWrite" ValueChanged="EnableCoalesceReadWriteChanged" />
 | 
					                <FieldSwitch Label="Coalesce reads & writes (requires libtorrent < 2.0)" Value="EnableCoalesceReadWrite" ValueChanged="EnableCoalesceReadWriteChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use piece extent affinity" Value="EnablePieceExtentAffinity" ValueChanged="EnablePieceExtentAffinityChanged" />
 | 
					                <FieldSwitch Label="Use piece extent affinity" Value="EnablePieceExtentAffinity" ValueChanged="EnablePieceExtentAffinityChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Send upload piece suggestions" Value="EnableUploadSuggestions" ValueChanged="EnableUploadSuggestionsChanged" />
 | 
					                <FieldSwitch Label="Send upload piece suggestions" Value="EnableUploadSuggestions" ValueChanged="EnableUploadSuggestionsChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudNumericField T="int" Label="Send buffer watermark" Value="SendBufferWatermark" ValueChanged="SendBufferWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
 | 
					                <MudNumericField T="int" Label="Send buffer watermark" Value="SendBufferWatermark" ValueChanged="SendBufferWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
 | 
				
			||||||
@@ -172,19 +172,19 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Support internationalized domain name (IDN)" Value="IdnSupportEnabled" ValueChanged="IdnSupportEnabledChanged" />
 | 
					                <FieldSwitch Label="Support internationalized domain name (IDN)" Value="IdnSupportEnabled" ValueChanged="IdnSupportEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Allow multiple connections from the same IP address" Value="EnableMultiConnectionsFromSameIp" ValueChanged="EnableMultiConnectionsFromSameIpChanged" />
 | 
					                <FieldSwitch Label="Allow multiple connections from the same IP address" Value="EnableMultiConnectionsFromSameIp" ValueChanged="EnableMultiConnectionsFromSameIpChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Validate HTTPS tracker certificate" Value="ValidateHttpsTrackerCertificate" ValueChanged="ValidateHttpsTrackerCertificateChanged" />
 | 
					                <FieldSwitch Label="Validate HTTPS tracker certificate" Value="ValidateHttpsTrackerCertificate" ValueChanged="ValidateHttpsTrackerCertificateChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Server-side request forgery (SSRF) mitigation" Value="SsrfMitigation" ValueChanged="SsrfMitigationChanged" />
 | 
					                <FieldSwitch Label="Server-side request forgery (SSRF) mitigation" Value="SsrfMitigation" ValueChanged="SsrfMitigationChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Disallow connection to peers on privileged ports" Value="BlockPeersOnPrivilegedPorts" ValueChanged="BlockPeersOnPrivilegedPortsChanged" />
 | 
					                <FieldSwitch Label="Disallow connection to peers on privileged ports" Value="BlockPeersOnPrivilegedPorts" ValueChanged="BlockPeersOnPrivilegedPortsChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudSelect T="int" Label="Upload slots behavior" Value="UploadSlotsBehavior" ValueChanged="UploadSlotsBehaviorChanged" Variant="Variant.Outlined">
 | 
					                <MudSelect T="int" Label="Upload slots behavior" Value="UploadSlotsBehavior" ValueChanged="UploadSlotsBehaviorChanged" Variant="Variant.Outlined">
 | 
				
			||||||
@@ -200,10 +200,10 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Always announce to all trackers in a tier" Value="AnnounceToAllTrackers" ValueChanged="AnnounceToAllTrackersChanged" />
 | 
					                <FieldSwitch Label="Always announce to all trackers in a tier" Value="AnnounceToAllTrackers" ValueChanged="AnnounceToAllTrackersChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Always announce to all tiers" Value="AnnounceToAllTiers" ValueChanged="AnnounceToAllTiersChanged" />
 | 
					                <FieldSwitch Label="Always announce to all tiers" Value="AnnounceToAllTiers" ValueChanged="AnnounceToAllTiersChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="IP address reported to trackers (requires restart)" Value="AnnounceIp" ValueChanged="AnnounceIpChanged" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="IP address reported to trackers (requires restart)" Value="AnnounceIp" ValueChanged="AnnounceIpChanged" Variant="Variant.Outlined" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,19 +26,19 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Log file" Value="FileLogEnabled" ValueChanged="FileLogEnabledChanged" />
 | 
					                <FieldSwitch Label="Log file" Value="FileLogEnabled" ValueChanged="FileLogEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Save Path" Value="FileLogPath" ValueChanged="FileLogPathChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Save Path" Value="FileLogPath" ValueChanged="FileLogPathChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="3">
 | 
					            <MudItem xs="3">
 | 
				
			||||||
                <MudFieldSwitch Label="Backup the log after" Value="FileLogBackupEnabled" ValueChanged="FileLogBackupEnabledChanged" Disabled="@(!FileLogEnabled)" />
 | 
					                <FieldSwitch Label="Backup the log after" Value="FileLogBackupEnabled" ValueChanged="FileLogBackupEnabledChanged" Disabled="@(!FileLogEnabled)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="9">
 | 
					            <MudItem xs="9">
 | 
				
			||||||
                <MudNumericField T="int" Value="FileLogMaxSize" ValueChanged="FileLogMaxSizeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
 | 
					                <MudNumericField T="int" Value="FileLogMaxSize" ValueChanged="FileLogMaxSizeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="3">
 | 
					            <MudItem xs="3">
 | 
				
			||||||
                <MudFieldSwitch Label="Delete backups older than" Value="FileLogDeleteOld" ValueChanged="FileLogDeleteOldChanged" Disabled="@(!FileLogEnabled)" />
 | 
					                <FieldSwitch Label="Delete backups older than" Value="FileLogDeleteOld" ValueChanged="FileLogDeleteOldChanged" Disabled="@(!FileLogEnabled)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="9">
 | 
					            <MudItem xs="9">
 | 
				
			||||||
                <MudGrid>
 | 
					                <MudGrid>
 | 
				
			||||||
@@ -67,7 +67,7 @@
 | 
				
			|||||||
    <MudCardContent>
 | 
					    <MudCardContent>
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Log performance warnings" Value="PerformanceWarning" ValueChanged="PerformanceWarningChanged" />
 | 
					                <FieldSwitch Label="Log performance warnings" Value="PerformanceWarning" ValueChanged="PerformanceWarningChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,13 +9,13 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable DHT (decentralized network) to find more peers" Value="Dht" ValueChanged="DhtChanged" />
 | 
					                <FieldSwitch Label="Enable DHT (decentralized network) to find more peers" Value="Dht" ValueChanged="DhtChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable Peer Exchange (PeX) to find more peers" Value="Pex" ValueChanged="PexChanged" />
 | 
					                <FieldSwitch Label="Enable Peer Exchange (PeX) to find more peers" Value="Pex" ValueChanged="PexChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable Local Peer Discovery to find more peers" Value="Lsd" ValueChanged="LsdChanged" />
 | 
					                <FieldSwitch Label="Enable Local Peer Discovery to find more peers" Value="Lsd" ValueChanged="LsdChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudSelect T="int" Label="Encryption mode" Value="Encryption" ValueChanged="EncryptionChanged" Variant="Variant.Outlined">
 | 
					                <MudSelect T="int" Label="Encryption mode" Value="Encryption" ValueChanged="EncryptionChanged" Variant="Variant.Outlined">
 | 
				
			||||||
@@ -25,7 +25,7 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="6">
 | 
					            <MudItem xs="6">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable anonymous mode" Value="AnonymousMode" ValueChanged="AnonymousModeChanged" />
 | 
					                <FieldSwitch Label="Enable anonymous mode" Value="AnonymousMode" ValueChanged="AnonymousModeChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="6">
 | 
					            <MudItem xs="6">
 | 
				
			||||||
                <MudLink Href="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode" Underline="Underline.Always" Target="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode">More information</MudLink>
 | 
					                <MudLink Href="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode" Underline="Underline.Always" Target="https://github.com/qbittorrent/qBittorrent/wiki/Anonymous-Mode">More information</MudLink>
 | 
				
			||||||
@@ -53,7 +53,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Queueing enabled" Value="QueueingEnabled" ValueChanged="QueueingEnabledChanged" />
 | 
					                <FieldSwitch Label="Queueing enabled" Value="QueueingEnabled" ValueChanged="QueueingEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudNumericField T="int" Label="Maximum active downloads" Value="MaxActiveDownloads" ValueChanged="MaxActiveDownloadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveDownloadsValidation" />
 | 
					                <MudNumericField T="int" Label="Maximum active downloads" Value="MaxActiveDownloads" ValueChanged="MaxActiveDownloadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveDownloadsValidation" />
 | 
				
			||||||
@@ -65,7 +65,7 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label="Maximum active torrents" Value="MaxActiveTorrents" ValueChanged="MaxActiveTorrentsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveTorrentsValidation" />
 | 
					                <MudNumericField T="int" Label="Maximum active torrents" Value="MaxActiveTorrents" ValueChanged="MaxActiveTorrentsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveTorrentsValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Do not count slow torrents in these limits" Value="DontCountSlowTorrents" ValueChanged="DontCountSlowTorrentsChanged" Disabled="@(!QueueingEnabled)" />
 | 
					                <FieldSwitch Label="Do not count slow torrents in these limits" Value="DontCountSlowTorrents" ValueChanged="DontCountSlowTorrentsChanged" Disabled="@(!QueueingEnabled)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudNumericField T="int" Label="Download rate threshold" Value="SlowTorrentDlRateThreshold" ValueChanged="SlowTorrentDlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentDlRateThresholdValidation" />
 | 
					                <MudNumericField T="int" Label="Download rate threshold" Value="SlowTorrentDlRateThreshold" ValueChanged="SlowTorrentDlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentDlRateThresholdValidation" />
 | 
				
			||||||
@@ -89,19 +89,19 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="3">
 | 
					            <MudItem xs="3">
 | 
				
			||||||
                <MudFieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" />
 | 
					                <FieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="9">
 | 
					            <MudItem xs="9">
 | 
				
			||||||
                <MudNumericField T="int" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged" Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined" Validation="MaxRatioValidation" />
 | 
					                <MudNumericField T="int" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged" Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined" Validation="MaxRatioValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="3">
 | 
					            <MudItem xs="3">
 | 
				
			||||||
                <MudFieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" />
 | 
					                <FieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="9">
 | 
					            <MudItem xs="9">
 | 
				
			||||||
                <MudNumericField T="int" Label="minutes" Value="MaxSeedingTime" ValueChanged="MaxSeedingTimeChanged" Disabled="@(!MaxSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" Validation="MaxSeedingTimeValidation" />
 | 
					                <MudNumericField T="int" Label="minutes" Value="MaxSeedingTime" ValueChanged="MaxSeedingTimeChanged" Disabled="@(!MaxSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" Validation="MaxSeedingTimeValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="3">
 | 
					            <MudItem xs="3">
 | 
				
			||||||
                <MudFieldSwitch Label="When inactive seeding time reaches" Value="MaxInactiveSeedingTimeEnabled" ValueChanged="MaxInactiveSeedingTimeEnabledChanged" />
 | 
					                <FieldSwitch Label="When inactive seeding time reaches" Value="MaxInactiveSeedingTimeEnabled" ValueChanged="MaxInactiveSeedingTimeEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="9">
 | 
					            <MudItem xs="9">
 | 
				
			||||||
                <MudNumericField T="int" Label="minutes" Value="MaxInactiveSeedingTime" ValueChanged="MaxInactiveSeedingTimeChanged" Disabled="@(!MaxInactiveSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Validation="MaxInactiveSeedingTimeValidation" />
 | 
					                <MudNumericField T="int" Label="minutes" Value="MaxInactiveSeedingTime" ValueChanged="MaxInactiveSeedingTimeChanged" Disabled="@(!MaxInactiveSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Validation="MaxInactiveSeedingTimeValidation" />
 | 
				
			||||||
@@ -127,7 +127,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Automatically add these trackers to new downloads" Value="AddTrackersEnabled" ValueChanged="AddTrackersEnabledChanged" />
 | 
					                <FieldSwitch Label="Automatically add these trackers to new downloads" Value="AddTrackersEnabled" ValueChanged="AddTrackersEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Trackers" Value="AddTrackers" ValueChanged="AddTrackersChanged" Lines="5" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Trackers" Value="AddTrackers" ValueChanged="AddTrackersChanged" Lines="5" Variant="Variant.Outlined" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label="Port used for incoming connections" Value="ListenPort" ValueChanged="ListenPortChanged" Min="@MinNonNegativePortValue" Max="@MaxPortValue" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@CustomIcons.Random" OnAdornmentClick="GenerateRandomPort" HelperText="Set to 0 to let your system pick an unused port" Validation="PortNonNegativeValidation" />
 | 
					                <MudNumericField T="int" Label="Port used for incoming connections" Value="ListenPort" ValueChanged="ListenPortChanged" Min="@MinNonNegativePortValue" Max="@MaxPortValue" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@CustomIcons.Random" OnAdornmentClick="GenerateRandomPort" HelperText="Set to 0 to let your system pick an unused port" Validation="PortNonNegativeValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use UPnp / NAT-PMP port forwarding from my router" Value="Upnp" ValueChanged="UpnpChanged" />
 | 
					                <FieldSwitch Label="Use UPnp / NAT-PMP port forwarding from my router" Value="Upnp" ValueChanged="UpnpChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -41,25 +41,25 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudFieldSwitch T="bool" Label="Global maximum number of connections" Value="MaxConnecEnabled" ValueChanged="MaxConnecEnabledChanged" />
 | 
					                <FieldSwitch T="bool" Label="Global maximum number of connections" Value="MaxConnecEnabled" ValueChanged="MaxConnecEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudNumericField T="int" Label="Connections" Value="MaxConnec" ValueChanged="MaxConnecChanged" Min="0" Disabled="@(!MaxConnecEnabled)" Variant="Variant.Outlined" Validation="MaxConnectValidation" />
 | 
					                <MudNumericField T="int" Label="Connections" Value="MaxConnec" ValueChanged="MaxConnecChanged" Min="0" Disabled="@(!MaxConnecEnabled)" Variant="Variant.Outlined" Validation="MaxConnectValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudFieldSwitch Label="Maximum number of connections per torrent" Value="MaxConnecPerTorrentEnabled" ValueChanged="MaxConnecPerTorrentEnabledChanged" />
 | 
					                <FieldSwitch Label="Maximum number of connections per torrent" Value="MaxConnecPerTorrentEnabled" ValueChanged="MaxConnecPerTorrentEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudNumericField T="int" Label="Connections" Value="MaxConnecPerTorrent" ValueChanged="MaxConnecPerTorrentChanged" Min="0" Disabled="@(!MaxConnecPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxConnecPerTorrentValidation" />
 | 
					                <MudNumericField T="int" Label="Connections" Value="MaxConnecPerTorrent" ValueChanged="MaxConnecPerTorrentChanged" Min="0" Disabled="@(!MaxConnecPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxConnecPerTorrentValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudFieldSwitch Label="Global maximum number of upload slots" Value="MaxUploadsEnabled" ValueChanged="MaxUploadsEnabledChanged" />
 | 
					                <FieldSwitch Label="Global maximum number of upload slots" Value="MaxUploadsEnabled" ValueChanged="MaxUploadsEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudNumericField T="int" Label="Slots" Value="MaxUploads" ValueChanged="MaxUploadsChanged" Min="0" Disabled="@(!MaxUploadsEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsValidation" />
 | 
					                <MudNumericField T="int" Label="Slots" Value="MaxUploads" ValueChanged="MaxUploadsChanged" Min="0" Disabled="@(!MaxUploadsEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudFieldSwitch Label="Maximum number of upload slots per torrent" Value="MaxUploadsPerTorrentEnabled" ValueChanged="MaxUploadsPerTorrentEnabledChanged" />
 | 
					                <FieldSwitch Label="Maximum number of upload slots per torrent" Value="MaxUploadsPerTorrentEnabled" ValueChanged="MaxUploadsPerTorrentEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudNumericField T="int" Label="Slots" Value="MaxUploadsPerTorrent" ValueChanged="MaxUploadsPerTorrentChanged" Min="0" Disabled="@(!MaxUploadsPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsPerTorrentValidation" />
 | 
					                <MudNumericField T="int" Label="Slots" Value="MaxUploadsPerTorrent" ValueChanged="MaxUploadsPerTorrentChanged" Min="0" Disabled="@(!MaxUploadsPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsPerTorrentValidation" />
 | 
				
			||||||
@@ -72,7 +72,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="I2P (Experimental)" Value="I2pEnabled" ValueChanged="I2pEnabledChanged" />
 | 
					                <FieldSwitch Label="I2P (Experimental)" Value="I2pEnabled" ValueChanged="I2pEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudTextField T="string" Label="Host" Value="I2pAddress" ValueChanged="I2pAddressChanged" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Host" Value="I2pAddress" ValueChanged="I2pAddressChanged" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -81,7 +81,7 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label="Slots" Value="I2pPort" ValueChanged="I2pPortChanged" Min="0" Max="65535" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudNumericField T="int" Label="Slots" Value="I2pPort" ValueChanged="I2pPortChanged" Min="0" Max="65535" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Mixed mode" Value="I2pMixedMode" ValueChanged="I2pMixedModeChanged" Disabled="@(!I2pEnabled)" HelperText="If "mixed mode" is enabled, I2P torrents are allowed to also get peers from other sources than the tracker, and connect to regular IPs, not providing any anonymization. This may be useful if the user is not interested in the anonymization of I2P, but still wants to be able to connect to I2P peers." />
 | 
					                <FieldSwitch Label="Mixed mode" Value="I2pMixedMode" ValueChanged="I2pMixedModeChanged" Disabled="@(!I2pEnabled)" HelperText="If "mixed mode" is enabled, I2P torrents are allowed to also get peers from other sources than the tracker, and connect to regular IPs, not providing any anonymization. This may be useful if the user is not interested in the anonymization of I2P, but still wants to be able to connect to I2P peers." />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -110,10 +110,10 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label="Port" Value="ProxyPort" ValueChanged="ProxyPortChanged" Min="1" Max="@ConnectionOptions.MaxPortValue" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
 | 
					                <MudNumericField T="int" Label="Port" Value="ProxyPort" ValueChanged="ProxyPortChanged" Min="1" Max="@ConnectionOptions.MaxPortValue" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Perform hostname lookup via proxy" Value="ProxyHostnameLookup" ValueChanged="ProxyHostnameLookupChanged" HelperText="If checked, hostname lookups are done via the proxy." />
 | 
					                <FieldSwitch Label="Perform hostname lookup via proxy" Value="ProxyHostnameLookup" ValueChanged="ProxyHostnameLookupChanged" HelperText="If checked, hostname lookups are done via the proxy." />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Authentication" Value="ProxyAuthEnabled" ValueChanged="ProxyAuthEnabledChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
 | 
					                <FieldSwitch Label="Authentication" Value="ProxyAuthEnabled" ValueChanged="ProxyAuthEnabledChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudTextField T="string" Label="Username" Value="ProxyUsername" ValueChanged="ProxyUsernameChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Username" Value="ProxyUsername" ValueChanged="ProxyUsernameChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -123,16 +123,16 @@
 | 
				
			|||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use proxy for BitTorrent purposes" Value="ProxyBittorrent" ValueChanged="ProxyBittorrentChanged" Disabled="ProxyDisabled" />
 | 
					                <FieldSwitch Label="Use proxy for BitTorrent purposes" Value="ProxyBittorrent" ValueChanged="ProxyBittorrentChanged" Disabled="ProxyDisabled" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use proxy for peer connections" Value="ProxyPeerConnections" ValueChanged="ProxyPeerConnectionsChanged" Disabled="@(ProxyDisabled || ProxyAuthEnabled)" />
 | 
					                <FieldSwitch Label="Use proxy for peer connections" Value="ProxyPeerConnections" ValueChanged="ProxyPeerConnectionsChanged" Disabled="@(ProxyDisabled || ProxyAuthEnabled)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use proxy for RSS purposes" Value="ProxyRss" ValueChanged="ProxyRssChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
 | 
					                <FieldSwitch Label="Use proxy for RSS purposes" Value="ProxyRss" ValueChanged="ProxyRssChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use proxy for general purposes" Value="ProxyMisc" ValueChanged="ProxyMiscChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
 | 
					                <FieldSwitch Label="Use proxy for general purposes" Value="ProxyMisc" ValueChanged="ProxyMiscChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -147,13 +147,13 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="IP Filter" Value="IpFilterEnabled" ValueChanged="IpFilterEnabledChanged" />
 | 
					                <FieldSwitch Label="IP Filter" Value="IpFilterEnabled" ValueChanged="IpFilterEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Filter path (.dat, .p2p, .p2b)" Value="IpFilterPath" ValueChanged="IpFilterPathChanged" Disabled="@(!IpFilterEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Filter path (.dat, .p2p, .p2b)" Value="IpFilterPath" ValueChanged="IpFilterPathChanged" Disabled="@(!IpFilterEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Apply to trackers" Value="IpFilterTrackers" ValueChanged="IpFilterTrackersChanged" />
 | 
					                <FieldSwitch Label="Apply to trackers" Value="IpFilterTrackers" ValueChanged="IpFilterTrackersChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Manually banned IP addresses" Value="BannedIPs" ValueChanged="BannedIPsChanged" Lines="5" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Manually banned IP addresses" Value="BannedIPs" ValueChanged="BannedIPsChanged" Lines="5" Variant="Variant.Outlined" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,10 +16,10 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Add to top of queue" Value="AddToTopOfQueue" ValueChanged="AddToTopOfQueueChanged" />
 | 
					                <FieldSwitch Label="Add to top of queue" Value="AddToTopOfQueue" ValueChanged="AddToTopOfQueueChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Do not start the download automatically" Value="StartPausedEnabled" ValueChanged="StartPausedEnabledChanged" />
 | 
					                <FieldSwitch Label="Do not start the download automatically" Value="StartPausedEnabled" ValueChanged="StartPausedEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudSelect T="string" Label="Torrent stop condition" Value="TorrentStopCondition" ValueChanged="TorrentStopConditionChanged" Variant="Variant.Outlined">
 | 
					                <MudSelect T="string" Label="Torrent stop condition" Value="TorrentStopCondition" ValueChanged="TorrentStopConditionChanged" Variant="Variant.Outlined">
 | 
				
			||||||
@@ -29,7 +29,7 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Delete .torrent files afterwards" Value="AutoDeleteMode" ValueChanged="AutoDeleteModeChanged" />
 | 
					                <FieldSwitch Label="Delete .torrent files afterwards" Value="AutoDeleteMode" ValueChanged="AutoDeleteModeChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -44,10 +44,10 @@
 | 
				
			|||||||
    <MudCardContent>
 | 
					    <MudCardContent>
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Pre-allocate disk space for all files" Value="PreallocateAll" ValueChanged="PreallocateAllChanged" />
 | 
					                <FieldSwitch Label="Pre-allocate disk space for all files" Value="PreallocateAll" ValueChanged="PreallocateAllChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Append .!qB extension to incomplete files" Value="IncompleteFilesExt" ValueChanged="IncompleteFilesExtChanged" />
 | 
					                <FieldSwitch Label="Append .!qB extension to incomplete files" Value="IncompleteFilesExt" ValueChanged="IncompleteFilesExtChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -86,7 +86,7 @@
 | 
				
			|||||||
                </MudSelect>
 | 
					                </MudSelect>
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use Subcategories" Value="UseSubcategories" ValueChanged="UseSubcategoriesChanged" />
 | 
					                <FieldSwitch Label="Use Subcategories" Value="UseSubcategories" ValueChanged="UseSubcategoriesChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Default Save Path" Value="SavePath" ValueChanged="SavePathChanged" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Default Save Path" Value="SavePath" ValueChanged="SavePathChanged" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -94,7 +94,7 @@
 | 
				
			|||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudGrid>
 | 
					                <MudGrid>
 | 
				
			||||||
                    <MudItem xs="12" sm="6" md="3">
 | 
					                    <MudItem xs="12" sm="6" md="3">
 | 
				
			||||||
                        <MudFieldSwitch Label="Keep incomplete torrents in" Value="TempPathEnabled" ValueChanged="TempPathEnabledChanged" />
 | 
					                        <FieldSwitch Label="Keep incomplete torrents in" Value="TempPathEnabled" ValueChanged="TempPathEnabledChanged" />
 | 
				
			||||||
                    </MudItem>
 | 
					                    </MudItem>
 | 
				
			||||||
                    <MudItem xs="12" sm="6" md="9">
 | 
					                    <MudItem xs="12" sm="6" md="9">
 | 
				
			||||||
                        <MudTextField T="string" Label="Path" Value="TempPath" ValueChanged="TempPathChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
 | 
					                        <MudTextField T="string" Label="Path" Value="TempPath" ValueChanged="TempPathChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -104,7 +104,7 @@
 | 
				
			|||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudGrid>
 | 
					                <MudGrid>
 | 
				
			||||||
                    <MudItem xs="12" sm="6" md="3">
 | 
					                    <MudItem xs="12" sm="6" md="3">
 | 
				
			||||||
                        <MudFieldSwitch Label="Copy .torrent files to" Value="ExportDirEnabled" ValueChanged="ExportDirEnabledChanged" />
 | 
					                        <FieldSwitch Label="Copy .torrent files to" Value="ExportDirEnabled" ValueChanged="ExportDirEnabledChanged" />
 | 
				
			||||||
                    </MudItem>
 | 
					                    </MudItem>
 | 
				
			||||||
                    <MudItem xs="12" sm="6" md="9">
 | 
					                    <MudItem xs="12" sm="6" md="9">
 | 
				
			||||||
                        <MudTextField T="string" Label="Path" Value="ExportDir" ValueChanged="ExportDirChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
 | 
					                        <MudTextField T="string" Label="Path" Value="ExportDir" ValueChanged="ExportDirChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -114,7 +114,7 @@
 | 
				
			|||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudGrid>
 | 
					                <MudGrid>
 | 
				
			||||||
                    <MudItem xs="12" sm="6" md="3">
 | 
					                    <MudItem xs="12" sm="6" md="3">
 | 
				
			||||||
                        <MudFieldSwitch Label="Copy .torrent files for finished downloads to" Value="ExportDirFinEnabled" ValueChanged="ExportDirFinEnabledChanged" />
 | 
					                        <FieldSwitch Label="Copy .torrent files for finished downloads to" Value="ExportDirFinEnabled" ValueChanged="ExportDirFinEnabledChanged" />
 | 
				
			||||||
                    </MudItem>
 | 
					                    </MudItem>
 | 
				
			||||||
                    <MudItem xs="12" sm="6" md="9">
 | 
					                    <MudItem xs="12" sm="6" md="9">
 | 
				
			||||||
                        <MudTextField T="string" Label="Path" Value="ExportDirFin" ValueChanged="ExportDirFinChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
 | 
					                        <MudTextField T="string" Label="Path" Value="ExportDirFin" ValueChanged="ExportDirFinChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -222,7 +222,7 @@
 | 
				
			|||||||
    <MudCardContent>
 | 
					    <MudCardContent>
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Excluded file names" Value="ExcludedFileNamesEnabled" ValueChanged="ExcludedFileNamesEnabledChanged" />
 | 
					                <FieldSwitch Label="Excluded file names" Value="ExcludedFileNamesEnabled" ValueChanged="ExcludedFileNamesEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Excluded files names" Value="ExcludedFileNames" ValueChanged="ExcludedFileNamesChanged" Lines="5" Disabled="@(!ExcludedFileNamesEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Excluded files names" Value="ExcludedFileNames" ValueChanged="ExcludedFileNamesChanged" Lines="5" Disabled="@(!ExcludedFileNamesEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -240,7 +240,7 @@
 | 
				
			|||||||
    <MudCardContent>
 | 
					    <MudCardContent>
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Email notification upon download completion" Value="MailNotificationEnabled" ValueChanged="MailNotificationEnabledChanged" />
 | 
					                <FieldSwitch Label="Email notification upon download completion" Value="MailNotificationEnabled" ValueChanged="MailNotificationEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="From" Value="MailNotificationSender" ValueChanged="MailNotificationSenderChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="From" Value="MailNotificationSender" ValueChanged="MailNotificationSenderChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -252,10 +252,10 @@
 | 
				
			|||||||
                <MudTextField T="string" Label="SMTP server" Value="MailNotificationSmtp" ValueChanged="MailNotificationSmtpChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="SMTP server" Value="MailNotificationSmtp" ValueChanged="MailNotificationSmtpChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="This server requires a secure connection (SSL)" Value="MailNotificationSslEnabled" ValueChanged="MailNotificationSslEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
 | 
					                <FieldSwitch Label="This server requires a secure connection (SSL)" Value="MailNotificationSslEnabled" ValueChanged="MailNotificationSslEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Authentication" Value="MailNotificationAuthEnabled" ValueChanged="MailNotificationAuthEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
 | 
					                <FieldSwitch Label="Authentication" Value="MailNotificationAuthEnabled" ValueChanged="MailNotificationAuthEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Username" Value="MailNotificationUsername" ValueChanged="MailNotificationUsernameChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Username" Value="MailNotificationUsername" ValueChanged="MailNotificationUsernameChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -276,13 +276,13 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Run external program on torrent added" Value="AutorunOnTorrentAddedEnabled" ValueChanged="AutorunOnTorrentAddedEnabledChanged" />
 | 
					                <FieldSwitch Label="Run external program on torrent added" Value="AutorunOnTorrentAddedEnabled" ValueChanged="AutorunOnTorrentAddedEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="External program" Value="AutorunOnTorrentAddedProgram" ValueChanged="AutorunOnTorrentAddedProgramChanged" Disabled="@(!AutorunOnTorrentAddedEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="External program" Value="AutorunOnTorrentAddedProgram" ValueChanged="AutorunOnTorrentAddedProgramChanged" Disabled="@(!AutorunOnTorrentAddedEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Run external program on torrent finished" Value="AutorunEnabled" ValueChanged="AutorunEnabledChanged" />
 | 
					                <FieldSwitch Label="Run external program on torrent finished" Value="AutorunEnabled" ValueChanged="AutorunEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="External program" Value="AutorunProgram" ValueChanged="AutorunProgramChanged" Disabled="@(!AutorunEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="External program" Value="AutorunProgram" ValueChanged="AutorunProgramChanged" Disabled="@(!AutorunEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable fetching RSS feeds" Value="RssProcessingEnabled" ValueChanged="RssProcessingEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable fetching RSS feeds" Value="RssProcessingEnabled" ValueChanged="RssProcessingEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudNumericField T="int" Label="Feeds refresh interval" Value="RssRefreshInterval" ValueChanged="RssRefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
 | 
					                <MudNumericField T="int" Label="Feeds refresh interval" Value="RssRefreshInterval" ValueChanged="RssRefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
 | 
				
			||||||
@@ -30,7 +30,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable auto downloading of RSS torrents" Value="RssAutoDownloadingEnabled" ValueChanged="RssAutoDownloadingEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable auto downloading of RSS torrents" Value="RssAutoDownloadingEnabled" ValueChanged="RssAutoDownloadingEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudButton OnClick="OpenRssRulesDialog" Variant="Variant.Filled">Edit auto downloading rules</MudButton>
 | 
					                <MudButton OnClick="OpenRssRulesDialog" Variant="Variant.Filled">Edit auto downloading rules</MudButton>
 | 
				
			||||||
@@ -48,7 +48,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Download REPACK/PROPER episodes" Value="RssDownloadRepackProperEpisodes" ValueChanged="RssDownloadRepackProperEpisodesChanged" />
 | 
					                <FieldSwitch Label="Download REPACK/PROPER episodes" Value="RssDownloadRepackProperEpisodes" ValueChanged="RssDownloadRepackProperEpisodesChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Filters" Value="RssSmartEpisodeFilters" ValueChanged="RssSmartEpisodeFiltersChanged" Lines="5" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Filters" Value="RssSmartEpisodeFilters" ValueChanged="RssSmartEpisodeFiltersChanged" Lines="5" Variant="Variant.Outlined" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label="Download" Value="AltDlLimit" ValueChanged="AltDlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltDlLimitValidation" />
 | 
					                <MudNumericField T="int" Label="Download" Value="AltDlLimit" ValueChanged="AltDlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltDlLimitValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Schedule the use of alternative rate limits" Value="SchedulerEnabled" ValueChanged="SchedulerEnabledChanged" />
 | 
					                <FieldSwitch Label="Schedule the use of alternative rate limits" Value="SchedulerEnabled" ValueChanged="SchedulerEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12" md="6">
 | 
					            <MudItem xs="12" md="6">
 | 
				
			||||||
                <MudTimePicker Label="From" Editable="true" Time="ScheduleFrom" TimeChanged="ScheduleFromChanged" Disabled="@(!SchedulerEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTimePicker Label="From" Editable="true" Time="ScheduleFrom" TimeChanged="ScheduleFromChanged" Disabled="@(!SchedulerEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -68,13 +68,13 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Apply rate limit to µTP protocol" Value="LimitUtpRate" ValueChanged="LimitUtpRateChanged" />
 | 
					                <FieldSwitch Label="Apply rate limit to µTP protocol" Value="LimitUtpRate" ValueChanged="LimitUtpRateChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Apply rate limit to transport overhead" Value="LimitTcpOverhead" ValueChanged="LimitTcpOverheadChanged" />
 | 
					                <FieldSwitch Label="Apply rate limit to transport overhead" Value="LimitTcpOverhead" ValueChanged="LimitTcpOverheadChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Apply rate limit to peers on LAN" Value="LimitLanPeers" ValueChanged="LimitLanPeersChanged" />
 | 
					                <FieldSwitch Label="Apply rate limit to peers on LAN" Value="LimitLanPeers" ValueChanged="LimitLanPeersChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@
 | 
				
			|||||||
                <MudNumericField T="int" Label="Port" Value="WebUiPort" ValueChanged="WebUiPortChanged" Min="1" Max="@Options.MaxPortValue" Variant="Variant.Outlined" Validation="WebUiPortValidation" />
 | 
					                <MudNumericField T="int" Label="Port" Value="WebUiPort" ValueChanged="WebUiPortChanged" Min="1" Max="@Options.MaxPortValue" Variant="Variant.Outlined" Validation="WebUiPortValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use UPnP / NAT-PMP to forward the port from my router" Value="WebUiUpnp" ValueChanged="WebUiUpnpChanged" />
 | 
					                <FieldSwitch Label="Use UPnP / NAT-PMP to forward the port from my router" Value="WebUiUpnp" ValueChanged="WebUiUpnpChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
        </MudGrid>
 | 
					        </MudGrid>
 | 
				
			||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
@@ -30,7 +30,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use HTTPS instead of HTTP" Value="UseHttps" ValueChanged="UseHttpsChanged" />
 | 
					                <FieldSwitch Label="Use HTTPS instead of HTTP" Value="UseHttps" ValueChanged="UseHttpsChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Certificate" Value="WebUiHttpsCertPath" ValueChanged="WebUiHttpsCertPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsCertPathValidation" />
 | 
					                <MudTextField T="string" Label="Certificate" Value="WebUiHttpsCertPath" ValueChanged="WebUiHttpsCertPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsCertPathValidation" />
 | 
				
			||||||
@@ -57,10 +57,10 @@
 | 
				
			|||||||
                <MudTextField T="string" Label="Password" Value="WebUiPassword" ValueChanged="WebUiPasswordChanged" InputType="InputType.Password" Variant="Variant.Outlined" Validation="WebUiPasswordValidation" />
 | 
					                <MudTextField T="string" Label="Password" Value="WebUiPassword" ValueChanged="WebUiPasswordChanged" InputType="InputType.Password" Variant="Variant.Outlined" Validation="WebUiPasswordValidation" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Bypass authentication for clients on localhost" Value="BypassLocalAuth" ValueChanged="BypassLocalAuthChanged" />
 | 
					                <FieldSwitch Label="Bypass authentication for clients on localhost" Value="BypassLocalAuth" ValueChanged="BypassLocalAuthChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Bypass authentication for clients in whitelisted IP subnets" Value="BypassAuthSubnetWhitelistEnabled" ValueChanged="BypassAuthSubnetWhitelistEnabledChanged" />
 | 
					                <FieldSwitch Label="Bypass authentication for clients in whitelisted IP subnets" Value="BypassAuthSubnetWhitelistEnabled" ValueChanged="BypassAuthSubnetWhitelistEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Trackers" Value="BypassAuthSubnetWhitelist" ValueChanged="BypassAuthSubnetWhitelistChanged" Lines="5" Disabled="@(!BypassAuthSubnetWhitelistEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Trackers" Value="BypassAuthSubnetWhitelist" ValueChanged="BypassAuthSubnetWhitelistChanged" Lines="5" Disabled="@(!BypassAuthSubnetWhitelistEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -87,7 +87,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Use alternative Web UI" Value="AlternativeWebuiEnabled" ValueChanged="AlternativeWebuiEnabledChanged" />
 | 
					                <FieldSwitch Label="Use alternative Web UI" Value="AlternativeWebuiEnabled" ValueChanged="AlternativeWebuiEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Files location" Value="AlternativeWebuiPath" ValueChanged="AlternativeWebuiPathChanged" Variant="Variant.Outlined" Validation="AlternativeWebuiPathValidation" />
 | 
					                <MudTextField T="string" Label="Files location" Value="AlternativeWebuiPath" ValueChanged="AlternativeWebuiPathChanged" Variant="Variant.Outlined" Validation="AlternativeWebuiPathValidation" />
 | 
				
			||||||
@@ -105,16 +105,16 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable clickjacking protection" Value="WebUiClickjackingProtectionEnabled" ValueChanged="WebUiClickjackingProtectionEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable clickjacking protection" Value="WebUiClickjackingProtectionEnabled" ValueChanged="WebUiClickjackingProtectionEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable Cross-Site Request Forgery (CSRF) protection" Value="WebUiCsrfProtectionEnabled" ValueChanged="WebUiCsrfProtectionEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable Cross-Site Request Forgery (CSRF) protection" Value="WebUiCsrfProtectionEnabled" ValueChanged="WebUiCsrfProtectionEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable cookie Secure flag (requires HTTPS)" Value="WebUiSecureCookieEnabled" ValueChanged="WebUiSecureCookieEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable cookie Secure flag (requires HTTPS)" Value="WebUiSecureCookieEnabled" ValueChanged="WebUiSecureCookieEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -132,16 +132,16 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable clickjacking protection" Value="WebUiClickjackingProtectionEnabled" ValueChanged="WebUiClickjackingProtectionEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable clickjacking protection" Value="WebUiClickjackingProtectionEnabled" ValueChanged="WebUiClickjackingProtectionEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable Cross-Site Request Forgery (CSRF) protection" Value="WebUiCsrfProtectionEnabled" ValueChanged="WebUiCsrfProtectionEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable Cross-Site Request Forgery (CSRF) protection" Value="WebUiCsrfProtectionEnabled" ValueChanged="WebUiCsrfProtectionEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable cookie Secure flag (requires HTTPS)" Value="WebUiSecureCookieEnabled" ValueChanged="WebUiSecureCookieEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable cookie Secure flag (requires HTTPS)" Value="WebUiSecureCookieEnabled" ValueChanged="WebUiSecureCookieEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -159,7 +159,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Add custom HTTP headers" Value="WebUiUseCustomHttpHeadersEnabled" ValueChanged="WebUiUseCustomHttpHeadersEnabledChanged" />
 | 
					                <FieldSwitch Label="Add custom HTTP headers" Value="WebUiUseCustomHttpHeadersEnabled" ValueChanged="WebUiUseCustomHttpHeadersEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Server domains" Value="WebUiCustomHttpHeaders" ValueChanged="WebUiCustomHttpHeadersChanged" Lines="5" Disabled="@(!WebUiUseCustomHttpHeadersEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Server domains" Value="WebUiCustomHttpHeaders" ValueChanged="WebUiCustomHttpHeadersChanged" Lines="5" Disabled="@(!WebUiUseCustomHttpHeadersEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -177,7 +177,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Enable reverse proxy support" Value="WebUiReverseProxyEnabled" ValueChanged="WebUiReverseProxyEnabledChanged" />
 | 
					                <FieldSwitch Label="Enable reverse proxy support" Value="WebUiReverseProxyEnabled" ValueChanged="WebUiReverseProxyEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudTextField T="string" Label="Trusted proxies list" Value="WebUiReverseProxiesList" ValueChanged="WebUiReverseProxiesListChanged" Lines="5" Disabled="@(!WebUiReverseProxyEnabled)" Variant="Variant.Outlined" />
 | 
					                <MudTextField T="string" Label="Trusted proxies list" Value="WebUiReverseProxiesList" ValueChanged="WebUiReverseProxiesListChanged" Lines="5" Disabled="@(!WebUiReverseProxyEnabled)" Variant="Variant.Outlined" />
 | 
				
			||||||
@@ -195,7 +195,7 @@
 | 
				
			|||||||
    <MudCardContent Class="pt-0">
 | 
					    <MudCardContent Class="pt-0">
 | 
				
			||||||
        <MudGrid>
 | 
					        <MudGrid>
 | 
				
			||||||
            <MudItem xs="12">
 | 
					            <MudItem xs="12">
 | 
				
			||||||
                <MudFieldSwitch Label="Update my dynamic domain name" Value="DyndnsEnabled" ValueChanged="DyndnsEnabledChanged" />
 | 
					                <FieldSwitch Label="Update my dynamic domain name" Value="DyndnsEnabled" ValueChanged="DyndnsEnabledChanged" />
 | 
				
			||||||
            </MudItem>
 | 
					            </MudItem>
 | 
				
			||||||
            <MudItem xs="8">
 | 
					            <MudItem xs="8">
 | 
				
			||||||
                <MudSelect T="int" Value="DyndnsService" ValueChanged="DyndnsServiceChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined">
 | 
					                <MudSelect T="int" Value="DyndnsService" ValueChanged="DyndnsServiceChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,24 @@
 | 
				
			|||||||
<DynamicTable T="Peer"
 | 
					<ContextMenu @ref="ContextMenu" Dense="true">
 | 
				
			||||||
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddPeer">Add peer</MudMenuItem>
 | 
				
			||||||
 | 
					    @if (ContextMenuItem is not null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <MudMenuItem Icon="@Icons.Material.Filled.DisabledByDefault" IconColor="Color.Info" OnClick="BanPeerContextMenu">Ban peer</MudMenuItem>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<MudToolBar Gutters="false" Dense="true">
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.AddCircle" Color="Color.Info" OnClick="AddPeer">Add peer</MudIconButton>
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.DisabledByDefault" Color="Color.Error" OnClick="BanPeerToolbar" Disabled="@(SelectedItem is null)">Ban peer</MudIconButton>
 | 
				
			||||||
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
 | 
				
			||||||
 | 
					</MudToolBar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<DynamicTable T="Peer"
 | 
				
			||||||
              ColumnDefinitions="Columns"
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
              Items="Peers"
 | 
					              Items="Peers"
 | 
				
			||||||
              MultiSelection="false"
 | 
					              MultiSelection="false"
 | 
				
			||||||
 | 
					              SelectOnRowClick="true"
 | 
				
			||||||
 | 
					              OnTableDataLongPress="TableDataLongPress"
 | 
				
			||||||
 | 
					              OnTableDataContextMenu="TableDataContextMenu"
 | 
				
			||||||
 | 
					              SelectedItemChanged="SelectedItemChanged"
 | 
				
			||||||
              Class="details-list" />
 | 
					              Class="details-list" />
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Lantean.QBTMudBlade.Services;
 | 
					using Lantean.QBTMudBlade.Services;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
@@ -16,6 +17,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        private readonly CancellationTokenSource _timerCancellationToken = new();
 | 
					        private readonly CancellationTokenSource _timerCancellationToken = new();
 | 
				
			||||||
        private bool? _showFlags;
 | 
					        private bool? _showFlags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private const string _toolbar = nameof(_toolbar);
 | 
				
			||||||
 | 
					        private const string _context = nameof(_context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Parameter, EditorRequired]
 | 
					        [Parameter, EditorRequired]
 | 
				
			||||||
        public string? Hash { get; set; }
 | 
					        public string? Hash { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,6 +32,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        [CascadingParameter]
 | 
					        [CascadingParameter]
 | 
				
			||||||
        public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
 | 
					        public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IDialogService DialogService { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
        protected IApiClient ApiClient { get; set; } = default!;
 | 
					        protected IApiClient ApiClient { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,6 +47,14 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected bool ShowFlags => _showFlags.GetValueOrDefault();
 | 
					        protected bool ShowFlags => _showFlags.GetValueOrDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Peer? ContextMenuItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Peer? SelectedItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected ContextMenu? ContextMenu { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<Peer>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnParametersSetAsync()
 | 
					        protected override async Task OnParametersSetAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null)
 | 
					            if (Hash is null)
 | 
				
			||||||
@@ -83,6 +98,78 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            await InvokeAsync(StateHasChanged);
 | 
					            await InvokeAsync(StateHasChanged);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task AddPeer()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Hash is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var peers = await DialogService.ShowAddPeersDialog();
 | 
				
			||||||
 | 
					            if (peers is null || peers.Count == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await ApiClient.AddPeers([Hash], peers);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task BanPeerToolbar()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return BanPeer(SelectedItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task BanPeerContextMenu()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return BanPeer(ContextMenuItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task BanPeer(Peer? peer)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Hash is null || peer is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            await ApiClient.BanPeers([new QBitTorrentClient.Models.PeerId(peer.IPAddress, peer.Port)]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task TableDataContextMenu(TableDataContextMenuEventArgs<Peer> eventArgs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task TableDataLongPress(TableDataLongPressEventArgs<Peer> eventArgs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return ShowContextMenu(eventArgs.Item, eventArgs.LongPressEventArgs);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task ShowContextMenu(Peer? peer, EventArgs eventArgs)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            ContextMenuItem = peer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ContextMenu is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await ContextMenu.ToggleMenuAsync(eventArgs);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void SelectedItemChanged(Peer peer)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SelectedItem = peer;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task ColumnOptions()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Table is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await Table.ShowColumnOptionsDialog();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
					        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (firstRender)
 | 
					            if (firstRender)
 | 
				
			||||||
@@ -141,10 +228,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            new ColumnDefinition<Peer>("Downloaded", p => p.Downloaded, p => @DisplayHelpers.Size(p.Downloaded)),
 | 
					            new ColumnDefinition<Peer>("Downloaded", p => p.Downloaded, p => @DisplayHelpers.Size(p.Downloaded)),
 | 
				
			||||||
            new ColumnDefinition<Peer>("Uploaded", p => p.Uploaded, p => @DisplayHelpers.Size(p.Uploaded)),
 | 
					            new ColumnDefinition<Peer>("Uploaded", p => p.Uploaded, p => @DisplayHelpers.Size(p.Uploaded)),
 | 
				
			||||||
            new ColumnDefinition<Peer>("Relevance", p => p.Relevance, p => @DisplayHelpers.Percentage(p.Relevance)),
 | 
					            new ColumnDefinition<Peer>("Relevance", p => p.Relevance, p => @DisplayHelpers.Percentage(p.Relevance)),
 | 
				
			||||||
            new ColumnDefinition<Peer>("Files", p => p.Files, p => p.Files),
 | 
					            new ColumnDefinition<Peer>("Files", p => p.Files),
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected virtual async Task DisposeAsync(bool disposing)
 | 
					        protected virtual async Task DisposeAsync(bool disposing)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!_disposedValue)
 | 
					            if (!_disposedValue)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,8 +9,10 @@ using MudBlazor;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class TorrentActions
 | 
					    public partial class TorrentActions : IAsyncDisposable
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        private bool _disposedValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private List<TorrentAction>? _actions;
 | 
					        private List<TorrentAction>? _actions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
@@ -31,6 +33,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
        public IJSRuntime JSRuntime { get; set; } = default!;
 | 
					        public IJSRuntime JSRuntime { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IKeyboardService KeyboardService { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
        [EditorRequired]
 | 
					        [EditorRequired]
 | 
				
			||||||
        public IEnumerable<string> Hashes { get; set; } = default!;
 | 
					        public IEnumerable<string> Hashes { get; set; } = default!;
 | 
				
			||||||
@@ -103,6 +108,14 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            ];
 | 
					            ];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (firstRender)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await KeyboardService.RegisterKeypressEvent("Delete", k => Remove());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public int CalculateMenuHeight()
 | 
					        public int CalculateMenuHeight()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var visibleActions = GetActions();
 | 
					            var visibleActions = GetActions();
 | 
				
			||||||
@@ -568,6 +581,27 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
                return EventCallback.Factory.Create(this, action);
 | 
					                return EventCallback.Factory.Create(this, action);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async ValueTask DisposeAsync()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
				
			||||||
 | 
					            await DisposeAsync(disposing: true);
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected virtual async Task DisposeAsync(bool disposing)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!_disposedValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (disposing)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    await KeyboardService.UnregisterKeypressEvent("Delete");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _disposedValue = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public enum RenderType
 | 
					    public enum RenderType
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,19 +2,31 @@
 | 
				
			|||||||
    <MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddTracker">Add trackers</MudMenuItem>
 | 
					    <MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddTracker">Add trackers</MudMenuItem>
 | 
				
			||||||
    @if (ContextMenuItem is not null)
 | 
					    @if (ContextMenuItem is not null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        <MudMenuItem Icon="@Icons.Material.Filled.Edit" IconColor="Color.Info" OnClick="EditTracker">Edit tracker URL</MudMenuItem>
 | 
					        <MudMenuItem Icon="@Icons.Material.Filled.Edit" IconColor="Color.Info" OnClick="EditTrackerToolbar">Edit tracker URL</MudMenuItem>
 | 
				
			||||||
        <MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveTracker">Remove tracker</MudMenuItem>
 | 
					        <MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveTrackerContextMenu">Remove tracker</MudMenuItem>
 | 
				
			||||||
        <MudMenuItem Icon="@Icons.Material.Filled.FolderCopy" IconColor="Color.Info" OnClick="CopyTrackerUrl">Copy tracker url</MudMenuItem>
 | 
					        <MudMenuItem Icon="@Icons.Material.Filled.FolderCopy" IconColor="Color.Info" OnClick="CopyTrackerUrlContextMenu">Copy tracker url</MudMenuItem>
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
</ContextMenu>
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<DynamicTable T="Lantean.QBitTorrentClient.Models.TorrentTracker"
 | 
					<MudToolBar Gutters="false" Dense="true">
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.AddCircle" Color="Color.Info" OnClick="AddTracker">Add trackers</MudIconButton>
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Info" OnClick="EditTrackerToolbar" Disabled="@(SelectedItem is null)">Edit tracker URL</MudIconButton>
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="RemoveTrackerToolbar" Disabled="@(SelectedItem is null)">Remove tracker</MudIconButton>
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.FolderCopy" Color="Color.Info" OnClick="CopyTrackerUrlToolbar" Disabled="@(SelectedItem is null)">Copy tracker url</MudIconButton>
 | 
				
			||||||
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
 | 
				
			||||||
 | 
					</MudToolBar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<DynamicTable @ref="Table"
 | 
				
			||||||
 | 
					              T="Lantean.QBitTorrentClient.Models.TorrentTracker"
 | 
				
			||||||
              ColumnDefinitions="Columns"
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
              Items="Trackers"
 | 
					              Items="Trackers"
 | 
				
			||||||
              MultiSelection="false"
 | 
					              MultiSelection="false"
 | 
				
			||||||
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
              PreSorted="true"
 | 
					              PreSorted="true"
 | 
				
			||||||
              SortDirectionChanged="SortDirectionChanged"
 | 
					              SortDirectionChanged="SortDirectionChanged"
 | 
				
			||||||
              SortColumnChanged="SortColumnChanged"
 | 
					              SortColumnChanged="SortColumnChanged"
 | 
				
			||||||
              OnTableDataLongPress="TableDataLongPress"
 | 
					              OnTableDataLongPress="TableDataLongPress"
 | 
				
			||||||
              OnTableDataContextMenu="TableDataContextMenu"
 | 
					              OnTableDataContextMenu="TableDataContextMenu"
 | 
				
			||||||
              Class="details-list" />
 | 
					              SelectedItemChanged="SelectedItemChanged"
 | 
				
			||||||
 | 
					              Class="file-list" />
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
using Lantean.QBitTorrentClient.Models;
 | 
					using Lantean.QBitTorrentClient.Models;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Interop;
 | 
					using Lantean.QBTMudBlade.Interop;
 | 
				
			||||||
using Lantean.QBTMudBlade.Services;
 | 
					using Lantean.QBTMudBlade.Services;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
@@ -17,6 +18,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        private string? _sortColumn;
 | 
					        private string? _sortColumn;
 | 
				
			||||||
        private SortDirection _sortDirection;
 | 
					        private SortDirection _sortDirection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private const string _toolbar = nameof(_toolbar);
 | 
				
			||||||
 | 
					        private const string _context = nameof(_context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Parameter, EditorRequired]
 | 
					        [Parameter, EditorRequired]
 | 
				
			||||||
        public string? Hash { get; set; }
 | 
					        public string? Hash { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,8 +48,12 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected TorrentTracker? ContextMenuItem { get; set; }
 | 
					        protected TorrentTracker? ContextMenuItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected TorrentTracker? SelectedItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected ContextMenu? ContextMenu { get; set; }
 | 
					        protected ContextMenu? ContextMenu { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<TorrentTracker>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnParametersSetAsync()
 | 
					        protected override async Task OnParametersSetAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null)
 | 
					            if (Hash is null)
 | 
				
			||||||
@@ -102,6 +110,16 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            StateHasChanged();
 | 
					            StateHasChanged();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task ColumnOptions()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Table is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await Table.ShowColumnOptionsDialog();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected Task TableDataContextMenu(TableDataContextMenuEventArgs<TorrentTracker> eventArgs)
 | 
					        protected Task TableDataContextMenu(TableDataContextMenuEventArgs<TorrentTracker> eventArgs)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
 | 
					            return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs);
 | 
				
			||||||
@@ -131,6 +149,11 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            await ContextMenu.ToggleMenuAsync(eventArgs);
 | 
					            await ContextMenu.ToggleMenuAsync(eventArgs);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void SelectedItemChanged(TorrentTracker torrentTracker)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SelectedItem = torrentTracker;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task AddTracker()
 | 
					        protected async Task AddTracker()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null)
 | 
					            if (Hash is null)
 | 
				
			||||||
@@ -147,34 +170,69 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            await ApiClient.AddTrackersToTorrent(Hash, trackers);
 | 
					            await ApiClient.AddTrackersToTorrent(Hash, trackers);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task EditTracker()
 | 
					        protected Task EditTrackerToolbar()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null || ContextMenuItem is null)
 | 
					            return EditTracker(SelectedItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task EditTrackerContextMenu()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return EditTracker(ContextMenuItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task EditTracker(TorrentTracker? tracker)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Hash is null || tracker is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await DialogService.ShowSingleFieldDialog("Edit Tracker", "Tracker URL", ContextMenuItem.Url, async (value) => await ApiClient.EditTracker(Hash, ContextMenuItem.Url, value));
 | 
					            await DialogService.ShowSingleFieldDialog("Edit Tracker", "Tracker URL", tracker.Url, async (value) => await ApiClient.EditTracker(Hash, tracker.Url, value));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task RemoveTracker()
 | 
					        protected Task RemoveTrackerToolbar()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null || ContextMenuItem is null)
 | 
					            return RemoveTracker(SelectedItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task RemoveTrackerContextMenu()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return RemoveTracker(ContextMenuItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task RemoveTracker(TorrentTracker? tracker)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Hash is null || tracker is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await ApiClient.RemoveTrackers(Hash, [ContextMenuItem.Url]);
 | 
					            await ApiClient.RemoveTrackers(Hash, [tracker.Url]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task CopyTrackerUrl()
 | 
					        protected Task CopyTrackerUrlToolbar()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Hash is null || ContextMenuItem is null)
 | 
					            return CopyTrackerUrl(SelectedItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Task CopyTrackerUrlContextMenu()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return CopyTrackerUrl(ContextMenuItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task CopyTrackerUrl(TorrentTracker? tracker)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (Hash is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await JSRuntime.WriteToClipboard(ContextMenuItem.Url);
 | 
					            if (tracker is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await JSRuntime.WriteToClipboard(tracker.Url);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
					        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
@inherits MudMenu
 | 
					@inherits MudComponentBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<MudMenu @ref="FakeMenu" Style="display: none" OpenChanged="FakeOpenChanged"></MudMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@* The portal has to include the cascading values inside, because it's not able to teletransport the cascade *@
 | 
					@* The portal has to include the cascading values inside, because it's not able to teletransport the cascade *@
 | 
				
			||||||
<MudPopover tracker="@Id"
 | 
					<MudPopover tracker="@Id"
 | 
				
			||||||
            Open="@_open"
 | 
					            Open="@_open"
 | 
				
			||||||
            Class="@PopoverClass"
 | 
					            Class="unselectable"
 | 
				
			||||||
            MaxHeight="@MaxHeight"
 | 
					            MaxHeight="@MaxHeight"
 | 
				
			||||||
            AnchorOrigin="@AnchorOrigin"
 | 
					            AnchorOrigin="@AnchorOrigin"
 | 
				
			||||||
            TransformOrigin="TransformOrigin"
 | 
					            TransformOrigin="TransformOrigin"
 | 
				
			||||||
@@ -11,13 +13,16 @@
 | 
				
			|||||||
            OverflowBehavior="OverflowBehavior.FlipAlways"
 | 
					            OverflowBehavior="OverflowBehavior.FlipAlways"
 | 
				
			||||||
            Style="@_popoverStyle"
 | 
					            Style="@_popoverStyle"
 | 
				
			||||||
            @ontouchend:preventDefault>
 | 
					            @ontouchend:preventDefault>
 | 
				
			||||||
    <CascadingValue Value="@((MudMenu)this)">
 | 
					    <CascadingValue Value="@(FakeMenu)">
 | 
				
			||||||
 | 
					        @if (_showChildren)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            <MudList T="object"
 | 
					            <MudList T="object"
 | 
				
			||||||
                 Class="@ListClass"
 | 
					            Class="unselectable"
 | 
				
			||||||
                 Dense="@Dense">
 | 
					                 Dense="@Dense">
 | 
				
			||||||
                @ChildContent
 | 
					                @ChildContent
 | 
				
			||||||
        </MudList>
 | 
					        </MudList>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    </CascadingValue>
 | 
					    </CascadingValue>
 | 
				
			||||||
</MudPopover>
 | 
					</MudPopover>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<MudOverlay Visible="@(_open)" OnClick="@ToggleMenuAsync" LockScroll="@LockScroll" />
 | 
					<MudOverlay Visible="@(_open)" LockScroll="@LockScroll" AutoClose="true" OnClosed="@CloseMenuAsync" />
 | 
				
			||||||
@@ -5,17 +5,18 @@ using Microsoft.JSInterop;
 | 
				
			|||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
using MudBlazor.Utilities;
 | 
					using MudBlazor.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // This is a very hacky approach but works for now.
 | 
					    // This is a very hacky approach but works for now.
 | 
				
			||||||
    // This needs to inherit from MudMenu because MudMenuItem needs a MudMenu passed to it to control the close of the menu when an item is clicked.
 | 
					    // This needs to inherit from MudMenu because MudMenuItem needs a MudMenu passed to it to control the close of the menu when an item is clicked.
 | 
				
			||||||
    // MudPopover isn't ideal for this because that is designed to be used relative to an activator which in these cases it isn't.
 | 
					    // MudPopover isn't ideal for this because that is designed to be used relative to an activator which in these cases it isn't.
 | 
				
			||||||
    // Ideally this should be changed to use something like the way the DialogService works.
 | 
					    // Ideally this should be changed to use something like the way the DialogService works.
 | 
				
			||||||
    public partial class ContextMenu : MudMenu
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private const double _diff = 64;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Or - rework this to have a hidden MudMenu and hook into the OpenChanged event to monitor when the MudMenuItem closes it.
 | 
				
			||||||
 | 
					    public partial class ContextMenu : MudComponentBase
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        private bool _open;
 | 
					        private bool _open;
 | 
				
			||||||
 | 
					        private bool _showChildren;
 | 
				
			||||||
        private string? _popoverStyle;
 | 
					        private string? _popoverStyle;
 | 
				
			||||||
        private string? _id;
 | 
					        private string? _id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,7 +24,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        private double _y;
 | 
					        private double _y;
 | 
				
			||||||
        private bool _isResized = false;
 | 
					        private bool _isResized = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private const double _drawerWidth = 235;
 | 
					        private const double _diff = 64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private string Id
 | 
					        private string Id
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -41,44 +42,99 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
        public IPopoverService PopoverService { get; set; } = default!;
 | 
					        public IPopoverService PopoverService { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [CascadingParameter(Name = "DrawerOpen")]
 | 
					        /// <summary>
 | 
				
			||||||
        public bool DrawerOpen { get; set; }
 | 
					        /// If true, compact vertical padding will be applied to all menu items.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupAppearance)]
 | 
				
			||||||
 | 
					        public bool Dense { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Set to true if you want to prevent page from scrolling when the menu is open
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupAppearance)]
 | 
				
			||||||
 | 
					        public bool LockScroll { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// If true, the list menu will be same width as the parent.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupAppearance)]
 | 
				
			||||||
 | 
					        public bool FullWidth { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Sets the max height the menu can have when open.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupAppearance)]
 | 
				
			||||||
 | 
					        public int? MaxHeight { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Set the anchor origin point to determine where the popover will open from.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupAppearance)]
 | 
				
			||||||
 | 
					        public Origin AnchorOrigin { get; set; } = Origin.TopLeft;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Sets the transform origin point for the popover.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupAppearance)]
 | 
				
			||||||
 | 
					        public Origin TransformOrigin { get; set; } = Origin.TopLeft;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// If true, menu will be disabled.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.Behavior)]
 | 
				
			||||||
 | 
					        public bool Disabled { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets or sets whether to show a ripple effect when the user clicks the button. Default is true.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.Appearance)]
 | 
				
			||||||
 | 
					        public bool Ripple { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Determines whether the component has a drop-shadow. Default is true
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.Appearance)]
 | 
				
			||||||
 | 
					        public bool DropShadow { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Add menu items here
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupBehavior)]
 | 
				
			||||||
 | 
					        public RenderFragment? ChildContent { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Fired when the menu <see cref="Open"/> property changes.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        [Category(CategoryTypes.Menu.PopupBehavior)]
 | 
				
			||||||
 | 
					        public EventCallback<bool> OpenChanged { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
        public bool InsideDrawer { get; set; }
 | 
					        public int AdjustmentX { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public new string? Label { get; }
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        public int AdjustmentY { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public new string? AriaLabel { get; }
 | 
					        protected MudMenu? FakeMenu { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public new string? Icon { get; }
 | 
					        protected void FakeOpenChanged(bool value)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new Color IconColor { get; } = Color.Inherit;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new string? StartIcon { get; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new string? EndIcon { get; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new Color Color { get; } = Color.Default;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new Size Size { get; } = Size.Medium;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new Variant Variant { get; } = Variant.Text;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new bool PositionAtCursor { get; } = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new RenderFragment? ActivatorContent { get; } = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new MouseEvent ActivationEvent { get; } = MouseEvent.LeftClick;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new string? ListClass { get; } = "unselectable";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public new string? PopoverClass { get; } = "unselectable";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public ContextMenu()
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            AnchorOrigin = Origin.TopLeft;
 | 
					            if (!value)
 | 
				
			||||||
            TransformOrigin = Origin.TopLeft;
 | 
					            {
 | 
				
			||||||
 | 
					                _open = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            StateHasChanged();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
@@ -86,9 +142,8 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="args">
 | 
					        /// <param name="args">
 | 
				
			||||||
        /// The arguments of the calling mouse/pointer event.
 | 
					        /// The arguments of the calling mouse/pointer event.
 | 
				
			||||||
        /// If <see cref="PositionAtCursor"/> is true, the menu will be positioned using the coordinates in this parameter.
 | 
					 | 
				
			||||||
        /// </param>
 | 
					        /// </param>
 | 
				
			||||||
        public new async Task OpenMenuAsync(EventArgs args)
 | 
					        public async Task OpenMenuAsync(EventArgs args)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Disabled)
 | 
					            if (Disabled)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -98,6 +153,11 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            // long press on iOS triggers selection, so clear it
 | 
					            // long press on iOS triggers selection, so clear it
 | 
				
			||||||
            await JSRuntime.ClearSelection();
 | 
					            await JSRuntime.ClearSelection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (args is not LongPressEventArgs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _showChildren = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _open = true;
 | 
					            _open = true;
 | 
				
			||||||
            _isResized = false;
 | 
					            _isResized = false;
 | 
				
			||||||
            StateHasChanged();
 | 
					            StateHasChanged();
 | 
				
			||||||
@@ -111,11 +171,29 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            StateHasChanged();
 | 
					            StateHasChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await OpenChanged.InvokeAsync(_open);
 | 
					            await OpenChanged.InvokeAsync(_open);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // long press on iOS triggers selection, so clear it
 | 
				
			||||||
 | 
					            await JSRuntime.ClearSelection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (args is LongPressEventArgs)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await Task.Delay(1000);
 | 
				
			||||||
 | 
					                _showChildren = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Sets the popover style ONLY when there is an activator.
 | 
					        /// Closes the menu.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public Task CloseMenuAsync()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _open = false;
 | 
				
			||||||
 | 
					            _popoverStyle = null;
 | 
				
			||||||
 | 
					            StateHasChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return OpenChanged.InvokeAsync(_open);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void SetPopoverStyle(double x, double y)
 | 
					        private void SetPopoverStyle(double x, double y)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _popoverStyle = $"margin-top: {y.ToPx()}; margin-left: {x.ToPx()};";
 | 
					            _popoverStyle = $"margin-top: {y.ToPx()}; margin-left: {x.ToPx()};";
 | 
				
			||||||
@@ -124,7 +202,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Toggle the visibility of the menu.
 | 
					        /// Toggle the visibility of the menu.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public new async Task ToggleMenuAsync(EventArgs args)
 | 
					        public async Task ToggleMenuAsync(EventArgs args)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Disabled)
 | 
					            if (Disabled)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -169,17 +247,13 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // the bottom position of the popover will be rendered off screen
 | 
					            // the bottom position of the popover will be rendered off screen
 | 
				
			||||||
            if ((_y - _diff + contextMenuHeight.Value) >= (mainContentSize.Height))
 | 
					            if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // adjust the top of the context menu
 | 
					                // adjust the top of the context menu
 | 
				
			||||||
                var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
 | 
					                var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
 | 
				
			||||||
                _y -= overshoot;
 | 
					                _y -= overshoot;
 | 
				
			||||||
                //if (_y < 70)
 | 
					 | 
				
			||||||
                //{
 | 
					 | 
				
			||||||
                //    _y = 70;
 | 
					 | 
				
			||||||
                //}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if ((_y - _diff + contextMenuHeight) >= mainContentSize.Height)
 | 
					                if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    MaxHeight = (int)(mainContentSize.Height - _y + _diff);
 | 
					                    MaxHeight = (int)(mainContentSize.Height - _y + _diff);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -214,7 +288,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
                throw new NotSupportedException("Invalid eventArgs type.");
 | 
					                throw new NotSupportedException("Invalid eventArgs type.");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return (x - (DrawerOpen && !InsideDrawer ? _drawerWidth : 0), y - (InsideDrawer ? _diff : 0));
 | 
					            return (x + AdjustmentX, y + AdjustmentY);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -3,9 +3,9 @@ using Microsoft.AspNetCore.Components.Web;
 | 
				
			|||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
using MudBlazor.Utilities;
 | 
					using MudBlazor.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class FakeNavLink
 | 
					    public partial class CustomNavLink
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
        public bool Active { get; set; }
 | 
					        public bool Active { get; set; }
 | 
				
			||||||
@@ -23,9 +23,13 @@
 | 
				
			|||||||
    SelectedItemsChanged="SelectedItemsChangedInternal"
 | 
					    SelectedItemsChanged="SelectedItemsChangedInternal"
 | 
				
			||||||
    OnRowClick="OnRowClickInternal"
 | 
					    OnRowClick="OnRowClickInternal"
 | 
				
			||||||
    RowStyleFunc="RowStyleFuncInternal"
 | 
					    RowStyleFunc="RowStyleFuncInternal"
 | 
				
			||||||
 | 
					    RowClassFunc="RowClassFuncInternal"
 | 
				
			||||||
    Class="@Class">
 | 
					    Class="@Class">
 | 
				
			||||||
    <ColGroup>
 | 
					    <ColGroup>
 | 
				
			||||||
 | 
					        @if (MultiSelection)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            <col style="width: 30px" />
 | 
					            <col style="width: 30px" />
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        @foreach (var column in GetColumns())
 | 
					        @foreach (var column in GetColumns())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            <col style="@(GetColumnStyle(column))" />
 | 
					            <col style="@(GetColumnStyle(column))" />
 | 
				
			||||||
@@ -51,9 +55,9 @@
 | 
				
			|||||||
    <RowTemplate>
 | 
					    <RowTemplate>
 | 
				
			||||||
        @foreach (var column in GetColumns())
 | 
					        @foreach (var column in GetColumns())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            <MudTdExtended @ref="_tds[column.Id]" DataLabel="@column.Header" Class="@(GetColumnClass(column, context))" Style="@(GetColumnStyle(column))" OnLongPress="@(c => OnLongPressInternal(c, column.Id, context))" OnContextMenu="@(c => OnContextMenuInternal(c, column.Id, context))">
 | 
					            <TdExtended @ref="_tds[column.Id]" DataLabel="@column.Header" Class="@(GetColumnClass(column, context))" Style="@(GetColumnStyle(column))" OnLongPress="@(c => OnLongPressInternal(c, column.Id, context))" OnContextMenu="@(c => OnContextMenuInternal(c, column.Id, context))">
 | 
				
			||||||
                @column.RowTemplate(column.GetRowContext(context))
 | 
					                @column.RowTemplate(column.GetRowContext(context))
 | 
				
			||||||
            </MudTdExtended>
 | 
					            </TdExtended>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </RowTemplate>
 | 
					    </RowTemplate>
 | 
				
			||||||
</MudTable>
 | 
					</MudTable>
 | 
				
			||||||
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Components;
 | 
				
			|||||||
using Microsoft.AspNetCore.Components.Web;
 | 
					using Microsoft.AspNetCore.Components.Web;
 | 
				
			||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class DynamicTable<T> : MudComponentBase
 | 
					    public partial class DynamicTable<T> : MudComponentBase
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -71,6 +71,9 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
        public EventCallback<TableDataLongPressEventArgs<T>> OnTableDataLongPress { get; set; }
 | 
					        public EventCallback<TableDataLongPressEventArgs<T>> OnTableDataLongPress { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Parameter]
 | 
				
			||||||
 | 
					        public Func<T, int, string>? RowClassFunc { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected IEnumerable<T>? OrderedItems => GetOrderedItems();
 | 
					        protected IEnumerable<T>? OrderedItems => GetOrderedItems();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected HashSet<string> SelectedColumns { get; set; } = [];
 | 
					        protected HashSet<string> SelectedColumns { get; set; } = [];
 | 
				
			||||||
@@ -81,7 +84,7 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private SortDirection _sortDirection;
 | 
					        private SortDirection _sortDirection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly Dictionary<string, MudTdExtended> _tds = [];
 | 
					        private readonly Dictionary<string, TdExtended> _tds = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        protected override async Task OnInitializedAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -225,15 +228,12 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else if (SelectOnRowClick && !SelectedItems.Contains(eventArgs.Item))
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (!SelectedItems.Contains(eventArgs.Item))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                SelectedItems.Clear();
 | 
					                SelectedItems.Clear();
 | 
				
			||||||
                SelectedItems.Add(eventArgs.Item);
 | 
					                SelectedItems.Add(eventArgs.Item);
 | 
				
			||||||
                await SelectedItemChanged.InvokeAsync(eventArgs.Item);
 | 
					                await SelectedItemChanged.InvokeAsync(eventArgs.Item);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await OnRowClick.InvokeAsync(eventArgs);
 | 
					            await OnRowClick.InvokeAsync(eventArgs);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -241,14 +241,23 @@ namespace Lantean.QBTMudBlade.Components
 | 
				
			|||||||
        protected string RowStyleFuncInternal(T item, int index)
 | 
					        protected string RowStyleFuncInternal(T item, int index)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var style = "user-select: none; cursor: pointer;";
 | 
					            var style = "user-select: none; cursor: pointer;";
 | 
				
			||||||
            //EqualityComparer<T>.Default.Equals(item, SelectedItem) ||
 | 
					            if (SelectOnRowClick && SelectedItems.Contains(item))
 | 
				
			||||||
            if (SelectedItems.Contains(item))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                style += " background-color: var(--mud-palette-gray-dark); color: var(--mud-palette-gray-light) !important;";
 | 
					                style += " background-color: var(--mud-palette-gray-dark); color: var(--mud-palette-gray-light) !important;";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return style;
 | 
					            return style;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected string RowClassFuncInternal(T item, int index)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (RowClassFunc is not null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return RowClassFunc(item, index);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return string.Empty;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task SelectedItemsChangedInternal(HashSet<T> selectedItems)
 | 
					        protected async Task SelectedItemsChangedInternal(HashSet<T> selectedItems)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await SelectedItemsChanged.InvokeAsync(selectedItems);
 | 
					            await SelectedItemsChanged.InvokeAsync(selectedItems);
 | 
				
			||||||
							
								
								
									
										3
									
								
								Lantean.QBTMudBlade/Components/UI/FieldSwitch.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Lantean.QBTMudBlade/Components/UI/FieldSwitch.razor
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					<MudField Variant="Variant.Outlined" InnerPadding="false" Label="@Label" HelperText="@HelperText">
 | 
				
			||||||
 | 
					    <TickSwitch T="bool" Value="@Value" ValueChanged="ValueChangedCallback" Class="pt-1 pb-1" Disabled="Disabled" Validation="Validation" />
 | 
				
			||||||
 | 
					</MudField>
 | 
				
			||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class MudFieldSwitch
 | 
					    public partial class FieldSwitch
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <inheritdoc cref="MudBlazor.MudBooleanInput{T}.Value"/>
 | 
					        /// <inheritdoc cref="MudBlazor.MudBooleanInput{T}.Value"/>
 | 
				
			||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// A simple razor wrapper that only renders the child content without any additonal html markup
 | 
					    /// A simple razor wrapper that only renders the child content without any additonal html markup
 | 
				
			||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
using MudBlazor.Utilities;
 | 
					using MudBlazor.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class SortLabel : MudComponentBase
 | 
					    public partial class SortLabel : MudComponentBase
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Components.Web;
 | 
					using Microsoft.AspNetCore.Components.Web;
 | 
				
			||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class TableDataContextMenuEventArgs<T> : EventArgs
 | 
					    public class TableDataContextMenuEventArgs<T> : EventArgs
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class TableDataLongPressEventArgs<T> : EventArgs
 | 
					    public class TableDataLongPressEventArgs<T> : EventArgs
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -2,9 +2,9 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Components.Web;
 | 
					using Microsoft.AspNetCore.Components.Web;
 | 
				
			||||||
using MudBlazor;
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Components
 | 
					namespace Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class MudTdExtended : MudTd
 | 
					    public partial class TdExtended : MudTd
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        [Parameter]
 | 
					        [Parameter]
 | 
				
			||||||
        public EventCallback<LongPressEventArgs> OnLongPress { get; set; }
 | 
					        public EventCallback<LongPressEventArgs> OnLongPress { get; set; }
 | 
				
			||||||
@@ -2,4 +2,5 @@
 | 
				
			|||||||
              ColumnDefinitions="Columns"
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
              Items="WebSeeds"
 | 
					              Items="WebSeeds"
 | 
				
			||||||
              MultiSelection="false"
 | 
					              MultiSelection="false"
 | 
				
			||||||
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
              Class="details-list" />
 | 
					              Class="details-list" />
 | 
				
			||||||
@@ -98,6 +98,11 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public static async Task InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
 | 
					        public static async Task InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            if (hashes.Length == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var parameters = new DialogParameters
 | 
					            var parameters = new DialogParameters
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                { nameof(DeleteDialog.Count), hashes.Length }
 | 
					                { nameof(DeleteDialog.Count), hashes.Length }
 | 
				
			||||||
@@ -118,9 +123,9 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
            await Task.Delay(0);
 | 
					            await Task.Delay(0);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<string?> ShowAddCategoryDialog(this IDialogService dialogService, IApiClient apiClient)
 | 
					        public static async Task<string?> InvokeAddCategoryDialog(this IDialogService dialogService, IApiClient apiClient)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var reference = await dialogService.ShowAsync<CategoryPropertiesDialog>("New Category", NonBlurFormDialogOptions);
 | 
					            var reference = await dialogService.ShowAsync<CategoryPropertiesDialog>("Add Category", NonBlurFormDialogOptions);
 | 
				
			||||||
            var dialogResult = await reference.Result;
 | 
					            var dialogResult = await reference.Result;
 | 
				
			||||||
            if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
 | 
					            if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -134,7 +139,7 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
            return category.Name;
 | 
					            return category.Name;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<string?> ShowEditCategoryDialog(this IDialogService dialogService, IApiClient apiClient, string categoryName)
 | 
					        public static async Task<string?> InvokeEditCategoryDialog(this IDialogService dialogService, IApiClient apiClient, string categoryName)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var category = (await apiClient.GetAllCategories()).FirstOrDefault(c => c.Key == categoryName).Value;
 | 
					            var category = (await apiClient.GetAllCategories()).FirstOrDefault(c => c.Key == categoryName).Value;
 | 
				
			||||||
            var parameters = new DialogParameters
 | 
					            var parameters = new DialogParameters
 | 
				
			||||||
@@ -174,7 +179,7 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public static async Task<HashSet<string>?> ShowAddTrackersDialog(this IDialogService dialogService)
 | 
					        public static async Task<HashSet<string>?> ShowAddTrackersDialog(this IDialogService dialogService)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var reference = await dialogService.ShowAsync<AddTrackerDialog>("Add Tags", NonBlurFormDialogOptions);
 | 
					            var reference = await dialogService.ShowAsync<AddTrackerDialog>("Add Tracker", NonBlurFormDialogOptions);
 | 
				
			||||||
            var dialogResult = await reference.Result;
 | 
					            var dialogResult = await reference.Result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
 | 
					            if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
 | 
				
			||||||
@@ -187,6 +192,21 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
            return tags;
 | 
					            return tags;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static async Task<HashSet<QBitTorrentClient.Models.PeerId>?> ShowAddPeersDialog(this IDialogService dialogService)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var reference = await dialogService.ShowAsync<AddPeerDialog>("Add Peer", NonBlurFormDialogOptions);
 | 
				
			||||||
 | 
					            var dialogResult = await reference.Result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var peers = (HashSet<QBitTorrentClient.Models.PeerId>)dialogResult.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return peers;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<bool> ShowConfirmDialog(this IDialogService dialogService, string title, string content)
 | 
					        public static async Task<bool> ShowConfirmDialog(this IDialogService dialogService, string title, string content)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var parameters = new DialogParameters
 | 
					            var parameters = new DialogParameters
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,10 +11,10 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
	<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
 | 
						<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
 | 
				
			||||||
	<PackageReference Include="ByteSize" Version="2.1.2" />
 | 
						<PackageReference Include="ByteSize" Version="2.1.2" />
 | 
				
			||||||
	<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
 | 
						<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
 | 
				
			||||||
	<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
 | 
						<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.7" PrivateAssets="all" />
 | 
				
			||||||
	<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
 | 
						<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
 | 
				
			||||||
	<PackageReference Include="MudBlazor" Version="7.0.0" />
 | 
						<PackageReference Include="MudBlazor" Version="7.5.0" />
 | 
				
			||||||
	<PackageReference Include="MudBlazor.ThemeManager" Version="2.0.0" />
 | 
						<PackageReference Include="MudBlazor.ThemeManager" Version="2.0.0" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,6 +26,9 @@
 | 
				
			|||||||
    <Content Update="Components\Dialogs\ManageCategoriesDialog.razor">
 | 
					    <Content Update="Components\Dialogs\ManageCategoriesDialog.razor">
 | 
				
			||||||
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 | 
					      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 | 
				
			||||||
    </Content>
 | 
					    </Content>
 | 
				
			||||||
 | 
					    <Content Update="Pages\TagManagement.razor">
 | 
				
			||||||
 | 
					      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 | 
				
			||||||
 | 
					    </Content>
 | 
				
			||||||
    <Content Update="Pages\Statistics.razor">
 | 
					    <Content Update="Pages\Statistics.razor">
 | 
				
			||||||
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 | 
					      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 | 
				
			||||||
    </Content>
 | 
					    </Content>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
 | 
					<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
 | 
				
			||||||
    <EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
 | 
					    <EnhancedErrorBoundary @ref="ErrorBoundary" OnClear="Cleared">
 | 
				
			||||||
        <MudThemeProvider @ref="MudThemeProvider" @bind-IsDarkMode="IsDarkMode" Theme="Theme" />
 | 
					        <MudThemeProvider @ref="MudThemeProvider" @bind-IsDarkMode="IsDarkMode" Theme="Theme" />
 | 
				
			||||||
        <MudDialogProvider />
 | 
					        <MudDialogProvider CloseButton="true" CloseOnEscapeKey="true" />
 | 
				
			||||||
        <MudSnackbarProvider />
 | 
					        <MudSnackbarProvider />
 | 
				
			||||||
        <MudPopoverProvider />
 | 
					        <MudPopoverProvider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
                        <MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
 | 
					                        <MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
 | 
				
			||||||
                    </MudBadge>
 | 
					                    </MudBadge>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                <MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" />
 | 
					                <MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
 | 
				
			||||||
                @if (ShowMenu)
 | 
					                @if (ShowMenu)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    <Menu />
 | 
					                    <Menu />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
 | 
					<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
 | 
				
			||||||
    <MudNavMenu>
 | 
					    <MudNavMenu>
 | 
				
			||||||
        <MudNavLink Icon="@(Icons.Material.Outlined.NavigateBefore)" OnClick="NavigateBack">Back</MudNavLink>
 | 
					        <MudNavLink Icon="@(Icons.Material.Outlined.Navigation)" OnClick="NavigateBack">Torrents</MudNavLink>
 | 
				
			||||||
        <MudDivider />
 | 
					        <MudDivider />
 | 
				
			||||||
        <MudNavLink Icon="@Icons.Material.Filled.PieChart" Href="/statistics">Statistics</MudNavLink>
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.PieChart" Href="/statistics">Statistics</MudNavLink>
 | 
				
			||||||
        <MudNavLink Icon="@Icons.Material.Filled.Search" Href="/search">Search</MudNavLink>
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.Search" Href="/search">Search</MudNavLink>
 | 
				
			||||||
@@ -11,6 +11,9 @@
 | 
				
			|||||||
        <MudNavLink Icon="@Icons.Material.Filled.List" Href="/log">Execution Log</MudNavLink>
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.List" Href="/log">Execution Log</MudNavLink>
 | 
				
			||||||
        <MudNavLink Icon="@Icons.Material.Filled.DisabledByDefault" Href="/blocks">Blocked IPs</MudNavLink>
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.DisabledByDefault" Href="/blocks">Blocked IPs</MudNavLink>
 | 
				
			||||||
        <MudDivider />
 | 
					        <MudDivider />
 | 
				
			||||||
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.Label" Href="/tags">Tag Management</MudNavLink>
 | 
				
			||||||
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.List" Href="/categories">Category Management</MudNavLink>
 | 
				
			||||||
 | 
					        <MudDivider />
 | 
				
			||||||
        <MudNavLink Icon="@Icons.Material.Filled.Settings" Href="/settings">Settings</MudNavLink>
 | 
					        <MudNavLink Icon="@Icons.Material.Filled.Settings" Href="/settings">Settings</MudNavLink>
 | 
				
			||||||
    </MudNavMenu>
 | 
					    </MudNavMenu>
 | 
				
			||||||
</MudDrawer>
 | 
					</MudDrawer>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ namespace Lantean.QBTMudBlade.Models
 | 
				
			|||||||
            Index = index;
 | 
					            Index = index;
 | 
				
			||||||
            Priority = priority;
 | 
					            Priority = priority;
 | 
				
			||||||
            Progress = progress;
 | 
					            Progress = progress;
 | 
				
			||||||
            Size = priority == Priority.DoNotDownload ? 0 : size;
 | 
					            Size = size;
 | 
				
			||||||
            Availability = availability;
 | 
					            Availability = availability;
 | 
				
			||||||
            IsFolder = isFolder;
 | 
					            IsFolder = isFolder;
 | 
				
			||||||
            Level = level;
 | 
					            Level = level;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										100
									
								
								Lantean.QBTMudBlade/Models/KeyboardEvent.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								Lantean.QBTMudBlade/Models/KeyboardEvent.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
				
			|||||||
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Models
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class KeyboardEvent
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public KeyboardEvent(string key)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Key = key;
 | 
				
			||||||
 | 
					            Code = key;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [JsonConstructor]
 | 
				
			||||||
 | 
					        public KeyboardEvent(string key, bool repeat, bool ctrlKey, bool shiftKey, bool altKey, bool metaKey, string code) : this(key)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Repeat = repeat;
 | 
				
			||||||
 | 
					            CtrlKey = ctrlKey;
 | 
				
			||||||
 | 
					            ShiftKey = shiftKey;
 | 
				
			||||||
 | 
					            AltKey = altKey;
 | 
				
			||||||
 | 
					            MetaKey = metaKey;
 | 
				
			||||||
 | 
					            Code = code;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// The key value of the key represented by the event.
 | 
				
			||||||
 | 
					        /// If the value has a printed representation, this attribute's value is the same as the char attribute.
 | 
				
			||||||
 | 
					        /// Otherwise, it's one of the key value strings specified in 'Key values'.
 | 
				
			||||||
 | 
					        /// If the key can't be identified, this is the string "Unidentified"
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [JsonPropertyName("key")]
 | 
				
			||||||
 | 
					        public string Key { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// true if a key has been depressed long enough to trigger key repetition, otherwise false.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [JsonPropertyName("repeat")]
 | 
				
			||||||
 | 
					        public bool Repeat { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// true if the control key was down when the event was fired. false otherwise.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [JsonPropertyName("ctrlKey")]
 | 
				
			||||||
 | 
					        public bool CtrlKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// true if the shift key was down when the event was fired. false otherwise.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [JsonPropertyName("shiftKey")]
 | 
				
			||||||
 | 
					        public bool ShiftKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// true if the alt key was down when the event was fired. false otherwise.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [JsonPropertyName("altKey")]
 | 
				
			||||||
 | 
					        public bool AltKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// true if the meta key was down when the event was fired. false otherwise.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        [JsonPropertyName("metaKey")]
 | 
				
			||||||
 | 
					        public bool MetaKey { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [JsonPropertyName("code")]
 | 
				
			||||||
 | 
					        public string Code { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override bool Equals(object? obj)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return obj is KeyboardEvent @event &&
 | 
				
			||||||
 | 
					                   Key == @event.Key &&
 | 
				
			||||||
 | 
					                   Repeat == @event.Repeat &&
 | 
				
			||||||
 | 
					                   CtrlKey == @event.CtrlKey &&
 | 
				
			||||||
 | 
					                   ShiftKey == @event.ShiftKey &&
 | 
				
			||||||
 | 
					                   AltKey == @event.AltKey &&
 | 
				
			||||||
 | 
					                   MetaKey == @event.MetaKey;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override string? ToString()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var modifiers = (CtrlKey ? "Ctrl" : "") + (ShiftKey ? "Shift" : "") + (AltKey ? "Alt" : "") + (MetaKey ? "Meta" : "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return modifiers + (modifiers.Length == 0 ? "" : "+") + Key + (Repeat ? "-repeated" : "");
 | 
				
			||||||
 | 
					            //return Key + (CtrlKey ? '1' : '0') + (ShiftKey ? '1' : '0') + (AltKey ? '1' : '0') + (MetaKey ? '1' : '0') + (Repeat ? '1' : '0');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override int GetHashCode()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return HashCode.Combine(Key, Repeat, CtrlKey, ShiftKey, AltKey, MetaKey, Code);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static implicit operator KeyboardEvent(string input)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return new KeyboardEvent(input);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static implicit operator string(KeyboardEvent input)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return input.ToString()!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -26,37 +26,11 @@
 | 
				
			|||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
</MudCard>
 | 
					</MudCard>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<MudTable Items="Results"
 | 
					<DynamicTable @ref="Table"
 | 
				
			||||||
              T="Lantean.QBitTorrentClient.Models.PeerLog"
 | 
					              T="Lantean.QBitTorrentClient.Models.PeerLog"
 | 
				
			||||||
          Hover="true"
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
          FixedHeader="true"
 | 
					              Items="Results"
 | 
				
			||||||
          HeaderClass="table-head-bordered"
 | 
					              MultiSelection="false"
 | 
				
			||||||
          Dense="true"
 | 
					 | 
				
			||||||
          Breakpoint="Breakpoint.None"
 | 
					 | 
				
			||||||
          Bordered="true"
 | 
					 | 
				
			||||||
          Square="true"
 | 
					 | 
				
			||||||
          LoadingProgressColor="Color.Info"
 | 
					 | 
				
			||||||
          HorizontalScrollbar="true"
 | 
					 | 
				
			||||||
          Virtualize="true"
 | 
					 | 
				
			||||||
          AllowUnsorted="false"
 | 
					 | 
				
			||||||
              SelectOnRowClick="false"
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
          Class="search-list"
 | 
					              RowClassFunc="RowClass"
 | 
				
			||||||
          RowClassFunc="RowClass">
 | 
					              Class="search-list" />
 | 
				
			||||||
    <NoRecordsContent>
 | 
					 | 
				
			||||||
        <MudText Typo="Typo.h6">Nothing has been blocked.</MudText>
 | 
					 | 
				
			||||||
    </NoRecordsContent>
 | 
					 | 
				
			||||||
    <HeaderContent>
 | 
					 | 
				
			||||||
        <MudTh>Id</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Message</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Timestamp</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Status</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Reason</MudTh>
 | 
					 | 
				
			||||||
    </HeaderContent>
 | 
					 | 
				
			||||||
    <RowTemplate>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Id">@context.Id</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="IP">@context.IPAddress</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Timestamp">@DisplayHelpers.DateTime(context.Timestamp)</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Status">@(context.Blocked ? "Blocked" : "Banned")</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Reason">@context.Reason</MudTd>
 | 
					 | 
				
			||||||
    </RowTemplate>
 | 
					 | 
				
			||||||
</MudTable>
 | 
					 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
using Blazored.LocalStorage;
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBitTorrentClient.Models;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using Microsoft.AspNetCore.Components.Forms;
 | 
					using Microsoft.AspNetCore.Components.Forms;
 | 
				
			||||||
@@ -33,10 +35,12 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected LogForm Model { get; set; } = new LogForm();
 | 
					        protected LogForm Model { get; set; } = new LogForm();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected List<QBitTorrentClient.Models.PeerLog>? Results { get; private set; }
 | 
					        protected List<PeerLog>? Results { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected MudSelect<string>? CategoryMudSelect { get; set; }
 | 
					        protected MudSelect<string>? CategoryMudSelect { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<PeerLog>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        protected override async Task OnInitializedAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var selectedTypes = await LocalStorage.GetItemAsync<IEnumerable<string>>(_selectedTypesStorageKey);
 | 
					            var selectedTypes = await LocalStorage.GetItemAsync<IEnumerable<string>>(_selectedTypesStorageKey);
 | 
				
			||||||
@@ -90,7 +94,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected static string RowClass(QBitTorrentClient.Models.PeerLog log, int index)
 | 
					        protected static string RowClass(PeerLog log, int index)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return $"log-{(log.Blocked ? "critical" : "normal")}";
 | 
					            return $"log-{(log.Blocked ? "critical" : "normal")}";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -149,5 +153,15 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<ColumnDefinition<PeerLog>> Columns => ColumnsDefinitions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<ColumnDefinition<PeerLog>> ColumnsDefinitions { get; } =
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            new ColumnDefinition<PeerLog>("Id", l => l.Id),
 | 
				
			||||||
 | 
					            new ColumnDefinition<PeerLog>("Message", l => l.IPAddress),
 | 
				
			||||||
 | 
					            new ColumnDefinition<PeerLog>("Timestamp", l => l.Timestamp, l => @DisplayHelpers.DateTime(l.Timestamp)),
 | 
				
			||||||
 | 
					            new ColumnDefinition<PeerLog>("Blocked", l => l.Blocked ? "Blocked" : "Banned"),
 | 
				
			||||||
 | 
					            new ColumnDefinition<PeerLog>("Reason", l => l.Reason),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								Lantean.QBTMudBlade/Pages/CategoryManagement.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Lantean.QBTMudBlade/Pages/CategoryManagement.razor
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					@page "/categories"
 | 
				
			||||||
 | 
					@layout OtherLayout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<MudToolBar Gutters="false" Dense="true">
 | 
				
			||||||
 | 
					    @if (!DrawerOpen)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
 | 
				
			||||||
 | 
					        <MudDivider Vertical="true" />
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.PlaylistAdd" OnClick="AddCategory" title="Add Category" />
 | 
				
			||||||
 | 
					    <MudText Class="pl-5 no-wrap">Categories</MudText>
 | 
				
			||||||
 | 
					</MudToolBar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<DynamicTable @ref="Table"
 | 
				
			||||||
 | 
					              T="Category"
 | 
				
			||||||
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
 | 
					              Items="Results"
 | 
				
			||||||
 | 
					              MultiSelection="false"
 | 
				
			||||||
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
 | 
					              Class="details-list" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@code {
 | 
				
			||||||
 | 
					    private RenderFragment<RowContext<Category>> ActionsColumn
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return context => __builder =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var value = (Category?)context.GetValue();
 | 
				
			||||||
 | 
					                <MudButtonGroup>
 | 
				
			||||||
 | 
					                    <MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Warning" OnClick="@(e => EditCategory(value?.Name))" />
 | 
				
			||||||
 | 
					                    <MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@(e => DeleteCategory(value?.Name))" />
 | 
				
			||||||
 | 
					                </MudButtonGroup>
 | 
				
			||||||
 | 
					                ;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								Lantean.QBTMudBlade/Pages/CategoryManagement.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								Lantean.QBTMudBlade/Pages/CategoryManagement.razor.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Pages
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public partial class CategoryManagement
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly Dictionary<string, RenderFragment<RowContext<Category>>> _columnRenderFragments = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IApiClient ApiClient { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IDialogService DialogService { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected NavigationManager NavigationManager { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected ILocalStorageService LocalStorage { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [CascadingParameter(Name = "DrawerOpen")]
 | 
				
			||||||
 | 
					        public bool DrawerOpen { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [CascadingParameter]
 | 
				
			||||||
 | 
					        public MainData? MainData { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<Category>? Results => MainData?.Categories.Values;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<Category>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public CategoryManagement()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _columnRenderFragments.Add("Actions", ActionsColumn);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void NavigateBack()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            NavigationManager.NavigateTo("/");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task DeleteCategory(string? name)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (name is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            await ApiClient.RemoveCategories(name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task AddCategory()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await DialogService.InvokeAddCategoryDialog(ApiClient);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task EditCategory(string? name)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (name is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            await DialogService.InvokeEditCategoryDialog(ApiClient, name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<ColumnDefinition<Category>> Columns => GetColumnDefinitions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<ColumnDefinition<Category>> GetColumnDefinitions()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            foreach (var columnDefinition in ColumnsDefinitions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (_columnRenderFragments.TryGetValue(columnDefinition.Header, out var fragment))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    columnDefinition.RowTemplate = fragment;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                yield return columnDefinition;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<ColumnDefinition<Category>> ColumnsDefinitions { get; } =
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            new ColumnDefinition<Category>("Name", l => l.Name),
 | 
				
			||||||
 | 
					            new ColumnDefinition<Category>("Save path", l => l.SavePath)
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -34,32 +34,11 @@
 | 
				
			|||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
</MudCard>
 | 
					</MudCard>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<MudTable Items="Results"
 | 
					<DynamicTable @ref="Table"
 | 
				
			||||||
              T="Lantean.QBitTorrentClient.Models.Log"
 | 
					              T="Lantean.QBitTorrentClient.Models.Log"
 | 
				
			||||||
          Hover="true"
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
          FixedHeader="true"
 | 
					              Items="Results"
 | 
				
			||||||
          HeaderClass="table-head-bordered"
 | 
					              MultiSelection="false"
 | 
				
			||||||
          Dense="true"
 | 
					 | 
				
			||||||
          Breakpoint="Breakpoint.None"
 | 
					 | 
				
			||||||
          Bordered="true"
 | 
					 | 
				
			||||||
          Square="true"
 | 
					 | 
				
			||||||
          LoadingProgressColor="Color.Info"
 | 
					 | 
				
			||||||
          HorizontalScrollbar="true"
 | 
					 | 
				
			||||||
          Virtualize="true"
 | 
					 | 
				
			||||||
          AllowUnsorted="false"
 | 
					 | 
				
			||||||
              SelectOnRowClick="false"
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
          Class="search-list"
 | 
					              RowClassFunc="RowClass"
 | 
				
			||||||
          RowClassFunc="RowClass">
 | 
					              Class="search-list" />
 | 
				
			||||||
    <HeaderContent>
 | 
					 | 
				
			||||||
        <MudTh>Id</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Message</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Timestamp</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Log Type</MudTh>
 | 
					 | 
				
			||||||
    </HeaderContent>
 | 
					 | 
				
			||||||
    <RowTemplate>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Od">@context.Id</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Message">@context.Message</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Timestamp">@DisplayHelpers.DateTime(context.Timestamp)</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Log Type">@context.Type</MudTd>
 | 
					 | 
				
			||||||
    </RowTemplate>
 | 
					 | 
				
			||||||
</MudTable>
 | 
					 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
using Blazored.LocalStorage;
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBitTorrentClient.Models;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using Microsoft.AspNetCore.Components.Forms;
 | 
					using Microsoft.AspNetCore.Components.Forms;
 | 
				
			||||||
@@ -37,6 +39,8 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected MudSelect<string>? CategoryMudSelect { get; set; }
 | 
					        protected MudSelect<string>? CategoryMudSelect { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<QBitTorrentClient.Models.Log>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        protected override async Task OnInitializedAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var selectedTypes = await LocalStorage.GetItemAsync<IEnumerable<string>>(_selectedTypesStorageKey);
 | 
					            var selectedTypes = await LocalStorage.GetItemAsync<IEnumerable<string>>(_selectedTypesStorageKey);
 | 
				
			||||||
@@ -154,5 +158,14 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<ColumnDefinition<QBitTorrentClient.Models.Log>> Columns => ColumnsDefinitions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<ColumnDefinition<QBitTorrentClient.Models.Log>> ColumnsDefinitions { get; } =
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.Log>("Id", l => l.Id),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.Log>("Message", l => l.Message),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.Log>("Timestamp", l => l.Timestamp, l => @DisplayHelpers.DateTime(l.Timestamp)),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.Log>("Log type", l => l.Type),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -48,7 +48,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
#if DEBUG
 | 
					#if DEBUG
 | 
				
			||||||
        protected override Task OnInitializedAsync()
 | 
					        protected override Task OnInitializedAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return DoLogin("admin", "42jMKTW7C");
 | 
					            return DoLogin("admin", "ALKEVRPZP");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,34 +49,10 @@
 | 
				
			|||||||
    </MudCardContent>
 | 
					    </MudCardContent>
 | 
				
			||||||
</MudCard>
 | 
					</MudCard>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<MudTable 
 | 
					<DynamicTable @ref="Table"
 | 
				
			||||||
    Items="Results"
 | 
					 | 
				
			||||||
              T="Lantean.QBitTorrentClient.Models.SearchResult"
 | 
					              T="Lantean.QBitTorrentClient.Models.SearchResult"
 | 
				
			||||||
    Hover="true"
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
    FixedHeader="true" 
 | 
					              Items="Results"
 | 
				
			||||||
    HeaderClass="table-head-bordered" 
 | 
					              MultiSelection="false"
 | 
				
			||||||
    Dense="true" 
 | 
					 | 
				
			||||||
    Breakpoint="Breakpoint.None" 
 | 
					 | 
				
			||||||
    Bordered="true" 
 | 
					 | 
				
			||||||
    Square="true" 
 | 
					 | 
				
			||||||
    LoadingProgressColor="Color.Info" 
 | 
					 | 
				
			||||||
    HorizontalScrollbar="true" 
 | 
					 | 
				
			||||||
    Virtualize="true"
 | 
					 | 
				
			||||||
    AllowUnsorted="false"
 | 
					 | 
				
			||||||
              SelectOnRowClick="false"
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
    Class="search-list">
 | 
					              Class="search-list" />
 | 
				
			||||||
    <HeaderContent>
 | 
					 | 
				
			||||||
        <MudTh>Name</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Size</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Seeders</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Leechers</MudTh>
 | 
					 | 
				
			||||||
        <MudTh>Search engine</MudTh>
 | 
					 | 
				
			||||||
    </HeaderContent>
 | 
					 | 
				
			||||||
    <RowTemplate>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Name">@context.FileName</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Size">@DisplayHelpers.Size(context.FileSize)</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Seeders">@context.Seeders</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Leechers">@context.Leechers</MudTd>
 | 
					 | 
				
			||||||
        <MudTd DataLabel="Search engine">@context.SiteUrl</MudTd>
 | 
					 | 
				
			||||||
    </RowTemplate>
 | 
					 | 
				
			||||||
</MudTable>
 | 
					 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using Microsoft.AspNetCore.Components.Forms;
 | 
					using Microsoft.AspNetCore.Components.Forms;
 | 
				
			||||||
@@ -42,6 +43,8 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected IEnumerable<QBitTorrentClient.Models.SearchResult>? Results => _searchResults?.Results;
 | 
					        protected IEnumerable<QBitTorrentClient.Models.SearchResult>? Results => _searchResults?.Results;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<QBitTorrentClient.Models.SearchResult>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async Task OnInitializedAsync()
 | 
					        protected override async Task OnInitializedAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _plugins = await ApiClient.GetSearchPlugins();
 | 
					            _plugins = await ApiClient.GetSearchPlugins();
 | 
				
			||||||
@@ -146,6 +149,17 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<ColumnDefinition<QBitTorrentClient.Models.SearchResult>> Columns => ColumnsDefinitions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<ColumnDefinition<QBitTorrentClient.Models.SearchResult>> ColumnsDefinitions { get; } =
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.SearchResult>("Name", l => l.FileName),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.SearchResult>("Size", l => @DisplayHelpers.Size(l.FileSize)),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.SearchResult>("Seeders", l => l.Seeders),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.SearchResult>("Leechers", l => l.Leechers),
 | 
				
			||||||
 | 
					            new ColumnDefinition<QBitTorrentClient.Models.SearchResult>("Search engine", l => l.SiteUrl),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected virtual void Dispose(bool disposing)
 | 
					        protected virtual void Dispose(bool disposing)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!_disposedValue)
 | 
					            if (!_disposedValue)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								Lantean.QBTMudBlade/Pages/TagManagement.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Lantean.QBTMudBlade/Pages/TagManagement.razor
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					@page "/tags"
 | 
				
			||||||
 | 
					@layout OtherLayout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<MudToolBar Gutters="false" Dense="true">
 | 
				
			||||||
 | 
					    @if (!DrawerOpen)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
 | 
				
			||||||
 | 
					        <MudDivider Vertical="true" />
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
 | 
					    <MudIconButton Icon="@Icons.Material.Filled.NewLabel" OnClick="AddTag" title="Add Tag" />
 | 
				
			||||||
 | 
					    <MudText Class="pl-5 no-wrap">Tags</MudText>
 | 
				
			||||||
 | 
					</MudToolBar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<DynamicTable @ref="Table"
 | 
				
			||||||
 | 
					              T="string"
 | 
				
			||||||
 | 
					              ColumnDefinitions="Columns"
 | 
				
			||||||
 | 
					              Items="Results"
 | 
				
			||||||
 | 
					              MultiSelection="false"
 | 
				
			||||||
 | 
					              SelectOnRowClick="false"
 | 
				
			||||||
 | 
					              Class="details-list" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@code {
 | 
				
			||||||
 | 
					    private RenderFragment<RowContext<string>> ActionsColumn
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return context => __builder =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var value = (string?)context.GetValue();
 | 
				
			||||||
 | 
					                <MudButtonGroup>
 | 
				
			||||||
 | 
					                    <MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@(e => DeleteTag(value))" />
 | 
				
			||||||
 | 
					                </MudButtonGroup>
 | 
				
			||||||
 | 
					                ;
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								Lantean.QBTMudBlade/Pages/TagManagement.razor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Lantean.QBTMudBlade/Pages/TagManagement.razor.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					using Blazored.LocalStorage;
 | 
				
			||||||
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					using MudBlazor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Pages
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public partial class TagManagement
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly Dictionary<string, RenderFragment<RowContext<string>>> _columnRenderFragments = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IApiClient ApiClient { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IDialogService DialogService { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected NavigationManager NavigationManager { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected ILocalStorageService LocalStorage { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [CascadingParameter(Name = "DrawerOpen")]
 | 
				
			||||||
 | 
					        public bool DrawerOpen { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [CascadingParameter]
 | 
				
			||||||
 | 
					        public MainData? MainData { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<string>? Results => MainData?.Tags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected DynamicTable<string>? Table { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public TagManagement()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _columnRenderFragments.Add("Actions", ActionsColumn);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void NavigateBack()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            NavigationManager.NavigateTo("/");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task DeleteTag(string? tag)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (tag is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            await ApiClient.DeleteTags(tag);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task AddTag()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var tags = await DialogService.ShowAddTagsDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (tags is null || tags.Count == 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await ApiClient.CreateTags(tags);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected IEnumerable<ColumnDefinition<string>> Columns => GetColumnDefinitions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private IEnumerable<ColumnDefinition<string>> GetColumnDefinitions()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            foreach (var columnDefinition in ColumnsDefinitions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (_columnRenderFragments.TryGetValue(columnDefinition.Header, out var fragment))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    columnDefinition.RowTemplate = fragment;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                yield return columnDefinition;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<ColumnDefinition<string>> ColumnsDefinitions { get; } =
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            new ColumnDefinition<string>("Id", l => l),
 | 
				
			||||||
 | 
					            new ColumnDefinition<string>("Actions", l => l)
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,22 +1,24 @@
 | 
				
			|||||||
@page "/"
 | 
					@page "/"
 | 
				
			||||||
@layout ListLayout
 | 
					@layout ListLayout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ContextMenu @ref="ContextMenu" Dense="true" AdjustmentX="@(DrawerOpen ? -235 : 0)">
 | 
				
			||||||
 | 
					    <MudMenuItem Icon="@Icons.Material.Outlined.Info" IconColor="Color.Inherit" OnClick="ShowTorrentContextMenu">View torrent details</MudMenuItem>
 | 
				
			||||||
 | 
					    <MudDivider />
 | 
				
			||||||
 | 
					    <TorrentActions RenderType="RenderType.MenuItems" Hashes="GetContextMenuTargetHashes()" PrimaryHash="@(ContextMenuItem?.Hash)" Torrents="MainData.Torrents" Preferences="Preferences" />
 | 
				
			||||||
 | 
					</ContextMenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<MudToolBar Gutters="false" Dense="true">
 | 
					<MudToolBar Gutters="false" Dense="true">
 | 
				
			||||||
    <MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" title="Add torrent link" />
 | 
					    <MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" title="Add torrent link" />
 | 
				
			||||||
    <MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" title="Add torrent file" />
 | 
					    <MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" title="Add torrent file" />
 | 
				
			||||||
    <MudDivider Vertical="true" />
 | 
					    <MudDivider Vertical="true" />
 | 
				
			||||||
    <TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrentsHashes()" Torrents="MainData.Torrents" Preferences="Preferences" />
 | 
					    <TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrentsHashes()" Torrents="MainData.Torrents" Preferences="Preferences" />
 | 
				
			||||||
    <MudDivider Vertical="true" />
 | 
					    <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.Info" Color="Color.Inherit" Disabled="@(!ToolbarButtonsEnabled)" OnClick="ShowTorrentToolbar" title="View torrent details" />
 | 
				
			||||||
    <MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
 | 
					    <MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
 | 
				
			||||||
    <MudSpacer />
 | 
					    <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>
 | 
					    <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>
 | 
					</MudToolBar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ContextMenu @ref="ContextMenu" Dense="true">
 | 
					 | 
				
			||||||
    <TorrentActions RenderType="RenderType.MenuItems" Hashes="GetContextMenuTargetHashes()" PrimaryHash="@(ContextMenuItem?.Hash)" Torrents="MainData.Torrents" Preferences="Preferences" />
 | 
					 | 
				
			||||||
</ContextMenu>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0">
 | 
					<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0">
 | 
				
			||||||
    <DynamicTable
 | 
					    <DynamicTable
 | 
				
			||||||
        @ref="Table"
 | 
					        @ref="Table"
 | 
				
			||||||
@@ -26,6 +28,7 @@
 | 
				
			|||||||
        Items="Torrents" 
 | 
					        Items="Torrents" 
 | 
				
			||||||
        OnRowClick="RowClick" 
 | 
					        OnRowClick="RowClick" 
 | 
				
			||||||
        MultiSelection="true"
 | 
					        MultiSelection="true"
 | 
				
			||||||
 | 
					        SelectOnRowClick="true"
 | 
				
			||||||
        SelectedItemsChanged="SelectedItemsChanged"
 | 
					        SelectedItemsChanged="SelectedItemsChanged"
 | 
				
			||||||
        SortColumnChanged="SortColumnChangedHandler"
 | 
					        SortColumnChanged="SortColumnChangedHandler"
 | 
				
			||||||
        SortDirectionChanged="SortDirectionChangedHandler"
 | 
					        SortDirectionChanged="SortDirectionChangedHandler"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
using Lantean.QBitTorrentClient;
 | 
					using Lantean.QBitTorrentClient;
 | 
				
			||||||
using Lantean.QBTMudBlade.Components;
 | 
					using Lantean.QBTMudBlade.Components.UI;
 | 
				
			||||||
using Lantean.QBTMudBlade.Interop;
 | 
					 | 
				
			||||||
using Lantean.QBTMudBlade.Models;
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
 | 
					using Lantean.QBTMudBlade.Services;
 | 
				
			||||||
using Microsoft.AspNetCore.Components;
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
using Microsoft.AspNetCore.Components.Web;
 | 
					using Microsoft.AspNetCore.Components.Web;
 | 
				
			||||||
using Microsoft.JSInterop;
 | 
					using Microsoft.JSInterop;
 | 
				
			||||||
@@ -9,8 +9,14 @@ using MudBlazor;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Lantean.QBTMudBlade.Pages
 | 
					namespace Lantean.QBTMudBlade.Pages
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public partial class TorrentList
 | 
					    public partial class TorrentList : IAsyncDisposable
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        private bool _disposedValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static KeyboardEvent _addTorrentFileKey = new KeyboardEvent("a") { AltKey = true };
 | 
				
			||||||
 | 
					        private static KeyboardEvent _addTorrentLinkKey = new KeyboardEvent("l") { AltKey = true };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
        protected IApiClient ApiClient { get; set; } = default!;
 | 
					        protected IApiClient ApiClient { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,6 +29,9 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
        [Inject]
 | 
					        [Inject]
 | 
				
			||||||
        protected IJSRuntime JSRuntime { get; set; } = default!;
 | 
					        protected IJSRuntime JSRuntime { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Inject]
 | 
				
			||||||
 | 
					        protected IKeyboardService KeyboardService { get; set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [CascadingParameter]
 | 
					        [CascadingParameter]
 | 
				
			||||||
        public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
 | 
					        public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,6 +50,9 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
        [CascadingParameter(Name = "SortDirectionChanged")]
 | 
					        [CascadingParameter(Name = "SortDirectionChanged")]
 | 
				
			||||||
        public EventCallback<SortDirection> SortDirectionChanged { get; set; }
 | 
					        public EventCallback<SortDirection> SortDirectionChanged { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [CascadingParameter(Name = "DrawerOpen")]
 | 
				
			||||||
 | 
					        public bool DrawerOpen { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected string? SearchText { get; set; }
 | 
					        protected string? SearchText { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected HashSet<Torrent> SelectedItems { get; set; } = [];
 | 
					        protected HashSet<Torrent> SelectedItems { get; set; } = [];
 | 
				
			||||||
@@ -53,6 +65,15 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected ContextMenu? ContextMenu { get; set; }
 | 
					        protected ContextMenu? ContextMenu { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected override async Task OnAfterRenderAsync(bool firstRender)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (firstRender)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                await KeyboardService.RegisterKeypressEvent(_addTorrentFileKey, k => AddTorrentFile());
 | 
				
			||||||
 | 
					                await KeyboardService.RegisterKeypressEvent(_addTorrentLinkKey, k => AddTorrentLink());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
 | 
					        protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            SelectedItems = selectedItems;
 | 
					            SelectedItems = selectedItems;
 | 
				
			||||||
@@ -122,9 +143,20 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
            await Table.ShowColumnOptionsDialog();
 | 
					            await Table.ShowColumnOptionsDialog();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void ShowTorrent()
 | 
					        protected void ShowTorrentToolbar()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var torrent = SelectedItems.FirstOrDefault();
 | 
					            var torrent = SelectedItems.FirstOrDefault();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            NavigateToTorrent(torrent);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void ShowTorrentContextMenu()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            NavigateToTorrent(ContextMenuItem);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected void NavigateToTorrent(Torrent? torrent)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            if (torrent is null)
 | 
					            if (torrent is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@@ -162,11 +194,11 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
        public static List<ColumnDefinition<Torrent>> ColumnsDefinitions { get; } =
 | 
					        public static List<ColumnDefinition<Torrent>> ColumnsDefinitions { get; } =
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            CreateColumnDefinition("#", t => t.Priority),
 | 
					            CreateColumnDefinition("#", t => t.Priority),
 | 
				
			||||||
            CreateColumnDefinition("Icon", t => t.State, IconColumn, iconOnly: true),
 | 
					            CreateColumnDefinition("Icon", t => t.State, IconColumn, iconOnly: true, width: 25),
 | 
				
			||||||
            CreateColumnDefinition("Name", t => t.Name, width: 400),
 | 
					            CreateColumnDefinition("Name", t => t.Name, width: 400),
 | 
				
			||||||
            CreateColumnDefinition("Size", t => t.Size, t => DisplayHelpers.Size(t.Size)),
 | 
					            CreateColumnDefinition("Size", t => t.Size, t => DisplayHelpers.Size(t.Size)),
 | 
				
			||||||
            CreateColumnDefinition("Total Size", t => t.TotalSize, t => DisplayHelpers.Size(t.TotalSize), enabled: false),
 | 
					            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"),
 | 
					            CreateColumnDefinition("Done", t => t.Progress, ProgressBarColumn, tdClass: "table-progress"),
 | 
				
			||||||
            CreateColumnDefinition("Status", t => t.State, t => DisplayHelpers.State(t.State)),
 | 
					            CreateColumnDefinition("Status", t => t.State, t => DisplayHelpers.State(t.State)),
 | 
				
			||||||
            CreateColumnDefinition("Seeds", t => t.NumberSeeds),
 | 
					            CreateColumnDefinition("Seeds", t => t.NumberSeeds),
 | 
				
			||||||
            CreateColumnDefinition("Peers", t => t.NumberLeeches),
 | 
					            CreateColumnDefinition("Peers", t => t.NumberLeeches),
 | 
				
			||||||
@@ -199,7 +231,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
        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)
 | 
					        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);
 | 
					            var cd = new ColumnDefinition<Torrent>(name, selector, rowTemplate);
 | 
				
			||||||
            cd.Class = "no-wrap";
 | 
					            cd.Class = iconOnly ? "icon-cell" : "no-wrap";
 | 
				
			||||||
            if (tdClass is not null)
 | 
					            if (tdClass is not null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                cd.Class += " " + tdClass;
 | 
					                cd.Class += " " + tdClass;
 | 
				
			||||||
@@ -214,7 +246,7 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
        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)
 | 
					        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);
 | 
					            var cd = new ColumnDefinition<Torrent>(name, selector, formatter);
 | 
				
			||||||
            cd.Class = "no-wrap";
 | 
					            cd.Class = iconOnly ? "icon-cell" : "no-wrap";
 | 
				
			||||||
            if (tdClass is not null)
 | 
					            if (tdClass is not null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                cd.Class += " " + tdClass;
 | 
					                cd.Class += " " + tdClass;
 | 
				
			||||||
@@ -225,5 +257,26 @@ namespace Lantean.QBTMudBlade.Pages
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return cd;
 | 
					            return cd;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async ValueTask DisposeAsync()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 | 
				
			||||||
 | 
					            await DisposeAsync(disposing: true);
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected virtual async Task DisposeAsync(bool disposing)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!_disposedValue)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (disposing)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    await KeyboardService.UnregisterKeypressEvent(_addTorrentFileKey);
 | 
				
			||||||
 | 
					                    await KeyboardService.UnregisterKeypressEvent(_addTorrentLinkKey);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _disposedValue = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -42,6 +42,7 @@ namespace Lantean.QBTMudBlade
 | 
				
			|||||||
            builder.Services.AddSingleton<IDataManager, DataManager>();
 | 
					            builder.Services.AddSingleton<IDataManager, DataManager>();
 | 
				
			||||||
            builder.Services.AddBlazoredLocalStorage();
 | 
					            builder.Services.AddBlazoredLocalStorage();
 | 
				
			||||||
            builder.Services.AddSingleton<IClipboardService, ClipboardService>();
 | 
					            builder.Services.AddSingleton<IClipboardService, ClipboardService>();
 | 
				
			||||||
 | 
					            builder.Services.AddSingleton<IKeyboardService, KeyboardService>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if DEBUG
 | 
					#if DEBUG
 | 
				
			||||||
            builder.Logging.SetMinimumLevel(LogLevel.Information);
 | 
					            builder.Logging.SetMinimumLevel(LogLevel.Information);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								Lantean.QBTMudBlade/Services/IKeyboardService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Lantean.QBTMudBlade/Services/IKeyboardService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Services
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public interface IKeyboardService
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Task RegisterKeypressEvent(KeyboardEvent criteria, Func<KeyboardEvent, Task> onKeyPress);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Task UnregisterKeypressEvent(KeyboardEvent criteria);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								Lantean.QBTMudBlade/Services/KeyboardService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Lantean.QBTMudBlade/Services/KeyboardService.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					using Lantean.QBTMudBlade.Models;
 | 
				
			||||||
 | 
					using Microsoft.JSInterop;
 | 
				
			||||||
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Lantean.QBTMudBlade.Services
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class KeyboardService : IKeyboardService
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly IJSRuntime _jSRuntime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private DotNetObjectReference<KeyboardService>? _dotNetObjectReference;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly ConcurrentDictionary<string, Func<KeyboardEvent, Task>> _keyboardHandlers = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public KeyboardService(IJSRuntime jSRuntime)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _jSRuntime = jSRuntime;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task RegisterKeypressEvent(KeyboardEvent criteria, Func<KeyboardEvent, Task> onKeyPress)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await _jSRuntime.InvokeVoidAsync("qbt.registerKeypressEvent", criteria, GetObjectReference());
 | 
				
			||||||
 | 
					            _keyboardHandlers.TryAdd(criteria, onKeyPress);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private DotNetObjectReference<KeyboardService> GetObjectReference()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _dotNetObjectReference ??= DotNetObjectReference.Create(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return _dotNetObjectReference;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [JSInvokable]
 | 
				
			||||||
 | 
					        public async Task HandleKeyPressEvent(KeyboardEvent keyboardEvent)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!_keyboardHandlers.TryGetValue(keyboardEvent, out var handler))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await handler(keyboardEvent);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task UnregisterKeypressEvent(KeyboardEvent criteria)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await _jSRuntime.InvokeVoidAsync("qbt.unregisterKeypressEvent", criteria, GetObjectReference());
 | 
				
			||||||
 | 
					            _keyboardHandlers.Remove(criteria, out var _);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,3 +13,4 @@
 | 
				
			|||||||
@using Lantean.QBTMudBlade.Components
 | 
					@using Lantean.QBTMudBlade.Components
 | 
				
			||||||
@using Lantean.QBTMudBlade.Components.Dialogs
 | 
					@using Lantean.QBTMudBlade.Components.Dialogs
 | 
				
			||||||
@using Lantean.QBTMudBlade.Components.Options
 | 
					@using Lantean.QBTMudBlade.Components.Options
 | 
				
			||||||
 | 
					@using Lantean.QBTMudBlade.Components.UI
 | 
				
			||||||
@@ -5,10 +5,10 @@
 | 
				
			|||||||
- Rename multiple files dialog
 | 
					- Rename multiple files dialog
 | 
				
			||||||
- RSS feeds and dialogs
 | 
					- RSS feeds and dialogs
 | 
				
			||||||
- About
 | 
					- About
 | 
				
			||||||
- Context menu for files list/trackers list/peers list
 | 
					- ~~Context menu for files list/trackers list/peers list~~
 | 
				
			||||||
- Tag management page
 | 
					- ~~Tag management page~~
 | 
				
			||||||
- Category management page
 | 
					- ~~Category management page~~
 | 
				
			||||||
- Update all tables to use DynamicTable
 | 
					- ~~Update all tables to use DynamicTable~~
 | 
				
			||||||
  - Log
 | 
					  - ~~Log~~
 | 
				
			||||||
  - Blocks
 | 
					  - ~~Blocks~~
 | 
				
			||||||
  - Search
 | 
					  - ~~Search~~
 | 
				
			||||||
@@ -81,8 +81,7 @@ code {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.table-progress {
 | 
					.table-progress {
 | 
				
			||||||
    padding-top: 0 !important;
 | 
					    padding: 0 !important;
 | 
				
			||||||
    padding-bottom: 0 !important;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .table-progress .progress-expand {
 | 
					    .table-progress .progress-expand {
 | 
				
			||||||
@@ -212,3 +211,11 @@ tr.log-critical td {
 | 
				
			|||||||
    padding-left: 0px !important;
 | 
					    padding-left: 0px !important;
 | 
				
			||||||
    padding-inline-start: 10px !important;
 | 
					    padding-inline-start: 10px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td.icon-cell {
 | 
				
			||||||
 | 
					    padding: 2px 0 0 7px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td .folder-button {
 | 
				
			||||||
 | 
					    padding: 6px 16px 6px 16px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -102,6 +102,66 @@ window.qbt.clearSelection = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let supportedEvents = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('keyup', event => {
 | 
				
			||||||
 | 
					    const key = getKey(event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(key);
 | 
				
			||||||
 | 
					    console.log(event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const references = supportedEvents.get(key);
 | 
				
			||||||
 | 
					    if (!references) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(references);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    references.forEach(dotNetObjectReference => {
 | 
				
			||||||
 | 
					        dotNetObjectReference.invokeMethodAsync('HandleKeyPressEvent', {
 | 
				
			||||||
 | 
					            key: event.key,
 | 
				
			||||||
 | 
					            code: event.code,
 | 
				
			||||||
 | 
					            altKey: event.altKey,
 | 
				
			||||||
 | 
					            ctrlKey: event.ctrlKey,
 | 
				
			||||||
 | 
					            metaKey: event.metaKey,
 | 
				
			||||||
 | 
					            shiftKey: event.shiftKey,
 | 
				
			||||||
 | 
					        }).catch(error => {
 | 
				
			||||||
 | 
					            console.error("Error handling key press:", error);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.qbt.registerKeypressEvent = (keyboardEventArgs, dotNetObjectReference) => {
 | 
				
			||||||
 | 
					    const key = getKey(keyboardEventArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const references = supportedEvents.get(key);
 | 
				
			||||||
 | 
					    if (references) {
 | 
				
			||||||
 | 
					        references.set(dotNetObjectReference._id, dotNetObjectReference);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        const references = new Map();
 | 
				
			||||||
 | 
					        references.set(dotNetObjectReference._id, dotNetObjectReference);
 | 
				
			||||||
 | 
					        supportedEvents.set(key, references);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.qbt.unregisterKeypressEvent = (keyboardEventArgs, dotNetObjectReference) => {
 | 
				
			||||||
 | 
					    const key = getKey(keyboardEventArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const references = supportedEvents.get(key);
 | 
				
			||||||
 | 
					    if (!references) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    references.delete(dotNetObjectReference._id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getKey(keyboardEvent) {
 | 
				
			||||||
 | 
					    return keyboardEvent.key + (keyboardEvent.ctrlKey ? '1' : '0') + (keyboardEvent.shiftKey ? '1' : '0') + (keyboardEvent.altKey ? '1' : '0') + (keyboardEvent.metaKey ? '1' : '0') + (keyboardEvent.repeat ? '1' : '0');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function fallbackCopyTextToClipboard(text) {
 | 
					function fallbackCopyTextToClipboard(text) {
 | 
				
			||||||
    const textArea = document.createElement("textarea");
 | 
					    const textArea = document.createElement("textarea");
 | 
				
			||||||
    textArea.value = text;
 | 
					    textArea.value = text;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,442 +0,0 @@
 | 
				
			|||||||
using Lantean.QBitTorrentClient.Models;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Lantean.QBitTorrentClient
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public class MockApiClient : IApiClient
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly ApiClient _apiClient;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public MockApiClient(ApiClient apiClient)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _apiClient = apiClient;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task AddCategory(string category, string savePath)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.AddCategory(category, savePath);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task AddPeers(IEnumerable<string> hashes, IEnumerable<PeerId> peers)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.AddPeers(hashes, peers);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task AddTorrent(IEnumerable<string>? urls = null, Dictionary<string, Stream>? torrents = null, string? savePath = null, string? cookie = null, string? category = null, IEnumerable<string>? tags = null, bool? skipChecking = null, bool? paused = null, string? contentLayout = null, string? renameTorrent = null, long? uploadLimit = null, long? downloadLimit = null, float? ratioLimit = null, int? seedingTimeLimit = null, bool? autoTorrentManagement = null, bool? sequentialDownload = null, bool? firstLastPiecePriority = null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.AddTorrent(urls, torrents, savePath, cookie, category, tags, skipChecking, paused, contentLayout, renameTorrent, uploadLimit, downloadLimit, ratioLimit, seedingTimeLimit, autoTorrentManagement, sequentialDownload, firstLastPiecePriority);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task AddTorrentTags(IEnumerable<string> tags, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.AddTorrentTags(tags, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task AddTrackersToTorrent(string hash, IEnumerable<string> urls)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.AddTrackersToTorrent(hash, urls);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task BanPeers(IEnumerable<PeerId> peers)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.BanPeers(peers);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<bool> CheckAuthState()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.CheckAuthState();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task CreateTags(IEnumerable<string> tags)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.CreateTags(tags);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task DecreaseTorrentPriority(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.DecreaseTorrentPriority(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task DeleteTags(params string[] tags)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.DeleteTags(tags);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task DeleteTorrents(bool? all = null, bool deleteFiles = false, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.DeleteTorrents(all, deleteFiles, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task EditCategory(string category, string savePath)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.EditCategory(category, savePath);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task EditTracker(string hash, string originalUrl, string newUrl)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.EditTracker(hash, originalUrl, newUrl);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<string> GetExportUrl(string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetExportUrl(hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyDictionary<string, Category>> GetAllCategories()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetAllCategories();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<string>> GetAllTags()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetAllTags();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<bool> GetAlternativeSpeedLimitsState()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetAlternativeSpeedLimitsState();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<string> GetAPIVersion()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetAPIVersion();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<Preferences> GetApplicationPreferences()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetApplicationPreferences();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<string> GetApplicationVersion()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetApplicationVersion();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<BuildInfo> GetBuildInfo()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetBuildInfo();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<string> GetDefaultSavePath()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetDefaultSavePath();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<NetworkInterface>> GetNetworkInterfaces()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetNetworkInterfaces();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<string>> GetNetworkInterfaceAddressList(string @interface)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetNetworkInterfaceAddressList(@interface);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<long> GetGlobalDownloadLimit()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetGlobalDownloadLimit();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<GlobalTransferInfo> GetGlobalTransferInfo()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetGlobalTransferInfo();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<long> GetGlobalUploadLimit()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetGlobalUploadLimit();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<Log>> GetLog(bool? normal = null, bool? info = null, bool? warning = null, bool? critical = null, int? lastKnownId = null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetLog(normal, info, warning, critical, lastKnownId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<MainData> GetMainData(int requestId)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetMainData(requestId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<PeerLog>> GetPeerLog(int? lastKnownId = null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetPeerLog(lastKnownId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<FileData>> GetTorrentContents(string hash, params int[] indexes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var list = new List<FileData>();
 | 
					 | 
				
			||||||
            list.Add(new FileData(2, "slackware-14.2-iso/slackware-14.2-source-d6.iso", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(3, "slackware-14.2-iso/slackware-14.2-source-d6.iso.asc", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(4, "slackware-14.2-iso/slackware-14.2-source-d6.iso.md5", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(5, "slackware-14.2-iso/slackware-14.2-source-d6.iso.txt", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(6, "slackware-14.2-iso/temp/slackware-14.2-source-d6.iso.md5", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(7, "slackware-14.2-iso/temp/slackware-14.2-source-d6.iso.txt", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(8, "slackware-14.2-iso2/slackware-14.2-source-d6.iso2", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(9, "slackware-14.2-iso2/slackware-14.2-source-d6.iso2.asc", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(10, "slackware-14.2-iso2/slackware-14.2-source-d6.iso2.md5", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(11, "slackware-14.2-iso2/slackware-14.2-source-d6.iso2.txt", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(12, "really/long/directory/path/is/here/file.txt", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            list.Add(new FileData(13, "other.txt", 500, 0f, Priority.Normal, false, [1, 2], 0f));
 | 
					 | 
				
			||||||
            return Task.FromResult<IReadOnlyList<FileData>>(list);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyDictionary<string, long>> GetTorrentDownloadLimit(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentDownloadLimit(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<Torrent>> GetTorrentList(string? filter = null, string? category = null, string? tag = null, string? sort = null, bool? reverse = null, int? limit = null, int? offset = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentList(filter, category, tag, sort, reverse, limit, offset, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<TorrentPeers> GetTorrentPeersData(string hash, int requestId)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentPeersData(hash, requestId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<string>> GetTorrentPieceHashes(string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentPieceHashes(hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<PieceState>> GetTorrentPieceStates(string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentPieceStates(hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<TorrentProperties> GetTorrentProperties(string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentProperties(hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<TorrentTracker>> GetTorrentTrackers(string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentTrackers(hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyDictionary<string, long>> GetTorrentUploadLimit(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentUploadLimit(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<WebSeed>> GetTorrentWebSeeds(string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetTorrentWebSeeds(hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task IncreaseTorrentPriority(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.IncreaseTorrentPriority(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task Login(string username, string password)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.Login(username, password);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task Logout()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.Logout();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task MaximalTorrentPriority(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.MaximalTorrentPriority(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task MinimalTorrentPriority(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.MinimalTorrentPriority(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task PauseTorrents(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.PauseTorrents(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task ReannounceTorrents(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.ReannounceTorrents(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RecheckTorrents(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.ReannounceTorrents(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RemoveCategories(params string[] categories)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.RemoveCategories(categories);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RemoveTorrentTags(IEnumerable<string> tags, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.RemoveTorrentTags(tags, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RemoveTrackers(string hash, IEnumerable<string> urls)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.RemoveTrackers(hash, urls);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RenameFile(string hash, string oldPath, string newPath)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.RenameFile(hash, oldPath, newPath);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RenameFolder(string hash, string oldPath, string newPath)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.RenameFolder(hash, oldPath, newPath);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task ResumeTorrents(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.ResumeTorrents(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetApplicationPreferences(UpdatePreferences preferences)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetApplicationPreferences(preferences);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetAutomaticTorrentManagement(bool enable, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetAutomaticTorrentManagement(enable, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetFilePriority(string hash, IEnumerable<int> id, Priority priority)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetFilePriority(hash, id, priority);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetFirstLastPiecePriority(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetFirstLastPiecePriority(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetForceStart(bool value, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetForceStart(value, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetGlobalDownloadLimit(long limit)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetGlobalDownloadLimit(limit);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetGlobalUploadLimit(long limit)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetGlobalDownloadLimit(limit);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetSuperSeeding(bool value, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetSuperSeeding(value, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetTorrentCategory(string category, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetTorrentCategory(category, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetTorrentDownloadLimit(long limit, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetTorrentDownloadLimit(limit, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetTorrentLocation(string location, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetTorrentLocation(location, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetTorrentName(string name, string hash)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetTorrentName(name, hash);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetTorrentShareLimit(float ratioLimit, float seedingTimeLimit, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetTorrentShareLimit(ratioLimit, seedingTimeLimit, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task SetTorrentUploadLimit(long limit, bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.SetTorrentUploadLimit(limit, all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task Shutdown()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.Shutdown();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task ToggleAlternativeSpeedLimits()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.ToggleAlternativeSpeedLimits();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task ToggleSequentialDownload(bool? all = null, params string[] hashes)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.ToggleSequentialDownload(all, hashes);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<int> StartSearch(string pattern, IEnumerable<string> plugins, string category = "all")
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.StartSearch(pattern, plugins, category);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task StopSearch(int id)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.StopSearch(id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<SearchStatus?> GetSearchStatus(int id)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetSearchStatus(id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<SearchStatus>> GetSearchesStatus()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetSearchesStatus();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<SearchResults> GetSearchResults(int id, int? limit = null, int? offset = null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetSearchResults(id, limit, offset);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task DeleteSearch(int id)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.DeleteSearch(id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<IReadOnlyList<SearchPlugin>> GetSearchPlugins()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.GetSearchPlugins();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task InstallSearchPlugins(params string[] sources)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.InstallSearchPlugins(sources);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task UninstallSearchPlugins(params string[] names)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.UninstallSearchPlugins(names);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task EnableSearchPlugins(params string[] names)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.EnableSearchPlugins(names);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task DisableSearchPlugins(params string[] names)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.DisableSearchPlugins(names);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task UpdateSearchPlugins()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _apiClient.UpdateSearchPlugins();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user