mirror of
				https://github.com/lantean-code/qbtmud.git
				synced 2025-10-30 19:43:33 +00:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | aa80396862 | ||
|  | 30ced3293c | ||
|  | bad509e40f | ||
|  | 6a0796ef20 | ||
|  | dc4b515763 | ||
|  | 938702a7b3 | ||
|  | 6ca1c6edd4 | ||
|  | 24eb5cf5e9 | 
							
								
								
									
										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,7 +1,7 @@ | ||||
| <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> | ||||
|  | ||||
| @@ -10,11 +10,14 @@ | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="FluentAssertions" Version="6.12.1" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> | ||||
| 	<PackageReference Include="FluentAssertions" Version="7.1.0" AllowedVersions="[5.0.0,7.*.*)" /> | ||||
|     <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> | ||||
|     <PackageReference Include="MudBlazor" Version="8.2.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.0.1"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageReference> | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -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; } | ||||
|   | ||||
| @@ -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] | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -63,7 +63,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; } | ||||
| @@ -104,7 +104,7 @@ namespace Lantean.QBTMud.Components | ||||
|             _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 +441,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) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0</TargetFramework> | ||||
|     <TargetFramework>net9.0</TargetFramework> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
| 	<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
| @@ -12,13 +12,13 @@ | ||||
|   <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" /> | ||||
| 	<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" /> | ||||
| 	<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.1" PrivateAssets="all" /> | ||||
| 	<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1" /> | ||||
| 	<PackageReference Include="MudBlazor" Version="8.2.0" /> | ||||
| 	<PackageReference Include="MudBlazor.ThemeManager" Version="3.0.0" /> | ||||
|     <!-- added to fix vuln in dependency --> | ||||
| 	<PackageReference Include="System.Text.Json" Version="8.0.5" /> | ||||
| 	<PackageReference Include="System.Text.Json" Version="9.0.1" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -20,12 +20,15 @@ | ||||
|                         <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"> | ||||
|                 <ErrorDisplay ErrorBoundary="ErrorBoundary" /> | ||||
|             </MudDrawer> | ||||
|             @if (IsDebug) | ||||
|             { | ||||
|                 <MudDrawer 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"> | ||||
|   | ||||
| @@ -44,6 +44,12 @@ namespace Lantean.QBTMud.Layout | ||||
|  | ||||
|         protected MudTheme Theme { get; set; } | ||||
|  | ||||
| #if DEBUG | ||||
|         private bool IsDebug { get; } = true; | ||||
| #else | ||||
|         private bool IsDebug { get; } = false; | ||||
| #endif | ||||
|  | ||||
|         public MainLayout() | ||||
|         { | ||||
|             Theme = new MudTheme(); | ||||
|   | ||||
| @@ -49,7 +49,7 @@ namespace Lantean.QBTMud.Pages | ||||
|  | ||||
|         protected override Task OnInitializedAsync() | ||||
|         { | ||||
|             return DoLogin("admin", "eBGJzbjkJ"); | ||||
|             return DoLogin("admin", "5FUM5pATq"); | ||||
|         } | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| @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" /> | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
| @@ -241,3 +241,14 @@ 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; | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net8.0</TargetFramework> | ||||
|     <TargetFramework>net9.0</TargetFramework> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
| 	<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
|   | ||||
							
								
								
									
										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> | ||||
		Reference in New Issue
	
	Block a user