Remove custom ContextMenu and replace with MudMenu

This commit is contained in:
ahjephson
2025-10-20 13:30:40 +01:00
parent 300e81345c
commit 2ad7be1073
13 changed files with 31 additions and 347 deletions

View File

@@ -1,6 +1,6 @@
<ContextMenu @ref="ContextMenu" Dense="true">
<MudMenu @ref="ContextMenu" Dense="true" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileContextMenu">Rename</MudMenuItem>
</ContextMenu>
</MudMenu>
<div style="overflow-x: auto; white-space: nowrap; width: 100%;">
<MudToolBar Gutters="false" Dense="true">

View File

@@ -69,7 +69,7 @@ namespace Lantean.QBTMud.Components
private DynamicTable<ContentItem>? Table { get; set; }
private ContextMenu? ContextMenu { get; set; }
private MudMenu? ContextMenu { get; set; }
public FilesTab()
{

View File

@@ -1,8 +1,8 @@
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
<MudMenu @ref="StatusContextMenu" Dense="true" PositionAtCursor="true">
@TorrentControls(_statusType)
</ContextMenu>
</MudMenu>
<ContextMenu @ref="CategoryContextMenu" Dense="true" AdjustmentY="-60">
<MudMenu @ref="CategoryContextMenu" Dense="true" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddCategory">Add category</MudMenuItem>
@if (IsCategoryTarget)
{
@@ -12,9 +12,9 @@
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove unused categories</MudMenuItem>
<MudDivider />
@TorrentControls(_categoryType)
</ContextMenu>
</MudMenu>
<ContextMenu @ref="TagContextMenu" Dense="true" AdjustmentY="-60">
<MudMenu @ref="TagContextMenu" Dense="true" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Outlined.AddCircle" IconColor="Color.Info" OnClick="AddTag">Add tag</MudMenuItem>
@if (IsTagTarget)
{
@@ -23,13 +23,13 @@
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedTags">Remove unused tags</MudMenuItem>
<MudDivider />
@TorrentControls(_tagType)
</ContextMenu>
</MudMenu>
<ContextMenu @ref="TrackerContextMenu" Dense="true" AdjustmentY="-60">
<MudMenu @ref="TrackerContextMenu" Dense="true" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveUnusedCategories">Remove tracker</MudMenuItem>
<MudDivider />
@TorrentControls(_trackerType)
</ContextMenu>
</MudMenu>
<MudNavMenu Dense="true">
<MudNavGroup Title="Status" @bind-Expanded="_statusExpanded">

View File

@@ -69,13 +69,13 @@ namespace Lantean.QBTMud.Components
protected Dictionary<string, int> Statuses => GetStatuses();
protected ContextMenu? StatusContextMenu { get; set; }
protected MudMenu? StatusContextMenu { get; set; }
protected ContextMenu? CategoryContextMenu { get; set; }
protected MudMenu? CategoryContextMenu { get; set; }
protected ContextMenu? TagContextMenu { get; set; }
protected MudMenu? TagContextMenu { get; set; }
protected ContextMenu? TrackerContextMenu { get; set; }
protected MudMenu? TrackerContextMenu { get; set; }
protected string? ContextMenuStatus { get; set; }

View File

@@ -1,10 +1,10 @@
<ContextMenu @ref="ContextMenu" Dense="true">
<MudMenu @ref="ContextMenu" Dense="true" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddPeer">Add peer</MudMenuItem>
@if (ContextMenuItem is not null)
{
<MudMenuItem Icon="@Icons.Material.Filled.DisabledByDefault" IconColor="Color.Info" OnClick="BanPeerContextMenu">Ban peer</MudMenuItem>
}
</ContextMenu>
</MudMenu>
<MudToolBar Gutters="false" Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.AddCircle" Color="Color.Info" OnClick="AddPeer">Add peer</MudIconButton>

View File

@@ -52,7 +52,7 @@ namespace Lantean.QBTMud.Components
protected Peer? SelectedItem { get; set; }
protected ContextMenu? ContextMenu { get; set; }
protected MudMenu? ContextMenu { get; set; }
protected DynamicTable<Peer>? Table { get; set; }
@@ -153,7 +153,7 @@ namespace Lantean.QBTMud.Components
return;
}
await ContextMenu.ToggleMenuAsync(eventArgs);
await ContextMenu.OpenMenuAsync(eventArgs);
}
protected void SelectedItemChanged(Peer peer)

View File

@@ -1,4 +1,4 @@
<ContextMenu @ref="ContextMenu" Dense="true">
<MudMenu @ref="ContextMenu" Dense="true" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Filled.AddCircle" IconColor="Color.Info" OnClick="AddTracker">Add trackers</MudMenuItem>
@if (ContextMenuItem is not null)
{
@@ -6,7 +6,7 @@
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="RemoveTrackerContextMenu">Remove tracker</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.FolderCopy" IconColor="Color.Info" OnClick="CopyTrackerUrlContextMenu">Copy tracker url</MudMenuItem>
}
</ContextMenu>
</MudMenu>
<MudToolBar Gutters="false" Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.AddCircle" Color="Color.Info" OnClick="AddTracker">Add trackers</MudIconButton>

View File

@@ -52,7 +52,7 @@ namespace Lantean.QBTMud.Components
protected TorrentTracker? SelectedItem { get; set; }
protected ContextMenu? ContextMenu { get; set; }
protected MudMenu? ContextMenu { get; set; }
protected DynamicTable<TorrentTracker>? Table { get; set; }
@@ -148,7 +148,7 @@ namespace Lantean.QBTMud.Components
return;
}
await ContextMenu.ToggleMenuAsync(eventArgs);
await ContextMenu.OpenMenuAsync(eventArgs);
}
protected void SelectedItemChanged(TorrentTracker torrentTracker)

