mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-10-23 04:52:22 +00:00
Compare commits
70 Commits
v0.0.1-49
...
feature/v5
Author | SHA1 | Date | |
---|---|---|---|
|
b8412bb232 | ||
|
e64a13c7c9 | ||
|
e4ea79a8ed | ||
|
0976b72411 | ||
|
965fbcd010 | ||
|
3d0dbde9f4 | ||
|
5b4fbde7b2 | ||
|
0db0ad4374 | ||
|
c390d83e4d | ||
|
8dd29c238d | ||
|
fca17edfd1 | ||
|
d8535fa262 | ||
|
1c6bfed6ee | ||
|
281caf8026 | ||
|
ff905e7cac | ||
|
cb80dd0d6b | ||
|
9113fb90ee | ||
|
d8b4e932d1 | ||
|
3d0d211d10 | ||
|
7db4f2f78d | ||
|
1f606b4449 | ||
|
88d66b4887 | ||
|
2ad7be1073 | ||
|
300e81345c | ||
|
9d8d84168e | ||
|
bb66b97f45 | ||
|
4824037ba7 | ||
|
1f9b631a36 | ||
|
2c744cd972 | ||
|
b02bb7cfae | ||
|
e4dac8556e | ||
|
a9a8a4eba8 | ||
|
bb524450f0 | ||
|
d4ac79af00 | ||
|
7370d73c59 | ||
|
8796cc0f24 | ||
|
b24ae440d4 | ||
|
bb90ce5216 | ||
|
4eaa46b2b3 | ||
|
1cf9f97187 | ||
|
4f9129fd46 | ||
|
9a9d2c2ee2 | ||
|
736bc46745 | ||
|
23ae19c4c7 | ||
|
603470eb30 | ||
|
27c2406340 | ||
|
4578dcc11f | ||
|
3215fa3936 | ||
|
78e62f31d0 | ||
|
e23842fcb0 | ||
|
411c7f87cc | ||
|
4098f8f5a9 | ||
|
12f81c5978 | ||
|
717738d720 | ||
|
885c34c8cf | ||
|
ef3c68a6aa | ||
|
a29e64fc1b | ||
|
e55955c75e | ||
|
aa80396862 | ||
|
30ced3293c | ||
|
c54f73a517 | ||
|
bad509e40f | ||
|
6a0796ef20 | ||
|
dc4b515763 | ||
|
938702a7b3 | ||
|
6ca1c6edd4 | ||
|
24eb5cf5e9 | ||
|
7d62c9aecf | ||
|
b1e5424f55 | ||
|
66a6c2ca78 |
@@ -2,3 +2,78 @@
|
||||
|
||||
# IDE0290: Use primary constructor
|
||||
csharp_style_prefer_primary_constructors = false
|
||||
|
||||
[*.cs]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.severity = suggestion
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.symbols = private_or_internal_field
|
||||
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.style = begins_with_underscore
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
|
||||
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_underscore.required_prefix = _
|
||||
dotnet_naming_style.begins_with_underscore.required_suffix =
|
||||
dotnet_naming_style.begins_with_underscore.word_separator =
|
||||
dotnet_naming_style.begins_with_underscore.capitalization = camel_case
|
||||
csharp_indent_labels = one_less_than_current
|
||||
|
||||
[*.{cs,vb}]
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
|
4
.github/workflows/dotnet.yml
vendored
4
.github/workflows/dotnet.yml
vendored
@@ -21,12 +21,12 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Install GitVersion
|
||||
uses: gittools/actions/gitversion/setup@v3.0.0
|
||||
with:
|
||||
versionSpec: '6.x'
|
||||
versionSpec: '6.0.0'
|
||||
|
||||
- name: Determine Version
|
||||
id: gitversion
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -360,4 +360,5 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
/output
|
||||
|
@@ -1,24 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="AwesomeAssertions" Version="9.2.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
@@ -21,7 +21,7 @@ namespace Lantean.QBTMud.Test
|
||||
Test2(a => a.Name);
|
||||
}
|
||||
|
||||
private void Test2(Expression<Func<TestClass, object>> expr)
|
||||
private void Test2(Expression<Func<TestClass, object?>> expr)
|
||||
{
|
||||
var body = expr.Body;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Lantean.QBTMud.Test
|
||||
|
||||
var l = Expression.Lambda<Func<TestClass, object>>(convertExpression, expression);
|
||||
|
||||
Expression<Func<TestClass, object>> expr2 = a => a.Name;
|
||||
Expression<Func<TestClass, object?>> expr2 = a => a.Name;
|
||||
|
||||
var x = l.Compile();
|
||||
var res = (long)x(new TestClass { Name = "Name", Value = 12 });
|
||||
@@ -58,9 +58,9 @@ namespace Lantean.QBTMud.Test
|
||||
|
||||
public class TestClass
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
public long Value { get; set; }
|
||||
}
|
||||
|
@@ -12,8 +12,11 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BF1A631-87D7-4039-A701-88C5E0234B63}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
readme.md = readme.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lantean.QBitTorrentClient.Test", "Lantean.QBitTorrentClient.Test\Lantean.QBitTorrentClient.Test.csproj", "{796E865C-7AA6-4BD9-B12F-394801199A75}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -32,6 +35,10 @@ Global
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class AddPeerDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
public IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected HashSet<PeerId> Peers { get; } = [];
|
||||
|
||||
|
@@ -14,7 +14,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> Tags { get; } = [];
|
||||
|
||||
|
@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class AddTorrentFileDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<IBrowserFile> Files { get; set; } = [];
|
||||
|
||||
|
@@ -18,7 +18,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
protected IKeyboardService KeyboardService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Url { get; set; }
|
||||
|
@@ -1,33 +1,50 @@
|
||||
<MudGrid>
|
||||
@using Lantean.QBitTorrentClient.Models
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSwitch Label="Additional Options" @bind-Value="Expanded" LabelPosition="LabelPosition.End" />
|
||||
<MudSwitch Label="Additional Options" @bind-Value="Expanded" LabelPlacement="Placement.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudCollapse Expanded="Expanded">
|
||||
<MudGrid>
|
||||
<MudGrid Class="mt-2">
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Torrent Management Mode" @bind-Value="TorrentManagementMode" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
<MudSelect T="bool" Label="Torrent management mode" Value="@TorrentManagementMode" ValueChanged="@SetTorrentManagementMode" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="@true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudTextField T="string" Label="Save files to location" Value="@SavePath" ValueChanged="@SavePathChanged" Variant="Variant.Outlined" Disabled="@TorrentManagementMode" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6">
|
||||
<FieldSwitch Label="Use incomplete save path" Value="@UseDownloadPath" ValueChanged="@SetUseDownloadPath" Disabled="@TorrentManagementMode" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Save files to location" @bind-Value="SavePath" Variant="Variant.Outlined"></MudTextField>
|
||||
<MudTextField T="string" Label="Incomplete save path" Value="@DownloadPath" ValueChanged="@DownloadPathChanged" Variant="Variant.Outlined" Disabled="@DownloadPathDisabled" />
|
||||
</MudItem>
|
||||
@if (ShowCookieOption)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Cookie" @bind-Value="Cookie" Variant="Variant.Outlined"></MudTextField>
|
||||
<MudTextField Label="Cookie" @bind-Value="Cookie" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Rename" @bind-Value="RenameTorrent" Variant="Variant.Outlined"></MudTextField>
|
||||
<MudTextField Label="Rename" @bind-Value="RenameTorrent" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Category" @bind-Value="Category" Variant="Variant.Outlined">
|
||||
@foreach (var category in Categories)
|
||||
<MudSelect T="string" Label="Category" Value="@Category" ValueChanged="@CategoryChanged" Variant="Variant.Outlined" Clearable="true">
|
||||
<MudSelectItem Value="@string.Empty">None</MudSelectItem>
|
||||
@foreach (var category in CategoryOptions)
|
||||
{
|
||||
<MudSelectItem Value="category">@category</MudSelectItem>
|
||||
<MudSelectItem Value="@category.Name">@category.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Tags" Variant="Variant.Outlined" MultiSelection="true" SelectedValues="@SelectedTags" SelectedValuesChanged="@SelectedTagsChanged" Disabled="@(AvailableTags.Count == 0)">
|
||||
@foreach (var tag in AvailableTags)
|
||||
{
|
||||
<MudSelectItem Value="@tag">@tag</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@@ -38,7 +55,7 @@
|
||||
<FieldSwitch Label="Add to top of queue" @bind-Value="AddToTopOfQueue" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Stop condition" @bind-Value="StopCondition" Variant="Variant.Outlined">
|
||||
<MudSelect T="string" Label="Stop condition" Value="@StopCondition" ValueChanged="@StopConditionChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem Value="@("MetadataReceived")">Metadata received</MudSelectItem>
|
||||
<MudSelectItem Value="@("FilesChecked")">Files checked</MudSelectItem>
|
||||
@@ -47,22 +64,58 @@
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Skip hash check" @bind-Value="SkipHashCheck" />
|
||||
</MudItem>
|
||||
<MudSelect Label="Content layout" @bind-Value="ContentLayout" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder'</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download in sequentual order" @bind-Value="DownloadInSequentialOrder" />
|
||||
<MudSelect T="string" Label="Content layout" Value="@ContentLayout" ValueChanged="@ContentLayoutChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download in sequential order" @bind-Value="DownloadInSequentialOrder" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download first and last pieces first" @bind-Value="DownloadFirstAndLastPiecesFirst" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudNumericField Label="Limit download rate" @bind-Value="DownloadLimit" Variant="Variant.Outlined" Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudNumericField Label="Limit upload rate" @bind-Value="UploadLimit" Variant="Variant.Outlined" Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="ShareLimitMode" Label="Share limit preset" Value="@SelectedShareLimitMode" ValueChanged="@ShareLimitModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@ShareLimitMode.Global">Use global share limit</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitMode.NoLimit">Set no share limit</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitMode.Custom">Set custom share limit</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<FieldSwitch Label="Ratio" Value="@RatioLimitEnabled" ValueChanged="@RatioLimitEnabledChanged" Disabled="@(!IsCustomShareLimit)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="8">
|
||||
<MudNumericField T="float" Label="Ratio limit" Value="@RatioLimit" ValueChanged="@RatioLimitChanged" Disabled="@(!RatioLimitEnabled || !IsCustomShareLimit)" Min="0" Step="0.1f" Format="F2" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<FieldSwitch Label="Total minutes" Value="@SeedingTimeLimitEnabled" ValueChanged="@SeedingTimeLimitEnabledChanged" Disabled="@(!IsCustomShareLimit)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="8">
|
||||
<MudNumericField T="int" Label="Total minutes" Value="@SeedingTimeLimit" ValueChanged="@SeedingTimeLimitChanged" Disabled="@(!SeedingTimeLimitEnabled || !IsCustomShareLimit)" Min="1" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<FieldSwitch Label="Inactive minutes" Value="@InactiveSeedingTimeLimitEnabled" ValueChanged="@InactiveSeedingTimeLimitEnabledChanged" Disabled="@(!IsCustomShareLimit)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="8">
|
||||
<MudNumericField T="int" Label="Inactive minutes" Value="@InactiveSeedingTimeLimit" ValueChanged="@InactiveSeedingTimeLimitChanged" Disabled="@(!InactiveSeedingTimeLimitEnabled || !IsCustomShareLimit)" Min="1" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="ShareLimitAction" Label="Action when limit is reached" Value="@SelectedShareLimitAction" ValueChanged="@ShareLimitActionChanged" Disabled="@(!IsCustomShareLimit)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@ShareLimitAction.Default">Default</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.Stop">Stop torrent</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.Remove">Remove torrent</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.RemoveWithContent">Remove torrent and data</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.EnableSuperSeeding">Enable super seeding</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCollapse>
|
||||
|
@@ -1,4 +1,10 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -6,6 +12,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentOptions
|
||||
{
|
||||
private readonly List<CategoryOption> _categoryOptions = new();
|
||||
private readonly Dictionary<string, CategoryOption> _categoryLookup = new(StringComparer.Ordinal);
|
||||
private string _manualSavePath = string.Empty;
|
||||
private bool _manualUseDownloadPath;
|
||||
private string _manualDownloadPath = string.Empty;
|
||||
private string _defaultSavePath = string.Empty;
|
||||
private string _defaultDownloadPath = string.Empty;
|
||||
private bool _defaultDownloadPathEnabled;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
@@ -16,15 +31,25 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected bool TorrentManagementMode { get; set; }
|
||||
|
||||
protected string SavePath { get; set; } = default!;
|
||||
protected string SavePath { get; set; } = string.Empty;
|
||||
|
||||
protected string DownloadPath { get; set; } = string.Empty;
|
||||
|
||||
protected bool UseDownloadPath { get; set; }
|
||||
|
||||
protected bool DownloadPathDisabled => TorrentManagementMode || !UseDownloadPath;
|
||||
|
||||
protected string? Cookie { get; set; }
|
||||
|
||||
protected string? RenameTorrent { get; set; }
|
||||
|
||||
protected IEnumerable<string> Categories { get; set; } = [];
|
||||
protected IReadOnlyList<CategoryOption> CategoryOptions => _categoryOptions;
|
||||
|
||||
protected string? Category { get; set; }
|
||||
protected string? Category { get; set; } = string.Empty;
|
||||
|
||||
protected List<string> AvailableTags { get; private set; } = [];
|
||||
|
||||
protected HashSet<string> SelectedTags { get; private set; } = new(StringComparer.Ordinal);
|
||||
|
||||
protected bool StartTorrent { get; set; } = true;
|
||||
|
||||
@@ -32,41 +57,264 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected string StopCondition { get; set; } = "None";
|
||||
|
||||
protected bool SkipHashCheck { get; set; } = false;
|
||||
protected bool SkipHashCheck { get; set; }
|
||||
|
||||
protected string ContentLayout { get; set; } = "Original";
|
||||
|
||||
protected bool DownloadInSequentialOrder { get; set; } = false;
|
||||
protected bool DownloadInSequentialOrder { get; set; }
|
||||
|
||||
protected bool DownloadFirstAndLastPiecesFirst { get; set; } = false;
|
||||
protected bool DownloadFirstAndLastPiecesFirst { get; set; }
|
||||
|
||||
protected long DownloadLimit { get; set; }
|
||||
|
||||
protected long UploadLimit { get; set; }
|
||||
|
||||
protected ShareLimitMode SelectedShareLimitMode { get; set; } = ShareLimitMode.Global;
|
||||
|
||||
protected bool RatioLimitEnabled { get; set; }
|
||||
|
||||
protected float RatioLimit { get; set; } = 1.0f;
|
||||
|
||||
protected bool SeedingTimeLimitEnabled { get; set; }
|
||||
|
||||
protected int SeedingTimeLimit { get; set; } = 1440;
|
||||
|
||||
protected bool InactiveSeedingTimeLimitEnabled { get; set; }
|
||||
|
||||
protected int InactiveSeedingTimeLimit { get; set; } = 1440;
|
||||
|
||||
protected ShareLimitAction SelectedShareLimitAction { get; set; } = ShareLimitAction.Default;
|
||||
|
||||
protected bool IsCustomShareLimit => SelectedShareLimitMode == ShareLimitMode.Custom;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var categories = await ApiClient.GetAllCategories();
|
||||
Categories = categories.Select(c => c.Key).ToList();
|
||||
foreach (var (name, value) in categories.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var option = new CategoryOption(name, value.SavePath, value.DownloadPath);
|
||||
_categoryOptions.Add(option);
|
||||
_categoryLookup[name] = option;
|
||||
}
|
||||
|
||||
var tags = await ApiClient.GetAllTags();
|
||||
AvailableTags = tags.OrderBy(t => t, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var preferences = await ApiClient.GetApplicationPreferences();
|
||||
|
||||
TorrentManagementMode = preferences.AutoTmmEnabled;
|
||||
SavePath = preferences.SavePath;
|
||||
StartTorrent = !preferences.StartPausedEnabled;
|
||||
|
||||
_defaultSavePath = preferences.SavePath ?? string.Empty;
|
||||
_manualSavePath = _defaultSavePath;
|
||||
SavePath = _defaultSavePath;
|
||||
|
||||
_defaultDownloadPath = preferences.TempPath ?? string.Empty;
|
||||
_defaultDownloadPathEnabled = preferences.TempPathEnabled;
|
||||
_manualDownloadPath = _defaultDownloadPath;
|
||||
_manualUseDownloadPath = preferences.TempPathEnabled;
|
||||
UseDownloadPath = _manualUseDownloadPath;
|
||||
DownloadPath = UseDownloadPath ? _manualDownloadPath : string.Empty;
|
||||
|
||||
StartTorrent = !preferences.AddStoppedEnabled;
|
||||
AddToTopOfQueue = preferences.AddToTopOfQueue;
|
||||
StopCondition = preferences.TorrentStopCondition;
|
||||
ContentLayout = preferences.TorrentContentLayout;
|
||||
|
||||
RatioLimitEnabled = preferences.MaxRatioEnabled;
|
||||
RatioLimit = preferences.MaxRatio;
|
||||
SeedingTimeLimitEnabled = preferences.MaxSeedingTimeEnabled;
|
||||
if (preferences.MaxSeedingTimeEnabled)
|
||||
{
|
||||
SeedingTimeLimit = preferences.MaxSeedingTime;
|
||||
}
|
||||
InactiveSeedingTimeLimitEnabled = preferences.MaxInactiveSeedingTimeEnabled;
|
||||
if (preferences.MaxInactiveSeedingTimeEnabled)
|
||||
{
|
||||
InactiveSeedingTimeLimit = preferences.MaxInactiveSeedingTime;
|
||||
}
|
||||
SelectedShareLimitAction = MapShareLimitAction(preferences.MaxRatioAct);
|
||||
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
ApplyAutomaticPaths();
|
||||
}
|
||||
}
|
||||
|
||||
protected Task SetTorrentManagementMode(bool value)
|
||||
{
|
||||
if (TorrentManagementMode == value)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
TorrentManagementMode = value;
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
ApplyAutomaticPaths();
|
||||
}
|
||||
else
|
||||
{
|
||||
RestoreManualPaths();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SavePathChanged(string value)
|
||||
{
|
||||
SavePath = value;
|
||||
if (!TorrentManagementMode)
|
||||
{
|
||||
_manualSavePath = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SetUseDownloadPath(bool value)
|
||||
{
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_manualUseDownloadPath = value;
|
||||
UseDownloadPath = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_manualDownloadPath))
|
||||
{
|
||||
_manualDownloadPath = string.IsNullOrWhiteSpace(_defaultDownloadPath) ? string.Empty : _defaultDownloadPath;
|
||||
}
|
||||
|
||||
DownloadPath = _manualDownloadPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
_manualDownloadPath = DownloadPath;
|
||||
DownloadPath = string.Empty;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task DownloadPathChanged(string value)
|
||||
{
|
||||
DownloadPath = value;
|
||||
if (!TorrentManagementMode && UseDownloadPath)
|
||||
{
|
||||
_manualDownloadPath = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task CategoryChanged(string? value)
|
||||
{
|
||||
Category = string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
ApplyAutomaticPaths();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SelectedTagsChanged(IEnumerable<string> tags)
|
||||
{
|
||||
SelectedTags = tags is null
|
||||
? new HashSet<string>(StringComparer.Ordinal)
|
||||
: new HashSet<string>(tags, StringComparer.Ordinal);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task StopConditionChanged(string value)
|
||||
{
|
||||
StopCondition = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task ContentLayoutChanged(string value)
|
||||
{
|
||||
ContentLayout = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task ShareLimitModeChanged(ShareLimitMode mode)
|
||||
{
|
||||
SelectedShareLimitMode = mode;
|
||||
if (mode != ShareLimitMode.Custom)
|
||||
{
|
||||
RatioLimitEnabled = false;
|
||||
SeedingTimeLimitEnabled = false;
|
||||
InactiveSeedingTimeLimitEnabled = false;
|
||||
SelectedShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task RatioLimitEnabledChanged(bool value)
|
||||
{
|
||||
RatioLimitEnabled = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task RatioLimitChanged(float value)
|
||||
{
|
||||
RatioLimit = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SeedingTimeLimitEnabledChanged(bool value)
|
||||
{
|
||||
SeedingTimeLimitEnabled = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SeedingTimeLimitChanged(int value)
|
||||
{
|
||||
SeedingTimeLimit = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task InactiveSeedingTimeLimitEnabledChanged(bool value)
|
||||
{
|
||||
InactiveSeedingTimeLimitEnabled = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task InactiveSeedingTimeLimitChanged(int value)
|
||||
{
|
||||
InactiveSeedingTimeLimit = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task ShareLimitActionChanged(ShareLimitAction value)
|
||||
{
|
||||
SelectedShareLimitAction = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public TorrentOptions GetTorrentOptions()
|
||||
{
|
||||
return new TorrentOptions(
|
||||
var options = new TorrentOptions(
|
||||
TorrentManagementMode,
|
||||
SavePath,
|
||||
_manualSavePath,
|
||||
Cookie,
|
||||
RenameTorrent,
|
||||
Category,
|
||||
string.IsNullOrWhiteSpace(Category) ? null : Category,
|
||||
StartTorrent,
|
||||
AddToTopOfQueue,
|
||||
StopCondition,
|
||||
@@ -76,6 +324,152 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
DownloadFirstAndLastPiecesFirst,
|
||||
DownloadLimit,
|
||||
UploadLimit);
|
||||
|
||||
options.UseDownloadPath = TorrentManagementMode ? null : UseDownloadPath;
|
||||
options.DownloadPath = (!TorrentManagementMode && UseDownloadPath) ? DownloadPath : null;
|
||||
options.Tags = SelectedTags.Count > 0 ? SelectedTags.ToArray() : null;
|
||||
|
||||
switch (SelectedShareLimitMode)
|
||||
{
|
||||
case ShareLimitMode.Global:
|
||||
options.RatioLimit = Limits.GlobalLimit;
|
||||
options.SeedingTimeLimit = Limits.GlobalLimit;
|
||||
options.InactiveSeedingTimeLimit = Limits.GlobalLimit;
|
||||
options.ShareLimitAction = ShareLimitAction.Default.ToString();
|
||||
break;
|
||||
case ShareLimitMode.NoLimit:
|
||||
options.RatioLimit = Limits.NoLimit;
|
||||
options.SeedingTimeLimit = Limits.NoLimit;
|
||||
options.InactiveSeedingTimeLimit = Limits.NoLimit;
|
||||
options.ShareLimitAction = ShareLimitAction.Default.ToString();
|
||||
break;
|
||||
case ShareLimitMode.Custom:
|
||||
options.RatioLimit = RatioLimitEnabled ? RatioLimit : Limits.NoLimit;
|
||||
options.SeedingTimeLimit = SeedingTimeLimitEnabled ? SeedingTimeLimit : Limits.NoLimit;
|
||||
options.InactiveSeedingTimeLimit = InactiveSeedingTimeLimitEnabled ? InactiveSeedingTimeLimit : Limits.NoLimit;
|
||||
options.ShareLimitAction = SelectedShareLimitAction.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private void ApplyAutomaticPaths()
|
||||
{
|
||||
SavePath = ResolveAutomaticSavePath();
|
||||
var (enabled, path) = ResolveAutomaticDownloadPath();
|
||||
UseDownloadPath = enabled;
|
||||
DownloadPath = enabled ? path ?? string.Empty : string.Empty;
|
||||
}
|
||||
|
||||
private void RestoreManualPaths()
|
||||
{
|
||||
SavePath = _manualSavePath;
|
||||
UseDownloadPath = _manualUseDownloadPath;
|
||||
DownloadPath = _manualUseDownloadPath ? _manualDownloadPath : string.Empty;
|
||||
}
|
||||
|
||||
private string ResolveAutomaticSavePath()
|
||||
{
|
||||
var category = GetSelectedCategory();
|
||||
if (category is null)
|
||||
{
|
||||
return _defaultSavePath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(category.SavePath))
|
||||
{
|
||||
return category.SavePath!;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_defaultSavePath) && !string.IsNullOrWhiteSpace(category.Name))
|
||||
{
|
||||
return Path.Combine(_defaultSavePath, category.Name);
|
||||
}
|
||||
|
||||
return _defaultSavePath;
|
||||
}
|
||||
|
||||
private (bool Enabled, string? Path) ResolveAutomaticDownloadPath()
|
||||
{
|
||||
var category = GetSelectedCategory();
|
||||
if (category is null)
|
||||
{
|
||||
if (!_defaultDownloadPathEnabled)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
return (true, _defaultDownloadPath);
|
||||
}
|
||||
|
||||
if (category.DownloadPath is null)
|
||||
{
|
||||
if (!_defaultDownloadPathEnabled)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
return (true, ComposeDefaultDownloadPath(category.Name));
|
||||
}
|
||||
|
||||
if (!category.DownloadPath.Enabled)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(category.DownloadPath.Path))
|
||||
{
|
||||
return (true, category.DownloadPath.Path);
|
||||
}
|
||||
|
||||
return (true, ComposeDefaultDownloadPath(category.Name));
|
||||
}
|
||||
|
||||
private string ComposeDefaultDownloadPath(string categoryName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_defaultDownloadPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(categoryName))
|
||||
{
|
||||
return _defaultDownloadPath;
|
||||
}
|
||||
|
||||
return Path.Combine(_defaultDownloadPath, categoryName);
|
||||
}
|
||||
|
||||
private CategoryOption? GetSelectedCategory()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Category))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _categoryLookup.TryGetValue(Category, out var option) ? option : null;
|
||||
}
|
||||
|
||||
private static ShareLimitAction MapShareLimitAction(int preferenceValue)
|
||||
{
|
||||
return preferenceValue switch
|
||||
{
|
||||
0 => ShareLimitAction.Stop,
|
||||
1 => ShareLimitAction.Remove,
|
||||
2 => ShareLimitAction.RemoveWithContent,
|
||||
3 => ShareLimitAction.EnableSuperSeeding,
|
||||
_ => ShareLimitAction.Default
|
||||
};
|
||||
}
|
||||
|
||||
protected enum ShareLimitMode
|
||||
{
|
||||
Global,
|
||||
NoLimit,
|
||||
Custom
|
||||
}
|
||||
|
||||
protected sealed record CategoryOption(string Name, string? SavePath, DownloadPathOption? DownloadPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class AddTrackerDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> Trackers { get; } = [];
|
||||
|
||||
|
@@ -10,7 +10,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
private string _savePath = string.Empty;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
@@ -5,12 +5,13 @@
|
||||
<DialogContent>
|
||||
<MudCard Class="w-100" Elevation="0">
|
||||
<MudGrid>
|
||||
@for (var i = 0; i < Columns.Count; i++)
|
||||
@for (var i = 0; i < OrderedColumns.Length; i++)
|
||||
{
|
||||
var column = Columns[i];
|
||||
var item = OrderedColumns[i];
|
||||
var column = Columns.First(c => c.Id == item);
|
||||
var index = i;
|
||||
<MudItem xs="7">
|
||||
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPosition="LabelPosition.End" Value="@(SelectedColumnsInternal.Contains(column.Id))" />
|
||||
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPlacement="Placement.End" Value="@(SelectedColumnsInternal.Contains(column.Id))" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudTextField T="string" Value="@(GetValue(column.Width, column.Id))" ValueChanged="@(c => SetWidth(c, column.Id))" Label="Width" Variant="Variant.Text" HelperText="px" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Outlined.WidthNormal" OnAdornmentClick="@(c => SetWidth("auto", column.Id))" />
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class ColumnOptionsDialog<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
@@ -20,10 +20,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
[Parameter]
|
||||
public Dictionary<string, int?> Widths { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public Dictionary<string, int> Order { get; set; } = [];
|
||||
|
||||
protected HashSet<string> SelectedColumnsInternal { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, int?> WidthsInternal { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, int> OrderInternal { get; set; } = [];
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (SelectedColumnsInternal.Count == 0)
|
||||
@@ -51,6 +56,25 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
WidthsInternal[width.Key] = width.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (OrderInternal.Count == 0)
|
||||
{
|
||||
if (Order.Count == 0)
|
||||
{
|
||||
for (int i = 0; i < Columns.Count; i++)
|
||||
{
|
||||
var column = Columns[i];
|
||||
OrderInternal.Add(column.Id, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var order in Order)
|
||||
{
|
||||
OrderInternal[order.Key] = order.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetSelected(bool selected, string id)
|
||||
@@ -101,7 +125,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
return;
|
||||
}
|
||||
|
||||
(Columns[index], Columns[index - 1]) = (Columns[index - 1], Columns[index]);
|
||||
var currentId = OrderInternal.FirstOrDefault(o => o.Value == index).Key;
|
||||
var otherId = OrderInternal.FirstOrDefault(o => o.Value == index - 1).Key;
|
||||
|
||||
OrderInternal[otherId] = index;
|
||||
OrderInternal[currentId] = index - 1;
|
||||
|
||||
//(Columns[index], Columns[index - 1]) = (Columns[index - 1], Columns[index]);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected void MoveDown(int index)
|
||||
@@ -111,7 +143,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
return;
|
||||
}
|
||||
|
||||
(Columns[index], Columns[index + 1]) = (Columns[index + 1], Columns[index]);
|
||||
var currentId = OrderInternal.FirstOrDefault(o => o.Value == index).Key;
|
||||
var otherId = OrderInternal.FirstOrDefault(o => o.Value == index + 1).Key;
|
||||
|
||||
OrderInternal[otherId] = index;
|
||||
OrderInternal[currentId] = index + 1;
|
||||
|
||||
//(Columns[index], Columns[index + 1]) = (Columns[index + 1], Columns[index]);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected string GetValue(int? value, string columnId)
|
||||
@@ -134,6 +174,13 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
return value.Value.ToString();
|
||||
}
|
||||
|
||||
private string[] OrderedColumns => GetOrderedColumns();
|
||||
|
||||
private string[] GetOrderedColumns()
|
||||
{
|
||||
return OrderInternal.OrderBy(x => x.Value).Select(x => x.Key).ToArray();
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
@@ -141,7 +188,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected void Submit()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok((SelectedColumnsInternal, WidthsInternal)));
|
||||
MudDialog.Close(DialogResult.Ok((SelectedColumnsInternal, WidthsInternal, OrderInternal)));
|
||||
}
|
||||
|
||||
protected override Task Submit(KeyboardEvent keyboardEvent)
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class ConfirmDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; } = default!;
|
||||
|
@@ -6,7 +6,7 @@
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Also permanently delete the files" @bind-Value="DeleteFiles" LabelPosition="LabelPosition.End" />
|
||||
<MudCheckBox Label="Also permanently delete the files" @bind-Value="DeleteFiles" LabelPlacement="Placement.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class DeleteDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public int Count { get; set; }
|
||||
|
@@ -6,7 +6,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class ExceptionDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public Exception? Exception { get; set; }
|
||||
|
@@ -11,7 +11,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
private static readonly IReadOnlyList<PropertyInfo> _properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<PropertyInfo> Columns => _properties;
|
||||
|
||||
|
@@ -14,7 +14,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<string> Hashes { get; set; } = [];
|
||||
|
@@ -14,7 +14,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<string> Hashes { get; set; } = [];
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class MultipleFieldDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; } = default!;
|
||||
|
@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class NumericFieldDialog<T> where T : struct, INumber<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
@@ -9,16 +9,15 @@
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Search files" Value="Search" ValueChanged="SearchChanged" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" lg="4">
|
||||
<FieldSwitch Label="Use regular expressions" Value="UseRegex" ValueChanged="UseRegexChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" lg="4">
|
||||
<FieldSwitch Label="Match all occurrences" Value="MatchAllOccurrences" ValueChanged="MatchAllOccurrencesChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" lg="4">
|
||||
<FieldSwitch Label="Case sensitive" Value="CaseSensitive" ValueChanged="CaseSensitiveChanged" />
|
||||
</MudItem>
|
||||
<MudDivider />
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Replacement" Value="Replacement" ValueChanged="ReplacementChanged" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
@@ -29,20 +28,19 @@
|
||||
<MudSelectItem T="AppliesTo" Value="AppliesTo.Extension">Extension</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" lg="4">
|
||||
<FieldSwitch Label="Include files" Value="IncludeFiles" ValueChanged="IncludeFilesChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" lg="4">
|
||||
<FieldSwitch Label="Include folders" Value="IncludeFolders" ValueChanged="IncludeFoldersChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudItem xs="12" lg="4">
|
||||
<MudNumericField T="int" Label="Enumerate files" Value="FileEnumerationStart" ValueChanged="FileEnumerationStartChanged" Min="0" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudDivider />
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="Replace type" Value="ReplaceAll" ValueChanged="ReplaceAllChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem T="bool" Value="true">Replace</MudSelectItem>
|
||||
<MudSelectItem T="bool" Value="false">Replace all</MudSelectItem>
|
||||
<MudSelectItem T="bool" Value="false">Replace</MudSelectItem>
|
||||
<MudSelectItem T="bool" Value="true">Replace all</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
@@ -6,7 +6,6 @@ using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Collections.ObjectModel;
|
||||
using static MudBlazor.Colors;
|
||||
|
||||
namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
@@ -31,7 +30,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
protected ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Hash { get; set; }
|
||||
@@ -427,7 +426,6 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
await LocalStorage.RemoveItemAsync(_preferencesStorageKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -484,19 +482,20 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
ReplaceAll,
|
||||
FileEnumerationStart);
|
||||
|
||||
foreach (var (_, renamedFile) in renamedFiles)
|
||||
foreach (var (_, renamedFile) in renamedFiles.Where(f => !f.Value.IsFolder))
|
||||
{
|
||||
var oldPath = renamedFile.Path + renamedFile.OriginalName;
|
||||
var newPath = renamedFile.Path + renamedFile.NewName;
|
||||
if (renamedFile.IsFolder)
|
||||
{
|
||||
|
||||
await ApiClient.RenameFolder(Hash, oldPath, newPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.RenameFile(Hash, oldPath, newPath);
|
||||
}
|
||||
|
||||
await ApiClient.RenameFile(Hash, oldPath, newPath);
|
||||
}
|
||||
|
||||
foreach (var (_, renamedFile) in renamedFiles.Where(f => f.Value.IsFolder).OrderBy(f => f.Value.Path.Split(Extensions.DirectorySeparator)))
|
||||
{
|
||||
var oldPath = renamedFile.Path + renamedFile.OriginalName;
|
||||
var newPath = renamedFile.Path + renamedFile.NewName;
|
||||
|
||||
await ApiClient.RenameFolder(Hash, oldPath, newPath);
|
||||
}
|
||||
|
||||
MudDialog.Close();
|
||||
|
@@ -53,7 +53,7 @@
|
||||
<MudNumericField T="int" Label="Ignore Subsequent Matches for (0 to Disable)" Value="IgnoreDays" ValueChanged="IgnoreDaysChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Add paused" Value="AddPaused" ValueChanged="AddPausedChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelect T="string" Label="Add stopped" Value="AddStopped" ValueChanged="AddStoppedChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("default")">Use global settings</MudSelectItem>
|
||||
<MudSelectItem Value="@("always")">Always</MudSelectItem>
|
||||
<MudSelectItem Value="@("never")">Never</MudSelectItem>
|
||||
@@ -103,4 +103,4 @@
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
@@ -10,7 +10,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
private readonly List<string> _unsavedRuleNames = [];
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
@@ -114,11 +114,11 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
SelectedRule.IgnoreDays = value;
|
||||
}
|
||||
|
||||
protected string? AddPaused { get; set; }
|
||||
protected string? AddStopped { get; set; }
|
||||
|
||||
protected void AddPausedChanged(string value)
|
||||
protected void AddStoppedChanged(string value)
|
||||
{
|
||||
AddPaused = value;
|
||||
AddStopped = value;
|
||||
switch (value)
|
||||
{
|
||||
case "default":
|
||||
@@ -273,15 +273,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
switch (SelectedRule.TorrentParams.Stopped)
|
||||
{
|
||||
case null:
|
||||
AddPaused = "default";
|
||||
AddStopped = "default";
|
||||
break;
|
||||
|
||||
case true:
|
||||
AddPaused = "always";
|
||||
AddStopped = "always";
|
||||
break;
|
||||
|
||||
case false:
|
||||
AddPaused = "never";
|
||||
AddStopped = "never";
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
@inherits SubmittableDialog
|
||||
@inherits SubmittableDialog
|
||||
@using Lantean.QBitTorrentClient.Models
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@@ -34,10 +35,19 @@
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="InactiveMinutes" ValueChanged="InactiveMinutesChanged" Disabled="@(!(CustomEnabled && InactiveMinutesEnabled))" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="ShareLimitAction" Label="Action when limit is reached" Value="SelectedShareLimitAction" ValueChanged="ShareLimitActionChanged" Disabled="@(!CustomEnabled)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="ShareLimitAction.Default">Default</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.Stop">Stop torrent</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.Remove">Remove torrent</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.RemoveWithContent">Remove torrent and data</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.EnableSuperSeeding">Enable super seeding</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
@@ -1,4 +1,7 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
@@ -8,7 +11,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class ShareRatioDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
@@ -16,6 +19,9 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
[Parameter]
|
||||
public ShareRatioMax? Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ShareRatioMax? CurrentValue { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
@@ -33,6 +39,8 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected int InactiveMinutes { get; set; }
|
||||
|
||||
protected ShareLimitAction SelectedShareLimitAction { get; set; } = ShareLimitAction.Default;
|
||||
|
||||
protected bool CustomEnabled => ShareRatioType == 0;
|
||||
|
||||
protected void RatioEnabledChanged(bool value)
|
||||
@@ -65,40 +73,75 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
InactiveMinutes = value;
|
||||
}
|
||||
|
||||
protected void ShareLimitActionChanged(ShareLimitAction value)
|
||||
{
|
||||
SelectedShareLimitAction = value;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (Value is null || Value.RatioLimit == Limits.GlobalLimit && Value.SeedingTimeLimit == Limits.GlobalLimit && Value.InactiveSeedingTimeLimit == Limits.GlobalLimit)
|
||||
RatioEnabled = false;
|
||||
TotalMinutesEnabled = false;
|
||||
InactiveMinutesEnabled = false;
|
||||
|
||||
var baseline = Value ?? CurrentValue;
|
||||
SelectedShareLimitAction = baseline?.ShareLimitAction ?? ShareLimitAction.Default;
|
||||
|
||||
if (baseline is null || baseline.RatioLimit == Limits.GlobalLimit && baseline.SeedingTimeLimit == Limits.GlobalLimit && baseline.InactiveSeedingTimeLimit == Limits.GlobalLimit)
|
||||
{
|
||||
ShareRatioType = Limits.GlobalLimit;
|
||||
return;
|
||||
}
|
||||
else if (Value.MaxRatio == Limits.NoLimit && Value.MaxSeedingTime == Limits.NoLimit && Value.MaxInactiveSeedingTime == Limits.NoLimit)
|
||||
|
||||
if (baseline.MaxRatio == Limits.NoLimit && baseline.MaxSeedingTime == Limits.NoLimit && baseline.MaxInactiveSeedingTime == Limits.NoLimit)
|
||||
{
|
||||
ShareRatioType = Limits.NoLimit;
|
||||
return;
|
||||
}
|
||||
|
||||
ShareRatioType = 0;
|
||||
|
||||
if (baseline.RatioLimit >= 0)
|
||||
{
|
||||
RatioEnabled = true;
|
||||
Ratio = baseline.RatioLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShareRatioType = 0;
|
||||
if (Value.RatioLimit >= 0)
|
||||
{
|
||||
RatioEnabled = true;
|
||||
Ratio = Value.RatioLimit;
|
||||
}
|
||||
if (Value.SeedingTimeLimit >= 0)
|
||||
{
|
||||
TotalMinutesEnabled = true;
|
||||
TotalMinutes = (int)Value.SeedingTimeLimit;
|
||||
}
|
||||
if (Value.InactiveSeedingTimeLimit >= 0)
|
||||
{
|
||||
InactiveMinutesEnabled = true;
|
||||
InactiveMinutes = (int)Value.InactiveSeedingTimeLimit;
|
||||
}
|
||||
Ratio = 0;
|
||||
}
|
||||
|
||||
if (baseline.SeedingTimeLimit >= 0)
|
||||
{
|
||||
TotalMinutesEnabled = true;
|
||||
TotalMinutes = (int)baseline.SeedingTimeLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
TotalMinutes = 0;
|
||||
}
|
||||
|
||||
if (baseline.InactiveSeedingTimeLimit >= 0)
|
||||
{
|
||||
InactiveMinutesEnabled = true;
|
||||
InactiveMinutes = (int)baseline.InactiveSeedingTimeLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
InactiveMinutes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ShareRatioTypeChanged(int value)
|
||||
{
|
||||
ShareRatioType = value;
|
||||
if (!CustomEnabled)
|
||||
{
|
||||
RatioEnabled = false;
|
||||
TotalMinutesEnabled = false;
|
||||
InactiveMinutesEnabled = false;
|
||||
SelectedShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
@@ -112,16 +155,19 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
if (ShareRatioType == Limits.GlobalLimit)
|
||||
{
|
||||
result.RatioLimit = result.SeedingTimeLimit = result.InactiveSeedingTimeLimit = Limits.GlobalLimit;
|
||||
result.ShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
else if (ShareRatioType == Limits.NoLimit)
|
||||
{
|
||||
result.RatioLimit = result.SeedingTimeLimit = result.InactiveSeedingTimeLimit = Limits.NoLimit;
|
||||
result.ShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.RatioLimit = RatioEnabled ? Ratio : Limits.NoLimit;
|
||||
result.SeedingTimeLimit = TotalMinutesEnabled ? TotalMinutes : Limits.NoLimit;
|
||||
result.InactiveSeedingTimeLimit = InactiveMinutesEnabled ? InactiveMinutes : Limits.NoLimit;
|
||||
result.ShareLimitAction = SelectedShareLimitAction;
|
||||
}
|
||||
MudDialog.Close(DialogResult.Ok(result));
|
||||
}
|
||||
@@ -133,4 +179,4 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class SliderFieldDialog<T> where T : struct, INumber<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class StringFieldDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class SubMenuDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public UIAction? ParentAction { get; set; }
|
||||
|
@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
public partial class TorrentOptionsDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
private IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
|
@@ -1,43 +1,48 @@
|
||||
<ContextMenu @ref="ContextMenu" Dense="true">
|
||||
<MudMenu @ref="ContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileContextMenu">Rename</MudMenuItem>
|
||||
</ContextMenu>
|
||||
</MudMenu>
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileToolbar" title="Rename" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownloadOff" Label="Do Not Download" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Do Not Download">
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownload" Label="Normal Priority" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Download">
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterList" OnClick="ShowFilterDialog" title="Filter" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterListOff" OnClick="RemoveFilter" title="Remove Filter" />
|
||||
<MudSpacer />
|
||||
<MudTextField T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="ContentItem"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Files"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="true"
|
||||
PreSorted="true"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
Class="file-list"
|
||||
/>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar content-panel__toolbar--scroll">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileToolbar" title="Rename" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" title="Choose Columns" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownloadOff" Label="Do Not Download" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Do Not Download">
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownload" Label="Normal Priority" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft" title="Download">
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterList" OnClick="ShowFilterDialog" title="Filter" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterListOff" OnClick="RemoveFilter" title="Remove Filter" />
|
||||
<MudSpacer />
|
||||
<MudTextField T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="ContentItem"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Files"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="true"
|
||||
PreSorted="true"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
Class="file-list content-panel__table"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private RenderFragment<RowContext<ContentItem>> NameColumn
|
||||
|
@@ -20,6 +20,9 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
private static readonly ReadOnlyCollection<ContentItem> EmptyContentItems = new ReadOnlyCollection<ContentItem>(Array.Empty<ContentItem>());
|
||||
private ReadOnlyCollection<ContentItem> _visibleFiles = EmptyContentItems;
|
||||
private bool _filesDirty = true;
|
||||
|
||||
private List<PropertyFilterDefinition<ContentItem>>? _filterDefinitions;
|
||||
private readonly Dictionary<string, RenderFragment<RowContext<ContentItem>>> _columnRenderFragments = [];
|
||||
@@ -65,7 +68,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
private DynamicTable<ContentItem>? Table { get; set; }
|
||||
|
||||
private ContextMenu? ContextMenu { get; set; }
|
||||
private MudMenu? ContextMenu { get; set; }
|
||||
|
||||
public FilesTab()
|
||||
{
|
||||
@@ -102,6 +105,7 @@ namespace Lantean.QBTMud.Components
|
||||
if (_filterDefinitions is null)
|
||||
{
|
||||
Filters = null;
|
||||
MarkFilesDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,11 +117,13 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
|
||||
Filters = filters;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected void RemoveFilter()
|
||||
{
|
||||
Filters = null;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -157,6 +163,7 @@ namespace Lantean.QBTMud.Components
|
||||
protected void SearchTextChanged(string value)
|
||||
{
|
||||
SearchText = value;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected Task TableDataContextMenu(TableDataContextMenuEventArgs<ContentItem> eventArgs)
|
||||
@@ -178,7 +185,9 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.OpenMenuAsync(eventArgs);
|
||||
var normalizedEventArgs = eventArgs.NormalizeForContextMenu();
|
||||
|
||||
await ContextMenu.OpenMenuAsync(normalizedEventArgs);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
@@ -197,6 +206,7 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
var hasUpdates = false;
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
IReadOnlyList<QBitTorrentClient.Models.FileData> files;
|
||||
@@ -213,14 +223,20 @@ namespace Lantean.QBTMud.Components
|
||||
if (FileList is null)
|
||||
{
|
||||
FileList = DataManager.CreateContentsList(files);
|
||||
hasUpdates = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeContentsList(files, FileList);
|
||||
hasUpdates = DataManager.MergeContentsList(files, FileList);
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (hasUpdates)
|
||||
{
|
||||
MarkFilesDirty();
|
||||
PruneSelectionIfMissing();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +262,8 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
var contents = await ApiClient.GetTorrentContents(Hash);
|
||||
FileList = DataManager.CreateContentsList(contents);
|
||||
MarkFilesDirty();
|
||||
PruneSelectionIfMissing();
|
||||
|
||||
var expandedNodes = await LocalStorage.GetItemAsync<HashSet<string>>($"{_expandedNodesStorageKey}.{Hash}");
|
||||
if (expandedNodes is not null)
|
||||
@@ -256,6 +274,8 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
ExpandedNodes.Clear();
|
||||
}
|
||||
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected async Task PriorityValueChanged(ContentItem contentItem, Priority priority)
|
||||
@@ -320,11 +340,13 @@ namespace Lantean.QBTMud.Components
|
||||
protected void SortColumnChanged(string sortColumn)
|
||||
{
|
||||
_sortColumn = sortColumn;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected void SortDirectionChanged(SortDirection sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(ContentItem item)
|
||||
@@ -343,6 +365,7 @@ namespace Lantean.QBTMud.Components
|
||||
ExpandedNodes.Add(contentItem.Name);
|
||||
}
|
||||
|
||||
MarkFilesDirty();
|
||||
await LocalStorage.SetItemAsync($"{_expandedNodesStorageKey}.{Hash}", ExpandedNodes);
|
||||
}
|
||||
|
||||
@@ -368,44 +391,6 @@ namespace Lantean.QBTMud.Components
|
||||
return FileList!.Values.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem folder, int level)
|
||||
{
|
||||
level++;
|
||||
var descendantsKey = folder.GetDescendantsKey(level);
|
||||
|
||||
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey) && f.Level == level).OrderByDirection(_sortDirection, GetSortSelector()))
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var descendants = GetChildren(item, level);
|
||||
// if the filter returns some results then show folder item
|
||||
if (descendants.Any())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
// if the folder is not expanded - don't return children
|
||||
if (!ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// then show children
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterContentItem(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterContentItem(ContentItem item)
|
||||
{
|
||||
if (Filters is not null)
|
||||
@@ -429,38 +414,130 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ContentItem> GetFiles()
|
||||
{
|
||||
if (!_filesDirty)
|
||||
{
|
||||
return _visibleFiles;
|
||||
}
|
||||
|
||||
_visibleFiles = BuildVisibleFiles();
|
||||
_filesDirty = false;
|
||||
|
||||
return _visibleFiles;
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ContentItem> BuildVisibleFiles()
|
||||
{
|
||||
if (FileList is null || FileList.Values.Count == 0)
|
||||
{
|
||||
return new ReadOnlyCollection<ContentItem>([]);
|
||||
return EmptyContentItems;
|
||||
}
|
||||
|
||||
var maxLevel = FileList.Values.Max(f => f.Level);
|
||||
// this is a flat file structure
|
||||
if (maxLevel == 0)
|
||||
var lookup = BuildChildrenLookup();
|
||||
if (!lookup.TryGetValue(string.Empty, out var roots))
|
||||
{
|
||||
return FileList.Values.Where(FilterContentItem).OrderByDirection(_sortDirection, GetSortSelector()).ToList().AsReadOnly();
|
||||
return EmptyContentItems;
|
||||
}
|
||||
|
||||
var list = new List<ContentItem>();
|
||||
var sortSelector = GetSortSelector();
|
||||
var orderedRoots = roots.OrderByDirection(_sortDirection, sortSelector).ToList();
|
||||
var result = new List<ContentItem>(FileList.Values.Count);
|
||||
|
||||
var rootItems = FileList.Values.Where(c => c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
|
||||
foreach (var item in rootItems)
|
||||
foreach (var item in orderedRoots)
|
||||
{
|
||||
list.Add(item);
|
||||
|
||||
if (item.IsFolder && ExpandedNodes.Contains(item.Name))
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var level = 0;
|
||||
var descendants = GetChildren(item, level);
|
||||
foreach (var descendant in descendants)
|
||||
result.Add(item);
|
||||
|
||||
if (!ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
list.Add(descendant);
|
||||
continue;
|
||||
}
|
||||
|
||||
var descendants = GetVisibleDescendants(item, lookup, sortSelector);
|
||||
result.AddRange(descendants);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterContentItem(item))
|
||||
{
|
||||
result.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.AsReadOnly();
|
||||
return new ReadOnlyCollection<ContentItem>(result);
|
||||
}
|
||||
|
||||
private Dictionary<string, List<ContentItem>> BuildChildrenLookup()
|
||||
{
|
||||
var lookup = new Dictionary<string, List<ContentItem>>(FileList!.Count);
|
||||
|
||||
foreach (var item in FileList!.Values)
|
||||
{
|
||||
var parentPath = item.Level == 0 ? string.Empty : item.Name.GetDirectoryPath();
|
||||
if (!lookup.TryGetValue(parentPath, out var children))
|
||||
{
|
||||
children = [];
|
||||
lookup[parentPath] = children;
|
||||
}
|
||||
|
||||
children.Add(item);
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private List<ContentItem> GetVisibleDescendants(ContentItem folder, Dictionary<string, List<ContentItem>> lookup, Func<ContentItem, object?> sortSelector)
|
||||
{
|
||||
if (!lookup.TryGetValue(folder.Name, out var children))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var orderedChildren = children.OrderByDirection(_sortDirection, sortSelector).ToList();
|
||||
var visible = new List<ContentItem>();
|
||||
|
||||
foreach (var child in orderedChildren)
|
||||
{
|
||||
if (child.IsFolder)
|
||||
{
|
||||
var descendants = GetVisibleDescendants(child, lookup, sortSelector);
|
||||
if (descendants.Count != 0)
|
||||
{
|
||||
visible.Add(child);
|
||||
|
||||
if (ExpandedNodes.Contains(child.Name))
|
||||
{
|
||||
visible.AddRange(descendants);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (FilterContentItem(child))
|
||||
{
|
||||
visible.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
private void MarkFilesDirty()
|
||||
{
|
||||
_filesDirty = true;
|
||||
}
|
||||
|
||||
private void PruneSelectionIfMissing()
|
||||
{
|
||||
if (SelectedItem is not null && (FileList is null || !FileList.ContainsKey(SelectedItem.Name)))
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
if (ContextMenuItem is not null && (FileList is null || !FileList.ContainsKey(ContextMenuItem.Name)))
|
||||
{
|
||||
ContextMenuItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadLessThan100PercentAvailability()
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenu @ref="StatusContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
@TorrentControls(_statusType)
|
||||
</ContextMenu>
|
||||
</MudMenu>
|
||||
|
||||
<ContextMenu @ref="CategoryContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenu @ref="CategoryContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddCategory">Add category</MudMenuItem>
|
||||
@if (IsCategoryTarget)
|
||||
{
|
||||
@@ -12,9 +12,9 @@
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove unused categories</MudMenuItem>
|
||||
<MudDivider />
|
||||
@TorrentControls(_categoryType)
|
||||
</ContextMenu>
|
||||
</MudMenu>
|
||||
|
||||
<ContextMenu @ref="TagContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenu @ref="TagContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddTag">Add tag</MudMenuItem>
|
||||
@if (IsTagTarget)
|
||||
{
|
||||
@@ -23,13 +23,13 @@
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedTags">Remove unused tags</MudMenuItem>
|
||||
<MudDivider />
|
||||
@TorrentControls(_tagType)
|
||||
</ContextMenu>
|
||||
</MudMenu>
|
||||
|
||||
<ContextMenu @ref="TrackerContextMenu" Dense="true" AdjustmentY="-60">
|
||||
<MudMenu @ref="TrackerContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove tracker</MudMenuItem>
|
||||
<MudDivider />
|
||||
@TorrentControls(_trackerType)
|
||||
</ContextMenu>
|
||||
</MudMenu>
|
||||
|
||||
<MudNavMenu Dense="true">
|
||||
<MudNavGroup Title="Status" @bind-Expanded="_statusExpanded">
|
||||
@@ -65,9 +65,9 @@
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PlayArrow" IconColor="Color.Success" OnClick="@(e => ResumeTorrents(type))">Resume torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Pause" IconColor="Color.Warning" OnClick="@(e => PauseTorrents(type))">Pause torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PlayArrow" IconColor="Color.Success" OnClick="@(e => StartTorrents(type))">Start torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Stop" IconColor="Color.Warning" OnClick="@(e => StopTorrents(type))">Stop torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="@(e => RemoveTorrents(type))">Remove torrents</MudMenuItem>
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components.UI;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
@@ -69,13 +69,13 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected Dictionary<string, int> Statuses => GetStatuses();
|
||||
|
||||
protected ContextMenu? StatusContextMenu { get; set; }
|
||||
protected MudMenu? StatusContextMenu { get; set; }
|
||||
|
||||
protected ContextMenu? CategoryContextMenu { get; set; }
|
||||
protected MudMenu? CategoryContextMenu { get; set; }
|
||||
|
||||
protected ContextMenu? TagContextMenu { get; set; }
|
||||
protected MudMenu? TagContextMenu { get; set; }
|
||||
|
||||
protected ContextMenu? TrackerContextMenu { get; set; }
|
||||
protected MudMenu? TrackerContextMenu { get; set; }
|
||||
|
||||
protected string? ContextMenuStatus { get; set; }
|
||||
|
||||
@@ -154,7 +154,9 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
ContextMenuStatus = value;
|
||||
|
||||
return StatusContextMenu.OpenMenuAsync(args);
|
||||
var normalizedArgs = args.NormalizeForContextMenu();
|
||||
|
||||
return StatusContextMenu.OpenMenuAsync(normalizedArgs);
|
||||
}
|
||||
|
||||
protected async Task CategoryValueChanged(string value)
|
||||
@@ -192,7 +194,9 @@ namespace Lantean.QBTMud.Components
|
||||
IsCategoryTarget = value != FilterHelper.CATEGORY_ALL && value != FilterHelper.CATEGORY_UNCATEGORIZED;
|
||||
ContextMenuCategory = value;
|
||||
|
||||
return CategoryContextMenu.OpenMenuAsync(args);
|
||||
var normalizedArgs = args.NormalizeForContextMenu();
|
||||
|
||||
return CategoryContextMenu.OpenMenuAsync(normalizedArgs);
|
||||
}
|
||||
|
||||
protected async Task TagValueChanged(string value)
|
||||
@@ -230,7 +234,9 @@ namespace Lantean.QBTMud.Components
|
||||
IsTagTarget = value != FilterHelper.TAG_ALL && value != FilterHelper.TAG_UNTAGGED;
|
||||
ContextMenuTag = value;
|
||||
|
||||
return TagContextMenu.OpenMenuAsync(args);
|
||||
var normalizedArgs = args.NormalizeForContextMenu();
|
||||
|
||||
return TagContextMenu.OpenMenuAsync(normalizedArgs);
|
||||
}
|
||||
|
||||
protected async Task TrackerValueChanged(string value)
|
||||
@@ -267,7 +273,9 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
ContextMenuTracker = value;
|
||||
|
||||
return TrackerContextMenu.OpenMenuAsync(args);
|
||||
var normalizedArgs = args.NormalizeForContextMenu();
|
||||
|
||||
return TrackerContextMenu.OpenMenuAsync(normalizedArgs);
|
||||
}
|
||||
|
||||
protected async Task AddCategory()
|
||||
@@ -345,25 +353,25 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ResumeTorrents(string type)
|
||||
protected async Task StartTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await ApiClient.ResumeTorrents(torrents);
|
||||
await ApiClient.StartTorrents(hashes: torrents.ToArray());
|
||||
}
|
||||
|
||||
protected async Task PauseTorrents(string type)
|
||||
protected async Task StopTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await ApiClient.PauseTorrents(torrents);
|
||||
await ApiClient.StopTorrents(hashes: torrents.ToArray());
|
||||
}
|
||||
|
||||
protected async Task RemoveTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await DialogService.InvokeDeleteTorrentDialog(ApiClient, [.. torrents]);
|
||||
await DialogService.InvokeDeleteTorrentDialog(ApiClient, Preferences?.ConfirmTorrentDeletion == true, [.. torrents]);
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetTags()
|
||||
@@ -470,4 +478,4 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -98,4 +98,4 @@
|
||||
<MudField Label="Comment">@Properties?.Comment</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</MudContainer>
|
@@ -68,6 +68,21 @@
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Confirmation</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Confirm torrent recheck" Value="ConfirmTorrentRecheck" ValueChanged="ConfirmTorrentRecheckChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
|
@@ -16,6 +16,8 @@ namespace Lantean.QBTMud.Components.Options
|
||||
protected int SaveResumeDataInterval { get; private set; }
|
||||
protected int TorrentFileSizeLimit { get; private set; }
|
||||
protected bool RecheckCompletedTorrents { get; private set; }
|
||||
|
||||
protected bool ConfirmTorrentRecheck { get; private set; }
|
||||
protected string? AppInstanceName { get; private set; }
|
||||
protected int RefreshInterval { get; private set; }
|
||||
protected bool ResolvePeerCountries { get; private set; }
|
||||
@@ -97,6 +99,7 @@ namespace Lantean.QBTMud.Components.Options
|
||||
SaveResumeDataInterval = Preferences.SaveResumeDataInterval;
|
||||
TorrentFileSizeLimit = Preferences.TorrentFileSizeLimit / 1024 / 1024;
|
||||
RecheckCompletedTorrents = Preferences.RecheckCompletedTorrents;
|
||||
ConfirmTorrentRecheck = Preferences.ConfirmTorrentRecheck;
|
||||
AppInstanceName = Preferences.AppInstanceName;
|
||||
RefreshInterval = Preferences.RefreshInterval;
|
||||
ResolvePeerCountries = Preferences.ResolvePeerCountries;
|
||||
@@ -209,6 +212,13 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ConfirmTorrentRecheckChanged(bool value)
|
||||
{
|
||||
ConfirmTorrentRecheck = value;
|
||||
UpdatePreferences.ConfirmTorrentRecheck = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AppInstanceNameChanged(string value)
|
||||
{
|
||||
AppInstanceName = value;
|
||||
@@ -608,4 +618,4 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,24 @@
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Transfer List</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Confirm when deleting torrents" Value="ConfirmTorrentDeletion" ValueChanged="ConfirmTorrentDeletionChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Show external IP in status bar" Value="StatusBarExternalIp" ValueChanged="StatusBarExternalIpChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
@@ -71,4 +89,4 @@
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudCard>
|
||||
|
@@ -4,6 +4,10 @@ namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class BehaviourOptions : Options
|
||||
{
|
||||
protected bool ConfirmTorrentDeletion { get; set; }
|
||||
|
||||
protected bool StatusBarExternalIp { get; set; }
|
||||
|
||||
protected bool FileLogEnabled { get; set; }
|
||||
|
||||
protected string? FileLogPath { get; set; }
|
||||
@@ -27,6 +31,8 @@ namespace Lantean.QBTMud.Components.Options
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfirmTorrentDeletion = Preferences.ConfirmTorrentDeletion;
|
||||
StatusBarExternalIp = Preferences.StatusBarExternalIp;
|
||||
FileLogEnabled = Preferences.FileLogEnabled;
|
||||
FileLogPath = Preferences.FileLogPath;
|
||||
FileLogBackupEnabled = Preferences.FileLogBackupEnabled;
|
||||
@@ -39,6 +45,20 @@ namespace Lantean.QBTMud.Components.Options
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task ConfirmTorrentDeletionChanged(bool value)
|
||||
{
|
||||
ConfirmTorrentDeletion = value;
|
||||
UpdatePreferences.ConfirmTorrentDeletion = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StatusBarExternalIpChanged(bool value)
|
||||
{
|
||||
StatusBarExternalIp = value;
|
||||
UpdatePreferences.StatusBarExternalIp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogEnabledChanged(bool value)
|
||||
{
|
||||
FileLogEnabled = value;
|
||||
@@ -96,4 +116,4 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -92,7 +92,9 @@
|
||||
<FieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged" Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined" Validation="MaxRatioValidation" />
|
||||
<MudNumericField T="float" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged"
|
||||
Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined"
|
||||
Validation="MaxRatioValidation" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<FieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" />
|
||||
|
@@ -17,7 +17,7 @@
|
||||
protected int SlowTorrentUlRateThreshold { get; private set; }
|
||||
protected int SlowTorrentInactiveTimer { get; private set; }
|
||||
protected bool MaxRatioEnabled { get; private set; }
|
||||
protected int MaxRatio { get; private set; }
|
||||
protected float MaxRatio { get; private set; }
|
||||
protected bool MaxSeedingTimeEnabled { get; private set; }
|
||||
protected int MaxSeedingTime { get; private set; }
|
||||
protected int MaxRatioAct { get; private set; }
|
||||
@@ -275,7 +275,7 @@
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MaxRatioChanged(int value)
|
||||
protected async Task MaxRatioChanged(float value)
|
||||
{
|
||||
MaxRatio = value;
|
||||
UpdatePreferences.MaxRatio = value;
|
||||
|
@@ -19,7 +19,7 @@
|
||||
<FieldSwitch Label="Add to top of queue" Value="AddToTopOfQueue" ValueChanged="AddToTopOfQueueChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Do not start the download automatically" Value="StartPausedEnabled" ValueChanged="StartPausedEnabledChanged" />
|
||||
<FieldSwitch Label="Do not start the download automatically" Value="AddStoppedEnabled" ValueChanged="AddStoppedEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent stop condition" Value="TorrentStopCondition" ValueChanged="TorrentStopConditionChanged" Variant="Variant.Outlined">
|
||||
@@ -62,7 +62,7 @@
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoDeleteModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoTmmEnabledChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
@@ -306,4 +306,4 @@
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudCard>
|
||||
|
@@ -6,7 +6,7 @@ namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
protected string? TorrentContentLayout { get; set; }
|
||||
protected bool AddToTopOfQueue { get; set; }
|
||||
protected bool StartPausedEnabled { get; set; }
|
||||
protected bool AddStoppedEnabled { get; set; }
|
||||
protected string? TorrentStopCondition { get; set; }
|
||||
protected bool AutoDeleteMode { get; set; }
|
||||
protected bool PreallocateAll { get; set; }
|
||||
@@ -51,7 +51,7 @@ namespace Lantean.QBTMud.Components.Options
|
||||
// when adding a torrent
|
||||
TorrentContentLayout = Preferences.TorrentContentLayout;
|
||||
AddToTopOfQueue = Preferences.AddToTopOfQueue;
|
||||
StartPausedEnabled = Preferences.StartPausedEnabled;
|
||||
AddStoppedEnabled = Preferences.AddStoppedEnabled;
|
||||
TorrentStopCondition = Preferences.TorrentStopCondition;
|
||||
AutoDeleteMode = Preferences.AutoDeleteMode == 1;
|
||||
PreallocateAll = Preferences.PreallocateAll;
|
||||
@@ -116,10 +116,10 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StartPausedEnabledChanged(bool value)
|
||||
protected async Task AddStoppedEnabledChanged(bool value)
|
||||
{
|
||||
StartPausedEnabled = value;
|
||||
UpdatePreferences.StartPausedEnabled = value;
|
||||
AddStoppedEnabled = value;
|
||||
UpdatePreferences.AddStoppedEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
@@ -410,4 +410,4 @@ namespace Lantean.QBTMud.Components.Options
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,30 @@
|
||||
<ContextMenu @ref="ContextMenu" Dense="true">
|
||||
<MudMenu @ref="ContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<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>
|
||||
</MudMenu>
|
||||
|
||||
<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>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<DynamicTable T="Peer"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Peers"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="true"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
Class="details-list" />
|
||||
<div class="content-panel__body">
|
||||
<DynamicTable T="Peer"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Peers"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="true"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
Class="details-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
@@ -52,7 +52,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected Peer? SelectedItem { get; set; }
|
||||
|
||||
protected ContextMenu? ContextMenu { get; set; }
|
||||
protected MudMenu? ContextMenu { get; set; }
|
||||
|
||||
protected DynamicTable<Peer>? Table { get; set; }
|
||||
|
||||
@@ -153,7 +153,9 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.ToggleMenuAsync(eventArgs);
|
||||
var normalizedEventArgs = eventArgs.NormalizeForContextMenu();
|
||||
|
||||
await ContextMenu.OpenMenuAsync(normalizedEventArgs);
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(Peer peer)
|
||||
|
@@ -191,13 +191,13 @@ else if (RenderType == RenderType.MenuItems)
|
||||
|
||||
if (!action.Children.Any())
|
||||
{
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled" Class="icon-menu-dense">
|
||||
@action.Text
|
||||
</MudMenuItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="@(t => SubMenuTouch(action))">
|
||||
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="@(t => SubMenuTouch(action))" Class="icon-menu-dense">
|
||||
<MudMenu ListClass="unselectable" Dense="true" AnchorOrigin="Origin.TopRight" TransformOrigin="Origin.TopLeft" ActivationEvent="MouseEvent.MouseOver" Icon="@Icons.Material.Filled.ArrowDropDown" Ripple="false" Class="sub-menu">
|
||||
<ActivatorContent>
|
||||
@action.Text
|
||||
|
@@ -12,10 +12,7 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
public partial class TorrentActions : IAsyncDisposable
|
||||
{
|
||||
private const int _defaultVersion = 5;
|
||||
|
||||
private bool _disposedValue;
|
||||
private int? _version;
|
||||
|
||||
private List<UIAction>? _actions;
|
||||
|
||||
@@ -40,9 +37,6 @@ namespace Lantean.QBTMud.Components
|
||||
[Inject]
|
||||
protected IKeyboardService KeyboardService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter(Name = "Version")]
|
||||
public string? Version { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<string> Hashes { get; set; } = default!;
|
||||
@@ -63,7 +57,7 @@ namespace Lantean.QBTMud.Components
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public MudDialogInstance? MudDialog { get; set; }
|
||||
public IMudDialogInstance? MudDialog { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public UIAction? ParentAction { get; set; }
|
||||
@@ -74,37 +68,12 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected bool OverlayVisible { get; set; }
|
||||
|
||||
protected int MajorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_version is not null)
|
||||
{
|
||||
return _version.Value;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Version))
|
||||
{
|
||||
return _defaultVersion;
|
||||
}
|
||||
|
||||
if (!System.Version.TryParse(Version.Replace("v", ""), out var version))
|
||||
{
|
||||
return _defaultVersion;
|
||||
}
|
||||
|
||||
_version = version.Major;
|
||||
|
||||
return _version.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_actions =
|
||||
[
|
||||
new("start", "Start", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Resume)),
|
||||
new("pause", "Pause", Icons.Material.Filled.Pause, Color.Warning, CreateCallback(Pause)),
|
||||
new("start", "Start", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Start)),
|
||||
new("stop", "Stop", Icons.Material.Filled.Stop, Color.Warning, CreateCallback(Stop)),
|
||||
new("forceStart", "Force start", Icons.Material.Filled.Forward, Color.Warning, CreateCallback(ForceStart)),
|
||||
new("delete", "Remove", Icons.Material.Filled.Delete, Color.Error, CreateCallback(Remove), separatorBefore: true),
|
||||
new("setLocation", "Set location", Icons.Material.Filled.MyLocation, Color.Info, CreateCallback(SetLocation), separatorBefore: true),
|
||||
@@ -135,6 +104,8 @@ namespace Lantean.QBTMud.Components
|
||||
new("copyHashv2", "Info hash v2", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV2))),
|
||||
new("copyMagnet", "Magnet link", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.MagnetUri))),
|
||||
new("copyId", "Torrent ID", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Hash))),
|
||||
new("copyComment", "Comment", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Comment))),
|
||||
new("copyContentPath", "Content path", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.ContentPath))),
|
||||
]),
|
||||
new("export", "Export", Icons.Material.Filled.SaveAlt, Color.Info, CreateCallback(Export)),
|
||||
];
|
||||
@@ -172,32 +143,16 @@ namespace Lantean.QBTMud.Components
|
||||
OverlayVisible = value;
|
||||
}
|
||||
|
||||
protected async Task Pause()
|
||||
protected async Task Stop()
|
||||
{
|
||||
if (MajorVersion < 5)
|
||||
{
|
||||
await ApiClient.PauseTorrents(Hashes);
|
||||
Snackbar.Add("Torrent paused.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.StopTorrents(Hashes);
|
||||
Snackbar.Add("Torrent stopped.");
|
||||
}
|
||||
await ApiClient.StopTorrents(hashes: Hashes.ToArray());
|
||||
Snackbar.Add("Torrent stopped.");
|
||||
}
|
||||
|
||||
protected async Task Resume()
|
||||
protected async Task Start()
|
||||
{
|
||||
if (MajorVersion < 5)
|
||||
{
|
||||
await ApiClient.ResumeTorrents(Hashes);
|
||||
Snackbar.Add("Torrent resumed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.StartTorrents(Hashes);
|
||||
Snackbar.Add("Torrent started.");
|
||||
}
|
||||
await ApiClient.StartTorrents(hashes: Hashes.ToArray());
|
||||
Snackbar.Add("Torrent started.");
|
||||
}
|
||||
|
||||
protected async Task ForceStart()
|
||||
@@ -208,7 +163,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected async Task Remove()
|
||||
{
|
||||
var deleted = await DialogService.InvokeDeleteTorrentDialog(ApiClient, Hashes.ToArray());
|
||||
var deleted = await DialogService.InvokeDeleteTorrentDialog(ApiClient, Preferences?.ConfirmTorrentDeletion == true, Hashes.ToArray());
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
@@ -304,7 +259,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected async Task ForceRecheck()
|
||||
{
|
||||
await ApiClient.RecheckTorrents(null, Hashes.ToArray());
|
||||
await DialogService.ForceRecheckAsync(ApiClient, Hashes, Preferences?.ConfirmTorrentRecheck == true);
|
||||
}
|
||||
|
||||
protected async Task ForceReannounce()
|
||||
@@ -411,8 +366,8 @@ namespace Lantean.QBTMud.Components
|
||||
var allAreFirstLastPiecePrio = true;
|
||||
var thereAreFirstLastPiecePrio = false;
|
||||
var allAreDownloaded = true;
|
||||
var allArePaused = true;
|
||||
var thereArePaused = false;
|
||||
var allAreStopped = true;
|
||||
var thereAreStopped = false;
|
||||
var allAreForceStart = true;
|
||||
var thereAreForceStart = false;
|
||||
var allAreSuperSeeding = true;
|
||||
@@ -441,7 +396,7 @@ namespace Lantean.QBTMud.Components
|
||||
thereAreFirstLastPiecePrio = true;
|
||||
}
|
||||
|
||||
if (torrent.Progress != 1.0) // not downloaded
|
||||
if (torrent.Progress < 0.999999) // not downloaded
|
||||
{
|
||||
allAreDownloaded = false;
|
||||
}
|
||||
@@ -450,27 +405,13 @@ namespace Lantean.QBTMud.Components
|
||||
allAreSuperSeeding = false;
|
||||
}
|
||||
|
||||
if (MajorVersion < 5)
|
||||
if (torrent.State != "stoppedUP" && torrent.State != "stoppedDL")
|
||||
{
|
||||
if (torrent.State != "pausedUP" && torrent.State != "pausedDL")
|
||||
{
|
||||
allArePaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereArePaused = true;
|
||||
}
|
||||
allAreStopped = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (torrent.State != "stoppedUP" && torrent.State != "stoppedDL")
|
||||
{
|
||||
allArePaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereArePaused = true;
|
||||
}
|
||||
thereAreStopped = true;
|
||||
}
|
||||
|
||||
if (!torrent.ForceStart)
|
||||
@@ -558,7 +499,7 @@ namespace Lantean.QBTMud.Components
|
||||
actionStates["superSeeding"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (allArePaused)
|
||||
if (allAreStopped)
|
||||
{
|
||||
actionStates["pause"] = ActionState.Hidden;
|
||||
}
|
||||
@@ -566,13 +507,11 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
actionStates["forceStart"] = ActionState.Hidden;
|
||||
}
|
||||
else if (!thereArePaused && !thereAreForceStart)
|
||||
else if (!thereAreStopped && !thereAreForceStart)
|
||||
{
|
||||
actionStates["start"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (MajorVersion >= 5)
|
||||
{
|
||||
if (actionStates.TryGetValue("start", out ActionState? startActionState))
|
||||
{
|
||||
startActionState.TextOverride = "Start";
|
||||
@@ -590,7 +529,6 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
actionStates["pause"] = new ActionState { TextOverride = "Stop" };
|
||||
}
|
||||
}
|
||||
|
||||
if (!allAreAutoTmm && thereAreAutoTmm)
|
||||
{
|
||||
@@ -732,4 +670,4 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
MenuItems,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<ContextMenu @ref="ContextMenu" Dense="true">
|
||||
<MudMenu @ref="ContextMenu" Dense="true" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddTracker">Add trackers</MudMenuItem>
|
||||
@if (ContextMenuItem is not null)
|
||||
{
|
||||
@@ -6,27 +6,33 @@
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveTrackerContextMenu">Remove tracker</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.FolderCopy" IconColor="Color.Info" OnClick="CopyTrackerUrlContextMenu">Copy tracker url</MudMenuItem>
|
||||
}
|
||||
</ContextMenu>
|
||||
</MudMenu>
|
||||
|
||||
<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>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.TorrentTracker"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Trackers"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
PreSorted="true"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
Class="file-list" />
|
||||
<div class="content-panel__body">
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.TorrentTracker"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Trackers"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
PreSorted="true"
|
||||
SortDirectionChanged="SortDirectionChanged"
|
||||
SortColumnChanged="SortColumnChanged"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
SelectedItemChanged="SelectedItemChanged"
|
||||
Class="file-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
@@ -52,7 +52,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected TorrentTracker? SelectedItem { get; set; }
|
||||
|
||||
protected ContextMenu? ContextMenu { get; set; }
|
||||
protected MudMenu? ContextMenu { get; set; }
|
||||
|
||||
protected DynamicTable<TorrentTracker>? Table { get; set; }
|
||||
|
||||
@@ -148,7 +148,9 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.ToggleMenuAsync(eventArgs);
|
||||
var normalizedEventArgs = eventArgs.NormalizeForContextMenu();
|
||||
|
||||
await ContextMenu.OpenMenuAsync(normalizedEventArgs);
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(TorrentTracker torrentTracker)
|
||||
@@ -169,7 +171,7 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.AddTrackersToTorrent(Hash, trackers);
|
||||
await ApiClient.AddTrackersToTorrent(trackers, hashes: new[] { Hash });
|
||||
}
|
||||
|
||||
protected Task EditTrackerToolbar()
|
||||
@@ -209,7 +211,7 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.RemoveTrackers(Hash, [tracker.Url]);
|
||||
await ApiClient.RemoveTrackers([tracker.Url], hashes: new[] { Hash });
|
||||
}
|
||||
|
||||
protected Task CopyTrackerUrlToolbar()
|
||||
@@ -301,4 +303,4 @@ namespace Lantean.QBTMud.Components
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +0,0 @@
|
||||
@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 *@
|
||||
<MudPopover tracker="@Id"
|
||||
Open="@_open"
|
||||
Class="unselectable"
|
||||
MaxHeight="@MaxHeight"
|
||||
AnchorOrigin="@AnchorOrigin"
|
||||
TransformOrigin="TransformOrigin"
|
||||
RelativeWidth="@FullWidth"
|
||||
OverflowBehavior="OverflowBehavior.FlipAlways"
|
||||
Style="@_popoverStyle"
|
||||
@ontouchend:preventDefault>
|
||||
<CascadingValue Value="@(FakeMenu)">
|
||||
@if (_showChildren)
|
||||
{
|
||||
<MudList T="object"
|
||||
Class="unselectable"
|
||||
Dense="@Dense">
|
||||
@ChildContent
|
||||
</MudList>
|
||||
}
|
||||
</CascadingValue>
|
||||
</MudPopover>
|
||||
|
||||
<MudOverlay Visible="@(_open)" LockScroll="@LockScroll" AutoClose="true" OnClosed="@CloseMenuAsync" />
|
@@ -1,294 +0,0 @@
|
||||
using Lantean.QBTMud.Interop;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.JSInterop;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
// 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.
|
||||
// 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.
|
||||
|
||||
// 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 _showChildren;
|
||||
private string? _popoverStyle;
|
||||
private string? _id;
|
||||
|
||||
private double _x;
|
||||
private double _y;
|
||||
private bool _isResized = false;
|
||||
|
||||
private const double _diff = 64;
|
||||
|
||||
private string Id
|
||||
{
|
||||
get
|
||||
{
|
||||
_id ??= Guid.NewGuid().ToString();
|
||||
|
||||
return _id;
|
||||
}
|
||||
}
|
||||
|
||||
[Inject]
|
||||
public IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IPopoverService PopoverService { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
public int AdjustmentX { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int AdjustmentY { get; set; }
|
||||
|
||||
protected MudMenu? FakeMenu { get; set; }
|
||||
|
||||
protected void FakeOpenChanged(bool value)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
_open = false;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the menu.
|
||||
/// </summary>
|
||||
/// <param name="args">
|
||||
/// The arguments of the calling mouse/pointer event.
|
||||
/// </param>
|
||||
public async Task OpenMenuAsync(EventArgs args)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// long press on iOS triggers selection, so clear it
|
||||
await JSRuntime.ClearSelection();
|
||||
|
||||
if (args is not LongPressEventArgs)
|
||||
{
|
||||
_showChildren = true;
|
||||
}
|
||||
|
||||
_open = true;
|
||||
_isResized = false;
|
||||
StateHasChanged();
|
||||
|
||||
var (x, y) = GetPositionFromArgs(args);
|
||||
_x = x;
|
||||
_y = y;
|
||||
|
||||
SetPopoverStyle(x, y);
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
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>
|
||||
/// Closes the menu.
|
||||
/// </summary>
|
||||
public Task CloseMenuAsync()
|
||||
{
|
||||
_open = false;
|
||||
_popoverStyle = null;
|
||||
StateHasChanged();
|
||||
|
||||
return OpenChanged.InvokeAsync(_open);
|
||||
}
|
||||
|
||||
private void SetPopoverStyle(double x, double y)
|
||||
{
|
||||
_popoverStyle = $"margin-top: {y.ToPx()}; margin-left: {x.ToPx()};";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the visibility of the menu.
|
||||
/// </summary>
|
||||
public async Task ToggleMenuAsync(EventArgs args)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_open)
|
||||
{
|
||||
await CloseMenuAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await OpenMenuAsync(args);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_isResized)
|
||||
{
|
||||
await DeterminePosition();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeterminePosition()
|
||||
{
|
||||
var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content");
|
||||
double? contextMenuHeight = null;
|
||||
double? contextMenuWidth = null;
|
||||
|
||||
var popoverHolder = PopoverService.ActivePopovers.FirstOrDefault(p => p.UserAttributes.ContainsKey("tracker") && (string?)p.UserAttributes["tracker"] == Id);
|
||||
|
||||
var popoverSize = await JSRuntime.GetBoundingClientRect($"#popovercontent-{popoverHolder?.Id}");
|
||||
if (popoverSize.Height > 0)
|
||||
{
|
||||
contextMenuHeight = popoverSize.Height;
|
||||
contextMenuWidth = popoverSize.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the bottom position of the popover will be rendered off screen
|
||||
if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
|
||||
{
|
||||
// adjust the top of the context menu
|
||||
var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
|
||||
_y -= overshoot;
|
||||
|
||||
if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
|
||||
{
|
||||
MaxHeight = (int)(mainContentSize.Height - _y + _diff);
|
||||
}
|
||||
}
|
||||
|
||||
if (_x + contextMenuWidth.Value > mainContentSize.Width)
|
||||
{
|
||||
var overshoot = Math.Abs(mainContentSize.Width - (_x + contextMenuWidth.Value));
|
||||
_x -= overshoot;
|
||||
}
|
||||
|
||||
SetPopoverStyle(_x, _y);
|
||||
_isResized = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private (double x, double y) GetPositionFromArgs(EventArgs eventArgs)
|
||||
{
|
||||
double x, y;
|
||||
if (eventArgs is MouseEventArgs mouseEventArgs)
|
||||
{
|
||||
x = mouseEventArgs.ClientX;
|
||||
y = mouseEventArgs.ClientY;
|
||||
}
|
||||
else if (eventArgs is LongPressEventArgs longPressEventArgs)
|
||||
{
|
||||
x = longPressEventArgs.ClientX;
|
||||
y = longPressEventArgs.ClientY;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Invalid eventArgs type.");
|
||||
}
|
||||
|
||||
return (x + AdjustmentX, y + AdjustmentY);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<div class="@Classname">
|
||||
<div @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(OnClickHandler)" class="@LinkClassname" @onlongpress="OnLongPressInternal" @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
|
||||
<div @onclick="this.AsNonRenderingEventHandler<MouseEventArgs>(OnClickHandler)" class="@LinkClassname" @onlongpress="OnLongPressInternal" @onlongpress:preventDefault @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
|
||||
@if (!string.IsNullOrEmpty(Icon))
|
||||
{
|
||||
<MudIcon Icon="@Icon" Color="@IconColor" Class="@IconClassname" />
|
||||
|
@@ -59,6 +59,7 @@ namespace Lantean.QBTMud.Components.UI
|
||||
new CssBuilder("mud-nav-link")
|
||||
.AddClass($"mud-nav-link-disabled", Disabled)
|
||||
.AddClass("active", Active)
|
||||
.AddClass("unselectable", OnLongPress.HasDelegate || OnContextMenu.HasDelegate)
|
||||
.Build();
|
||||
|
||||
protected string IconClassname =>
|
||||
|
@@ -13,6 +13,7 @@ namespace Lantean.QBTMud.Components.UI
|
||||
private readonly string _columnSelectionStorageKey = $"DynamicTable{_typeName}.ColumnSelection";
|
||||
private readonly string _columnSortStorageKey = $"DynamicTable{_typeName}.ColumnSort";
|
||||
private readonly string _columnWidthsStorageKey = $"DynamicTable{_typeName}.ColumnWidths";
|
||||
private readonly string _columnOrderStorageKey = $"DynamicTable{_typeName}.ColumnOrder";
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
@@ -80,14 +81,26 @@ namespace Lantean.QBTMud.Components.UI
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = [];
|
||||
|
||||
private static readonly IReadOnlyList<ColumnDefinition<T>> EmptyColumns = Array.Empty<ColumnDefinition<T>>();
|
||||
|
||||
private Dictionary<string, int?> _columnWidths = [];
|
||||
|
||||
private Dictionary<string, int> _columnOrder = [];
|
||||
|
||||
private string? _sortColumn;
|
||||
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
private DateTimeOffset? _suppressRowClickUntil;
|
||||
|
||||
private readonly Dictionary<string, TdExtended> _tds = [];
|
||||
|
||||
private IReadOnlyList<ColumnDefinition<T>> _visibleColumns = EmptyColumns;
|
||||
|
||||
private bool _columnsDirty = true;
|
||||
|
||||
private IEnumerable<ColumnDefinition<T>>? _lastColumnDefinitions;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
HashSet<string> selectedColumns;
|
||||
@@ -106,6 +119,13 @@ namespace Lantean.QBTMud.Components.UI
|
||||
SelectedColumns = selectedColumns;
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedColumns = selectedColumns;
|
||||
}
|
||||
|
||||
_lastColumnDefinitions = ColumnDefinitions;
|
||||
MarkColumnsDirty();
|
||||
|
||||
string? sortColumn;
|
||||
SortDirection sortDirection;
|
||||
@@ -134,11 +154,24 @@ namespace Lantean.QBTMud.Components.UI
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
|
||||
MarkColumnsDirty();
|
||||
|
||||
var storedColumnsWidths = await LocalStorage.GetItemAsync<Dictionary<string, int?>>(_columnWidthsStorageKey);
|
||||
if (storedColumnsWidths is not null)
|
||||
{
|
||||
_columnWidths = storedColumnsWidths;
|
||||
}
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
if (!ReferenceEquals(_lastColumnDefinitions, ColumnDefinitions))
|
||||
{
|
||||
_lastColumnDefinitions = ColumnDefinitions;
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<T>? GetOrderedItems()
|
||||
@@ -162,18 +195,74 @@ namespace Lantean.QBTMud.Components.UI
|
||||
return Items.OrderByDirection(_sortDirection, sortSelector);
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<T>> GetColumns()
|
||||
protected IReadOnlyList<ColumnDefinition<T>> GetColumns()
|
||||
{
|
||||
var filteredColumns = ColumnDefinitions.Where(c => SelectedColumns.Contains(c.Id)).Where(ColumnFilter);
|
||||
foreach (var column in filteredColumns)
|
||||
if (!_columnsDirty)
|
||||
{
|
||||
return _visibleColumns;
|
||||
}
|
||||
|
||||
_visibleColumns = BuildVisibleColumns();
|
||||
_columnsDirty = false;
|
||||
|
||||
return _visibleColumns;
|
||||
}
|
||||
|
||||
private IReadOnlyList<ColumnDefinition<T>> BuildVisibleColumns()
|
||||
{
|
||||
var filteredColumns = ColumnDefinitions
|
||||
.Where(c => SelectedColumns.Contains(c.Id))
|
||||
.Where(ColumnFilter)
|
||||
.ToList();
|
||||
|
||||
if (filteredColumns.Count == 0)
|
||||
{
|
||||
return EmptyColumns;
|
||||
}
|
||||
|
||||
List<ColumnDefinition<T>> orderedColumns;
|
||||
if (_columnOrder.Count == 0)
|
||||
{
|
||||
orderedColumns = filteredColumns;
|
||||
}
|
||||
else
|
||||
{
|
||||
var orderLookup = _columnOrder.OrderBy(entry => entry.Value).ToList();
|
||||
var columnDictionary = filteredColumns.ToDictionary(c => c.Id);
|
||||
orderedColumns = new List<ColumnDefinition<T>>(filteredColumns.Count);
|
||||
|
||||
foreach (var (columnId, _) in orderLookup)
|
||||
{
|
||||
if (!columnDictionary.TryGetValue(columnId, out var column))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
orderedColumns.Add(column);
|
||||
}
|
||||
|
||||
if (orderedColumns.Count != filteredColumns.Count)
|
||||
{
|
||||
var existingIds = new HashSet<string>(orderedColumns.Select(c => c.Id));
|
||||
foreach (var column in filteredColumns)
|
||||
{
|
||||
if (existingIds.Add(column.Id))
|
||||
{
|
||||
orderedColumns.Add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var column in orderedColumns)
|
||||
{
|
||||
if (_columnWidths.TryGetValue(column.Id, out var value))
|
||||
{
|
||||
column.Width = value;
|
||||
}
|
||||
|
||||
yield return column;
|
||||
}
|
||||
|
||||
return orderedColumns;
|
||||
}
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
@@ -199,6 +288,17 @@ namespace Lantean.QBTMud.Components.UI
|
||||
|
||||
protected async Task OnRowClickInternal(TableRowClickEventArgs<T> eventArgs)
|
||||
{
|
||||
if (_suppressRowClickUntil is not null)
|
||||
{
|
||||
if (DateTimeOffset.UtcNow <= _suppressRowClickUntil.Value)
|
||||
{
|
||||
_suppressRowClickUntil = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_suppressRowClickUntil = null;
|
||||
}
|
||||
|
||||
if (eventArgs.Item is null)
|
||||
{
|
||||
return;
|
||||
@@ -274,13 +374,14 @@ namespace Lantean.QBTMud.Components.UI
|
||||
|
||||
protected Task OnLongPressInternal(LongPressEventArgs eventArgs, string columnId, T item)
|
||||
{
|
||||
_suppressRowClickUntil = DateTimeOffset.UtcNow.AddMilliseconds(500);
|
||||
var data = _tds[columnId];
|
||||
return OnTableDataLongPress.InvokeAsync(new TableDataLongPressEventArgs<T>(eventArgs, data, item));
|
||||
}
|
||||
|
||||
public async Task ShowColumnOptionsDialog()
|
||||
{
|
||||
var result = await DialogService.ShowColumnsOptionsDialog(ColumnDefinitions.Where(ColumnFilter).ToList(), SelectedColumns, _columnWidths);
|
||||
var result = await DialogService.ShowColumnsOptionsDialog(ColumnDefinitions.Where(ColumnFilter).ToList(), SelectedColumns, _columnWidths, _columnOrder);
|
||||
|
||||
if (result == default)
|
||||
{
|
||||
@@ -292,18 +393,27 @@ namespace Lantean.QBTMud.Components.UI
|
||||
SelectedColumns = result.SelectedColumns;
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnWidths, result.ColumnWidths))
|
||||
{
|
||||
_columnWidths = result.ColumnWidths;
|
||||
await LocalStorage.SetItemAsync(_columnWidthsStorageKey, _columnWidths);
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnOrder, result.ColumnOrder))
|
||||
{
|
||||
_columnOrder = result.ColumnOrder;
|
||||
await LocalStorage.SetItemAsync(_columnOrderStorageKey, _columnOrder);
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DictionaryEqual(Dictionary<string, int?> left, Dictionary<string, int?> right)
|
||||
private static bool DictionaryEqual<TKey, TValue>(Dictionary<TKey, TValue> left, Dictionary<TKey, TValue> right) where TKey : notnull
|
||||
{
|
||||
return left.Keys.Count == right.Keys.Count && left.Keys.All(k => right.ContainsKey(k) && left[k] == right[k]);
|
||||
return left.Keys.Count == right.Keys.Count && left.Keys.All(k => right.ContainsKey(k) && Equals(left[k], right[k]));
|
||||
}
|
||||
|
||||
private static string? GetColumnStyle(ColumnDefinition<T> column)
|
||||
@@ -338,17 +448,34 @@ namespace Lantean.QBTMud.Components.UI
|
||||
|
||||
if (column.Width.HasValue)
|
||||
{
|
||||
className = $"overflow-cell {className}";
|
||||
className = string.IsNullOrWhiteSpace(className)
|
||||
? "overflow-cell"
|
||||
: $"overflow-cell {className}";
|
||||
}
|
||||
|
||||
if (OnTableDataContextMenu.HasDelegate)
|
||||
{
|
||||
className = $"no-default-context-menu {className}";
|
||||
className = string.IsNullOrWhiteSpace(className)
|
||||
? "no-default-context-menu"
|
||||
: $"no-default-context-menu {className}";
|
||||
}
|
||||
|
||||
if (OnTableDataLongPress.HasDelegate)
|
||||
{
|
||||
className = string.IsNullOrWhiteSpace(className)
|
||||
? "unselectable"
|
||||
: $"unselectable {className}";
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
private void MarkColumnsDirty()
|
||||
{
|
||||
_columnsDirty = true;
|
||||
_visibleColumns = EmptyColumns;
|
||||
}
|
||||
|
||||
private sealed record SortData
|
||||
{
|
||||
public SortData(string sortColumn, SortDirection sortDirection)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@inherits MudTd
|
||||
|
||||
<td data-label="@DataLabel" style="@Style" class="@Classname" @attributes="@UserAttributes" @onlongpress="OnLongPressInternal" @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
|
||||
<td data-label="@DataLabel" style="@Style" class="@Classname" @attributes="@UserAttributes" @onlongpress="OnLongPressInternal" @onlongpress:preventDefault @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
|
||||
@ChildContent
|
||||
</td>
|
||||
</td>
|
@@ -1,6 +1,10 @@
|
||||
<DynamicTable T="Lantean.QBitTorrentClient.Models.WebSeed"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="WebSeeds"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="details-list" />
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__body">
|
||||
<DynamicTable T="Lantean.QBitTorrentClient.Models.WebSeed"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="WebSeeds"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="details-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
@@ -1,4 +1,5 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using ShareLimitAction = Lantean.QBitTorrentClient.Models.ShareLimitAction;
|
||||
using Lantean.QBTMud.Components.Dialogs;
|
||||
using Lantean.QBTMud.Filter;
|
||||
using Lantean.QBTMud.Models;
|
||||
@@ -56,7 +57,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
var addTorrentParams = CreateAddTorrentParams(options);
|
||||
addTorrentParams.Torrents = files;
|
||||
|
||||
await apiClient.AddTorrent(addTorrentParams);
|
||||
_ = await apiClient.AddTorrent(addTorrentParams);
|
||||
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
@@ -74,18 +75,19 @@ namespace Lantean.QBTMud.Helpers
|
||||
{
|
||||
addTorrentParams.ContentLayout = Enum.Parse<QBitTorrentClient.Models.TorrentContentLayout>(options.ContentLayout);
|
||||
}
|
||||
if (string.IsNullOrEmpty(options.Cookie))
|
||||
{
|
||||
addTorrentParams.Cookie = options.Cookie;
|
||||
}
|
||||
addTorrentParams.DownloadLimit = options.DownloadLimit;
|
||||
addTorrentParams.DownloadPath = options.DownloadPath;
|
||||
if (!string.IsNullOrWhiteSpace(options.DownloadPath))
|
||||
{
|
||||
addTorrentParams.DownloadPath = options.DownloadPath;
|
||||
}
|
||||
addTorrentParams.FirstLastPiecePriority = options.DownloadFirstAndLastPiecesFirst;
|
||||
addTorrentParams.InactiveSeedingTimeLimit = options.InactiveSeedingTimeLimit;
|
||||
addTorrentParams.Paused = !options.StartTorrent;
|
||||
addTorrentParams.RatioLimit = options.RatioLimit;
|
||||
addTorrentParams.RenameTorrent = options.RenameTorrent;
|
||||
addTorrentParams.SavePath = options.SavePath;
|
||||
if (!options.TorrentManagementMode)
|
||||
{
|
||||
addTorrentParams.SavePath = options.SavePath;
|
||||
}
|
||||
addTorrentParams.SeedingTimeLimit = options.SeedingTimeLimit;
|
||||
addTorrentParams.SequentialDownload = options.DownloadInSequentialOrder;
|
||||
if (!string.IsNullOrEmpty(options.ShareLimitAction))
|
||||
@@ -100,7 +102,10 @@ namespace Lantean.QBTMud.Helpers
|
||||
addTorrentParams.Stopped = !options.StartTorrent;
|
||||
addTorrentParams.Tags = options.Tags;
|
||||
addTorrentParams.UploadLimit = options.UploadLimit;
|
||||
addTorrentParams.UseDownloadPath = options.UseDownloadPath;
|
||||
if (options.UseDownloadPath.HasValue)
|
||||
{
|
||||
addTorrentParams.UseDownloadPath = options.UseDownloadPath;
|
||||
}
|
||||
return addTorrentParams;
|
||||
}
|
||||
|
||||
@@ -123,10 +128,10 @@ namespace Lantean.QBTMud.Helpers
|
||||
var addTorrentParams = CreateAddTorrentParams(options);
|
||||
addTorrentParams.Urls = options.Urls;
|
||||
|
||||
await apiClient.AddTorrent(addTorrentParams);
|
||||
_ = await apiClient.AddTorrent(addTorrentParams);
|
||||
}
|
||||
|
||||
public static async Task<bool> InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
|
||||
public static async Task<bool> InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, bool confirmTorrentDeletion, params string[] hashes)
|
||||
{
|
||||
if (hashes.Length == 0)
|
||||
{
|
||||
@@ -138,6 +143,12 @@ namespace Lantean.QBTMud.Helpers
|
||||
{ nameof(DeleteDialog.Count), hashes.Length }
|
||||
};
|
||||
|
||||
if (!confirmTorrentDeletion)
|
||||
{
|
||||
await apiClient.DeleteTorrents(hashes: hashes, deleteFiles: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
var reference = await dialogService.ShowAsync<DeleteDialog>($"Remove torrent{(hashes.Length == 1 ? "" : "s")}?", parameters, ConfirmDialogOptions);
|
||||
var dialogResult = await reference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
|
||||
@@ -150,6 +161,28 @@ namespace Lantean.QBTMud.Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task ForceRecheckAsync(this IDialogService dialogService, IApiClient apiClient, IEnumerable<string> hashes, bool confirmTorrentRecheck)
|
||||
{
|
||||
var hashArray = hashes?.ToArray() ?? [];
|
||||
if (hashArray.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirmTorrentRecheck)
|
||||
{
|
||||
var content = $"Are you sure you want to recheck the selected torrent{(hashArray.Length == 1 ? "" : "s")}?";
|
||||
|
||||
var confirmed = await dialogService.ShowConfirmDialog("Force recheck", content);
|
||||
if (!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await apiClient.RecheckTorrents(null, hashArray);
|
||||
}
|
||||
|
||||
public static async Task InvokeDownloadRateDialog(this IDialogService dialogService, IApiClient apiClient, long rate, IEnumerable<string> hashes)
|
||||
{
|
||||
Func<long, string> valueDisplayFunc = v => v == Limits.NoLimit ? "∞" : v.ToString();
|
||||
@@ -217,21 +250,30 @@ namespace Lantean.QBTMud.Helpers
|
||||
|
||||
public static async Task InvokeShareRatioDialog(this IDialogService dialogService, IApiClient apiClient, IEnumerable<Torrent> torrents)
|
||||
{
|
||||
var torrentShareRatios = torrents.Select(t => new ShareRatioMax
|
||||
var torrentList = torrents.ToList();
|
||||
if (torrentList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shareRatioValues = torrentList.Select(t => new ShareRatioMax
|
||||
{
|
||||
InactiveSeedingTimeLimit = t.InactiveSeedingTimeLimit,
|
||||
MaxInactiveSeedingTime = t.InactiveSeedingTimeLimit,
|
||||
MaxInactiveSeedingTime = t.MaxInactiveSeedingTime,
|
||||
MaxRatio = t.MaxRatio,
|
||||
MaxSeedingTime = t.MaxSeedingTime,
|
||||
RatioLimit = t.RatioLimit,
|
||||
SeedingTimeLimit = t.SeedingTimeLimit,
|
||||
});
|
||||
ShareLimitAction = t.ShareLimitAction,
|
||||
}).ToList();
|
||||
|
||||
var torrentsHaveSameShareRatio = torrentShareRatios.Distinct().Count() == 1;
|
||||
var referenceValue = shareRatioValues[0];
|
||||
var torrentsHaveSameShareRatio = shareRatioValues.Distinct().Count() == 1;
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ShareRatioDialog.Value), torrentsHaveSameShareRatio ? torrentShareRatios.FirstOrDefault() : null },
|
||||
{ nameof(ShareRatioDialog.Value), torrentsHaveSameShareRatio ? referenceValue : null },
|
||||
{ nameof(ShareRatioDialog.CurrentValue), referenceValue },
|
||||
};
|
||||
var result = await dialogService.ShowAsync<ShareRatioDialog>("Share ratio", parameters, FormDialogOptions);
|
||||
|
||||
@@ -243,7 +285,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
|
||||
var shareRatio = (ShareRatio)dialogResult.Data;
|
||||
|
||||
await apiClient.SetTorrentShareLimit(shareRatio.RatioLimit, shareRatio.SeedingTimeLimit, shareRatio.InactiveSeedingTimeLimit, null, torrents.Select(t => t.Hash).ToArray());
|
||||
await apiClient.SetTorrentShareLimit(shareRatio.RatioLimit, shareRatio.SeedingTimeLimit, shareRatio.InactiveSeedingTimeLimit, shareRatio.ShareLimitAction ?? ShareLimitAction.Default, hashes: torrentList.Select(t => t.Hash).ToArray());
|
||||
}
|
||||
|
||||
public static async Task InvokeStringFieldDialog(this IDialogService dialogService, string title, string label, string? value, Func<string, Task> onSuccess)
|
||||
@@ -328,13 +370,14 @@ namespace Lantean.QBTMud.Helpers
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static async Task<(HashSet<string> SelectedColumns, Dictionary<string, int?> ColumnWidths)> ShowColumnsOptionsDialog<T>(this IDialogService dialogService, List<ColumnDefinition<T>> columnDefinitions, HashSet<string> selectedColumns, Dictionary<string, int?> widths)
|
||||
public static async Task<(HashSet<string> SelectedColumns, Dictionary<string, int?> ColumnWidths, Dictionary<string, int> ColumnOrder)> ShowColumnsOptionsDialog<T>(this IDialogService dialogService, List<ColumnDefinition<T>> columnDefinitions, HashSet<string> selectedColumns, Dictionary<string, int?> widths, Dictionary<string, int> order)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ColumnOptionsDialog<T>.Columns), columnDefinitions },
|
||||
{ nameof(ColumnOptionsDialog<T>.SelectedColumns), selectedColumns },
|
||||
{ nameof(ColumnOptionsDialog<T>.Widths), widths },
|
||||
{ nameof(ColumnOptionsDialog<T>.Order), order },
|
||||
};
|
||||
|
||||
var reference = await dialogService.ShowAsync<ColumnOptionsDialog<T>>("Column Options", parameters, FormDialogOptions);
|
||||
@@ -344,7 +387,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
return default;
|
||||
}
|
||||
|
||||
return ((HashSet<string>, Dictionary<string, int?>))dialogResult.Data;
|
||||
return ((HashSet<string>, Dictionary<string, int?>, Dictionary<string, int>))dialogResult.Data;
|
||||
}
|
||||
|
||||
public static async Task<bool> ShowConfirmDialog(this IDialogService dialogService, string title, string content)
|
||||
@@ -435,4 +478,6 @@ namespace Lantean.QBTMud.Helpers
|
||||
await dialogService.ShowAsync<SubMenuDialog>(parent.Text, parameters, FormDialogOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using ByteSizeLib;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using MudBlazor;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
@@ -19,28 +20,28 @@ namespace Lantean.QBTMud.Helpers
|
||||
{
|
||||
if (seconds is null)
|
||||
{
|
||||
return "";
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (seconds == 8640000)
|
||||
const long InfiniteEtaSentinelSeconds = 8_640_000; // ~100 days, used by qBittorrent for "infinite" ETA.
|
||||
var value = seconds.Value;
|
||||
|
||||
if (value >= long.MaxValue || value >= TimeSpan.MaxValue.TotalSeconds || value == InfiniteEtaSentinelSeconds)
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
|
||||
if (seconds < 60)
|
||||
if (value <= 0)
|
||||
{
|
||||
return "< 1m";
|
||||
}
|
||||
|
||||
TimeSpan time;
|
||||
try
|
||||
var time = TimeSpan.FromSeconds(value);
|
||||
if (time.TotalMinutes < 1)
|
||||
{
|
||||
time = TimeSpan.FromSeconds(seconds.Value);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
return "∞";
|
||||
return "< 1m";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
@@ -129,7 +130,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
return "";
|
||||
}
|
||||
|
||||
return Size(size);
|
||||
return Size(size, prefix, suffix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -404,8 +405,6 @@ namespace Lantean.QBTMud.Helpers
|
||||
Status.Downloading => (Icons.Material.Filled.Downloading, Color.Success),
|
||||
Status.Seeding => (Icons.Material.Filled.Upload, Color.Info),
|
||||
Status.Completed => (Icons.Material.Filled.Check, Color.Default),
|
||||
Status.Resumed => (Icons.Material.Filled.PlayArrow, Color.Success),
|
||||
Status.Paused => (Icons.Material.Filled.Pause, Color.Default),
|
||||
Status.Stopped => (Icons.Material.Filled.Stop, Color.Default),
|
||||
Status.Active => (Icons.Material.Filled.Sort, Color.Success),
|
||||
Status.Inactive => (Icons.Material.Filled.Sort, Color.Error),
|
||||
@@ -417,5 +416,25 @@ namespace Lantean.QBTMud.Helpers
|
||||
_ => (Icons.Material.Filled.QuestionMark, Color.Inherit),
|
||||
};
|
||||
}
|
||||
|
||||
public static string Bool(bool value, string trueText = "Yes", string falseText = "No")
|
||||
{
|
||||
return value ? trueText : falseText;
|
||||
}
|
||||
|
||||
public static string RatioLimit(float value)
|
||||
{
|
||||
if (value == Limits.GlobalLimit)
|
||||
{
|
||||
return "Global";
|
||||
}
|
||||
|
||||
if (value <= Limits.NoLimit)
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
|
||||
return value.ToString("0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
Lantean.QBTMud/Helpers/EventArgsExtensions.cs
Normal file
40
Lantean.QBTMud/Helpers/EventArgsExtensions.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace Lantean.QBTMud.Helpers
|
||||
{
|
||||
public static class EventArgsExtensions
|
||||
{
|
||||
public static EventArgs NormalizeForContextMenu(this EventArgs eventArgs)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(eventArgs);
|
||||
|
||||
if (eventArgs is LongPressEventArgs longPressEventArgs)
|
||||
{
|
||||
return longPressEventArgs.ToMouseEventArgs();
|
||||
}
|
||||
|
||||
return eventArgs;
|
||||
}
|
||||
|
||||
public static MouseEventArgs ToMouseEventArgs(this LongPressEventArgs longPressEventArgs)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(longPressEventArgs);
|
||||
|
||||
return new MouseEventArgs
|
||||
{
|
||||
Button = 2,
|
||||
Buttons = 2,
|
||||
ClientX = longPressEventArgs.ClientX,
|
||||
ClientY = longPressEventArgs.ClientY,
|
||||
OffsetX = longPressEventArgs.OffsetX,
|
||||
OffsetY = longPressEventArgs.OffsetY,
|
||||
PageX = longPressEventArgs.PageX,
|
||||
PageY = longPressEventArgs.PageY,
|
||||
ScreenX = longPressEventArgs.ScreenX,
|
||||
ScreenY = longPressEventArgs.ScreenY,
|
||||
Type = longPressEventArgs.Type ?? "contextmenu",
|
||||
Detail = -1,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -119,34 +119,35 @@ namespace Lantean.QBTMud.Helpers
|
||||
switch (category)
|
||||
{
|
||||
case CATEGORY_ALL:
|
||||
break;
|
||||
return true;
|
||||
|
||||
case CATEGORY_UNCATEGORIZED:
|
||||
if (!string.IsNullOrEmpty(torrent.Category))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
return true;
|
||||
|
||||
default:
|
||||
if (string.IsNullOrEmpty(torrent.Category))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!useSubcategories)
|
||||
{
|
||||
if (torrent.Category != category)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!torrent.Category.StartsWith(category))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return string.Equals(torrent.Category, category, StringComparison.Ordinal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (string.Equals(torrent.Category, category, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var prefix = string.Concat(category, "/");
|
||||
return torrent.Category.StartsWith(prefix, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool FilterTag(Torrent torrent, string tag)
|
||||
@@ -199,15 +200,8 @@ namespace Lantean.QBTMud.Helpers
|
||||
|
||||
break;
|
||||
|
||||
case Status.Resumed:
|
||||
if (!state.Contains("resumed"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Paused:
|
||||
if (!state.Contains("paused") || !state.Contains("stopped"))
|
||||
case Status.Stopped:
|
||||
if (state != "stoppedDL" && state != "stoppedUP")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -284,4 +278,4 @@ namespace Lantean.QBTMud.Helpers
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
<LangVersion>12</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CompressionEnabled>false</CompressionEnabled>
|
||||
<LangVersion>12</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="ByteSize" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageReference Include="MudBlazor" Version="7.15.0" />
|
||||
<PackageReference Include="MudBlazor.ThemeManager" Version="2.1.0" />
|
||||
<!-- added to fix vuln in dependency -->
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="ByteSize" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.10" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||
<PackageReference Include="MudBlazor" Version="8.13.0" />
|
||||
<PackageReference Include="MudBlazor.ThemeManager" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -1,9 +1,11 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout LoggedInLayout
|
||||
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
|
||||
<TorrentsListNav Torrents="Torrents" SelectedTorrent="@SelectedTorrent" SortDirection="SortDirection" SortColumn="@SortColumn" />
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
@Body
|
||||
</MudMainContent>
|
||||
<div class="app-shell__body">
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false" Class="app-shell__sidebar">
|
||||
<TorrentsListNav Torrents="Torrents" SelectedTorrent="@SelectedTorrent" SortDirection="SortDirection" SortColumn="@SortColumn" />
|
||||
</MudDrawer>
|
||||
<MudMainContent Class="app-shell__main">
|
||||
@Body
|
||||
</MudMainContent>
|
||||
</div>
|
@@ -1,11 +1,13 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout LoggedInLayout
|
||||
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
|
||||
<FiltersNav CategoryChanged="CategoryChanged" StatusChanged="StatusChanged" TagChanged="TagChanged" TrackerChanged="TrackerChanged" />
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</MudMainContent>
|
||||
<div class="app-shell__body">
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false" Class="app-shell__sidebar">
|
||||
<FiltersNav CategoryChanged="CategoryChanged" StatusChanged="StatusChanged" TagChanged="TagChanged" TrackerChanged="TrackerChanged" />
|
||||
</MudDrawer>
|
||||
<MudMainContent Class="app-shell__main">
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</MudMainContent>
|
||||
</div>
|
@@ -10,20 +10,61 @@
|
||||
}
|
||||
|
||||
<CascadingValue Value="Torrents">
|
||||
<CascadingValue Value="MainData">
|
||||
<CascadingValue Value="Preferences">
|
||||
<CascadingValue Value="SortColumnChanged" Name="SortColumnChanged">
|
||||
<CascadingValue Value="SortColumn" Name="SortColumn">
|
||||
<CascadingValue Value="SortDirectionChanged" Name="SortDirectionChanged">
|
||||
<CascadingValue Value="SortDirection" Name="SortDirection">
|
||||
<CascadingValue Value="CategoryChanged" Name="CategoryChanged">
|
||||
<CascadingValue Value="StatusChanged" Name="StatusChanged">
|
||||
<CascadingValue Value="TagChanged" Name="TagChanged">
|
||||
<CascadingValue Value="TrackerChanged" Name="TrackerChanged">
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
<CascadingValue Value="@(MainData?.LostConnection ?? false)" Name="LostConnection">
|
||||
<CascadingValue Value="Version" Name="Version">
|
||||
@Body
|
||||
<CascadingValue Value="_torrentsVersion" Name="TorrentsVersion">
|
||||
<CascadingValue Value="MainData">
|
||||
<CascadingValue Value="Preferences">
|
||||
<CascadingValue Value="SortColumnChanged" Name="SortColumnChanged">
|
||||
<CascadingValue Value="SortColumn" Name="SortColumn">
|
||||
<CascadingValue Value="SortDirectionChanged" Name="SortDirectionChanged">
|
||||
<CascadingValue Value="SortDirection" Name="SortDirection">
|
||||
<CascadingValue Value="CategoryChanged" Name="CategoryChanged">
|
||||
<CascadingValue Value="StatusChanged" Name="StatusChanged">
|
||||
<CascadingValue Value="TagChanged" Name="TagChanged">
|
||||
<CascadingValue Value="TrackerChanged" Name="TrackerChanged">
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
<CascadingValue Value="@(MainData?.LostConnection ?? false)" Name="LostConnection">
|
||||
<CascadingValue Value="Version" Name="Version">
|
||||
<div class="app-shell">
|
||||
@Body
|
||||
<MudAppBar Bottom="true" Elevation="0" Dense="true" Class="app-shell__status-bar">
|
||||
@if (MainData?.LostConnection == true)
|
||||
{
|
||||
<MudText Class="mx-2 mb-1 d-none d-sm-flex" Color="Color.Error">qBittorrent client is not reachable</MudText>
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudText Class="mx-2 mb-1 d-none d-sm-flex">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
|
||||
@{
|
||||
var externalIpLabel = Preferences?.StatusBarExternalIp == true ? BuildExternalIpLabel(MainData?.ServerState) : null;
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(externalIpLabel))
|
||||
{
|
||||
<MudDivider Vertical="true" Class="d-none d-sm-flex" />
|
||||
<MudText Class="mx-2 mb-1 d-none d-sm-flex">@externalIpLabel</MudText>
|
||||
}
|
||||
<MudDivider Vertical="true" Class="d-none d-sm-flex" />
|
||||
<MudText Class="mx-2 mb-1 d-none d-sm-flex">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
|
||||
<MudDivider Vertical="true" Class="d-none d-sm-flex" />
|
||||
@{
|
||||
var (icon, colour) = GetConnectionIcon(MainData?.ServerState.ConnectionStatus);
|
||||
}
|
||||
<MudIcon Class="mx-1 mb-1" Icon="@icon" Color="@colour" Title="@MainData?.ServerState.ConnectionStatus" />
|
||||
<MudDivider Vertical="true" Class="" />
|
||||
<MudIcon Class="mx-1 mb-1" Icon="@Icons.Material.Outlined.Speed" Color="@((MainData?.ServerState.UseAltSpeedLimits ?? false) ? Color.Error : Color.Success)" />
|
||||
<MudDivider Vertical="true" Class="" />
|
||||
<MudIcon Class="ml-1 mb-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowDown" Color="Color.Success" />
|
||||
<MudText Class="mr-1 mb-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoData, "(", ")")
|
||||
</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="ml-1 mb-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowUp" Color="Color.Info" />
|
||||
<MudText Class="mr-1 mb-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoData, "(", ")")
|
||||
</MudText>
|
||||
</MudAppBar>
|
||||
</div>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
@@ -36,34 +77,5 @@
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
<MudAppBar Bottom="true" Fixed="true" Elevation="0" Dense="true" Style="background-color: var(--mud-palette-dark-lighten);">
|
||||
@if (MainData?.LostConnection == true)
|
||||
{
|
||||
<MudText Class="mx-2 mb-1" Color="Color.Error">qBittorrent client is not reachable</MudText>
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudText Class="mx-2 mb-1">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="mx-2 mb-1">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
@{
|
||||
var (icon, colour) = GetConnectionIcon(MainData?.ServerState.ConnectionStatus);
|
||||
}
|
||||
<MudIcon Class="mx-1 mb-1" Icon="@icon" Color="@colour" Title="MainData?.ServerState.ConnectionStatus" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="mx-1 mb-1" Icon="@Icons.Material.Outlined.Speed" Color="@((MainData?.ServerState.UseAltSpeedLimits ?? false) ? Color.Error : Color.Success)" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="ml-1 mb-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowDown" Color="Color.Success" />
|
||||
<MudText Class="mr-1 mb-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoData, "(", ")")
|
||||
</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="ml-1 mb-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowUp" Color="Color.Info" />
|
||||
<MudText Class="mr-1 mb-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoData, "(", ")")
|
||||
</MudText>
|
||||
</MudAppBar>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
@@ -52,22 +52,36 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected IEnumerable<Torrent> Torrents => GetTorrents();
|
||||
protected IReadOnlyList<Torrent> Torrents => GetTorrents();
|
||||
|
||||
protected bool IsAuthenticated { get; set; }
|
||||
|
||||
protected bool LostConnection { get; set; }
|
||||
|
||||
private List<Torrent> GetTorrents()
|
||||
private IReadOnlyList<Torrent> _visibleTorrents = Array.Empty<Torrent>();
|
||||
|
||||
private bool _torrentsDirty = true;
|
||||
private int _torrentsVersion;
|
||||
|
||||
private IReadOnlyList<Torrent> GetTorrents()
|
||||
{
|
||||
if (!_torrentsDirty)
|
||||
{
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
_visibleTorrents = Array.Empty<Torrent>();
|
||||
_torrentsDirty = false;
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
var filterState = new FilterState(Category, Status, Tag, Tracker, MainData.ServerState.UseSubcategories, SearchText);
|
||||
_visibleTorrents = MainData.Torrents.Values.Filter(filterState).ToList();
|
||||
_torrentsDirty = false;
|
||||
|
||||
return MainData.Torrents.Values.Filter(filterState).ToList();
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -84,6 +98,7 @@ namespace Lantean.QBTMud.Layout
|
||||
Version = await ApiClient.GetApplicationVersion();
|
||||
var data = await ApiClient.GetMainData(_requestId);
|
||||
MainData = DataManager.CreateMainData(data);
|
||||
MarkTorrentsDirty();
|
||||
|
||||
_requestId = data.ResponseId;
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
@@ -126,32 +141,51 @@ namespace Lantean.QBTMud.Layout
|
||||
return;
|
||||
}
|
||||
|
||||
var shouldRender = false;
|
||||
|
||||
if (MainData is null || data.FullUpdate)
|
||||
{
|
||||
MainData = DataManager.CreateMainData(data);
|
||||
MarkTorrentsDirty();
|
||||
shouldRender = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeMainData(data, MainData);
|
||||
var dataChanged = DataManager.MergeMainData(data, MainData, out var filterChanged);
|
||||
if (filterChanged)
|
||||
{
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
else if (dataChanged)
|
||||
{
|
||||
IncrementTorrentsVersion();
|
||||
}
|
||||
shouldRender = dataChanged;
|
||||
}
|
||||
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
if (MainData is not null)
|
||||
{
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
}
|
||||
_requestId = data.ResponseId;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (shouldRender)
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected EventCallback<string> CategoryChanged => EventCallback.Factory.Create<string>(this, category => Category = category);
|
||||
protected EventCallback<string> CategoryChanged => EventCallback.Factory.Create<string>(this, OnCategoryChanged);
|
||||
|
||||
protected EventCallback<Status> StatusChanged => EventCallback.Factory.Create<Status>(this, status => Status = status);
|
||||
protected EventCallback<Status> StatusChanged => EventCallback.Factory.Create<Status>(this, OnStatusChanged);
|
||||
|
||||
protected EventCallback<string> TagChanged => EventCallback.Factory.Create<string>(this, tag => Tag = tag);
|
||||
protected EventCallback<string> TagChanged => EventCallback.Factory.Create<string>(this, OnTagChanged);
|
||||
|
||||
protected EventCallback<string> TrackerChanged => EventCallback.Factory.Create<string>(this, tracker => Tracker = tracker);
|
||||
protected EventCallback<string> TrackerChanged => EventCallback.Factory.Create<string>(this, OnTrackerChanged);
|
||||
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, term => SearchText = term);
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, OnSearchTermChanged);
|
||||
|
||||
protected EventCallback<string> SortColumnChanged => EventCallback.Factory.Create<string>(this, columnId => SortColumn = columnId);
|
||||
|
||||
@@ -159,12 +193,107 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
protected static (string, Color) GetConnectionIcon(string? status)
|
||||
{
|
||||
if (status is null)
|
||||
return status switch
|
||||
{
|
||||
return (Icons.Material.Outlined.SignalWifiOff, Color.Warning);
|
||||
"firewalled" => (Icons.Material.Outlined.SignalWifiStatusbarConnectedNoInternet4, Color.Warning),
|
||||
"connected" => (Icons.Material.Outlined.SignalWifi4Bar, Color.Success),
|
||||
_ => (Icons.Material.Outlined.SignalWifiOff, Color.Error),
|
||||
};
|
||||
}
|
||||
|
||||
private static string? BuildExternalIpLabel(ServerState? serverState)
|
||||
{
|
||||
if (serverState is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (Icons.Material.Outlined.SignalWifi4Bar, Color.Success);
|
||||
var v4 = serverState.LastExternalAddressV4;
|
||||
var v6 = serverState.LastExternalAddressV6;
|
||||
var hasV4 = !string.IsNullOrWhiteSpace(v4);
|
||||
var hasV6 = !string.IsNullOrWhiteSpace(v6);
|
||||
|
||||
if (!hasV4 && !hasV6)
|
||||
{
|
||||
return "External IP: N/A";
|
||||
}
|
||||
|
||||
if (hasV4 && hasV6)
|
||||
{
|
||||
return $"External IPs: {v4}, {v6}";
|
||||
}
|
||||
|
||||
var address = hasV4 ? v4 : v6;
|
||||
return $"External IP: {address}";
|
||||
}
|
||||
|
||||
private void OnCategoryChanged(string category)
|
||||
{
|
||||
if (Category == category)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Category = category;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnStatusChanged(Status status)
|
||||
{
|
||||
if (Status == status)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Status = status;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnTagChanged(string tag)
|
||||
{
|
||||
if (Tag == tag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Tag = tag;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnTrackerChanged(string tracker)
|
||||
{
|
||||
if (Tracker == tracker)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Tracker = tracker;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnSearchTermChanged(string term)
|
||||
{
|
||||
if (SearchText == term)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SearchText = term;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void MarkTorrentsDirty()
|
||||
{
|
||||
_torrentsDirty = true;
|
||||
IncrementTorrentsVersion();
|
||||
}
|
||||
|
||||
private void IncrementTorrentsVersion()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
_torrentsVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
@@ -188,4 +317,4 @@ namespace Lantean.QBTMud.Layout
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,16 +20,18 @@
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
|
||||
</MudBadge>
|
||||
}
|
||||
<MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
|
||||
<MudSwitch T="bool" Label="Dark Mode" LabelPlacement="Placement.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
|
||||
<Menu @ref="Menu" />
|
||||
</MudAppBar>
|
||||
<MudDrawer Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
|
||||
<MudDrawer @bind-Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
|
||||
<ErrorDisplay ErrorBoundary="ErrorBoundary" />
|
||||
</MudDrawer>
|
||||
<CascadingValue Value="Theme">
|
||||
<CascadingValue Value="IsDarkMode" Name="IsDarkMode">
|
||||
<CascadingValue Value="Menu">
|
||||
@Body
|
||||
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
@@ -13,9 +13,6 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private IBrowserViewportService BrowserViewportService { get; set; } = default!;
|
||||
|
||||
@@ -78,21 +75,21 @@ namespace Lantean.QBTMud.Layout
|
||||
{
|
||||
IsDarkMode = isDarkMode.Value;
|
||||
}
|
||||
await MudThemeProvider.WatchSystemPreference(OnSystemPreferenceChanged);
|
||||
await MudThemeProvider.WatchSystemDarkModeAsync(OnSystemDarkModeChanged);
|
||||
await BrowserViewportService.SubscribeAsync(this, fireImmediately: true);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
protected Task OnSystemPreferenceChanged(bool value)
|
||||
protected Task OnSystemDarkModeChanged(bool value)
|
||||
{
|
||||
IsDarkMode = value;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
{
|
||||
if (browserViewportEventArgs.Breakpoint == Breakpoint.Sm && DrawerOpen)
|
||||
if (browserViewportEventArgs.Breakpoint <= Breakpoint.Sm)
|
||||
{
|
||||
DrawerOpen = false;
|
||||
}
|
||||
@@ -101,7 +98,17 @@ namespace Lantean.QBTMud.Layout
|
||||
DrawerOpen = true;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
if (ErrorBoundary?.Errors.Count > 0)
|
||||
{
|
||||
ErrorDrawerOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(250);
|
||||
ErrorDrawerOpen = false;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected void ToggleErrorDrawer()
|
||||
|
@@ -1,11 +1,13 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout LoggedInLayout
|
||||
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
|
||||
<MudNavMenu>
|
||||
<ApplicationActions IsMenu="false" Preferences="Preferences" />
|
||||
</MudNavMenu>
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
@Body
|
||||
</MudMainContent>
|
||||
<div class="app-shell__body">
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false" Class="app-shell__sidebar">
|
||||
<MudNavMenu>
|
||||
<ApplicationActions IsMenu="false" Preferences="Preferences" />
|
||||
</MudNavMenu>
|
||||
</MudDrawer>
|
||||
<MudMainContent Class="app-shell__main">
|
||||
@Body
|
||||
</MudMainContent>
|
||||
</div>
|
||||
|
@@ -37,4 +37,4 @@
|
||||
public string? SelectedTorrentHash { get; set; }
|
||||
public bool LostConnection { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,17 @@
|
||||
long uploadRateLimit,
|
||||
bool useAltSpeedLimits,
|
||||
bool useSubcategories,
|
||||
float writeCacheOverload) : base(connectionStatus, dHTNodes, downloadInfoData, downloadInfoSpeed, downloadRateLimit, uploadInfoData, uploadInfoSpeed, uploadRateLimit)
|
||||
float writeCacheOverload,
|
||||
string lastExternalAddressV4,
|
||||
string lastExternalAddressV6) : base(
|
||||
connectionStatus,
|
||||
dHTNodes,
|
||||
downloadInfoData,
|
||||
downloadInfoSpeed,
|
||||
downloadRateLimit,
|
||||
uploadInfoData,
|
||||
uploadInfoSpeed,
|
||||
uploadRateLimit)
|
||||
{
|
||||
AllTimeDownloaded = allTimeDownloaded;
|
||||
AllTimeUploaded = allTimeUploaded;
|
||||
@@ -46,6 +56,8 @@
|
||||
UseAltSpeedLimits = useAltSpeedLimits;
|
||||
UseSubcategories = useSubcategories;
|
||||
WriteCacheOverload = writeCacheOverload;
|
||||
LastExternalAddressV4 = lastExternalAddressV4;
|
||||
LastExternalAddressV6 = lastExternalAddressV6;
|
||||
}
|
||||
|
||||
public ServerState()
|
||||
@@ -85,5 +97,9 @@
|
||||
public bool UseSubcategories { get; set; }
|
||||
|
||||
public float WriteCacheOverload { get; set; }
|
||||
|
||||
public string LastExternalAddressV4 { get; set; } = string.Empty;
|
||||
|
||||
public string LastExternalAddressV6 { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,13 @@
|
||||
namespace Lantean.QBTMud.Models
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public record ShareRatio
|
||||
{
|
||||
public float RatioLimit { get; set; }
|
||||
public float SeedingTimeLimit { get; set; }
|
||||
public float InactiveSeedingTimeLimit { get; set; }
|
||||
public ShareLimitAction? ShareLimitAction { get; set; }
|
||||
}
|
||||
|
||||
public record ShareRatioMax : ShareRatio
|
||||
@@ -13,4 +16,4 @@
|
||||
public float MaxSeedingTime { get; set; }
|
||||
public float MaxInactiveSeedingTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,7 @@
|
||||
Downloading,
|
||||
Seeding,
|
||||
Completed,
|
||||
Resumed,
|
||||
Paused,
|
||||
Stopped,
|
||||
Active,
|
||||
Inactive,
|
||||
Stalled,
|
||||
@@ -15,6 +14,5 @@
|
||||
StalledDownloading,
|
||||
Checking,
|
||||
Errored,
|
||||
Stopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
namespace Lantean.QBTMud.Models
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public class Torrent
|
||||
{
|
||||
@@ -52,7 +57,13 @@
|
||||
long uploadSpeed,
|
||||
long reannounce,
|
||||
float inactiveSeedingTimeLimit,
|
||||
float maxInactiveSeedingTime)
|
||||
float maxInactiveSeedingTime,
|
||||
float popularity,
|
||||
string downloadPath,
|
||||
string rootPath,
|
||||
bool isPrivate,
|
||||
ShareLimitAction shareLimitAction,
|
||||
string comment)
|
||||
{
|
||||
Hash = hash;
|
||||
AddedOn = addedOn;
|
||||
@@ -104,21 +115,31 @@
|
||||
Reannounce = reannounce;
|
||||
InactiveSeedingTimeLimit = inactiveSeedingTimeLimit;
|
||||
MaxInactiveSeedingTime = maxInactiveSeedingTime;
|
||||
Popularity = popularity;
|
||||
DownloadPath = downloadPath;
|
||||
RootPath = rootPath;
|
||||
IsPrivate = isPrivate;
|
||||
ShareLimitAction = shareLimitAction;
|
||||
Comment = comment;
|
||||
}
|
||||
|
||||
protected Torrent()
|
||||
{
|
||||
Hash = "";
|
||||
Category = "";
|
||||
ContentPath = "";
|
||||
InfoHashV1 = "";
|
||||
InfoHashV2 = "";
|
||||
MagnetUri = "";
|
||||
Name = "";
|
||||
SavePath = "";
|
||||
State = "";
|
||||
Tags = [];
|
||||
Tracker = "";
|
||||
Hash = string.Empty;
|
||||
Category = string.Empty;
|
||||
ContentPath = string.Empty;
|
||||
InfoHashV1 = string.Empty;
|
||||
InfoHashV2 = string.Empty;
|
||||
MagnetUri = string.Empty;
|
||||
Name = string.Empty;
|
||||
SavePath = string.Empty;
|
||||
DownloadPath = string.Empty;
|
||||
RootPath = string.Empty;
|
||||
State = string.Empty;
|
||||
Tags = new List<string>();
|
||||
Tracker = string.Empty;
|
||||
ShareLimitAction = ShareLimitAction.Default;
|
||||
Comment = string.Empty;
|
||||
}
|
||||
|
||||
public string Hash { get; }
|
||||
@@ -183,8 +204,14 @@
|
||||
|
||||
public float RatioLimit { get; set; }
|
||||
|
||||
public float Popularity { get; set; }
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
public string DownloadPath { get; set; }
|
||||
|
||||
public string RootPath { get; set; }
|
||||
|
||||
public long SeedingTime { get; set; }
|
||||
|
||||
public int SeedingTimeLimit { get; set; }
|
||||
@@ -221,6 +248,12 @@
|
||||
|
||||
public float MaxInactiveSeedingTime { get; set; }
|
||||
|
||||
public bool IsPrivate { get; set; }
|
||||
|
||||
public ShareLimitAction ShareLimitAction { get; set; }
|
||||
|
||||
public string Comment { get; set; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is null)
|
||||
@@ -241,4 +274,4 @@
|
||||
return Hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Models
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public record TorrentOptions
|
||||
{
|
||||
|
@@ -1,107 +1,118 @@
|
||||
@page "/about"
|
||||
@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" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">About</MudText>
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">About</MudText>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true">
|
||||
<MudTabPanel Text="About">
|
||||
<div class="d-flex gap-4">
|
||||
<MudImage Src="images/mascot.png" Alt="Mascot" Class="ma-6" Fluid ObjectFit="ObjectFit.None" ObjectPosition="ObjectPosition.LeftTop" Height="162" Width="94" />
|
||||
<MudGrid Class="mx-0 mt-0 mb-3">
|
||||
<MudItem xs="12">
|
||||
<div class="d-flex gap-3">
|
||||
<MudImage Src="images/qbittorrent32.png" Fluid ObjectFit="ObjectFit.None" Alt="QBT" Height="32" Width="32" /><MudText Typo="Typo.h6">qBittorrent @QBittorrentVersion</MudText>
|
||||
<div class="content-panel__body">
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true">
|
||||
<MudTabPanel Text="About">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 content-panel__container options-tab-contents">
|
||||
<MudGrid Class="mt-0 mb-4">
|
||||
<MudItem xs="12" sm="3" md="2" lg="2" xl="1" Class="d-flex justify-center">
|
||||
<MudImage Src="images/mascot.png" Alt="Mascot" Class="ma-6"
|
||||
Fluid ObjectFit="ObjectFit.None" ObjectPosition="ObjectPosition.LeftTop"
|
||||
Height="162" Width="94" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="9" md="10" lg="10" xl="11">
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<div class="d-flex gap-3 align-items-center">
|
||||
<MudImage Src="images/qbittorrent32.png" Fluid ObjectFit="ObjectFit.None"
|
||||
Alt="QBT" Height="32" Width="32" />
|
||||
<MudText Typo="Typo.h6">qBittorrent @QBittorrentVersion</MudText>
|
||||
</div>
|
||||
|
||||
<MudText Typo="Typo.body1">
|
||||
An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1">Copyright © 2006-2024 The qBittorrent project</MudText>
|
||||
|
||||
<div class="d-flex flex-wrap">
|
||||
<MudText Typo="Typo.body1" Class="fw-bold">Home Page: </MudText>
|
||||
<MudLink Href="https://www.qbittorrent.org" Target="_blank" Class="ms-2">
|
||||
qbittorrent.org
|
||||
</MudLink>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap">
|
||||
<MudText Typo="Typo.body1" Class="fw-bold">Bug Tracker: </MudText>
|
||||
<MudLink Href="https://bugs.qbittorrent.org" Target="_blank" Class="ms-2">
|
||||
bugs.qbittorrent.org
|
||||
</MudLink>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap">
|
||||
<MudText Typo="Typo.body1" Class="fw-bold">Forum: </MudText>
|
||||
<MudLink Href="https://forum.qbittorrent.org" Target="_blank" Class="ms-2">
|
||||
forum.qbittorrent.org
|
||||
</MudLink>
|
||||
</div>
|
||||
</div>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.body1">An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.</MudText>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.body1">Copyright © 2006-2024 The qBittorrent project</MudText>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Home Page</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudLink Href="https://www.qbittorrent.org" Target="https://www.qbittorrent.org">https://www.qbittorrent.org</MudLink>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Bug Tracker</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudLink Href="https://bugs.qbittorrent.org" Target="https://bugs.qbittorrent.org">https://bugs.qbittorrent.org</MudLink>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Forum</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudLink Href="https://forum.qbittorrent.org" Target="https://forum.qbittorrent.org">https://forum.qbittorrent.org</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudContainer>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Authors">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3">
|
||||
<MudText Typo="Typo.body1" Class="py-1">Current maintainer</MudText>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 options-tab-contents">
|
||||
<MudText Typo="Typo.h5" Class="py-1">Current maintainer</MudText>
|
||||
|
||||
<MudGrid Class="mt-0 mb-4">
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Name</MudText>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.h6">Name</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudText Typo="Typo.body1">Sledgehammer999</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Nationality</MudText>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.h6">Nationality</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudText Typo="Typo.body1">Greece</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">E-mail</MudText>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.h6">E-mail</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudLink Href="mailto:sledgehammer999@qbittorrent.org">sledgehammer999@qbittorrent.org</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="py-1">Original author</MudText>
|
||||
<MudText Typo="Typo.h5" Class="py-1">Original author</MudText>
|
||||
<MudGrid Class="mt-0 mb-4">
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Name</MudText>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.h6">Name</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudText Typo="Typo.body1">Christophe Dumez</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">Nationality</MudText>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.h6">Nationality</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudText Typo="Typo.body1">France</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudText Typo="Typo.body1">E-mail</MudText>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.h6">E-mail</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudLink Href="mailto:chris@qbittorrent.org">chris@qbittorrent.org</MudLink>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Special Thanks">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 options-tab-contents">
|
||||
<MudText Typo="Typo.body1" Class="py-1">I would first like to thank sourceforge.net for hosting qBittorrent project and for their support.</MudText>
|
||||
<MudText Typo="Typo.body1" Class="py-1">I am pleased that people from all over the world are contributing to qBittorrent: Ishan Arora (India), Arnaud Demaizière (France) and Stephanos Antaris (Greece). Their help is greatly appreciated</MudText>
|
||||
<MudText Typo="Typo.body1" Class="py-1">I also want to thank Στέφανος Αντάρης (santaris@csd.auth.gr) and Mirco Chinelli (infinity89@fastwebmail.it) for working on Mac OS X packaging.</MudText>
|
||||
@@ -111,14 +122,14 @@
|
||||
</MudContainer>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Translators">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 options-tab-contents">
|
||||
<MudText Typo="Typo.body1" Class="py-1">
|
||||
I would like to thank the people who volunteered to Circle qBittorrent.<br>
|
||||
Most of them Circled via <MudLink Target="https://www.transifex.com/sledgehammer999/qbittorrent/" Href="https://www.transifex.com/sledgehammer999/qbittorrent/">Transifex</MudLink> and some of them are mentioned below:<br>
|
||||
(the list might not be up to date)
|
||||
</MudText>
|
||||
<MudList T="string" ReadOnly>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Circle" IconColor="Color.Info"><u>Arabic:</u> SDERAWI (abz8868@msn.com), sn51234 (nesseyan@gmail.com) and Ibrahim Saed ibraheem_alex(Transifex)</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Arabic:</u> SDERAWI (abz8868@msn.com), sn51234 (nesseyan@gmail.com) and Ibrahim Saed ibraheem_alex(Transifex)</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Armenian:</u> Hrant Ohanyan (hrantohanyan@mail.am)</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Basque:</u> Xabier Aramendi (azpidatziak@gmail.com)</MudListItem>
|
||||
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Belarusian:</u> Mihas Varantsou (meequz@gmail.com)</MudListItem>
|
||||
@@ -161,7 +172,7 @@
|
||||
</MudContainer>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Licence">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 options-tab-contents">
|
||||
<MudText Typo="Typo.body1" Class="py-1">
|
||||
The qBittorrent source code is licensed under the GNU General Public License, version 2 or (at your option) any later version (GPLv2+).
|
||||
However, this binary distribution is licensed under GNU General Public License, version 3 or (at your option) any later version (GPLv3+),
|
||||
@@ -1054,42 +1065,42 @@
|
||||
</MudContainer>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Software Used">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 mb-3">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3 mb-3 options-tab-contents">
|
||||
<MudText Typo="Typo.body1" Class="py-1">qBittorrent was built with the following libraries:</MudText>
|
||||
|
||||
<MudGrid Class="mt-1 mb-4">
|
||||
<MudItem xs="2">
|
||||
<MudItem xs="3">
|
||||
<MudText Typo="Typo.body1">Qt</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="9">
|
||||
<MudText Typo="Typo.body1">@QtVersion</MudText>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudItem xs="3">
|
||||
<MudText Typo="Typo.body1">Libtorrent</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="9">
|
||||
<MudText Typo="Typo.body1">@LibtorrentVersion</MudText>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudItem xs="3">
|
||||
<MudText Typo="Typo.body1">Boost</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="9">
|
||||
<MudText Typo="Typo.body1">@BoostVersion</MudText>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudItem xs="3">
|
||||
<MudText Typo="Typo.body1">OpenSSL</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="9">
|
||||
<MudText Typo="Typo.body1">@OpensslVersion</MudText>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="2">
|
||||
<MudItem xs="3">
|
||||
<MudText Typo="Typo.body1">zlib</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="9">
|
||||
<MudText Typo="Typo.body1">@ZlibVersion</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
@@ -1097,4 +1108,6 @@
|
||||
<MudText Typo="Typo.body1" Class="py-1">The free IP to Country Lite database by DB-IP is used for resolving the countries of peers. The database is licensed under the Creative Commons Attribution 4.0 International License (<MudLink Target="https://db-ip.com/" Href="https://db-ip.com/" rel="noopener ">https://db-ip.com/</MudLink>)</MudText>
|
||||
</MudContainer>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</MudTabs>
|
||||
</div>
|
||||
</div>
|
@@ -1,36 +1,41 @@
|
||||
@page "/blocks"
|
||||
@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" />
|
||||
<MudText Class="pl-5 no-wrap">Blocked IPs</MudText>
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<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" />
|
||||
<MudText Class="pl-5 no-wrap">Blocked IPs</MudText>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<EditForm Model="Model" OnSubmit="Submit">
|
||||
<MudGrid>
|
||||
<MudItem md="10">
|
||||
<MudTextField T="string" Label="Criteria" @bind-Value="Model.Criteria" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem md="2">
|
||||
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">Filter</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</EditForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<EditForm Model="Model" OnSubmit="Submit">
|
||||
<MudGrid>
|
||||
<MudItem md="10">
|
||||
<MudTextField T="string" Label="Criteria" @bind-Value="Model.Criteria" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem md="2">
|
||||
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">Filter</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</EditForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.PeerLog"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
RowClassFunc="RowClass"
|
||||
Class="search-list" />
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.PeerLog"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
RowClassFunc="RowClass"
|
||||
Class="search-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
@@ -1,24 +1,30 @@
|
||||
@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" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">Categories</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.PlaylistAdd" OnClick="AddCategory" title="Add Category" />
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">Categories</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.PlaylistAdd" OnClick="AddCategory" title="Add Category" />
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="Category"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="details-list" />
|
||||
<div class="content-panel__body">
|
||||
<DynamicTable @ref="Table"
|
||||
T="Category"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="details-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private RenderFragment<RowContext<Category>> ActionsColumn
|
||||
|
@@ -1,39 +1,45 @@
|
||||
@page "/details/{hash}"
|
||||
@layout DetailsLayout
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
@if (Hash is not null)
|
||||
{
|
||||
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="@([Hash])" Torrents="MainData.Torrents" Preferences="Preferences" />
|
||||
}
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="pl-5 no-wrap">@Name</MudText>
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar content-panel__toolbar--scroll">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
@if (Hash is not null)
|
||||
{
|
||||
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="@([Hash])" Torrents="MainData.Torrents" Preferences="Preferences" />
|
||||
}
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="pl-5 no-wrap">@Name</MudText>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
@if (ShowTabs)
|
||||
{
|
||||
<CascadingValue Value="RefreshInterval" Name="RefreshInterval">
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" KeepPanelsAlive="true" Border="true">
|
||||
<MudTabPanel Text="General">
|
||||
<GeneralTab Hash="@Hash" Active="@(ActiveTab == 0)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Trackers">
|
||||
<TrackersTab Hash="@Hash" Active="@(ActiveTab == 1)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Peers">
|
||||
<PeersTab Hash="@Hash" Active="@(ActiveTab == 2)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="HTTP Sources">
|
||||
<WebSeedsTab Hash="@Hash" Active="@(ActiveTab == 3)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Content">
|
||||
<FilesTab Hash="@Hash" Active="@(ActiveTab == 4)" />
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</CascadingValue>
|
||||
}
|
||||
<div class="content-panel__body">
|
||||
@if (ShowTabs)
|
||||
{
|
||||
<CascadingValue Value="RefreshInterval" Name="RefreshInterval">
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" KeepPanelsAlive="true" Border="true">
|
||||
<MudTabPanel Text="General">
|
||||
<GeneralTab Hash="@Hash" Active="@(ActiveTab == 0)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Trackers">
|
||||
<TrackersTab Hash="@Hash" Active="@(ActiveTab == 1)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Peers">
|
||||
<PeersTab Hash="@Hash" Active="@(ActiveTab == 2)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="HTTP Sources">
|
||||
<WebSeedsTab Hash="@Hash" Active="@(ActiveTab == 3)" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Content">
|
||||
<FilesTab Hash="@Hash" Active="@(ActiveTab == 4)" />
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</CascadingValue>
|
||||
}
|
||||
</div>
|
||||
</div>
|
@@ -1,44 +1,49 @@
|
||||
@page "/log"
|
||||
@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" />
|
||||
<MudText Class="pl-5 no-wrap">Execution Log</MudText>
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<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" />
|
||||
<MudText Class="pl-5 no-wrap">Execution Log</MudText>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<EditForm Model="Model" OnSubmit="Submit">
|
||||
<MudGrid>
|
||||
<MudItem md="7">
|
||||
<MudTextField T="string" Label="Criteria" @bind-Value="Model.Criteria" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem md="3">
|
||||
<MudSelect @ref="CategoryMudSelect" T="string" Label="Categories" SelectedValues="Model.SelectedTypes" SelectedValuesChanged="SelectedValuesChanged" Variant="Variant.Outlined" MultiSelection="true" MultiSelectionTextFunc="GenerateSelectedText" SelectAll="true">
|
||||
<MudSelectItem Value="@("Normal")">Normal</MudSelectItem>
|
||||
<MudSelectItem Value="@("Info")">Info</MudSelectItem>
|
||||
<MudSelectItem Value="@("Warning")">Warning</MudSelectItem>
|
||||
<MudSelectItem Value="@("Critical")">Critical</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem md="2">
|
||||
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">Filter</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</EditForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<EditForm Model="Model" OnSubmit="Submit">
|
||||
<MudGrid>
|
||||
<MudItem md="7">
|
||||
<MudTextField T="string" Label="Criteria" @bind-Value="Model.Criteria" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem md="3">
|
||||
<MudSelect @ref="CategoryMudSelect" T="string" Label="Categories" SelectedValues="Model.SelectedTypes" SelectedValuesChanged="SelectedValuesChanged" Variant="Variant.Outlined" MultiSelection="true" MultiSelectionTextFunc="GenerateSelectedText" SelectAll="true">
|
||||
<MudSelectItem Value="@("Normal")">Normal</MudSelectItem>
|
||||
<MudSelectItem Value="@("Info")">Info</MudSelectItem>
|
||||
<MudSelectItem Value="@("Warning")">Warning</MudSelectItem>
|
||||
<MudSelectItem Value="@("Critical")">Critical</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem md="2">
|
||||
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">Filter</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</EditForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.Log"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
RowClassFunc="RowClass"
|
||||
Class="search-list" />
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.Log"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
RowClassFunc="RowClass"
|
||||
Class="search-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
@@ -49,7 +49,7 @@ namespace Lantean.QBTMud.Pages
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
return DoLogin("admin", "eBGJzbjkJ");
|
||||
return DoLogin("admin", "5FUM5pATq");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -3,41 +3,63 @@
|
||||
|
||||
<NavigationLock ConfirmExternalNavigation="@(UpdatePreferences is not null)" OnBeforeInternalNavigation="ValidateExit" />
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">Settings</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Save" OnClick="Save" Disabled="@(LostConnection || UpdatePreferences is null)" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Undo" OnClick="Undo" Disabled="@(LostConnection || UpdatePreferences is null)" />
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">Settings</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Save" OnClick="Save" Disabled="@(LostConnection || UpdatePreferences is null)" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Undo" OnClick="Undo" Disabled="@(LostConnection || UpdatePreferences is null)" />
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" Border="true">
|
||||
<MudTabPanel Text="Behaviour">
|
||||
<BehaviourOptions @ref="BehaviourOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Downloads">
|
||||
<DownloadsOptions @ref="DownloadsOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Connection">
|
||||
<ConnectionOptions @ref="ConnectionOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Speed">
|
||||
<SpeedOptions @ref="SpeedOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="BitTorrent">
|
||||
<BitTorrentOptions @ref="BitTorrentOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="RSS">
|
||||
<RSSOptions @ref="RSSOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Web UI">
|
||||
<WebUIOptions @ref="WebUIOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Advanced">
|
||||
<AdvancedOptions @ref="AdvancedOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
<div class="content-panel__body">
|
||||
<MudTabs Elevation="2" ApplyEffectsToContainer="true" @bind-ActivePanelIndex="ActiveTab" Border="true">
|
||||
<MudTabPanel Text="Behaviour">
|
||||
<div class="options-tab-contents">
|
||||
<BehaviourOptions @ref="BehaviourOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Downloads">
|
||||
<div class="options-tab-contents">
|
||||
<DownloadsOptions @ref="DownloadsOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Connection">
|
||||
<div class="options-tab-contents">
|
||||
<ConnectionOptions @ref="ConnectionOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Speed">
|
||||
<div class="options-tab-contents">
|
||||
<SpeedOptions @ref="SpeedOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="BitTorrent">
|
||||
<div class="options-tab-contents">
|
||||
<BitTorrentOptions @ref="BitTorrentOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="RSS">
|
||||
<div class="options-tab-contents">
|
||||
<RSSOptions @ref="RSSOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Web UI">
|
||||
<div class="options-tab-contents">
|
||||
<WebUIOptions @ref="WebUIOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
<MudTabPanel Text="Advanced">
|
||||
<div class="options-tab-contents">
|
||||
<AdvancedOptions @ref="AdvancedOptions" Preferences="Preferences" UpdatePreferences="@UpdatePreferences" PreferencesChanged="PreferencesChanged" />
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</div>
|
||||
</div>
|
@@ -1,73 +1,79 @@
|
||||
@page "/rss"
|
||||
@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" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">RSS</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Subscriptions" OnClick="NewSubscription" title="New subscription" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.MarkEmailRead" OnClick="MarkAsRead" Disabled="@(SelectedFeed is null)" title="Mark items read" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Update" OnClick="UpdateAll" title="Update all" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.DownloadForOffline" OnClick="EditDownloadRules" title="Edit auto downloading rules" />
|
||||
</MudToolBar>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
|
||||
<MudGrid Class="rss-contents">
|
||||
<MudItem xs="4" Style="height: 100%">
|
||||
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedFeed" SelectedValueChanged="SelectedFeedChanged" Dense>
|
||||
<MudListItem Icon="@Icons.Material.Filled.MarkEmailUnread" Text="@($"Unread ({UnreadCount})")" Value="@("unread")" />
|
||||
@foreach (var (key, feed) in Feeds)
|
||||
{
|
||||
<MudListItem Icon="@(feed.IsLoading ? Icons.Material.Filled.Sync : Icons.Material.Filled.Wifi)" Class="@(feed.IsLoading ? "spin-animation" : "")" Text="@($"{feed.Title} ({feed.UnreadCount})")" Value="@key" />
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
<MudItem xs="4" Style="height: 100%; overflow: auto">
|
||||
@if (Articles.Count > 0)
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedArticle" SelectedValueChanged="SelectedArticleChanged" Dense>
|
||||
@foreach (var article in Articles)
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">RSS</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Subscriptions" OnClick="NewSubscription" title="New subscription" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.MarkEmailRead" OnClick="MarkAsRead" Disabled="@(SelectedFeed is null)" title="Mark items read" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Update" OnClick="UpdateAll" title="Update all" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.DownloadForOffline" OnClick="EditDownloadRules" title="Edit auto downloading rules" />
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
<div class="content-panel__body">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="content-panel__container">
|
||||
<MudGrid Class="rss-contents">
|
||||
<MudItem xs="4" Style="height: 100%">
|
||||
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedFeed" SelectedValueChanged="SelectedFeedChanged" Dense>
|
||||
<MudListItem Icon="@Icons.Material.Filled.MarkEmailUnread" Text="@($"Unread ({UnreadCount})")" Value="@("unread")" />
|
||||
@foreach (var (key, feed) in Feeds)
|
||||
{
|
||||
<MudListItem Icon="@(feed.IsLoading ? Icons.Material.Filled.Sync : Icons.Material.Filled.Wifi)" Class="@(feed.IsLoading ? "spin-animation" : "")" Text="@($"{feed.Title} ({feed.UnreadCount})")" Value="@key" />
|
||||
}
|
||||
</MudList>
|
||||
</MudItem>
|
||||
<MudItem xs="4" Style="height: 100%; overflow: auto">
|
||||
@if (Articles.Count > 0)
|
||||
{
|
||||
<MudListItem Text="@article.Title" Value="article.Id" Icon="@Icons.Material.Filled.Check" IconColor="@(article.IsRead ? Color.Success : Color.Transparent)" />
|
||||
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedArticle" SelectedValueChanged="SelectedArticleChanged" Dense>
|
||||
@foreach (var article in Articles)
|
||||
{
|
||||
<MudListItem Text="@article.Title" Value="article.Id" Icon="@Icons.Material.Filled.Check" IconColor="@(article.IsRead ? Color.Success : Color.Transparent)" />
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="100%" Animation="Animation.False" Width="100%" />
|
||||
}
|
||||
</MudItem>
|
||||
<MudItem xs="4" Style="height: 100%">
|
||||
@if (Article is not null)
|
||||
{
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6" Style="overflow-wrap: anywhere">@Article.Title</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Dense>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Download" OnClick="c => DownloadItem(Article.TorrentURL)" title="Download">Download</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Link" Href="@Article.TorrentURL" Target="@Article.TorrentURL" title="Download">Open torrent URL</MudMenuItem>
|
||||
</MudMenu>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
else
|
||||
{
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="100%" Animation="Animation.False" Width="100%" />
|
||||
}
|
||||
</MudItem>
|
||||
<MudItem xs="4" Style="height: 100%">
|
||||
@if (Article is not null)
|
||||
{
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6" Style="overflow-wrap: anywhere">@Article.Title</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Dense>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Download" OnClick="c => DownloadItem(Article.TorrentURL)" title="Download">Download</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Link" Href="@Article.TorrentURL" Target="@Article.TorrentURL" title="Download">Open torrent URL</MudMenuItem>
|
||||
</MudMenu>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.subtitle2">@Article.Date</MudText>
|
||||
<MudText Typo="Typo.body1">@Article.Description</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="100%" Animation="Animation.False" Width="100%" />
|
||||
}
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.subtitle2">@Article.Date</MudText>
|
||||
<MudText Typo="Typo.body1">@Article.Description</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="100%" Animation="Animation.False" Width="100%" />
|
||||
}
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</div>
|
||||
</div>
|
@@ -1,58 +1,67 @@
|
||||
@page "/search"
|
||||
@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" />
|
||||
<MudText Class="pl-5 no-wrap">Search</MudText>
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<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" />
|
||||
<MudText Class="pl-5 no-wrap">Search</MudText>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<EditForm Model="Model" OnValidSubmit="DoSearch">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudTextField T="string" Label="Criteria" @bind-Value="Model.SearchText" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudSelect T="string" Label="Categories" @bind-Value="Model.SelectedCategory" Variant="Variant.Outlined">
|
||||
@foreach (var (value, name) in Categories)
|
||||
{
|
||||
<MudSelectItem Value="value">@name</MudSelectItem>
|
||||
if (value == "all")
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudSelect T="string" Label="Plugins" @bind-Value="Model.SelectedPlugin" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("all")">All</MudSelectItem>
|
||||
@if (Plugins.Count > 0)
|
||||
{
|
||||
<MudDivider />
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<EditForm Model="Model" OnValidSubmit="DoSearch">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudTextField T="string" Label="Criteria" @bind-Value="Model.SearchText" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="2" md="3">
|
||||
<MudSelect T="string" Label="Categories" @bind-Value="Model.SelectedCategory" Variant="Variant.Outlined">
|
||||
@foreach (var (value, name) in Categories)
|
||||
{
|
||||
<MudSelectItem Value="value">@name</MudSelectItem>
|
||||
if (value == "all")
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="2" md="3">
|
||||
<MudSelect T="string" Label="Plugins" @bind-Value="Model.SelectedPlugin" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("all")">All</MudSelectItem>
|
||||
<MudDivider />
|
||||
@foreach (var (value, name) in Plugins)
|
||||
{
|
||||
<MudSelectItem Value="value">@name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="2" md="2">
|
||||
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">@(_searchId is null ? "Search" : "Stop")</MudButton>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</EditForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
@foreach (var (value, name) in Plugins)
|
||||
{
|
||||
<MudSelectItem Value="value">@name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">@(_searchId is null ? "Search" : "Stop")</MudButton>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</EditForm>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.SearchResult"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="search-list" />
|
||||
<DynamicTable @ref="Table"
|
||||
T="Lantean.QBitTorrentClient.Models.SearchResult"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="search-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
@@ -1,62 +1,68 @@
|
||||
@page "/statistics"
|
||||
@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" />
|
||||
<MudText Class="pl-5 no-wrap">Statistics</MudText>
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<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" />
|
||||
<MudText Class="pl-5 no-wrap">Statistics</MudText>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="details-tab-contents">
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">User statistics</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="All-time uploaded">@DisplayHelpers.Size(ServerState?.AllTimeUploaded)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="All-time downloaded">@DisplayHelpers.Size(ServerState?.AllTimeDownloaded)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="All-time share ratio">@DisplayHelpers.EmptyIfNull(ServerState?.GlobalRatio, format: "0.00")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Session waste">@DisplayHelpers.Size(ServerState?.TotalWastedSession)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Connected peers">@DisplayHelpers.EmptyIfNull(ServerState?.TotalPeerConnections)</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<div class="content-panel__body">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="details-tab-contents content-panel__container">
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">User statistics</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="All-time uploaded">@DisplayHelpers.Size(ServerState?.AllTimeUploaded)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="All-time downloaded">@DisplayHelpers.Size(ServerState?.AllTimeDownloaded)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="All-time share ratio">@DisplayHelpers.EmptyIfNull(ServerState?.GlobalRatio, format: "0.00")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Session waste">@DisplayHelpers.Size(ServerState?.TotalWastedSession)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Connected peers">@DisplayHelpers.EmptyIfNull(ServerState?.TotalPeerConnections)</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Cache statistics</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Read cache hits">@DisplayHelpers.Percentage(ServerState?.ReadCacheHits)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Total buffer size">@DisplayHelpers.Size(ServerState?.TotalBuffersSize)</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Cache statistics</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Read cache hits">@DisplayHelpers.Percentage(ServerState?.ReadCacheHits)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Total buffer size">@DisplayHelpers.Size(ServerState?.TotalBuffersSize)</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Performance statistics</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Write cache overload">@DisplayHelpers.Percentage(ServerState?.WriteCacheOverload)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Read cache overload">@DisplayHelpers.Percentage(ServerState?.ReadCacheOverload)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Queued I/O jobs">@DisplayHelpers.EmptyIfNull(ServerState?.QueuedIOJobs)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Average time in queue">@DisplayHelpers.EmptyIfNull(ServerState?.AverageTimeQueue, suffix: "ms")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Total queued size">@DisplayHelpers.Size(ServerState?.TotalQueuedSize)</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
<MudText Typo="Typo.subtitle2" Class="pt-6">Performance statistics</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Write cache overload">@DisplayHelpers.Percentage(ServerState?.WriteCacheOverload)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Read cache overload">@DisplayHelpers.Percentage(ServerState?.ReadCacheOverload)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Queued I/O jobs">@DisplayHelpers.EmptyIfNull(ServerState?.QueuedIOJobs)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Average time in queue">@DisplayHelpers.EmptyIfNull(ServerState?.AverageTimeQueue, suffix: "ms")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Total queued size">@DisplayHelpers.Size(ServerState?.TotalQueuedSize)</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</div>
|
||||
</div>
|
@@ -1,24 +1,30 @@
|
||||
@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" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">Tags</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.NewLabel" OnClick="AddTag" title="Add Tag" />
|
||||
</MudToolBar>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
@if (!DrawerOpen)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
<MudText Class="px-5 no-wrap">Tags</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.NewLabel" OnClick="AddTag" title="Add Tag" />
|
||||
</MudToolBar>
|
||||
</div>
|
||||
|
||||
<DynamicTable @ref="Table"
|
||||
T="string"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="details-list" />
|
||||
<div class="content-panel__body">
|
||||
<DynamicTable @ref="Table"
|
||||
T="string"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Results"
|
||||
MultiSelection="false"
|
||||
SelectOnRowClick="false"
|
||||
Class="details-list content-panel__table" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private RenderFragment<RowContext<string>> ActionsColumn
|
||||
|
@@ -1,41 +1,46 @@
|
||||
@page "/"
|
||||
@layout ListLayout
|
||||
|
||||
<ContextMenu @ref="ContextMenu" Dense="true" AdjustmentX="@(DrawerOpen ? -235 : 0)">
|
||||
<MudMenu @ref="ContextMenu" Dense="true" RelativeWidth="DropdownWidth.Ignore" PositionAtCursor="true" ListClass="unselectable" PopoverClass="unselectable">
|
||||
<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>
|
||||
</MudMenu>
|
||||
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" title="Add torrent link" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" title="Add torrent file" />
|
||||
<MudDivider Vertical="true" />
|
||||
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrentsHashes()" Torrents="MainData.Torrents" Preferences="Preferences" />
|
||||
<MudDivider Vertical="true" />
|
||||
<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" />
|
||||
<MudSpacer />
|
||||
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0">
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="Torrent"
|
||||
Class="torrent-list"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Torrents"
|
||||
OnRowClick="RowClick"
|
||||
MultiSelection="true"
|
||||
SelectOnRowClick="true"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
SortColumnChanged="SortColumnChangedHandler"
|
||||
SortDirectionChanged="SortDirectionChangedHandler"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
/>
|
||||
</MudContainer>
|
||||
<div class="content-panel">
|
||||
<div class="content-panel__toolbar content-panel__toolbar--scroll">
|
||||
<MudToolBar Gutters="false" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" title="Add torrent link" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" title="Add torrent file" />
|
||||
<MudDivider Vertical="true" />
|
||||
<TorrentActions RenderType="RenderType.InitialIconsOnly" Hashes="GetSelectedTorrentsHashes()" Torrents="MainData.Torrents" Preferences="Preferences" />
|
||||
<MudDivider Vertical="true" />
|
||||
<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" />
|
||||
<MudSpacer />
|
||||
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</MudToolBar>
|
||||
</div>
|
||||
<div class="content-panel__body">
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0 content-panel__container">
|
||||
<DynamicTable
|
||||
@ref="Table"
|
||||
T="Torrent"
|
||||
Class="torrent-list content-panel__table"
|
||||
ColumnDefinitions="Columns"
|
||||
Items="Torrents"
|
||||
OnRowClick="RowClick"
|
||||
MultiSelection="true"
|
||||
SelectOnRowClick="true"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
SortColumnChanged="SortColumnChangedHandler"
|
||||
SortDirectionChanged="SortDirectionChangedHandler"
|
||||
OnTableDataContextMenu="TableDataContextMenu"
|
||||
OnTableDataLongPress="TableDataLongPress"
|
||||
/>
|
||||
</MudContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private static RenderFragment<RowContext<Torrent>> ProgressBarColumn
|
||||
|
@@ -35,11 +35,17 @@ namespace Lantean.QBTMud.Pages
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
public IReadOnlyList<Torrent>? Torrents { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData MainData { get; set; } = default!;
|
||||
|
||||
[CascadingParameter(Name = "LostConnection")]
|
||||
public bool LostConnection { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "TorrentsVersion")]
|
||||
public int TorrentsVersion { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SearchTermChanged")]
|
||||
public EventCallback<string> SearchTermChanged { get; set; }
|
||||
|
||||
@@ -56,13 +62,23 @@ namespace Lantean.QBTMud.Pages
|
||||
|
||||
protected HashSet<Torrent> SelectedItems { get; set; } = [];
|
||||
|
||||
protected bool ToolbarButtonsEnabled => SelectedItems.Count > 0;
|
||||
protected bool ToolbarButtonsEnabled => _toolbarButtonsEnabled;
|
||||
|
||||
protected DynamicTable<Torrent>? Table { get; set; }
|
||||
|
||||
protected Torrent? ContextMenuItem { get; set; }
|
||||
|
||||
protected ContextMenu? ContextMenu { get; set; }
|
||||
protected MudMenu? ContextMenu { get; set; }
|
||||
|
||||
private object? _lastRenderedTorrents;
|
||||
private QBitTorrentClient.Models.Preferences? _lastPreferences;
|
||||
private bool _lastLostConnection;
|
||||
private bool _hasRendered;
|
||||
private int _lastSelectionCount;
|
||||
private int _lastTorrentsVersion = -1;
|
||||
private bool _pendingSelectionChange;
|
||||
|
||||
private bool _toolbarButtonsEnabled;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
@@ -73,9 +89,81 @@ namespace Lantean.QBTMud.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
if (!_hasRendered)
|
||||
{
|
||||
_hasRendered = true;
|
||||
_lastRenderedTorrents = Torrents;
|
||||
_lastPreferences = Preferences;
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastTorrentsVersion = TorrentsVersion;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_pendingSelectionChange)
|
||||
{
|
||||
_pendingSelectionChange = false;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastTorrentsVersion != TorrentsVersion)
|
||||
{
|
||||
_lastTorrentsVersion = TorrentsVersion;
|
||||
_lastRenderedTorrents = Torrents;
|
||||
_lastPreferences = Preferences;
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(_lastRenderedTorrents, Torrents))
|
||||
{
|
||||
_lastRenderedTorrents = Torrents;
|
||||
_lastPreferences = Preferences;
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(_lastPreferences, Preferences))
|
||||
{
|
||||
_lastPreferences = Preferences;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastLostConnection != LostConnection)
|
||||
{
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastSelectionCount != SelectedItems.Count)
|
||||
{
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
|
||||
{
|
||||
SelectedItems = selectedItems;
|
||||
_toolbarButtonsEnabled = SelectedItems.Count > 0;
|
||||
_pendingSelectionChange = true;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task SortDirectionChangedHandler(SortDirection sortDirection)
|
||||
@@ -185,7 +273,9 @@ namespace Lantean.QBTMud.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
await ContextMenu.ToggleMenuAsync(eventArgs);
|
||||
var normalizedEventArgs = eventArgs.NormalizeForContextMenu();
|
||||
|
||||
await ContextMenu.OpenMenuAsync(normalizedEventArgs);
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<Torrent>> Columns => ColumnsDefinitions.Where(c => c.Id != "#" || Preferences?.QueueingEnabled == true);
|
||||
@@ -193,7 +283,7 @@ namespace Lantean.QBTMud.Pages
|
||||
public static List<ColumnDefinition<Torrent>> ColumnsDefinitions { get; } =
|
||||
[
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("#", t => t.Priority),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition("Icon", t => t.State, IconColumn, iconOnly: true, width: 25),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition("Icon", t => t.State, IconColumn, iconOnly: true, width: 25, tdClass: "table-icon"),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Name", t => t.Name, width: 400),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Size", t => t.Size, t => DisplayHelpers.Size(t.Size)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Total Size", t => t.TotalSize, t => DisplayHelpers.Size(t.TotalSize), enabled: false),
|
||||
@@ -205,6 +295,7 @@ namespace Lantean.QBTMud.Pages
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Up Speed", t => t.UploadSpeed, t => DisplayHelpers.Speed(t.UploadSpeed)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("ETA", t => t.EstimatedTimeOfArrival, t => DisplayHelpers.Duration(t.EstimatedTimeOfArrival)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Ratio", t => t.Ratio, t => t.Ratio.ToString("0.00")),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Popularity", t => t.Popularity, t => t.Popularity.ToString("0.00")),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Category", t => t.Category),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Tags", t => t.Tags, t => string.Join(", ", t.Tags)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Added On", t => t.AddedOn, t => DisplayHelpers.DateTime(t.AddedOn)),
|
||||
@@ -220,11 +311,15 @@ namespace Lantean.QBTMud.Pages
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Time Active", t => t.TimeActive, t => DisplayHelpers.Duration(t.TimeActive), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Save path", t => t.SavePath, enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Completed", t => t.Completed, t => DisplayHelpers.Size(t.Completed), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Ratio Limit", t => t.RatioLimit, t => t.Ratio.ToString("0.00"), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Ratio Limit", t => t.RatioLimit, t => DisplayHelpers.RatioLimit(t.RatioLimit), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Last Seen Complete", t => t.SeenComplete, t => DisplayHelpers.DateTime(t.SeenComplete), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Last Activity", t => t.LastActivity, t => DisplayHelpers.DateTime(t.LastActivity), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Availability", t => t.Availability, t => t.Availability.ToString("0.##"), enabled: false),
|
||||
//ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Reannounce In", t => t.Reannounce, enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Incomplete Save Path", t => t.DownloadPath, t => DisplayHelpers.EmptyIfNull(t.DownloadPath), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Info Hash v1", t => t.InfoHashV1, t => DisplayHelpers.EmptyIfNull(t.InfoHashV1), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Info Hash v2", t => t.InfoHashV2, t => DisplayHelpers.EmptyIfNull(t.InfoHashV2), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Reannounce In", t => t.Reannounce, t => DisplayHelpers.Duration(t.Reannounce), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Private", t => t.IsPrivate, t => DisplayHelpers.Bool(t.IsPrivate), enabled: false),
|
||||
];
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -248,4 +343,4 @@ namespace Lantean.QBTMud.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Blazored.LocalStorage;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Services;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Services
|
||||
|
||||
Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent);
|
||||
|
||||
void MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList);
|
||||
bool MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList, out bool filterChanged);
|
||||
|
||||
PeerList CreatePeerList(QBitTorrentClient.Models.TorrentPeers torrentPeers);
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace Lantean.QBTMud.Services
|
||||
|
||||
Dictionary<string, ContentItem> CreateContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files);
|
||||
|
||||
void MergeContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files, Dictionary<string, ContentItem> contents);
|
||||
bool MergeContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files, Dictionary<string, ContentItem> contents);
|
||||
|
||||
QBitTorrentClient.Models.UpdatePreferences MergePreferences(QBitTorrentClient.Models.UpdatePreferences? original, QBitTorrentClient.Models.UpdatePreferences changed);
|
||||
|
||||
RssList CreateRssList(IReadOnlyDictionary<string, QBitTorrentClient.Models.RssItem> rssItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +0,0 @@
|
||||
# qbt-mud
|
||||
|
||||
## To-Do
|
||||
|
||||
- Rename multiple files dialog
|
||||
- ~~RSS feeds and dialogs~~
|
||||
- ~~About~~
|
||||
- ~~Context menu for files list/trackers list/peers list~~
|
||||
- ~~Tag management page~~
|
||||
- ~~Category management page~~
|
||||
- ~~Update all tables to use DynamicTable~~
|
||||
- ~~Log~~
|
||||
- ~~Blocks~~
|
||||
- ~~Search~~
|
@@ -65,15 +65,11 @@ code {
|
||||
}
|
||||
|
||||
.mud-appbar.mud-appbar-fixed-bottom {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.mud-main-content {
|
||||
padding-bottom: 35px;
|
||||
height: calc(var(--app-status-bar-height) + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.mud-drawer-fixed.mud-drawer-mini.mud-drawer-clipped-always, .mud-drawer-fixed.mud-drawer-persistent:not(.mud-drawer-clipped-never), .mud-drawer-fixed.mud-drawer-responsive.mud-drawer-clipped-always, .mud-drawer-fixed.mud-drawer-temporary.mud-drawer-clipped-always {
|
||||
height: calc(100% - var(--mud-appbar-height) - 35px);
|
||||
height: calc(100% - var(--mud-appbar-height) - (var(--app-status-bar-height) + env(safe-area-inset-bottom, 0px)));
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
@@ -154,25 +150,91 @@ code {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.torrent-list .mud-table-container {
|
||||
height: calc(100vh - 149px);
|
||||
/*. Layout helpers */
|
||||
.content-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.file-list .mud-table-container {
|
||||
height: calc(100vh - 245px);
|
||||
.content-panel__toolbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.details-list .mud-table-container {
|
||||
height: calc(100vh - 200px);
|
||||
.content-panel__toolbar--scroll {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.details-tab-contents {
|
||||
height: calc(100vh - 200px);
|
||||
.content-panel__body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-panel__container {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.content-panel__table {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.content-panel__table .mud-table-container {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-panel__body > .mud-tabs {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.content-panel__body > .mud-tabs .mud-tabs-tabbar {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content-panel__body > .mud-tabs .mud-tabs-panels {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding-top: 0;
|
||||
margin-top: -1px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.content-panel__body .mud-tabs .mud-tabs-panels .mud-tab-panel {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.torrent-list .mud-table-container,
|
||||
.file-list .mud-table-container,
|
||||
.details-list .mud-table-container,
|
||||
.search-list .mud-table-container {
|
||||
height: calc(100vh - 260px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.details-tab-contents,
|
||||
.options-tab-contents,
|
||||
.rss-contents {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
tr.log-normal td {
|
||||
@@ -220,10 +282,6 @@ td .folder-button {
|
||||
padding: 6px 16px 6px 16px !important;
|
||||
}
|
||||
|
||||
.rss-contents {
|
||||
height: calc(100vh - 149px);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@@ -240,4 +298,132 @@ td .folder-button {
|
||||
|
||||
.mud-dialog .mud-dialog-content {
|
||||
padding-top: 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-menu-dense {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.table-icon {
|
||||
width: 25px;
|
||||
max-width: 25px;
|
||||
padding: 0 8px !important;
|
||||
}
|
||||
|
||||
.mud-popover .mud-divider:last-child {
|
||||
display: none;
|
||||
}
|
||||
:root {
|
||||
--app-viewport-height: 100vh;
|
||||
--app-status-bar-height: 35px;
|
||||
}
|
||||
|
||||
@supports (height: 100svh) {
|
||||
:root {
|
||||
--app-viewport-height: 100svh;
|
||||
}
|
||||
}
|
||||
|
||||
@supports ((height: 100dvh) and (not (height: 100svh))) {
|
||||
:root {
|
||||
--app-viewport-height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: var(--app-viewport-height);
|
||||
min-height: var(--app-viewport-height);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#app,
|
||||
.mud-layout {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: var(--app-viewport-height);
|
||||
min-height: var(--app-viewport-height);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-shell__body {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-shell__sidebar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.app-shell__main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: var(--mud-appbar-height) 0 calc(var(--app-status-bar-height) + env(safe-area-inset-bottom, 0px));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.app-shell__status-bar.mud-appbar {
|
||||
flex: 0 0 calc(var(--app-status-bar-height) + env(safe-area-inset-bottom, 0px));
|
||||
height: calc(var(--app-status-bar-height) + env(safe-area-inset-bottom, 0px));
|
||||
width: 100%;
|
||||
background-color: var(--mud-palette-dark-lighten);
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.app-shell__status-bar .mud-toolbar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
background-color: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
:root {
|
||||
--app-viewport-height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: -webkit-fill-available;
|
||||
min-height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
height: -webkit-fill-available;
|
||||
min-height: -webkit-fill-available;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Tab bar gap fix */
|
||||
.content-panel__body > .mud-tabs .mud-tabs-tabbar {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.content-panel__body > .mud-tabs .mud-tabs-tabbar .mud-tabs-wrapper {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.content-panel__body > .mud-tabs .mud-tabs-tabbar .mud-tabs-slider {
|
||||
bottom: 0;
|
||||
}
|
||||
|
@@ -9,12 +9,12 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap" rel="stylesheet">
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link href="./_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="./css/app.css" />
|
||||
<link rel="icon" type="image/png" href="images/qbittorrent32.png" />
|
||||
<link rel="icon" href="images/qbittorrent-tray.svg">
|
||||
<link rel="mask-icon" href="images/qbittorrent-tray.svg" color="#000000">
|
||||
<link rel="apple-touch-icon" href="images/qbittorrent32.png">
|
||||
<link rel="icon" href="./images/qbittorrent-tray.svg">
|
||||
<link rel="mask-icon" href="./images/qbittorrent-tray.svg" color="#000000">
|
||||
<link rel="apple-touch-icon" href="./images/qbittorrent32.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -31,10 +31,10 @@
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="js/piecesbar.js"></script>
|
||||
<script src="js/interop.js"></script>
|
||||
<script src="./_framework/blazor.webassembly.js"></script>
|
||||
<script src="./_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="./js/piecesbar.js"></script>
|
||||
<script src="./js/interop.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
@@ -5,4 +5,4 @@
|
||||
// * @author John Doherty <www.johndoherty.info>
|
||||
// * @license MIT
|
||||
// */
|
||||
!function (e, t) { "use strict"; var n = null, a = "PointerEvent" in e || e.navigator && "msPointerEnabled" in e.navigator, i = "ontouchstart" in e || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0, o = a ? "pointerdown" : i ? "touchstart" : "mousedown", r = a ? "pointerup" : i ? "touchend" : "mouseup", m = a ? "pointermove" : i ? "touchmove" : "mousemove", u = a ? "pointerleave" : i ? "touchleave" : "mouseleave", s = 0, c = 0, l = 10, v = 10; function f(e) { p(), e = function (e) { if (void 0 !== e.changedTouches) return e.changedTouches[0]; return e }(e), this.dispatchEvent(new CustomEvent("longpress", { bubbles: !0, cancelable: !0, detail: { clientX: e.clientX, clientY: e.clientY, offsetX: e.offsetX, offsetY: e.offsetY, pageX: e.pageX, pageY: e.pageY }, clientX: e.clientX, clientY: e.clientY, offsetX: e.offsetX, offsetY: e.offsetY, pageX: e.pageX, pageY: e.pageY, screenX: e.screenX, screenY: e.screenY })) || t.addEventListener("click", function e(n) { t.removeEventListener("click", e, !0), function (e) { e.stopImmediatePropagation(), e.preventDefault(), e.stopPropagation() }(n) }, !0) } function d(a) { p(a); var i = a.target, o = parseInt(function (e, n, a) { for (; e && e !== t.documentElement;) { var i = e.getAttribute(n); if (i) return i; e = e.parentNode } return a }(i, "data-long-press-delay", "400"), 10); n = function (t, n) { if (!(e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame && e.mozCancelRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame)) return e.setTimeout(t, n); var a = (new Date).getTime(), i = {}, o = function () { (new Date).getTime() - a >= n ? t.call() : i.value = requestAnimFrame(o) }; return i.value = requestAnimFrame(o), i }(f.bind(i, a), o) } function p(t) { var a; (a = n) && (e.cancelAnimationFrame ? e.cancelAnimationFrame(a.value) : e.webkitCancelAnimationFrame ? e.webkitCancelAnimationFrame(a.value) : e.webkitCancelRequestAnimationFrame ? e.webkitCancelRequestAnimationFrame(a.value) : e.mozCancelRequestAnimationFrame ? e.mozCancelRequestAnimationFrame(a.value) : e.oCancelRequestAnimationFrame ? e.oCancelRequestAnimationFrame(a.value) : e.msCancelRequestAnimationFrame ? e.msCancelRequestAnimationFrame(a.value) : clearTimeout(a)), n = null } "function" != typeof e.CustomEvent && (e.CustomEvent = function (e, n) { n = n || { bubbles: !1, cancelable: !1, detail: void 0 }; var a = t.createEvent("CustomEvent"); return a.initCustomEvent(e, n.bubbles, n.cancelable, n.detail), a }, e.CustomEvent.prototype = e.Event.prototype), e.requestAnimFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (t) { e.setTimeout(t, 1e3 / 60) }, t.addEventListener(r, p, !0), t.addEventListener(u, p, !0), t.addEventListener(m, function (e) { var t = Math.abs(s - e.clientX), n = Math.abs(c - e.clientY); (t >= l || n >= v) && p() }, !0), t.addEventListener("wheel", p, !0), t.addEventListener("scroll", p, !0), t.addEventListener(o, function (e) { s = e.clientX, c = e.clientY, d(e) }, !0) }(window, document);
|
||||
!function (e, t) { "use strict"; var n = null, a = "PointerEvent" in e || e.navigator && "msPointerEnabled" in e.navigator, i = "ontouchstart" in e || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0, o = a ? "pointerdown" : i ? "touchstart" : "mousedown", r = a ? "pointerup" : i ? "touchend" : "mouseup", m = a ? "pointermove" : i ? "touchmove" : "mousemove", u = a ? "pointerleave" : i ? "touchleave" : "mouseleave", s = 0, c = 0, l = 10, v = 10; function f(e) { p(), e = function (e) { if (void 0 !== e.changedTouches) return e.changedTouches[0]; return e }(e); var n = new CustomEvent("longpress", { bubbles: !0, cancelable: !0, detail: { clientX: e.clientX, clientY: e.clientY, offsetX: e.offsetX, offsetY: e.offsetY, pageX: e.pageX, pageY: e.pageY }, clientX: e.clientX, clientY: e.clientY, offsetX: e.offsetX, offsetY: e.offsetY, pageX: e.pageX, pageY: e.pageY, screenX: e.screenX, screenY: e.screenY }); n.__longPress = !0, this.dispatchEvent(n) || t.addEventListener("click", function e(n) { t.removeEventListener("click", e, !0), function (e) { e.stopImmediatePropagation(), e.preventDefault(), e.stopPropagation() }(n) }, !0) } function d(a) { p(a); var i = a.target, o = parseInt(function (e, n, a) { for (; e && e !== t.documentElement;) { var i = e.getAttribute(n); if (i) return i; e = e.parentNode } return a }(i, "data-long-press-delay", "400"), 10); n = function (t, n) { if (!(e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame && e.mozCancelRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame)) return e.setTimeout(t, n); var a = (new Date).getTime(), i = {}, o = function () { (new Date).getTime() - a >= n ? t.call() : i.value = requestAnimFrame(o) }; return i.value = requestAnimFrame(o), i }(f.bind(i, a), o) } function p(t) { var a; (a = n) && (e.cancelAnimationFrame ? e.cancelAnimationFrame(a.value) : e.webkitCancelAnimationFrame ? e.webkitCancelAnimationFrame(a.value) : e.webkitCancelRequestAnimationFrame ? e.webkitCancelRequestAnimationFrame(a.value) : e.mozCancelRequestAnimationFrame ? e.mozCancelRequestAnimationFrame(a.value) : e.oCancelRequestAnimationFrame ? e.oCancelRequestAnimationFrame(a.value) : e.msCancelRequestAnimationFrame ? e.msCancelRequestAnimationFrame(a.value) : clearTimeout(a)), n = null } "function" != typeof e.CustomEvent && (e.CustomEvent = function (e, n) { n = n || { bubbles: !1, cancelable: !1, detail: void 0 }; var a = t.createEvent("CustomEvent"); return a.initCustomEvent(e, n.bubbles, n.cancelable, n.detail), a }, e.CustomEvent.prototype = e.Event.prototype), e.requestAnimFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (t) { e.setTimeout(t, 1e3 / 60) }, t.addEventListener(r, p, !0), t.addEventListener(u, p, !0), t.addEventListener(m, function (e) { var t = Math.abs(s - e.clientX), n = Math.abs(c - e.clientY); (t >= l || n >= v) && p() }, !0), t.addEventListener("wheel", p, !0), t.addEventListener("scroll", p, !0), t.addEventListener(o, function (e) { s = e.clientX, c = e.clientY, d(e) }, !0) }(window, document);
|
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user