mirror of
				https://github.com/lantean-code/qbtmud.git
				synced 2025-10-31 12:03:42 +00:00 
			
		
		
		
	Compare commits
	
		
			30 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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 | ||||
|   | ||||
| @@ -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.0.0" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.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.0"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageReference> | ||||
| 	  <PackageReference Include="System.Net.Http" Version="4.3.4" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -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,6 +12,7 @@ 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 | ||||
| Global | ||||
|   | ||||
| @@ -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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         IMudDialogInstance MudDialog { get; set; } = default!; | ||||
|  | ||||
|         [Parameter] | ||||
|         public string? Url { get; set; } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <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"> | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| using Lantean.QBitTorrentClient; | ||||
| using Lantean.QBTMud.Models; | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using MudBlazor; | ||||
|  | ||||
| namespace Lantean.QBTMud.Components.Dialogs | ||||
| { | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs | ||||
|     public partial class AddTrackerDialog | ||||
|     { | ||||
|         [CascadingParameter] | ||||
|         public MudDialogInstance MudDialog { get; set; } = default!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         IMudDialogInstance MudDialog { get; set; } = default!; | ||||
|  | ||||
|         [Parameter] | ||||
|         public string? Hash { get; set; } | ||||
| @@ -484,19 +483,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(); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ namespace Lantean.QBTMud.Components.Dialogs | ||||
|         private readonly List<string> _unsavedRuleNames = []; | ||||
|  | ||||
|         [CascadingParameter] | ||||
|         public MudDialogInstance MudDialog { get; set; } = default!; | ||||
|         IMudDialogInstance MudDialog { get; set; } = default!; | ||||
|  | ||||
|         [Inject] | ||||
|         protected IDialogService DialogService { get; set; } = default!; | ||||
|   | ||||
| @@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs | ||||
|     public partial class ShareRatioDialog | ||||
|     { | ||||
|         [CascadingParameter] | ||||
|         public MudDialogInstance MudDialog { get; set; } = default!; | ||||
|         IMudDialogInstance MudDialog { get; set; } = default!; | ||||
|  | ||||
|         [Parameter] | ||||
|         public string? Label { get; set; } | ||||
|   | ||||
| @@ -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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         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!; | ||||
|         IMudDialogInstance MudDialog { get; set; } = default!; | ||||
|  | ||||
|         [Parameter] | ||||
|         [EditorRequired] | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|     <MudMenuItem Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileContextMenu">Rename</MudMenuItem> | ||||
| </ContextMenu> | ||||
|  | ||||
| <div style="overflow-x: auto; white-space: nowrap; width: 100%;"> | ||||
| <MudToolBar Gutters="false" Dense="true"> | ||||
|     <MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileToolbar" title="Rename" /> | ||||
|     <MudDivider Vertical="true" /> | ||||
| @@ -22,6 +23,7 @@ | ||||
|     <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> | ||||
|  | ||||
| <DynamicTable | ||||
|     @ref="Table" | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60"> | ||||
| <ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">  | ||||
|     @TorrentControls(_statusType) | ||||
| </ContextMenu> | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| @@ -63,7 +60,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 +71,14 @@ 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 int MajorVersion => VersionHelper.GetMajorVersion(Version); | ||||
|  | ||||
|         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("pause", "Pause", MajorVersion < 5 ? Icons.Material.Filled.Pause : Icons.Material.Filled.Stop, Color.Warning, CreateCallback(Pause)), | ||||
|                 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), | ||||
| @@ -441,7 +415,7 @@ namespace Lantean.QBTMud.Components | ||||
|                     thereAreFirstLastPiecePrio = true; | ||||
|                 } | ||||
|  | ||||
|                 if (torrent.Progress != 1.0) // not downloaded | ||||
|                 if (torrent.Progress < 0.999999) // not downloaded | ||||
|                 { | ||||
|                     allAreDownloaded = false; | ||||
|                 } | ||||
|   | ||||
| @@ -8,19 +8,17 @@ | ||||
|             Class="unselectable" | ||||
|             MaxHeight="@MaxHeight" | ||||
|             AnchorOrigin="@AnchorOrigin" | ||||
|             TransformOrigin="TransformOrigin" | ||||
|             RelativeWidth="@FullWidth" | ||||
|             TransformOrigin="@TransformOrigin" | ||||
|             RelativeWidth="@RelativeWidth" | ||||
|             OverflowBehavior="OverflowBehavior.FlipAlways" | ||||
|             Style="@_popoverStyle" | ||||
|             @ontouchend:preventDefault> | ||||
|     <CascadingValue Value="@(FakeMenu)"> | ||||
|         @if (_showChildren) | ||||
|         { | ||||
|             <MudList T="object" | ||||
|             Class="unselectable" | ||||
|                  Dense="@Dense"> | ||||
|             <MudList T="object" Class="unselectable"  Dense="@Dense"> | ||||
|                 @ChildContent | ||||
|         </MudList> | ||||
|             </MudList> | ||||
|         } | ||||
|     </CascadingValue> | ||||
| </MudPopover> | ||||
|   | ||||
| @@ -7,12 +7,6 @@ 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; | ||||
| @@ -61,7 +55,7 @@ namespace Lantean.QBTMud.Components.UI | ||||
|         /// </summary> | ||||
|         [Parameter] | ||||
|         [Category(CategoryTypes.Menu.PopupAppearance)] | ||||
|         public bool FullWidth { get; set; } | ||||
|         public DropdownWidth RelativeWidth { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Sets the max height the menu can have when open. | ||||
| @@ -219,56 +213,58 @@ namespace Lantean.QBTMud.Components.UI | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         protected override async Task OnAfterRenderAsync(bool firstRender) | ||||
|         protected override Task OnAfterRenderAsync(bool firstRender) | ||||
|         { | ||||
|             if (!_isResized) | ||||
|             { | ||||
|                 await DeterminePosition(); | ||||
|                 //await DeterminePosition(); | ||||
|             } | ||||
|  | ||||
|             return Task.CompletedTask; | ||||
|         } | ||||
|  | ||||
|         private async Task DeterminePosition() | ||||
|         { | ||||
|             var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content"); | ||||
|             double? contextMenuHeight = null; | ||||
|             double? contextMenuWidth = null; | ||||
|         //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 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; | ||||
|             } | ||||
|         //    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; | ||||
|         //    // 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 (_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; | ||||
|             } | ||||
|         //    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); | ||||
|         } | ||||
|         //    SetPopoverStyle(_x, _y); | ||||
|         //    _isResized = true; | ||||
|         //    await InvokeAsync(StateHasChanged); | ||||
|         //} | ||||
|  | ||||
|         private (double x, double y) GetPositionFromArgs(EventArgs eventArgs) | ||||
|         { | ||||
|   | ||||
| @@ -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" @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault> | ||||
|         @if (!string.IsNullOrEmpty(Icon)) | ||||
|         { | ||||
|             <MudIcon Icon="@Icon" Color="@IconColor" Class="@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!; | ||||
| @@ -82,6 +83,8 @@ namespace Lantean.QBTMud.Components.UI | ||||
|  | ||||
|         private Dictionary<string, int?> _columnWidths = []; | ||||
|  | ||||
|         private Dictionary<string, int> _columnOrder = []; | ||||
|  | ||||
|         private string? _sortColumn; | ||||
|  | ||||
|         private SortDirection _sortDirection; | ||||
| @@ -165,8 +168,29 @@ namespace Lantean.QBTMud.Components.UI | ||||
|         protected IEnumerable<ColumnDefinition<T>> GetColumns() | ||||
|         { | ||||
|             var filteredColumns = ColumnDefinitions.Where(c => SelectedColumns.Contains(c.Id)).Where(ColumnFilter); | ||||
|             foreach (var column in filteredColumns) | ||||
|             if (_columnOrder.Count == 0) | ||||
|             { | ||||
|                 foreach (var column in filteredColumns) | ||||
|                 { | ||||
|                     if (_columnWidths.TryGetValue(column.Id, out var value)) | ||||
|                     { | ||||
|                         column.Width = value; | ||||
|                     } | ||||
|  | ||||
|                     yield return column; | ||||
|                 } | ||||
|  | ||||
|                 yield break; | ||||
|             } | ||||
|  | ||||
|             var columnDictionary = filteredColumns.ToDictionary(c => c.Id); | ||||
|             foreach (var columnId in _columnOrder.OrderBy(c => c.Value).Select(c => c.Key)) | ||||
|             { | ||||
|                 if (!columnDictionary.TryGetValue(columnId, out var column)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (_columnWidths.TryGetValue(column.Id, out var value)) | ||||
|                 { | ||||
|                     column.Width = value; | ||||
| @@ -280,7 +304,7 @@ namespace Lantean.QBTMud.Components.UI | ||||
|  | ||||
|         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) | ||||
|             { | ||||
| @@ -299,11 +323,17 @@ namespace Lantean.QBTMud.Components.UI | ||||
|                 _columnWidths = result.ColumnWidths; | ||||
|                 await LocalStorage.SetItemAsync(_columnWidthsStorageKey, _columnWidths); | ||||
|             } | ||||
|  | ||||
|             if (!DictionaryEqual(_columnOrder, result.ColumnOrder)) | ||||
|             { | ||||
|                 _columnOrder = result.ColumnOrder; | ||||
|                 await LocalStorage.SetItemAsync(_columnOrderStorageKey, _columnOrder); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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) | ||||
|   | ||||
| @@ -328,13 +328,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 +345,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) | ||||
|   | ||||
| @@ -37,7 +37,7 @@ namespace Lantean.QBTMud.Helpers | ||||
|             { | ||||
|                 time = TimeSpan.FromSeconds(seconds.Value); | ||||
|             } | ||||
|             catch (OverflowException) | ||||
|             catch | ||||
|             { | ||||
|                 return "∞"; | ||||
|             } | ||||
| @@ -129,7 +129,7 @@ namespace Lantean.QBTMud.Helpers | ||||
|                 return ""; | ||||
|             } | ||||
|  | ||||
|             return Size(size); | ||||
|             return Size(size, prefix, suffix); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|   | ||||
							
								
								
									
										34
									
								
								Lantean.QBTMud/Helpers/VersionHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Lantean.QBTMud/Helpers/VersionHelper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
|  | ||||
| namespace Lantean.QBTMud.Helpers | ||||
| { | ||||
|     internal static class VersionHelper | ||||
|     { | ||||
|         private static int? _version; | ||||
|  | ||||
|         private const int _defaultVersion = 5; | ||||
|  | ||||
|         public static int DefaultVersion => _defaultVersion; | ||||
|  | ||||
|         public static int GetMajorVersion(string? version) | ||||
|         { | ||||
|             if (_version is not null) | ||||
|             { | ||||
|                 return _version.Value; | ||||
|             } | ||||
|  | ||||
|             if (string.IsNullOrEmpty(version)) | ||||
|             { | ||||
|                 return _defaultVersion; | ||||
|             } | ||||
|  | ||||
|             if (!Version.TryParse(version?.Replace("v", ""), out var theVersion)) | ||||
|             { | ||||
|                 return _defaultVersion; | ||||
|             } | ||||
|  | ||||
|             _version = theVersion.Major; | ||||
|  | ||||
|             return _version.Value; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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.5" /> | ||||
| 	  <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.5" PrivateAssets="all" /> | ||||
| 	  <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" /> | ||||
| 	  <PackageReference Include="MudBlazor" Version="8.7.0" /> | ||||
| 	  <PackageReference Include="MudBlazor.ThemeManager" Version="3.0.0" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -36,23 +36,23 @@ | ||||
|                 </CascadingValue> | ||||
|             </CascadingValue> | ||||
|         </CascadingValue> | ||||
|         <MudAppBar Bottom="true" Fixed="true" Elevation="0" Dense="true" Style="background-color: var(--mud-palette-dark-lighten);"> | ||||
|         <MudAppBar Bottom="true" Fixed="true" Elevation="0" Dense="true" Style="background-color: var(--mud-palette-dark-lighten); z-index: 900"> | ||||
|             @if (MainData?.LostConnection == true) | ||||
|             { | ||||
|                 <MudText Class="mx-2 mb-1" Color="Color.Error">qBittorrent client is not reachable</MudText> | ||||
|                 <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">@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" /> | ||||
|             <MudText Class="mx-2 mb-1 d-none d-sm-flex">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</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" /> | ||||
|             <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" /> | ||||
|             <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") | ||||
|   | ||||
| @@ -83,7 +83,7 @@ namespace Lantean.QBTMud.Layout | ||||
|             Preferences = await ApiClient.GetApplicationPreferences(); | ||||
|             Version = await ApiClient.GetApplicationVersion(); | ||||
|             var data = await ApiClient.GetMainData(_requestId); | ||||
|             MainData = DataManager.CreateMainData(data); | ||||
|             MainData = DataManager.CreateMainData(data, Version); | ||||
|  | ||||
|             _requestId = data.ResponseId; | ||||
|             _refreshInterval = MainData.ServerState.RefreshInterval; | ||||
| @@ -128,7 +128,7 @@ namespace Lantean.QBTMud.Layout | ||||
|  | ||||
|                         if (MainData is null || data.FullUpdate) | ||||
|                         { | ||||
|                             MainData = DataManager.CreateMainData(data); | ||||
|                             MainData = DataManager.CreateMainData(data, Version); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -11,7 +11,8 @@ | ||||
|             Dictionary<string, HashSet<string>> tagState, | ||||
|             Dictionary<string, HashSet<string>> categoriesState, | ||||
|             Dictionary<string, HashSet<string>> statusState, | ||||
|             Dictionary<string, HashSet<string>> trackersState) | ||||
|             Dictionary<string, HashSet<string>> trackersState, | ||||
|             int majorVersion) | ||||
|         { | ||||
|             Torrents = torrents.ToDictionary(); | ||||
|             Tags = tags.ToHashSet(); | ||||
| @@ -22,6 +23,7 @@ | ||||
|             CategoriesState = categoriesState; | ||||
|             StatusState = statusState; | ||||
|             TrackersState = trackersState; | ||||
|             MajorVersion = majorVersion; | ||||
|         } | ||||
|  | ||||
|         public Dictionary<string, Torrent> Torrents { get; } | ||||
| @@ -36,5 +38,6 @@ | ||||
|         public Dictionary<string, HashSet<string>> TrackersState { get; } | ||||
|         public string? SelectedTorrentHash { get; set; } | ||||
|         public bool LostConnection { get; set; } | ||||
|         public int MajorVersion { get; } | ||||
|     } | ||||
| } | ||||
| @@ -8,6 +8,7 @@ | ||||
|         Completed, | ||||
|         Resumed, | ||||
|         Paused, | ||||
|         Stopped, | ||||
|         Active, | ||||
|         Inactive, | ||||
|         Stalled, | ||||
| @@ -15,6 +16,6 @@ | ||||
|         StalledDownloading, | ||||
|         Checking, | ||||
|         Errored, | ||||
|         Stopped | ||||
|          | ||||
|     } | ||||
| } | ||||
| @@ -12,89 +12,96 @@ | ||||
|  | ||||
| <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> | ||||
|         <MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3"> | ||||
|             <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> | ||||
|             <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> | ||||
| @@ -118,7 +125,7 @@ | ||||
|                 (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> | ||||
| @@ -1058,38 +1065,38 @@ | ||||
|             <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> | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| @page "/details/{hash}" | ||||
| @layout DetailsLayout | ||||
|  | ||||
| <div style="overflow-x: auto; white-space: nowrap; width: 100%;"> | ||||
| <MudToolBar Gutters="false" Dense="true"> | ||||
|     @if (!DrawerOpen) | ||||
|     { | ||||
| @@ -14,6 +15,7 @@ | ||||
|     <MudDivider Vertical="true" /> | ||||
|     <MudText Class="pl-5 no-wrap">@Name</MudText> | ||||
| </MudToolBar> | ||||
| </div> | ||||
|  | ||||
| @if (ShowTabs) | ||||
| { | ||||
|   | ||||
| @@ -49,7 +49,7 @@ namespace Lantean.QBTMud.Pages | ||||
|  | ||||
|         protected override Task OnInitializedAsync() | ||||
|         { | ||||
|             return DoLogin("admin", "eBGJzbjkJ"); | ||||
|             return DoLogin("admin", "5FUM5pATq"); | ||||
|         } | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|                 <MudItem xs="12" md="4"> | ||||
|                     <MudTextField T="string" Label="Criteria" @bind-Value="Model.SearchText" Variant="Variant.Outlined" /> | ||||
|                 </MudItem> | ||||
|                 <MudItem xs="2" md="3"> | ||||
|                 <MudItem xs="12" md="3"> | ||||
|                     <MudSelect T="string" Label="Categories" @bind-Value="Model.SelectedCategory" Variant="Variant.Outlined"> | ||||
|                         @foreach (var (value, name) in Categories) | ||||
|                         { | ||||
| @@ -30,17 +30,21 @@ | ||||
|                         } | ||||
|                     </MudSelect> | ||||
|                 </MudItem> | ||||
|                 <MudItem xs="2" md="3"> | ||||
|                 <MudItem xs="12" md="3"> | ||||
|                     <MudSelect T="string" Label="Plugins" @bind-Value="Model.SelectedPlugin" Variant="Variant.Outlined"> | ||||
|                         <MudSelectItem Value="@("all")">All</MudSelectItem> | ||||
|                         <MudDivider /> | ||||
|                         @if (Plugins.Count > 0) | ||||
|                         { | ||||
|                             <MudDivider /> | ||||
|  | ||||
|                         } | ||||
|                         @foreach (var (value, name) in Plugins) | ||||
|                         { | ||||
|                             <MudSelectItem Value="value">@name</MudSelectItem> | ||||
|                         } | ||||
|                     </MudSelect> | ||||
|                 </MudItem> | ||||
|                 <MudItem xs="2" md="2"> | ||||
|                 <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> | ||||
|              | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| @page "/" | ||||
| @layout ListLayout | ||||
|  | ||||
| <ContextMenu @ref="ContextMenu" Dense="true" AdjustmentX="@(DrawerOpen ? -235 : 0)"> | ||||
| <ContextMenu @ref="ContextMenu" Dense="true" RelativeWidth="DropdownWidth.Ignore" AdjustmentX="-242" AdjustmentY="0"> | ||||
|     <MudMenuItem Icon="@Icons.Material.Outlined.Info" IconColor="Color.Inherit" OnClick="ShowTorrentContextMenu">View torrent details</MudMenuItem> | ||||
|     <MudDivider /> | ||||
|     <TorrentActions RenderType="RenderType.MenuItems" Hashes="GetContextMenuTargetHashes()" PrimaryHash="@(ContextMenuItem?.Hash)" Torrents="MainData.Torrents" Preferences="Preferences" /> | ||||
| </ContextMenu> | ||||
|  | ||||
| <div style="overflow-x: auto; white-space: nowrap; width: 100%;"> | ||||
| <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" /> | ||||
| @@ -18,6 +19,7 @@ | ||||
|     <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> | ||||
|  | ||||
| <MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0"> | ||||
|     <DynamicTable | ||||
|   | ||||
| @@ -193,7 +193,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), | ||||
|   | ||||
| @@ -5,7 +5,7 @@ namespace Lantean.QBTMud.Services | ||||
| { | ||||
|     public class DataManager : IDataManager | ||||
|     { | ||||
|         private static readonly Status[] _statuses = Enum.GetValues<Status>(); | ||||
|         private static Status[]? _statusArray = null; | ||||
|  | ||||
|         public PeerList CreatePeerList(QBitTorrentClient.Models.TorrentPeers torrentPeers) | ||||
|         { | ||||
| @@ -25,8 +25,9 @@ namespace Lantean.QBTMud.Services | ||||
|             return peerList; | ||||
|         } | ||||
|  | ||||
|         public MainData CreateMainData(QBitTorrentClient.Models.MainData mainData) | ||||
|         public MainData CreateMainData(QBitTorrentClient.Models.MainData mainData, string version) | ||||
|         { | ||||
|             var majorVersion = VersionHelper.GetMajorVersion(version); | ||||
|             var torrents = new Dictionary<string, Torrent>(mainData.Torrents?.Count ?? 0); | ||||
|             if (mainData.Torrents is not null) | ||||
|             { | ||||
| @@ -87,8 +88,9 @@ namespace Lantean.QBTMud.Services | ||||
|                 categoriesState.Add(category, torrents.Values.Where(t => FilterHelper.FilterCategory(t, category, serverState.UseSubcategories)).ToHashesHashSet()); | ||||
|             } | ||||
|  | ||||
|             var statusState = new Dictionary<string, HashSet<string>>(_statuses.Length + 2); | ||||
|             foreach (var status in _statuses) | ||||
|             var statuses = GetStatuses(majorVersion).ToArray(); | ||||
|             var statusState = new Dictionary<string, HashSet<string>>(statuses.Length + 2); | ||||
|             foreach (var status in statuses) | ||||
|             { | ||||
|                 statusState.Add(status.ToString(), torrents.Values.Where(t => FilterHelper.FilterStatus(t, status)).ToHashesHashSet()); | ||||
|             } | ||||
| @@ -101,7 +103,7 @@ namespace Lantean.QBTMud.Services | ||||
|                 trackersState.Add(tracker, torrents.Values.Where(t => FilterHelper.FilterTracker(t, tracker)).ToHashesHashSet()); | ||||
|             } | ||||
|  | ||||
|             var torrentList = new MainData(torrents, tags, categories, trackers, serverState, tagState, categoriesState, statusState, trackersState); | ||||
|             var torrentList = new MainData(torrents, tags, categories, trackers, serverState, tagState, categoriesState, statusState, trackersState, majorVersion); | ||||
|  | ||||
|             return torrentList; | ||||
|         } | ||||
| @@ -206,7 +208,7 @@ namespace Lantean.QBTMud.Services | ||||
|             { | ||||
|                 foreach (var (url, hashes) in mainData.Trackers) | ||||
|                 { | ||||
|                     if (!torrentList.Trackers.TryGetValue(url, out var existingHashes)) | ||||
|                     if (!torrentList.Trackers.TryGetValue(url, out _)) | ||||
|                     { | ||||
|                         torrentList.Trackers.Add(url, hashes); | ||||
|                     } | ||||
| @@ -225,7 +227,7 @@ namespace Lantean.QBTMud.Services | ||||
|                     { | ||||
|                         var newTorrent = CreateTorrent(hash, torrent); | ||||
|                         torrentList.Torrents.Add(hash, newTorrent); | ||||
|                         AddTorrentToStates(torrentList, hash); | ||||
|                         AddTorrentToStates(torrentList, hash, torrentList.MajorVersion); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
| @@ -241,7 +243,7 @@ namespace Lantean.QBTMud.Services | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static void AddTorrentToStates(MainData torrentList, string hash) | ||||
|         private static void AddTorrentToStates(MainData torrentList, string hash, int version) | ||||
|         { | ||||
|             var torrent = torrentList.Torrents[hash]; | ||||
|  | ||||
| @@ -271,7 +273,7 @@ namespace Lantean.QBTMud.Services | ||||
|                 value.AddIfTrue(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories)); | ||||
|             } | ||||
|  | ||||
|             foreach (var status in _statuses) | ||||
|             foreach (var status in GetStatuses(version)) | ||||
|             { | ||||
|                 torrentList.StatusState[status.ToString()].AddIfTrue(hash, FilterHelper.FilterStatus(torrent, status)); | ||||
|             } | ||||
| @@ -289,6 +291,25 @@ namespace Lantean.QBTMud.Services | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static Status[] GetStatuses(int version) | ||||
|         { | ||||
|             if (_statusArray is not null) | ||||
|             { | ||||
|                 return _statusArray; | ||||
|             } | ||||
|  | ||||
|             if (version == 5) | ||||
|             { | ||||
|                 _statusArray = Enum.GetValues<Status>().Where(s => s != Status.Paused).ToArray(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _statusArray = Enum.GetValues<Status>().Where(s => s != Status.Stopped).ToArray(); | ||||
|             } | ||||
|  | ||||
|             return _statusArray; | ||||
|         } | ||||
|  | ||||
|         private static void UpdateTorrentStates(MainData torrentList, string hash) | ||||
|         { | ||||
|             var torrent = torrentList.Torrents[hash]; | ||||
| @@ -317,7 +338,7 @@ namespace Lantean.QBTMud.Services | ||||
|                 value.AddIfTrueOrRemove(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories)); | ||||
|             } | ||||
|  | ||||
|             foreach (var status in _statuses) | ||||
|             foreach (var status in GetStatuses(torrentList.MajorVersion)) | ||||
|             { | ||||
|                 torrentList.StatusState[status.ToString()].AddIfTrueOrRemove(hash, FilterHelper.FilterStatus(torrent, status)); | ||||
|             } | ||||
| @@ -361,7 +382,7 @@ namespace Lantean.QBTMud.Services | ||||
|                 categoryState.RemoveIfTrue(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories)); | ||||
|             } | ||||
|  | ||||
|             foreach (var status in _statuses) | ||||
|             foreach (var status in GetStatuses(torrentList.MajorVersion)) | ||||
|             { | ||||
|                 if (!torrentList.StatusState.TryGetValue(status.ToString(), out var statusState)) | ||||
|                 { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ namespace Lantean.QBTMud.Services | ||||
| { | ||||
|     public interface IDataManager | ||||
|     { | ||||
|         MainData CreateMainData(QBitTorrentClient.Models.MainData mainData); | ||||
|         MainData CreateMainData(QBitTorrentClient.Models.MainData mainData, string version); | ||||
|  | ||||
|         Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent); | ||||
|  | ||||
|   | ||||
| @@ -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~~ | ||||
| @@ -155,7 +155,7 @@ code { | ||||
| } | ||||
|  | ||||
| .torrent-list .mud-table-container { | ||||
|     height: calc(100vh - 149px); | ||||
|     height: calc(100vh - 160px); | ||||
| } | ||||
|  | ||||
| .file-list .mud-table-container { | ||||
| @@ -240,4 +240,19 @@ 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; | ||||
| } | ||||
| @@ -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> | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0</TargetFramework> | ||||
|     <TargetFramework>net9.0</TargetFramework> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
| 	<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
| 	  <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
|   </PropertyGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
							
								
								
									
										11
									
								
								nuget.config
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								nuget.config
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <configuration> | ||||
|   <packageSources> | ||||
|     <!-- Define package sources here --> | ||||
|   </packageSources> | ||||
|   <packageSourceMapping> | ||||
|     <!-- Optional source mapping --> | ||||
|   </packageSourceMapping> | ||||
|   <packageVersionOverride> | ||||
|     <package id="FluentAssertions" allowedVersions="[7.0.0,8.0.0)" /> | ||||
|   </packageVersionOverride> | ||||
| </configuration> | ||||
							
								
								
									
										82
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| # qbtmud | ||||
|  | ||||
| qbtmud is a drop-in replacement for qBittorrent's default WebUI, implementing all of its functionality with a modern and user-friendly interface. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| qbtmud replicates all core features of the qBittorrent WebUI, including: | ||||
|  | ||||
| - **Torrent Management** – Add, remove, and control torrents. | ||||
| - **Tracker Control** – View and manage trackers. | ||||
| - **Peer Management** – Monitor and manage peers connected to torrents. | ||||
| - **File Prioritization** – Select and prioritize specific files within a torrent. | ||||
| - **Speed Limits** – Set global and per-torrent speed limits. | ||||
| - **RSS Integration** – Subscribe to RSS feeds for automated torrent downloads. | ||||
| - **Search Functionality** – Integrated torrent search. | ||||
| - **Sequential Downloading** – Download files in order for media streaming. | ||||
| - **Super Seeding Mode** – Efficiently distribute torrents as an initial seeder. | ||||
| - **IP Filtering** – Improve security by filtering specific IP addresses. | ||||
| - **IPv6 Support** – Full support for IPv6 networks. | ||||
| - **Bandwidth Scheduler** – Schedule bandwidth limits. | ||||
| - **WebUI Access** – Remotely manage torrents through the WebUI. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| For a detailed explanation of these features, refer to the [qBittorrent Options Guide](https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| To install qbtmud without building from source: | ||||
|  | ||||
| ### 1. Download the Latest Release | ||||
| - Go to the [qbtmud Releases](https://github.com/lantean-code/qbtmud/releases) page. | ||||
| - Download the latest release archive for your operating system. | ||||
|  | ||||
| ### 2. Extract the Archive | ||||
| - Extract the contents of the downloaded archive to a directory of your choice. | ||||
|  | ||||
| ### 3. Configure qBittorrent to Use qbtmud | ||||
| - Open qBittorrent and navigate to `Tools` > `Options` > `Web UI`. | ||||
| - Enable the option **"Use alternative WebUI"**. | ||||
| - Set the **"Root Folder"** to the directory where you extracted qbtmud. | ||||
| - Click **OK** to save the settings. | ||||
|  | ||||
| ### 4. Access qbtmud | ||||
| - Open your web browser and go to `http://localhost:8080` (or the port configured in qBittorrent). | ||||
|  | ||||
| For more detailed instructions, refer to the [Alternate WebUI Usage Guide](https://github.com/qbittorrent/qBittorrent/wiki/Alternate-WebUI-usage). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Building from Source | ||||
|  | ||||
| To build qbtmud from source, you need to have the **.NET 9.0 SDK** installed on your system. | ||||
|  | ||||
| ### 1. Clone the Repository | ||||
| ```sh | ||||
| git clone https://github.com/lantean-code/qbtmud.git | ||||
| cd qbtmud | ||||
| ``` | ||||
|  | ||||
| ### 2. Restore Dependencies | ||||
| ```sh | ||||
| dotnet restore | ||||
| ``` | ||||
|  | ||||
| ### 3. Build the Application | ||||
| ```sh | ||||
| dotnet build --configuration Release | ||||
| ``` | ||||
|  | ||||
| ### 4. Configure qBittorrent to Use qbtmud | ||||
| Follow the same steps as in the **Installation** section to set qbtmud as your WebUI. | ||||
|  | ||||
| ### 5. Run qbtmud | ||||
| Navigate to the directory containing the built files and run the application using the appropriate command for your OS. | ||||
|  | ||||
| By following these steps, you can set up qbtmud to manage your qBittorrent server with an improved web interface, offering better functionality and usability. | ||||
		Reference in New Issue
	
	Block a user