View File

@@ -1,26 +0,0 @@
@inherits MudComponentBase
<MudMenu @ref="FakeMenu" Style="display: none" OpenChanged="FakeOpenChanged"></MudMenu>
@* The portal has to include the cascading values inside, because it's not able to teletransport the cascade *@
<MudPopover tracker="@Id"
Open="@_open"
Class="unselectable"
MaxHeight="@MaxHeight"
AnchorOrigin="@AnchorOrigin"
TransformOrigin="@TransformOrigin"
RelativeWidth="@RelativeWidth"
OverflowBehavior="OverflowBehavior.FlipAlways"
Style="@_popoverStyle"
@ontouchend:preventDefault>
<CascadingValue Value="@(FakeMenu)">
@if (_showChildren)
{
<MudList T="object" Class="unselectable" Dense="@Dense">
@ChildContent
</MudList>
}
</CascadingValue>
</MudPopover>
<MudOverlay Visible="@(_open)" LockScroll="@LockScroll" AutoClose="true" OnClosed="@CloseMenuAsync" />

View File

@@ -1,290 +0,0 @@
using Lantean.QBTMud.Interop;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using MudBlazor;
using MudBlazor.Utilities;
namespace Lantean.QBTMud.Components.UI
{
public partial class ContextMenu : MudComponentBase
{
private bool _open;
private bool _showChildren;
private string? _popoverStyle;
private string? _id;
private double _x;
private double _y;
private bool _isResized = false;
private const double _diff = 64;
private string Id
{
get
{
_id ??= Guid.NewGuid().ToString();
return _id;
}
}
[Inject]
public IJSRuntime JSRuntime { get; set; } = default!;
[Inject]
public IPopoverService PopoverService { get; set; } = default!;
/// <summary>
/// If true, compact vertical padding will be applied to all menu items.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public bool Dense { get; set; }
/// <summary>
/// Set to true if you want to prevent page from scrolling when the menu is open
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public bool LockScroll { get; set; }
/// <summary>
/// If true, the list menu will be same width as the parent.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public DropdownWidth RelativeWidth { get; set; }
/// <summary>
/// Sets the max height the menu can have when open.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public int? MaxHeight { get; set; }
/// <summary>
/// Set the anchor origin point to determine where the popover will open from.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public Origin AnchorOrigin { get; set; } = Origin.TopLeft;
/// <summary>
/// Sets the transform origin point for the popover.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public Origin TransformOrigin { get; set; } = Origin.TopLeft;
/// <summary>
/// If true, menu will be disabled.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.Behavior)]
public bool Disabled { get; set; }
/// <summary>
/// Gets or sets whether to show a ripple effect when the user clicks the button. Default is true.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.Appearance)]
public bool Ripple { get; set; } = true;
/// <summary>
/// Determines whether the component has a drop-shadow. Default is true
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.Appearance)]
public bool DropShadow { get; set; } = true;
/// <summary>
/// Add menu items here
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupBehavior)]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Fired when the menu <see cref="Open"/> property changes.
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupBehavior)]
public EventCallback<bool> OpenChanged { get; set; }
[Parameter]
public int AdjustmentX { get; set; }
[Parameter]
public int AdjustmentY { get; set; }
protected MudMenu? FakeMenu { get; set; }
protected void FakeOpenChanged(bool value)
{
if (!value)
{
_open = false;
}
StateHasChanged();
}
/// <summary>
/// Opens the menu.
/// </summary>
/// <param name="args">
/// The arguments of the calling mouse/pointer event.
/// </param>
public async Task OpenMenuAsync(EventArgs args)
{
if (Disabled)
{
return;
}
// long press on iOS triggers selection, so clear it
await JSRuntime.ClearSelection();
if (args is not LongPressEventArgs)
{
_showChildren = true;
}
_open = true;
_isResized = false;
StateHasChanged();
var (x, y) = GetPositionFromArgs(args);
_x = x;
_y = y;
SetPopoverStyle(x, y);
StateHasChanged();
await OpenChanged.InvokeAsync(_open);
// long press on iOS triggers selection, so clear it
await JSRuntime.ClearSelection();
if (args is LongPressEventArgs)
{
await Task.Delay(1000);
_showChildren = true;
}
}
/// <summary>
/// Closes the menu.
/// </summary>
public Task CloseMenuAsync()
{
_open = false;
_popoverStyle = null;
StateHasChanged();
return OpenChanged.InvokeAsync(_open);
}
private void SetPopoverStyle(double x, double y)
{
_popoverStyle = $"margin-top: {y.ToPx()}; margin-left: {x.ToPx()};";
}
/// <summary>
/// Toggle the visibility of the menu.
/// </summary>
public async Task ToggleMenuAsync(EventArgs args)
{
if (Disabled)
{
return;
}
if (_open)
{
await CloseMenuAsync();
}
else
{
await OpenMenuAsync(args);
}
}
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (!_isResized)
{
//await DeterminePosition();
}
return Task.CompletedTask;
}
//private async Task DeterminePosition()
//{
// var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content");
// double? contextMenuHeight = null;
// double? contextMenuWidth = null;
// var popoverHolder = PopoverService.ActivePopovers.FirstOrDefault(p => p.UserAttributes.ContainsKey("tracker") && (string?)p.UserAttributes["tracker"] == Id);
// var popoverSize = await JSRuntime.GetBoundingClientRect($"#popovercontent-{popoverHolder?.Id}");
// if (popoverSize.Height > 0)
// {
// contextMenuHeight = popoverSize.Height;
// contextMenuWidth = popoverSize.Width;
// }
// else
// {
// return;
// }
// // the bottom position of the popover will be rendered off screen
// if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
// {
// // adjust the top of the context menu
// var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
// _y -= overshoot;
// if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
// {
// MaxHeight = (int)(mainContentSize.Height - _y + _diff);
// }
// }
// if (_x + contextMenuWidth.Value > mainContentSize.Width)
// {
// var overshoot = Math.Abs(mainContentSize.Width - (_x + contextMenuWidth.Value));
// _x -= overshoot;
// }
// SetPopoverStyle(_x, _y);
// _isResized = true;
// await InvokeAsync(StateHasChanged);
//}
private (double x, double y) GetPositionFromArgs(EventArgs eventArgs)
{
double x, y;
if (eventArgs is MouseEventArgs mouseEventArgs)
{
x = mouseEventArgs.ClientX;
y = mouseEventArgs.ClientY;
}
else if (eventArgs is LongPressEventArgs longPressEventArgs)
{
x = longPressEventArgs.ClientX;
y = longPressEventArgs.ClientY;
}
else
{
throw new NotSupportedException("Invalid eventArgs type.");
}
return (x + AdjustmentX, y + AdjustmentY);
}
}
}

