Fix styling issues with torrent list

Only display errors in debug mode
Add column sorting
This commit is contained in:
ahjephson
2025-02-07 09:23:54 +00:00
parent 938702a7b3
commit dc4b515763
15 changed files with 166 additions and 67 deletions

View File

@@ -5,9 +5,10 @@
<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" LabelPlacement="Placement.End" Value="@(SelectedColumnsInternal.Contains(column.Id))" />

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class ColumnOptionsDialog<T>
{
[CascadingParameter]
IMudDialogInstance 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)

View File

@@ -1,4 +1,4 @@
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60" FullWidth="false">
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
@TorrentControls(_statusType)
</ContextMenu>

View File

@@ -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

View File

@@ -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),

View File

@@ -8,19 +8,17 @@
Class="unselectable"
MaxHeight="@MaxHeight"
AnchorOrigin="@AnchorOrigin"
TransformOrigin="TransformOrigin"
RelativeWidth="@(FullWidth ? DropdownWidth.Relative : DropdownWidth.Adaptive)"
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>

View File

@@ -55,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.
@@ -213,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)
{

View File

@@ -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)

View File

@@ -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)

View File

@@ -23,9 +23,12 @@
<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">

View File

@@ -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();

View File

@@ -49,7 +49,7 @@ namespace Lantean.QBTMud.Pages
protected override Task OnInitializedAsync()
{
return DoLogin("admin", "cIYfeJr49");
return DoLogin("admin", "5FUM5pATq");
}
#endif

View File

@@ -1,7 +1,7 @@
@page "/"
@layout ListLayout
<ContextMenu @ref="ContextMenu" Dense="true" FullWidth="false">
<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" />

View File

@@ -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),

View File

@@ -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;
}