View File

@@ -12,10 +12,10 @@
<ItemGroup>
<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="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.10" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" />
<PackageReference Include="MudBlazor" Version="8.13.0" />
<PackageReference Include="MudBlazor.ThemeManager" Version="3.0.0" />
</ItemGroup>

View File

@@ -1,11 +1,11 @@
@page "/"
@layout ListLayout
<ContextMenu @ref="ContextMenu" Dense="true" RelativeWidth="DropdownWidth.Ignore" AdjustmentX="-242" AdjustmentY="0">
<MudMenu @ref="ContextMenu" Dense="true" RelativeWidth="DropdownWidth.Ignore" PositionAtCursor="true">
<MudMenuItem Icon="@Icons.Material.Outlined.Info" IconColor="Color.Inherit" OnClick="ShowTorrentContextMenu">View torrent details</MudMenuItem>
<MudDivider />
<TorrentActions RenderType="RenderType.MenuItems" Hashes="GetContextMenuTargetHashes()" PrimaryHash="@(ContextMenuItem?.Hash)" Torrents="MainData.Torrents" Preferences="Preferences" />
</ContextMenu>
</MudMenu>
<div style="overflow-x: auto; white-space: nowrap; width: 100%;">
<MudToolBar Gutters="false" Dense="true">

View File

@@ -68,7 +68,7 @@ namespace Lantean.QBTMud.Pages
protected Torrent? ContextMenuItem { get; set; }
protected ContextMenu? ContextMenu { get; set; }
protected MudMenu? ContextMenu { get; set; }
private object? _lastRenderedTorrents;
private QBitTorrentClient.Models.Preferences? _lastPreferences;
@@ -272,7 +272,7 @@ namespace Lantean.QBTMud.Pages
return;
}
await ContextMenu.ToggleMenuAsync(eventArgs);
await ContextMenu.OpenMenuAsync(eventArgs);
}
protected IEnumerable<ColumnDefinition<Torrent>> Columns => ColumnsDefinitions.Where(c => c.Id != "#" || Preferences?.QueueingEnabled == true);